Blender V4.5
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
8
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 <optional>
50#include <thread>
51#include <unordered_set>
52
53#ifdef WITH_GHOST_WAYLAND_DYNLOAD
55#endif
56#include <wayland-cursor.h>
57
58#include <xkbcommon/xkbcommon-compose.h>
59#include <xkbcommon/xkbcommon.h>
60
61/* Generated by `wayland-scanner`. */
62#include <fractional-scale-v1-client-protocol.h>
63#include <pointer-constraints-unstable-v1-client-protocol.h>
64#include <pointer-gestures-unstable-v1-client-protocol.h>
65#include <primary-selection-unstable-v1-client-protocol.h>
66#include <relative-pointer-unstable-v1-client-protocol.h>
67#include <tablet-v2-client-protocol.h>
68#include <viewporter-client-protocol.h>
69#include <xdg-activation-v1-client-protocol.h>
70#include <xdg-output-unstable-v1-client-protocol.h>
71#ifdef WITH_INPUT_IME
72# include <text-input-unstable-v3-client-protocol.h>
73#endif
74
75/* Decorations `xdg_decor`. */
76#include <xdg-decoration-unstable-v1-client-protocol.h>
77#include <xdg-shell-client-protocol.h>
78/* End `xdg_decor`. */
79
80#include <fcntl.h>
81#include <sys/mman.h>
82#include <unistd.h>
83
84#include <cstdlib> /* For `exit`. */
85#include <cstring>
86#include <mutex>
87
88#include <pthread.h> /* For setting the thread priority. */
89
90#ifdef HAVE_POLL
91# include <poll.h>
92#endif
93
94/* Logging, use `ghost.wl.*` prefix. */
95#include "CLG_log.h"
96
97#ifdef USE_EVENT_BACKGROUND_THREAD
98# include "GHOST_TimerTask.hh"
99#endif
100
101#ifdef WITH_GHOST_WAYLAND_LIBDECOR
102static bool use_libdecor = true;
103# ifdef WITH_GHOST_WAYLAND_DYNLOAD
104static bool has_libdecor = false;
105# else
106static bool has_libdecor = true;
107# endif
108#endif
109
111
112#include "IMB_imbuf.hh"
113#include "IMB_imbuf_types.hh"
114
115/* -------------------------------------------------------------------- */
118
120
121static void output_handle_done(void *data, wl_output *wl_output);
122
126
127static void gwl_seat_cursor_anim_begin(GWL_Seat *seat);
129static void gwl_seat_cursor_anim_end(GWL_Seat *seat);
130static void gwl_seat_cursor_anim_reset(GWL_Seat *seat);
131
133 uint32_t name,
134 int *r_interface_slot);
135static void gwl_registry_entry_remove_all(GWL_Display *display);
136
140static const GWL_RegistryHandler *gwl_registry_handler_from_interface_slot(int interface_slot);
141
143 xkb_compose_state *compose_state,
144 xkb_state *state,
145 const xkb_keycode_t key,
146 char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)]);
147
148#ifdef USE_EVENT_BACKGROUND_THREAD
150
151static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex);
152
154constexpr size_t events_pending_default_size = 4096 / sizeof(void *);
155
156#endif /* USE_EVENT_BACKGROUND_THREAD */
157
160#define pushEvent DONT_USE
161
163
164/* -------------------------------------------------------------------- */
167
175#define USE_GNOME_CONFINE_HACK
180// #define USE_GNOME_CONFINE_HACK_ALWAYS_ON
181
182#ifdef USE_GNOME_CONFINE_HACK
183static bool use_gnome_confine_hack = false;
184#endif
185
191#define USE_KDE_TABLET_HIDDEN_CURSOR_HACK
192
194
195/* -------------------------------------------------------------------- */
200
214// #define USE_VERBOSE_OLD_IFACE_PRINT
215
216#ifdef USE_VERBOSE_OLD_IFACE_PRINT
217# define _VERBOSE_OLD_IFACE_PRINT(params_version, version_max) \
218 ((params_version > version_max) ? \
219 fprintf(stderr, \
220 "%s: version_max=%u, is smaller than run-time version=%u\n", \
221 __func__, \
222 version_max, \
223 params_version) : \
224 0)
225#else
226# define _VERBOSE_OLD_IFACE_PRINT(params_version, version_max) \
227 ((void)(params_version), (version_max))
228#endif
229
230#define GWL_IFACE_VERSION_CLAMP(params_version, version_min, version_max) \
231 ((void)_VERBOSE_OLD_IFACE_PRINT(params_version, version_max), \
232 std::clamp(params_version, version_min, version_max))
233
238#define USE_NON_LATIN_KB_WORKAROUND
239
240#define WL_NAME_UNSET uint32_t(-1)
241
246#define WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy) \
247 wl_fixed_to_int((win)->wl_fixed_to_window((xy)[0])), \
248 wl_fixed_to_int((win)->wl_fixed_to_window((xy)[1])),
249
251
252/* -------------------------------------------------------------------- */
259
263enum {
264 BTN_LEFT = 0x110,
265 BTN_RIGHT = 0x111,
266 BTN_MIDDLE = 0x112,
267 BTN_SIDE = 0x113,
268 BTN_EXTRA = 0x114,
269 BTN_FORWARD = 0x115,
270 BTN_BACK = 0x116
271};
272// #define BTN_TASK 0x117 /* UNUSED. */
273
274#define BTN_RANGE_MIN BTN_LEFT
275#define BTN_RANGE_MAX BTN_BACK
276
285enum {
287 BTN_STYLUS = 0x14b,
289 BTN_STYLUS2 = 0x14c,
291 BTN_STYLUS3 = 0x149,
292};
293
299enum {
307
308#ifdef USE_NON_LATIN_KB_WORKAROUND
309 KEY_1 = 2,
310 KEY_2 = 3,
311 KEY_3 = 4,
312 KEY_4 = 5,
313 KEY_5 = 6,
314 KEY_6 = 7,
315 KEY_7 = 8,
316 KEY_8 = 9,
317 KEY_9 = 10,
318 KEY_0 = 11,
319#endif
320};
321
322/* Only defined in XKB 1.8x and newer, it seems XKB doesn't provide a version define. */
323#ifndef XKB_VMOD_NAME_HYPER
324# define XKB_VMOD_NAME_HYPER "Hyper"
325#endif
326
328
329/* -------------------------------------------------------------------- */
334
335enum {
341};
342#define MOD_INDEX_NUM (MOD_INDEX_HYPER + 1)
343
351
353 /*MOD_INDEX_SHIFT*/
354 {
355 /*display_name*/ "Shift",
356 /*xkb_id*/ XKB_MOD_NAME_SHIFT,
357 /*key_l*/ GHOST_kKeyLeftShift,
358 /*key_r*/ GHOST_kKeyRightShift,
361 },
362 /*MOD_INDEX_ALT*/
363 {
364 /*display_name*/ "Alt",
365 /*xkb_id*/ XKB_MOD_NAME_ALT,
366 /*key_l*/ GHOST_kKeyLeftAlt,
367 /*key_r*/ GHOST_kKeyRightAlt,
370 },
371 /*MOD_INDEX_CTRL*/
372 {
373 /*display_name*/ "Control",
374 /*xkb_id*/ XKB_MOD_NAME_CTRL,
375 /*key_l*/ GHOST_kKeyLeftControl,
376 /*key_r*/ GHOST_kKeyRightControl,
379 },
380 /*MOD_INDEX_OS*/
381 {
382 /*display_name*/ "OS",
383 /*xkb_id*/ XKB_MOD_NAME_LOGO,
384 /*key_l*/ GHOST_kKeyLeftOS,
385 /*key_r*/ GHOST_kKeyRightOS,
386 /*mod_l*/ GHOST_kModifierKeyLeftOS,
388 },
389 /*MOD_INDEX_HYPER*/
390 {
391 /*display_name*/ "Hyper",
392 /*xkb_id*/ XKB_VMOD_NAME_HYPER,
393 /*key_l*/ GHOST_kKeyLeftHyper,
394 /*key_r*/ GHOST_kKeyRightHyper,
397 },
398};
399
401
402/* -------------------------------------------------------------------- */
405
408 const char *data = nullptr;
409 size_t data_size = 0;
410};
411
413{
414 free(const_cast<char *>(buffer->data));
415 buffer->data = nullptr;
416 buffer->data_size = 0;
417}
418
420{
421 free(const_cast<char *>(buffer->data));
422 buffer->data_size = strlen(str);
423 char *data = static_cast<char *>(malloc(buffer->data_size));
424 std::memcpy(data, str, buffer->data_size);
425 buffer->data = data;
426}
427
429
430/* -------------------------------------------------------------------- */
433
438#define EVDEV_OFFSET 8
439
445 std::atomic<bool> exit_pending = false;
446};
447
449
451 struct {
453 wl_buffer *buffer = nullptr;
454 wl_cursor_image image = {0};
455 wl_cursor_theme *theme = nullptr;
457 const wl_cursor *theme_cursor = nullptr;
459 const char *theme_cursor_name = nullptr;
460 } wl;
461
462 bool visible = false;
469 bool is_hardware = true;
471 bool is_custom = false;
472 void *custom_data = nullptr;
475
478
483 std::string theme_name;
489 int theme_size = 0;
491};
492
494
495/* -------------------------------------------------------------------- */
498
506
508
509 /* NOTE: Keep buttons last (simplifies switch statement). */
510
511 /* Left mouse button. */
514 /* Middle mouse button. */
517 /* Right mouse button. */
520 /* Mouse button number 4. */
523
524#define GWL_TabletTool_FrameTypes_NUM (int(GWL_TabletTool_EventTypes::Stylus3_Up) + 1)
525};
526
528 GHOST_kButtonMaskLeft, /* `Stylus0_*`. */
529 GHOST_kButtonMaskMiddle, /* `Stylus1_*`. */
530 GHOST_kButtonMaskRight, /* `Stylus2_*`. */
531 GHOST_kButtonMaskButton4, /* `Stylus3_*`. */
532};
533
540
542 struct {
548 } wl;
549
550 GWL_Seat *seat = nullptr;
552 bool proximity = false;
553
555
557 int32_t xy[2] = {0};
558 bool has_xy = false;
559
563 struct {
569
570 struct {
574};
575
578{
579 const int ty_mask = 1 << int(ty);
580 /* Motion callback may run multiple times. */
581 if (tablet_tool->frame_pending.frame_types_mask & ty_mask) {
582 return;
583 }
584 tablet_tool->frame_pending.frame_types_mask |= ty_mask;
585 int i = tablet_tool->frame_pending.frame_types_num++;
586 tablet_tool->frame_pending.frame_types[i] = ty;
587}
588
590{
591 tablet_tool->frame_pending.frame_types_num = 0;
592 tablet_tool->frame_pending.frame_types_mask = 0;
593}
594
596
597/* -------------------------------------------------------------------- */
600
605
607 struct {
608 wl_data_offer *id = nullptr;
609 } wl;
610
611 std::unordered_set<std::string> types;
612
613 struct {
620 enum wl_data_device_manager_dnd_action source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
621 enum wl_data_device_manager_dnd_action action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
623 wl_fixed_t xy[2] = {0, 0};
625};
626
628
629/* -------------------------------------------------------------------- */
632
634
636 struct {
637 wl_data_source *source = nullptr;
638 } wl;
639
641};
642
644
645/* -------------------------------------------------------------------- */
648
658 GWL_Seat *seat = nullptr;
659
660 xkb_keycode_t key_code;
661
664
669 struct {
672};
673
676 bool use_lock = false;
677 bool use_confine = false;
678};
679
684
686 struct {
692 } wl;
693
707 wl_fixed_t xy[2] = {0, 0};
708
710 std::unordered_set<const GWL_Output *> outputs;
711
712 int theme_scale = 1;
713
715 uint32_t serial = 0;
716
718};
719
722
730
731 /* NOTE: Keep buttons last (simplifies switch statement). */
732
733 /* #BTN_LEFT mouse button. */
736 /* #BTN_RIGHT mouse button. */
739 /* #BTN_MIDDLE mouse button. */
742 /* #BTN_SIDE mouse button. */
745 /* #BTN_EXTRA mouse button. */
748 /* #BTN_FORWARD mouse button. */
751 /* #BTN_BACK mouse button. */
754
755#define GWL_SeatStatePointer_EventTypes_NUM (int(GWL_Pointer_EventTypes::Button6_Up) + 1)
756};
757
759 GHOST_kButtonMaskLeft, /* `Button0_*` / #BTN_LEFT. */
760 GHOST_kButtonMaskRight, /* `Button1_*` / #BTN_RIGHT. */
761 GHOST_kButtonMaskMiddle, /* `Button2_*` / #BTN_MIDDLE. */
762 GHOST_kButtonMaskButton4, /* `Button3_*` / #BTN_SIDE. */
763 GHOST_kButtonMaskButton5, /* `Button4_*` / #BTN_EXTRA. */
764 GHOST_kButtonMaskButton6, /* `Button5_*` / #BTN_FORWARD. */
765 GHOST_kButtonMaskButton7, /* `Button6_*` / #BTN_BACK. */
766};
767
770 "Buttons missing");
771
785
787 const GWL_Pointer_EventTypes ty,
788 const uint64_t event_ms)
789{
790 /* It's a quirk of WAYLAND that most scroll events don't have a time-stamp.
791 * Scroll events use their own time-stamp (see #GWL_SeatStatePointerScroll::event_ms usage).
792 * Ensure the API is used as intended. */
794 GHOST_ASSERT(event_ms == 0, "Scroll events must not have a time-stamp");
795 }
796 else {
797 GHOST_ASSERT(event_ms != 0, "Non-scroll events must have a time-stamp");
798 }
799
800 const int ty_mask = 1 << int(ty);
801 /* Motion callback may run multiple times. */
802 if (pointer_events->frame_pending.frame_types_mask & ty_mask) {
803 return;
804 }
805 pointer_events->frame_pending.frame_types_mask |= ty_mask;
806 int i = pointer_events->frame_pending.frame_types_num++;
807 pointer_events->frame_pending.frame_types[i] = ty;
808 pointer_events->frame_pending.frame_event_ms[i] = event_ms;
809}
810
812{
813 pointer_events->frame_pending.frame_types_num = 0;
814 pointer_events->frame_pending.frame_types_mask = 0;
815}
816
823
824static constexpr int smooth_as_discrete_steps = 2400;
825
832 wl_fixed_t smooth_xy[2] = {0, 0};
834 int32_t discrete_xy[2] = {0, 0};
840 bool inverted_xy[2] = {false, false};
842 enum wl_pointer_axis_source axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
843
848 bool has_event_ms = false;
851
853};
854
866 wl_fixed_t value = 0;
867 wl_fixed_t factor = 1;
868};
869
871 const wl_fixed_t add)
872{
873 const int result_prev = wl_fixed_to_int(sf->value * sf->factor);
874 sf->value += add;
875 const int result_curr = wl_fixed_to_int(sf->value * sf->factor);
876 return result_curr - result_prev;
877}
878
887
892
894 struct {
900 } wl;
901
903 uint32_t serial = 0;
904};
905
914
915#ifdef WITH_GHOST_WAYLAND_LIBDECOR
916struct GWL_LibDecor_System {
917 libdecor *context = nullptr;
918};
919
920static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor)
921{
922 if (decor->context) {
923 libdecor_unref(decor->context);
924 decor->context = nullptr;
925 }
926 delete decor;
927}
928#endif
929
931 xdg_wm_base *shell = nullptr;
933
934 zxdg_decoration_manager_v1 *manager = nullptr;
936};
937
939{
940 if (decor->manager) {
941 gwl_registry_entry_remove_by_name(display, decor->manager_name, nullptr);
942 GHOST_ASSERT(decor->manager == nullptr, "Internal registry error");
943 }
944 if (decor->shell) {
945 gwl_registry_entry_remove_by_name(display, decor->shell_name, nullptr);
946 GHOST_ASSERT(decor->shell == nullptr, "Internal registry error");
947 }
948 delete decor;
949}
950
952
954 struct {
955 zwp_primary_selection_offer_v1 *id = nullptr;
956 } wp;
957
958 std::unordered_set<std::string> types;
959};
960
962
964 struct {
965 zwp_primary_selection_source_v1 *source = nullptr;
966 } wp;
967
969};
970
980
982{
983 if (primary->data_offer == nullptr) {
984 return;
985 }
986 zwp_primary_selection_offer_v1_destroy(primary->data_offer->wp.id);
987 delete primary->data_offer;
988 primary->data_offer = nullptr;
989}
990
992{
993 GWL_PrimarySelection_DataSource *data_source = primary->data_source;
994 if (data_source == nullptr) {
995 return;
996 }
998 if (data_source->wp.source) {
999 zwp_primary_selection_source_v1_destroy(data_source->wp.source);
1000 }
1001 delete primary->data_source;
1002 primary->data_source = nullptr;
1003}
1004
1005#ifdef WITH_INPUT_IME
1006struct GWL_SeatIME {
1014 wl_surface *surface_window = nullptr;
1015 GHOST_TEventImeData event_ime_data = {
1017 /*result*/ "",
1020 /*composite*/ "",
1021 /*cursor_position*/ -1,
1022 /*target_start*/ -1,
1023 /*target_end*/ -1,
1024 };
1026 bool is_enabled = false;
1031 bool has_preedit = false;
1032
1034 bool result_is_null = false;
1036 bool composite_is_null = false;
1037
1039 bool has_preedit_string_callback = false;
1041 bool has_commit_string_callback = false;
1042
1044 struct {
1045 int x = -1;
1046 int y = -1;
1047 int w = -1;
1048 int h = -1;
1049 } rect;
1050};
1051#endif /* WITH_INPUT_IME */
1052
1053struct GWL_Seat {
1054
1056 struct {
1057 wl_seat *seat = nullptr;
1058 wl_pointer *pointer = nullptr;
1059 wl_touch *touch = nullptr;
1060 wl_keyboard *keyboard = nullptr;
1061
1063 wl_data_device *data_device = nullptr;
1065
1067 struct {
1068 zwp_tablet_seat_v2 *tablet_seat = nullptr;
1069
1070#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
1071 zwp_pointer_gesture_hold_v1 *pointer_gesture_hold = nullptr;
1072#endif
1073#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
1074 zwp_pointer_gesture_pinch_v1 *pointer_gesture_pinch = nullptr;
1075#endif
1076#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
1077 zwp_pointer_gesture_swipe_v1 *pointer_gesture_swipe = nullptr;
1078#endif
1079
1080 zwp_relative_pointer_v1 *relative_pointer = nullptr;
1081 zwp_locked_pointer_v1 *locked_pointer = nullptr;
1082 zwp_confined_pointer_v1 *confined_pointer = nullptr;
1083
1084 zwp_primary_selection_device_v1 *primary_selection_device = nullptr;
1085
1087 std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools;
1088
1089#ifdef WITH_INPUT_IME
1090 zwp_text_input_v3 *text_input = nullptr;
1091#endif
1093
1095 struct {
1096 xkb_context *context = nullptr;
1097
1099 xkb_compose_table *compose_table = nullptr;
1100
1102 xkb_compose_state *compose_state = nullptr;
1103
1104 xkb_state *state = nullptr;
1108 xkb_state *state_empty = nullptr;
1109
1114 xkb_state *state_empty_with_shift = nullptr;
1119 xkb_state *state_empty_with_numlock = nullptr;
1120
1124 xkb_layout_index_t layout_active = 0;
1126
1127#ifdef WITH_INPUT_IME
1128 GWL_SeatIME ime;
1129#endif
1130
1132
1133 std::string name;
1134
1137
1143
1146
1148
1149#ifdef USE_GNOME_CONFINE_HACK
1151#endif
1153 wl_fixed_t grab_lock_xy[2] = {0, 0};
1154
1156
1157#ifdef USE_NON_LATIN_KB_WORKAROUND
1159#endif
1160
1163
1169 xkb_mod_index_t xkb_keymap_mod_index[MOD_INDEX_NUM] = {XKB_MOD_INVALID};
1170
1171 /* Cache result for other modifiers which aren't stored in `xkb_keymap_mod_index`
1172 * since their depressed state isn't tracked. */
1173
1175 xkb_mod_index_t xkb_keymap_mod_index_mod2 = XKB_MOD_INVALID;
1177 xkb_mod_index_t xkb_keymap_mod_index_numlock = XKB_MOD_INVALID;
1178
1179 struct {
1192
1196
1200
1206 std::optional<GHOST_TSuccess> data_offer_copy_paste_has_image = std::nullopt;
1207
1210
1212
1220};
1221
1223{
1224 if (seat->pointer.serial == seat->cursor_source_serial) {
1225 return &seat->pointer;
1226 }
1227 if (seat->tablet.serial == seat->cursor_source_serial) {
1228 return &seat->tablet;
1229 }
1230 return nullptr;
1231}
1232
1234 GWL_Seat *seat, const wl_surface *wl_surface)
1235{
1237 return &seat->pointer;
1238 }
1240 return &seat->tablet;
1241 }
1242 GHOST_ASSERT(0, "Surface found without pointer/tablet tag");
1243 return nullptr;
1244}
1245
1253{
1254 const xkb_layout_index_t group = seat->xkb.layout_active;
1255 const xkb_mod_index_t mod_shift = seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT];
1256 const xkb_mod_index_t mod_mod2 = seat->xkb_keymap_mod_index_mod2;
1257 const xkb_mod_index_t mod_numlock = seat->xkb_keymap_mod_index_numlock;
1258
1259 /* Handle `state_empty`. */
1260 xkb_state_update_mask(seat->xkb.state_empty, 0, 0, 0, 0, 0, group);
1261
1262 /* Handle `state_empty_with_shift`. */
1263 GHOST_ASSERT((mod_shift == XKB_MOD_INVALID) == (seat->xkb.state_empty_with_shift == nullptr),
1264 "Invalid state for SHIFT");
1265 if (seat->xkb.state_empty_with_shift) {
1266 xkb_state_update_mask(seat->xkb.state_empty_with_shift, 1 << mod_shift, 0, 0, 0, 0, group);
1267 }
1268
1269 /* Handle `state_empty_with_shift`. */
1270 GHOST_ASSERT((mod_mod2 == XKB_MOD_INVALID || mod_numlock == XKB_MOD_INVALID) ==
1271 (seat->xkb.state_empty_with_numlock == nullptr),
1272 "Invalid state for NUMLOCK");
1273 if (seat->xkb.state_empty_with_numlock) {
1274 xkb_state_update_mask(
1275 seat->xkb.state_empty_with_numlock, 1 << mod_mod2, 0, 1 << mod_numlock, 0, 0, group);
1276 }
1277}
1278
1281{
1282 GWL_KeyRepeatPlayload *payload = static_cast<GWL_KeyRepeatPlayload *>(task->getUserData());
1283
1284 GWL_Seat *seat = payload->seat;
1285 wl_surface *wl_surface_focus = seat->keyboard.wl.surface_window;
1286 if (UNLIKELY(wl_surface_focus == nullptr)) {
1287 return;
1288 }
1289
1290 GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
1291 GHOST_SystemWayland *system = seat->system;
1292 const uint64_t event_ms = payload->time_ms_init + time_ms;
1293 /* Calculate this value every time in case modifier keys are pressed. */
1294
1295 char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
1296 if (seat->xkb.compose_state &&
1298 seat->xkb.compose_state, seat->xkb.state, payload->key_code, utf8_buf))
1299 {
1300 /* `utf8_buf` has been filled by a compose action. */
1301 }
1302 else {
1303 xkb_state_key_get_utf8(seat->xkb.state, payload->key_code, utf8_buf, sizeof(utf8_buf));
1304 }
1305
1307 event_ms, GHOST_kEventKeyDown, win, payload->key_data.gkey, true, utf8_buf));
1308}
1309
1314 GHOST_TimerProcPtr key_repeat_fn,
1315 GHOST_TUserDataPtr payload,
1316 const bool use_delay)
1317{
1318 GHOST_SystemWayland *system = seat->system;
1319 const uint64_t time_now = system->getMilliSeconds();
1320 const uint64_t time_step = 1000 / seat->key_repeat.rate;
1321 const uint64_t time_start = use_delay ? seat->key_repeat.delay : time_step;
1322
1323 static_cast<GWL_KeyRepeatPlayload *>(payload)->time_ms_init = time_now;
1324
1325#ifdef USE_EVENT_BACKGROUND_THREAD
1327 time_now + time_start, time_step, key_repeat_fn, payload);
1328 seat->key_repeat.timer = timer;
1330#else
1331 seat->key_repeat.timer = system->installTimer(time_start, time_step, key_repeat_fn, payload);
1332#endif
1333}
1334
1339{
1340 GHOST_SystemWayland *system = seat->system;
1341#ifdef USE_EVENT_BACKGROUND_THREAD
1343 static_cast<GHOST_TimerTask *>(seat->key_repeat.timer));
1344#else
1345 system->removeTimer(seat->key_repeat.timer);
1346#endif
1347 seat->key_repeat.timer = nullptr;
1348}
1349
1350#ifdef WITH_INPUT_IME
1351
1352static void gwl_seat_ime_full_reset(GWL_Seat *seat)
1353{
1354 const GWL_SeatIME ime_default{};
1355 /* Preserve the following members since they represent the state of the connection to Wayland.
1356 * or which callbacks have run (which shouldn't be reset). */
1357 wl_surface *surface_window = seat->ime.surface_window;
1358 const bool is_enabled = seat->ime.is_enabled;
1359 const bool has_preedit_string_callback = seat->ime.has_preedit_string_callback;
1360 const bool has_commit_string_callback = seat->ime.has_commit_string_callback;
1361
1362 seat->ime = ime_default;
1363
1364 seat->ime.surface_window = surface_window;
1365 seat->ime.is_enabled = is_enabled;
1366 seat->ime.has_preedit_string_callback = has_preedit_string_callback;
1367 seat->ime.has_commit_string_callback = has_commit_string_callback;
1368}
1369
1370static void gwl_seat_ime_result_reset(GWL_Seat *seat)
1371{
1372 GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
1373 event_ime_data.result.clear();
1374 seat->ime.result_is_null = false;
1375}
1376
1377static void gwl_seat_ime_preedit_reset(GWL_Seat *seat)
1378{
1379
1380 GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
1381 event_ime_data.composite.clear();
1382 seat->ime.composite_is_null = false;
1383
1384 event_ime_data.cursor_position = -1;
1385 event_ime_data.target_start = -1;
1386 event_ime_data.target_end = -1;
1387}
1388
1389static void gwl_seat_ime_rect_reset(GWL_Seat *seat)
1390{
1391 seat->ime.rect.x = -1;
1392 seat->ime.rect.y = -1;
1393 seat->ime.rect.w = -1;
1394 seat->ime.rect.h = -1;
1395}
1396
1397#endif /* WITH_INPUT_IME */
1398
1400
1401/* -------------------------------------------------------------------- */
1404
1405struct GWL_RegistryEntry;
1406
1418 bool exact_match = false;
1419 uint32_t last = 0;
1421};
1422
1424
1426 struct {
1427 wl_registry *registry = nullptr;
1429 wl_compositor *compositor = nullptr;
1430 wl_shm *shm = nullptr;
1431
1432 /* Managers. */
1433 wl_data_device_manager *data_device_manager = nullptr;
1435
1437 struct {
1438 /* Managers. */
1439 zwp_tablet_manager_v2 *tablet_manager = nullptr;
1440 zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr;
1441 zwp_primary_selection_device_manager_v1 *primary_selection_device_manager = nullptr;
1442 wp_fractional_scale_manager_v1 *fractional_scale_manager = nullptr;
1443 wp_viewporter *viewporter = nullptr;
1444 zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
1445 zwp_pointer_gestures_v1 *pointer_gestures = nullptr;
1446#ifdef WITH_INPUT_IME
1447 zwp_text_input_manager_v3 *text_input_manager = nullptr;
1448#endif
1450
1452 struct {
1453 /* Managers. */
1454 zxdg_output_manager_v1 *output_manager = nullptr;
1455 xdg_activation_v1 *activation_manager = nullptr;
1457
1459
1461
1467
1470
1471#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1472 GWL_LibDecor_System *libdecor = nullptr;
1473#endif
1475
1481 std::vector<GWL_Output *> outputs;
1482 std::vector<GWL_Seat *> seats;
1495
1504 bool background = false;
1505
1506 /* Threaded event handling. */
1507#ifdef USE_EVENT_BACKGROUND_THREAD
1514 pthread_t events_pthread = 0;
1521
1526 std::vector<const GHOST_IEvent *> events_pending;
1529
1539
1540#endif /* USE_EVENT_BACKGROUND_THREAD */
1541};
1542
1550{
1551#ifdef USE_EVENT_BACKGROUND_THREAD
1552 if (!display->background) {
1553 if (display->events_pthread) {
1555 display->events_pthread_is_active = false;
1556 }
1557 }
1558#endif
1559
1560 /* Stop all animated cursors (freeing their #GWL_Cursor_AnimHandle). */
1561 for (GWL_Seat *seat : display->seats) {
1563 }
1564
1565 /* For typical WAYLAND use this will always be set.
1566 * However when WAYLAND isn't running, this will early-exit and be null. */
1567 if (display->wl.registry) {
1568 wl_registry_destroy(display->wl.registry);
1569 display->wl.registry = nullptr;
1570 }
1571
1572 /* Unregister items in reverse order. */
1574
1575#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1576 if (use_libdecor) {
1577 if (display->libdecor) {
1578 gwl_libdecor_system_destroy(display->libdecor);
1579 display->libdecor = nullptr;
1580 }
1581 }
1582 else
1583#endif
1584 {
1585 if (display->xdg_decor) {
1586 gwl_xdg_decor_system_destroy(display, display->xdg_decor);
1587 display->xdg_decor = nullptr;
1588 }
1589 }
1590
1591#ifdef WITH_OPENGL_BACKEND
1592 if (display->wl.display) {
1593 if (eglGetDisplay) {
1594 ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl.display)));
1595 }
1596 }
1597#endif
1598
1599#ifdef USE_EVENT_BACKGROUND_THREAD
1600 if (!display->background) {
1601 if (display->events_pthread) {
1603 display->system->server_mutex->unlock();
1604 }
1605 }
1606
1607 /* Important to remove after the seats which may have key repeat timers active. */
1608 if (display->ghost_timer_manager) {
1609 delete display->ghost_timer_manager;
1610 display->ghost_timer_manager = nullptr;
1611 }
1612 /* Pending events may be left unhandled. */
1613 for (const GHOST_IEvent *event : display->events_pending) {
1614 delete event;
1615 }
1616
1617#endif /* USE_EVENT_BACKGROUND_THREAD */
1618
1619 if (display->wl.display) {
1621 }
1622
1623 delete display;
1624}
1625
1626static int gwl_display_seat_index(GWL_Display *display, const GWL_Seat *seat)
1627{
1628 std::vector<GWL_Seat *>::iterator iter = std::find(
1629 display->seats.begin(), display->seats.end(), seat);
1630 const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) :
1631 -1;
1632 GHOST_ASSERT(index != -1, "invalid internal state");
1633 return index;
1634}
1635
1644{
1645 if (UNLIKELY(display->seats.empty())) {
1646 return nullptr;
1647 }
1648 return display->seats[display->seats_active_index];
1649}
1650
1651static bool gwl_display_seat_active_set(GWL_Display *display, const GWL_Seat *seat)
1652{
1653 if (UNLIKELY(display->seats.empty())) {
1654 return false;
1655 }
1656 const int index = gwl_display_seat_index(display, seat);
1657 if (index == display->seats_active_index) {
1658 return false;
1659 }
1660 display->seats_active_index = index;
1661 return true;
1662}
1663
1665
1666/* -------------------------------------------------------------------- */
1669
1671 uint32_t name = 0;
1674 uint32_t version = 0;
1675};
1676
1685using GWL_RegistryHandler_AddFn = void (*)(GWL_Display *display,
1687
1689 uint32_t name = 0;
1692 uint32_t version = 0;
1693
1695 void *user_data = nullptr;
1696};
1697
1706
1717using GWL_RegistryEntry_RemoveFn = void (*)(GWL_Display *display, void *user_data, bool on_exit);
1718
1730
1732
1733/* -------------------------------------------------------------------- */
1736
1748 void *user_data = nullptr;
1756 uint32_t version;
1762};
1763
1766 void *user_data)
1767{
1769
1770 reg->interface_slot = params.interface_slot;
1771 reg->name = params.name;
1772 reg->version = params.version;
1773 reg->user_data = user_data;
1774
1775 reg->next = display->registry_entry;
1776 display->registry_entry = reg;
1777}
1778
1780 uint32_t name,
1781 int *r_interface_slot)
1782{
1783 GWL_RegistryEntry *reg = display->registry_entry;
1784 GWL_RegistryEntry **reg_link_p = &display->registry_entry;
1785 bool found = false;
1786
1787 if (r_interface_slot) {
1788 *r_interface_slot = -1;
1789 }
1790
1791 while (reg) {
1792 if (reg->name == name) {
1793 GWL_RegistryEntry *reg_next = reg->next;
1795 reg->interface_slot);
1796 handler->remove_fn(display, reg->user_data, false);
1797 if (r_interface_slot) {
1798 *r_interface_slot = reg->interface_slot;
1799 }
1800 delete reg;
1801 *reg_link_p = reg_next;
1802 found = true;
1803 break;
1804 }
1805 reg_link_p = &reg->next;
1806 reg = reg->next;
1807 }
1808 return found;
1809}
1810
1812 int interface_slot,
1813 bool on_exit)
1814{
1815 GWL_RegistryEntry *reg = display->registry_entry;
1816 GWL_RegistryEntry **reg_link_p = &display->registry_entry;
1817 bool found = false;
1818
1819 while (reg) {
1820 if (reg->interface_slot == interface_slot) {
1821 GWL_RegistryEntry *reg_next = reg->next;
1823 interface_slot);
1824 handler->remove_fn(display, reg->user_data, on_exit);
1825 delete reg;
1826 *reg_link_p = reg_next;
1827 reg = reg_next;
1828 found = true;
1829 continue;
1830 }
1831 reg_link_p = &reg->next;
1832 reg = reg->next;
1833 }
1834 return found;
1835}
1836
1841{
1842 const bool on_exit = true;
1843
1844 /* NOTE(@ideasman42): Free by slot instead of simply looping over
1845 * `display->registry_entry` so the order of freeing is always predictable.
1846 * Otherwise global objects would be feed in the order they are registered.
1847 * While this works in my tests, it could cause difficult to reproduce bugs
1848 * where lesser used compositors or changes to existing compositors could
1849 * crash on exit based on the order of freeing objects is out of our control.
1850 *
1851 * To give a concrete example of how this could fail, it's possible removing
1852 * a tablet interface could reference the pointer interface, or the output interface.
1853 * Even though references between interfaces shouldn't be necessary in most cases
1854 * when `on_exit` is enabled. */
1855 int interface_slot = gwl_registry_handler_interface_slot_max();
1856 while (interface_slot--) {
1857 gwl_registry_entry_remove_by_interface_slot(display, interface_slot, on_exit);
1858 }
1859
1860 GHOST_ASSERT(display->registry_entry == nullptr, "Failed to remove all entries!");
1861 display->registry_entry = nullptr;
1862}
1863
1880static void gwl_registry_entry_update_all(GWL_Display *display, const int interface_slot_exclude)
1881{
1882 GHOST_ASSERT(interface_slot_exclude == -1 || (uint(interface_slot_exclude) <
1884 "Invalid exclude slot");
1885
1886 for (GWL_RegistryEntry *reg = display->registry_entry; reg; reg = reg->next) {
1887 if (reg->interface_slot == interface_slot_exclude) {
1888 continue;
1889 }
1891 reg->interface_slot);
1892 if (handler->update_fn == nullptr) {
1893 continue;
1894 }
1895
1897 params.name = reg->name;
1898 params.interface_slot = reg->interface_slot;
1899 params.version = reg->version;
1900 params.user_data = reg->user_data;
1901
1902 handler->update_fn(display, params);
1903 }
1904}
1905
1907
1908/* -------------------------------------------------------------------- */
1911
1912#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1913static const char *strchr_or_end(const char *str, const char ch)
1914{
1915 const char *p = str;
1916 while (!ELEM(*p, ch, '\0')) {
1917 p++;
1918 }
1919 return p;
1920}
1921
1922static bool string_elem_split_by_delim(const char *haystack, const char delim, const char *needle)
1923{
1924 /* Local copy of #BLI_string_elem_split_by_delim (would be a bad level call). */
1925
1926 /* May be zero, returns true when an empty span exists. */
1927 const size_t needle_len = strlen(needle);
1928 const char *p = haystack, *p_next;
1929 while (true) {
1930 p_next = strchr_or_end(p, delim);
1931 if ((size_t(p_next - p) == needle_len) && (memcmp(p, needle, needle_len) == 0)) {
1932 return true;
1933 }
1934 if (*p_next == '\0') {
1935 break;
1936 }
1937 p = p_next + 1;
1938 }
1939 return false;
1940}
1941#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
1942
1944{
1945 return a > b ? a - b : b - a;
1946}
1947
1951static uint64_t ghost_wl_ms_from_utime_pair(uint32_t utime_hi, uint32_t utime_lo)
1952{
1953 return ((uint64_t(utime_hi) << 32) | uint64_t(utime_lo)) / 1000;
1954}
1955
1960{
1961 const char *locale = getenv("LC_ALL");
1962 if (!locale || !*locale) {
1963 locale = getenv("LC_CTYPE");
1964 if (!locale || !*locale) {
1965 locale = getenv("LANG");
1966 if (!locale || !*locale) {
1967 locale = "C";
1968 }
1969 }
1970 }
1971 return locale;
1972}
1973
1974static void ghost_wl_display_report_error_from_code(wl_display *display, const int ecode)
1975{
1976 GHOST_ASSERT(ecode, "Error not set!");
1977 if (ELEM(ecode, EPIPE, ECONNRESET)) {
1978 fprintf(stderr, "The Wayland connection broke. Did the Wayland compositor die?\n");
1979 return;
1980 }
1981
1982 if (ecode == EPROTO) {
1983 const wl_interface *interface = nullptr;
1984 const int ecode_proto = wl_display_get_protocol_error(display, &interface, nullptr);
1985 fprintf(stderr,
1986 "The Wayland connection experienced a protocol error %d in interface: %s\n",
1987 ecode_proto,
1988 interface ? interface->name : "<nil>");
1989 const char *env_debug = "WAYLAND_DEBUG";
1990 if (getenv(env_debug) == nullptr) {
1991 fprintf(stderr, "Run with the environment variable \"%s=1\" for details.\n", env_debug);
1992 }
1993 return;
1994 }
1995
1996 fprintf(stderr, "The Wayland connection experienced a fatal error: %s\n", strerror(ecode));
1997}
1998
2000{
2001 int ecode = wl_display_get_error(display);
2002 GHOST_ASSERT(ecode, "Error not set!");
2004
2005 /* NOTE(@ideasman42): The application is running,
2006 * however an error closes all windows and most importantly:
2007 * shuts down the GPU context (loosing all GPU state - shaders, bind codes etc),
2008 * so recovering from this effectively involves restarting.
2009 *
2010 * Keeping the GPU state alive doesn't seem to be supported as windows EGL context must use the
2011 * same connection as the used for all other WAYLAND interactions (see #wl_display_connect).
2012 * So in practice re-connecting to the display server isn't an option.
2013 *
2014 * Exit since leaving the process open will simply flood the output and do nothing.
2015 * Although as the process is in a valid state, auto-save for example is possible, see: #100855.
2016 */
2017 ::exit(-1);
2018}
2019
2021{
2022 const int ecode = wl_display_get_error(display);
2023 if (ecode == 0) {
2024 return false;
2025 }
2027 return true;
2028}
2029
2030#ifdef __GNUC__
2031static void ghost_wayland_log_handler(const char *msg, va_list arg)
2032 __attribute__((format(printf, 1, 0)));
2033static void ghost_wayland_log_handler_background(const char *msg, va_list arg)
2034 __attribute__((format(printf, 1, 0)));
2035#endif
2036
2043static void ghost_wayland_log_handler(const char *msg, va_list arg)
2044{
2045 fprintf(stderr, "GHOST/Wayland: ");
2046 vfprintf(stderr, msg, arg); /* Includes newline. */
2047
2049 if (backtrace_fn) {
2050 backtrace_fn(stderr); /* Includes newline. */
2051 }
2052}
2053
2055static void ghost_wayland_log_handler_background(const char *msg, va_list arg)
2056{
2057 /* This is fine in background mode, we will try to fall back to headless GPU context.
2058 * Happens when render farm process runs without user login session. */
2059 if (strstr(msg, "error: XDG_RUNTIME_DIR not set in the environment") ||
2060 strstr(msg, "error: XDG_RUNTIME_DIR is invalid or not set in the environment"))
2061 {
2062 return;
2063 }
2064 ghost_wayland_log_handler(msg, arg);
2065}
2066
2067#if defined(WITH_GHOST_X11) && defined(WITH_GHOST_WAYLAND_LIBDECOR)
2073static bool ghost_wayland_is_x11_available()
2074{
2075 const char *x11_display = getenv("DISPLAY");
2076 if (x11_display && x11_display[0]) {
2077 return true;
2078 }
2079 return false;
2080}
2081#endif /* WITH_GHOST_X11 && WITH_GHOST_WAYLAND_LIBDECOR */
2082
2083static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym)
2084{
2085
2086 GHOST_TKey gkey;
2087 if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) {
2088 gkey = GHOST_TKey(sym);
2089 }
2090 else if (sym >= XKB_KEY_KP_0 && sym <= XKB_KEY_KP_9) {
2091 gkey = GHOST_TKey(GHOST_kKeyNumpad0 + sym - XKB_KEY_KP_0);
2092 }
2093 else if (sym >= XKB_KEY_A && sym <= XKB_KEY_Z) {
2094 gkey = GHOST_TKey(sym);
2095 }
2096 else if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) {
2097 gkey = GHOST_TKey(sym - XKB_KEY_a + XKB_KEY_A);
2098 }
2099 else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F24) {
2100 gkey = GHOST_TKey(GHOST_kKeyF1 + sym - XKB_KEY_F1);
2101 }
2102 else {
2103
2104#define GXMAP(k, x, y) \
2105 case x: \
2106 k = y; \
2107 break
2108
2109 switch (sym) {
2110 GXMAP(gkey, XKB_KEY_BackSpace, GHOST_kKeyBackSpace);
2111 GXMAP(gkey, XKB_KEY_Tab, GHOST_kKeyTab);
2112 GXMAP(gkey, XKB_KEY_Linefeed, GHOST_kKeyLinefeed);
2113 GXMAP(gkey, XKB_KEY_Clear, GHOST_kKeyClear);
2114 GXMAP(gkey, XKB_KEY_Return, GHOST_kKeyEnter);
2115
2116 GXMAP(gkey, XKB_KEY_Escape, GHOST_kKeyEsc);
2117 GXMAP(gkey, XKB_KEY_space, GHOST_kKeySpace);
2118 GXMAP(gkey, XKB_KEY_apostrophe, GHOST_kKeyQuote);
2119 GXMAP(gkey, XKB_KEY_comma, GHOST_kKeyComma);
2120 GXMAP(gkey, XKB_KEY_minus, GHOST_kKeyMinus);
2121 GXMAP(gkey, XKB_KEY_plus, GHOST_kKeyPlus);
2122 GXMAP(gkey, XKB_KEY_period, GHOST_kKeyPeriod);
2123 GXMAP(gkey, XKB_KEY_slash, GHOST_kKeySlash);
2124
2125 GXMAP(gkey, XKB_KEY_semicolon, GHOST_kKeySemicolon);
2126 GXMAP(gkey, XKB_KEY_equal, GHOST_kKeyEqual);
2127
2128 GXMAP(gkey, XKB_KEY_bracketleft, GHOST_kKeyLeftBracket);
2129 GXMAP(gkey, XKB_KEY_bracketright, GHOST_kKeyRightBracket);
2130 GXMAP(gkey, XKB_KEY_backslash, GHOST_kKeyBackslash);
2131 GXMAP(gkey, XKB_KEY_grave, GHOST_kKeyAccentGrave);
2132
2133 GXMAP(gkey, XKB_KEY_Shift_L, GHOST_kKeyLeftShift);
2134 GXMAP(gkey, XKB_KEY_Shift_R, GHOST_kKeyRightShift);
2135 GXMAP(gkey, XKB_KEY_Control_L, GHOST_kKeyLeftControl);
2136 GXMAP(gkey, XKB_KEY_Control_R, GHOST_kKeyRightControl);
2137 GXMAP(gkey, XKB_KEY_Alt_L, GHOST_kKeyLeftAlt);
2138 GXMAP(gkey, XKB_KEY_Alt_R, GHOST_kKeyRightAlt);
2139 GXMAP(gkey, XKB_KEY_Super_L, GHOST_kKeyLeftOS);
2140 GXMAP(gkey, XKB_KEY_Super_R, GHOST_kKeyRightOS);
2141 GXMAP(gkey, XKB_KEY_Hyper_L, GHOST_kKeyLeftHyper);
2142 GXMAP(gkey, XKB_KEY_Hyper_R, GHOST_kKeyRightHyper);
2143 GXMAP(gkey, XKB_KEY_Menu, GHOST_kKeyApp);
2144
2145 GXMAP(gkey, XKB_KEY_Caps_Lock, GHOST_kKeyCapsLock);
2146 GXMAP(gkey, XKB_KEY_Num_Lock, GHOST_kKeyNumLock);
2147 GXMAP(gkey, XKB_KEY_Scroll_Lock, GHOST_kKeyScrollLock);
2148
2149 GXMAP(gkey, XKB_KEY_Left, GHOST_kKeyLeftArrow);
2150 GXMAP(gkey, XKB_KEY_Right, GHOST_kKeyRightArrow);
2151 GXMAP(gkey, XKB_KEY_Up, GHOST_kKeyUpArrow);
2152 GXMAP(gkey, XKB_KEY_Down, GHOST_kKeyDownArrow);
2153
2154 GXMAP(gkey, XKB_KEY_Print, GHOST_kKeyPrintScreen);
2155 GXMAP(gkey, XKB_KEY_Pause, GHOST_kKeyPause);
2156
2157 GXMAP(gkey, XKB_KEY_Insert, GHOST_kKeyInsert);
2158 GXMAP(gkey, XKB_KEY_Delete, GHOST_kKeyDelete);
2159 GXMAP(gkey, XKB_KEY_Home, GHOST_kKeyHome);
2160 GXMAP(gkey, XKB_KEY_End, GHOST_kKeyEnd);
2161 GXMAP(gkey, XKB_KEY_Page_Up, GHOST_kKeyUpPage);
2162 GXMAP(gkey, XKB_KEY_Page_Down, GHOST_kKeyDownPage);
2163
2164 GXMAP(gkey, XKB_KEY_KP_Decimal, GHOST_kKeyNumpadPeriod);
2165 GXMAP(gkey, XKB_KEY_KP_Enter, GHOST_kKeyNumpadEnter);
2166 GXMAP(gkey, XKB_KEY_KP_Add, GHOST_kKeyNumpadPlus);
2167 GXMAP(gkey, XKB_KEY_KP_Subtract, GHOST_kKeyNumpadMinus);
2168 GXMAP(gkey, XKB_KEY_KP_Multiply, GHOST_kKeyNumpadAsterisk);
2169 GXMAP(gkey, XKB_KEY_KP_Divide, GHOST_kKeyNumpadSlash);
2170
2171 GXMAP(gkey, XKB_KEY_XF86AudioPlay, GHOST_kKeyMediaPlay);
2172 GXMAP(gkey, XKB_KEY_XF86AudioStop, GHOST_kKeyMediaStop);
2173 GXMAP(gkey, XKB_KEY_XF86AudioPrev, GHOST_kKeyMediaFirst);
2174 GXMAP(gkey, XKB_KEY_XF86AudioNext, GHOST_kKeyMediaLast);
2175
2176 /* Additional keys for non US layouts. */
2177
2178 /* Uses the same physical key as #XKB_KEY_KP_Decimal for QWERTZ layout, see: #102287. */
2179 GXMAP(gkey, XKB_KEY_KP_Separator, GHOST_kKeyNumpadPeriod);
2180 GXMAP(gkey, XKB_KEY_less, GHOST_kKeyGrLess);
2181
2182 default:
2183 /* Rely on #xkb_map_gkey_or_scan_code to report when no key can be found. */
2184 gkey = GHOST_kKeyUnknown;
2185 }
2186#undef GXMAP
2187 }
2188
2189 return gkey;
2190}
2191
2199static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32_t key)
2200{
2201 GHOST_TKey gkey = xkb_map_gkey(sym);
2202
2203 if (UNLIKELY(gkey == GHOST_kKeyUnknown)) {
2204 /* Fall back to physical location for keys that would otherwise do nothing. */
2205 switch (key) {
2206 case KEY_GRAVE: {
2207 gkey = GHOST_kKeyAccentGrave;
2208 break;
2209 }
2210 case KEY_102ND: {
2211 gkey = GHOST_kKeyGrLess;
2212 break;
2213 }
2214 default: {
2216 /* Key-code. */
2217 "unhandled key: " << std::hex << std::showbase << sym << /* Hex. */
2218 std::dec << " (" << sym << "), " << /* Decimal. */
2219 /* Scan-code. */
2220 "scan-code: " << std::hex << std::showbase << key << /* Hex. */
2221 std::dec << " (" << key << ")" << /* Decimal. */
2222 std::endl);
2223 break;
2224 }
2225 }
2226 }
2227
2228 return gkey;
2229}
2230
2231static int pointer_axis_as_index(const uint32_t axis)
2232{
2233 switch (axis) {
2234 case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {
2235 return 0;
2236 }
2237 case WL_POINTER_AXIS_VERTICAL_SCROLL: {
2238 return 1;
2239 }
2240 default: {
2241 return -1;
2242 }
2243 }
2244}
2245
2246static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type)
2247{
2248 switch (wp_tablet_tool_type) {
2249 case ZWP_TABLET_TOOL_V2_TYPE_ERASER: {
2251 }
2252 case ZWP_TABLET_TOOL_V2_TYPE_PEN:
2253 case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
2254 case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
2255 case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
2256 case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
2257 case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
2258 case ZWP_TABLET_TOOL_V2_TYPE_LENS: {
2260 }
2261 }
2262
2263 GHOST_PRINT("unknown tablet tool: " << wp_tablet_tool_type << std::endl);
2265}
2266
2267static const int default_cursor_size = 24;
2268
2270 const char *names[GHOST_kStandardCursorNumCursors] = {nullptr};
2271};
2272
2274 GWL_Cursor_ShapeInfo info{};
2275
2276#define CASE_CURSOR(shape_id, shape_name_in_theme) \
2277 case shape_id: { \
2278 info.names[int(shape_id)] = shape_name_in_theme; \
2279 } \
2280 ((void)0)
2281
2282 /* Use a switch to ensure missing values show a compiler warning. */
2287 CASE_CURSOR(GHOST_kStandardCursorInfo, "left_ptr_help");
2289 CASE_CURSOR(GHOST_kStandardCursorHelp, "question_arrow");
2313 CASE_CURSOR(GHOST_kStandardCursorStop, "not-allowed");
2314 CASE_CURSOR(GHOST_kStandardCursorUpDown, "sb_v_double_arrow");
2315 CASE_CURSOR(GHOST_kStandardCursorLeftRight, "sb_h_double_arrow");
2330 }
2331#undef CASE_CURSOR
2332
2333 return info;
2334}();
2335
2336static constexpr const char *ghost_wl_mime_text_plain = "text/plain";
2337static constexpr const char *ghost_wl_mime_text_utf8 = "text/plain;charset=utf-8";
2338static constexpr const char *ghost_wl_mime_text_uri_list = "text/uri-list";
2339
2345/* Aligned to `ghost_wl_mime_preference_order`. */
2351
2352static const char *ghost_wl_mime_send[] = {
2353 "UTF8_STRING",
2354 "COMPOUND_TEXT",
2355 "TEXT",
2356 "STRING",
2357 "text/plain;charset=utf-8",
2358 "text/plain",
2359};
2360
2365static std::vector<std::string_view> gwl_clipboard_uri_ranges(const char *data_buf,
2366 size_t data_buf_len)
2367{
2368 std::vector<std::string_view> uris;
2369 const char file_proto[] = "file://";
2370 /* NOTE: some applications CRLF (`\r\n`) GTK3 for example & others don't `pcmanfm-qt`.
2371 * So support both, once `\n` is found, strip the preceding `\r` if found. */
2372 const char lf = '\n';
2373
2374 const std::string_view data = std::string_view(data_buf, data_buf_len);
2375
2376 size_t pos = 0;
2377 while (pos != std::string::npos) {
2378 pos = data.find(file_proto, pos);
2379 if (pos == std::string::npos) {
2380 break;
2381 }
2382 const size_t start = pos + sizeof(file_proto) - 1;
2383 pos = data.find(lf, pos);
2384
2385 size_t end = pos;
2386 if (UNLIKELY(end == std::string::npos)) {
2387 /* Note that most well behaved file managers will add a trailing newline,
2388 * Gnome's web browser (44.3) doesn't, so support reading up until the last byte. */
2389 end = data.size();
2390 }
2391 /* Account for 'CRLF' case. */
2392 if (data[end - 1] == '\r') {
2393 end -= 1;
2394 }
2395
2396 std::string_view data_substr = data.substr(start, end - start);
2397 uris.push_back(data_substr);
2398 }
2399 return uris;
2400}
2401
2402#ifdef USE_EVENT_BACKGROUND_THREAD
2403static void pthread_set_min_priority(pthread_t handle)
2404{
2405 int policy;
2406 sched_param sch_params;
2407 if (pthread_getschedparam(handle, &policy, &sch_params) == 0) {
2408 sch_params.sched_priority = sched_get_priority_min(policy);
2409 pthread_setschedparam(handle, policy, &sch_params);
2410 }
2411}
2412
2413static void thread_set_min_priority(std::thread &thread)
2414{
2415 constexpr bool is_pthread = std::is_same<std::thread::native_handle_type, pthread_t>();
2416 if (!is_pthread) {
2417 return;
2418 }
2419 /* The cast is "safe" as non-matching types will have returned already.
2420 * This cast might be avoided with clever template use. */
2421 pthread_set_min_priority(reinterpret_cast<pthread_t>(thread.native_handle()));
2422}
2423#endif /* USE_EVENT_BACKGROUND_THREAD */
2424
2425static int memfd_create_sealed(const char *name)
2426{
2427#ifdef HAVE_MEMFD_CREATE
2428 const int fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
2429 if (fd >= 0) {
2430 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
2431 }
2432 return fd;
2433#else /* HAVE_MEMFD_CREATE */
2434 char *path = getenv("XDG_RUNTIME_DIR");
2435 if (!path) {
2436 errno = ENOENT;
2437 return -1;
2438 }
2439 char *tmpname;
2440 asprintf(&tmpname, "%s/%s-XXXXXX", path, name);
2441 const int fd = mkostemp(tmpname, O_CLOEXEC);
2442 if (fd >= 0) {
2443 unlink(tmpname);
2444 }
2445 free(tmpname);
2446 return fd;
2447#endif /* !HAVE_MEMFD_CREATE */
2448}
2449
2450enum {
2454};
2455
2456static int file_descriptor_is_io_ready(int fd, const int flags, const int timeout_ms)
2457{
2458 int result;
2459
2460 GHOST_ASSERT(flags & (GWL_IOR_READ | GWL_IOR_WRITE), "X");
2461
2462 /* NOTE: We don't bother to account for elapsed time if we get #EINTR. */
2463 do {
2464#ifdef HAVE_POLL
2465 pollfd info;
2466
2467 info.fd = fd;
2468 info.events = 0;
2469 if (flags & GWL_IOR_READ) {
2470 info.events |= POLLIN | POLLPRI;
2471 }
2472 if (flags & GWL_IOR_WRITE) {
2473 info.events |= POLLOUT;
2474 }
2475 result = poll(&info, 1, timeout_ms);
2476#else
2477 fd_set rfdset, *rfdp = nullptr;
2478 fd_set wfdset, *wfdp = nullptr;
2479 struct timeval tv, *tvp = nullptr;
2480
2481 /* If this assert triggers we'll corrupt memory here */
2482 GHOST_ASSERT(fd >= 0 && fd < FD_SETSIZE, "X");
2483
2484 if (flags & GWL_IOR_READ) {
2485 FD_ZERO(&rfdset);
2486 FD_SET(fd, &rfdset);
2487 rfdp = &rfdset;
2488 }
2489 if (flags & GWL_IOR_WRITE) {
2490 FD_ZERO(&wfdset);
2491 FD_SET(fd, &wfdset);
2492 wfdp = &wfdset;
2493 }
2494
2495 if (timeout_ms >= 0) {
2496 tv.tv_sec = timeout_ms / 1000;
2497 tv.tv_usec = (timeout_ms % 1000) * 1000;
2498 tvp = &tv;
2499 }
2500
2501 result = select(fd + 1, rfdp, wfdp, nullptr, tvp);
2502#endif /* !HAVE_POLL */
2503 } while (result < 0 && errno == EINTR && !(flags & GWL_IOR_NO_RETRY));
2504
2505 return result;
2506}
2507
2509{
2510 /* Based on SDL's `Wayland_PumpEvents`. */
2511 int err;
2512
2513 /* NOTE: Without this, interactions with window borders via LIBDECOR doesn't function. */
2515
2517 /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
2520 {
2522 }
2523 else {
2525 err = 0;
2526 }
2527 }
2528 else {
2530 }
2531 return err;
2532}
2533
2534#ifdef USE_EVENT_BACKGROUND_THREAD
2535
2536static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex)
2537{
2538 const int fd = wl_display_get_fd(wl_display);
2539 int state;
2540 do {
2542 /* Re-check `state` with a lock held, needed to avoid holding the lock. */
2543 if (state == 0) {
2544 server_mutex->lock();
2546 if (state == 0) {
2547 break;
2548 }
2549 }
2550 } while (state == 0);
2551}
2552
2554 const int fd,
2555 std::mutex *server_mutex)
2556{
2557 /* Based on SDL's `Wayland_PumpEvents`. */
2558 server_mutex->lock();
2559 int err = 0;
2561 bool wait_on_fd = false;
2562 /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
2565 }
2566 else {
2568 /* Without this, the thread will loop continuously, 100% CPU. */
2569 wait_on_fd = true;
2570 }
2571
2572 server_mutex->unlock();
2573
2574 if (wait_on_fd) {
2575 /* Important this runs after unlocking. */
2577 }
2578 }
2579 else {
2580 server_mutex->unlock();
2581
2582 /* Wait for input (unlocked, so as not to block other threads). */
2584 /* Re-check `state` with a lock held, needed to avoid holding the lock. */
2585 if (state > 0) {
2586 server_mutex->lock();
2588 if (state > 0) {
2590 }
2591 server_mutex->unlock();
2592 }
2593 }
2594
2595 return err;
2596}
2597#endif /* USE_EVENT_BACKGROUND_THREAD */
2598
2599static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format)
2600{
2601 switch (format) {
2602 case WL_SHM_FORMAT_ARGB8888: {
2603 return 4;
2604 }
2605 default: {
2606 /* Support other formats as needed. */
2607 GHOST_ASSERT(0, "Unexpected format passed in!");
2608 return 4;
2609 }
2610 }
2611}
2612
2620static wl_buffer *ghost_wl_buffer_create_for_image(wl_shm *shm,
2621 const int32_t size_xy[2],
2622 enum wl_shm_format format,
2623 void **r_buffer_data,
2624 size_t *r_buffer_data_size)
2625{
2626 const int fd = memfd_create_sealed("ghost-wl-buffer");
2627 wl_buffer *buffer = nullptr;
2628 if (fd >= 0) {
2629 const int32_t buffer_stride = size_xy[0] * ghost_wl_shm_format_as_size(format);
2630 const int32_t buffer_size = buffer_stride * size_xy[1];
2631 if (posix_fallocate(fd, 0, buffer_size) == 0) {
2632 void *buffer_data = mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
2633 if (buffer_data != MAP_FAILED) {
2634 wl_shm_pool *pool = wl_shm_create_pool(shm, fd, buffer_size);
2635 buffer = wl_shm_pool_create_buffer(pool, 0, UNPACK2(size_xy), buffer_stride, format);
2636 wl_shm_pool_destroy(pool);
2637 if (buffer) {
2638 *r_buffer_data = buffer_data;
2639 *r_buffer_data_size = size_t(buffer_size);
2640 }
2641 else {
2642 /* Highly unlikely. */
2643 munmap(buffer_data, buffer_size);
2644 }
2645 }
2646 }
2647 close(fd);
2648 }
2649 return buffer;
2650}
2651
2656static ssize_t read_exhaustive(const int fd, void *data, size_t nbytes)
2657{
2658 ssize_t nbytes_read = read(fd, data, nbytes);
2659 if (nbytes_read > 0) {
2660 while (nbytes_read < nbytes) {
2661 const ssize_t nbytes_extra = read(
2662 fd, static_cast<char *>(data) + nbytes_read, nbytes - nbytes_read);
2663 if (nbytes_extra > 0) {
2664 nbytes_read += nbytes_extra;
2665 }
2666 else {
2667 if (UNLIKELY(nbytes_extra < 0)) {
2668 nbytes_read = nbytes_extra; /* Error. */
2669 }
2670 break;
2671 }
2672 }
2673 }
2674 return nbytes_read;
2675}
2676
2684static char *read_file_as_buffer(const int fd, const bool nil_terminate, size_t *r_len)
2685{
2686 struct ByteChunk {
2687 ByteChunk *next;
2688 char data[4096 - sizeof(ByteChunk *)];
2689 };
2690 bool ok = true;
2691 size_t len = 0;
2692
2693 ByteChunk *chunk_first = static_cast<ByteChunk *>(malloc(sizeof(ByteChunk)));
2694 {
2695 ByteChunk **chunk_link_p = &chunk_first;
2696 ByteChunk *chunk = chunk_first;
2697 while (true) {
2698 if (UNLIKELY(chunk == nullptr)) {
2699 errno = ENOMEM;
2700 ok = false;
2701 break;
2702 }
2703 chunk->next = nullptr;
2704 /* Using `read` causes issues with GNOME, see: #106040). */
2705 const ssize_t len_chunk = read_exhaustive(fd, chunk->data, sizeof(ByteChunk::data));
2706 if (len_chunk <= 0) {
2707 if (UNLIKELY(len_chunk < 0)) {
2708 ok = false;
2709 }
2710 if (chunk == chunk_first) {
2711 chunk_first = nullptr;
2712 }
2713 free(chunk);
2714 break;
2715 }
2716 *chunk_link_p = chunk;
2717 chunk_link_p = &chunk->next;
2718 len += len_chunk;
2719
2720 if (len_chunk != sizeof(ByteChunk::data)) {
2721 break;
2722 }
2723 chunk = static_cast<ByteChunk *>(malloc(sizeof(ByteChunk)));
2724 }
2725 }
2726
2727 char *buf = nullptr;
2728 if (ok) {
2729 buf = static_cast<char *>(malloc(len + (nil_terminate ? 1 : 0)));
2730 if (UNLIKELY(buf == nullptr)) {
2731 errno = ENOMEM;
2732 ok = false;
2733 }
2734 }
2735
2736 if (ok) {
2737 *r_len = len;
2738 if (nil_terminate) {
2739 buf[len] = '\0';
2740 }
2741 }
2742 else {
2743 *r_len = 0;
2744 }
2745
2746 char *buf_stride = buf;
2747 while (chunk_first) {
2748 if (ok) {
2749 const size_t len_chunk = std::min(len, sizeof(chunk_first->data));
2750 memcpy(buf_stride, chunk_first->data, len_chunk);
2751 buf_stride += len_chunk;
2752 len -= len_chunk;
2753 }
2754 ByteChunk *chunk = chunk_first->next;
2755 free(chunk_first);
2756 chunk_first = chunk;
2757 }
2758
2759 return buf;
2760}
2761
2763
2764/* -------------------------------------------------------------------- */
2767
2768static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image,
2769 wl_buffer *buffer,
2771 const int scale)
2772{
2773 const int32_t image_size_x = int32_t(wl_image->width);
2774 const int32_t image_size_y = int32_t(wl_image->height);
2775 GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0,
2776 "The size must be a multiple of the scale!");
2777
2778 wl_surface_set_buffer_scale(wl_surface, scale);
2779 wl_surface_attach(wl_surface, buffer, 0, 0);
2780 wl_surface_damage(wl_surface, 0, 0, image_size_x, image_size_y);
2781 wl_surface_commit(wl_surface);
2782}
2783
2787static int cursor_buffer_compatible_scale_from_image(const wl_cursor_image *wl_image, int scale)
2788{
2789 const int32_t image_size_x = int32_t(wl_image->width);
2790 const int32_t image_size_y = int32_t(wl_image->height);
2791 while (scale > 1) {
2792 if ((image_size_x % scale) == 0 && (image_size_y % scale) == 0) {
2793 break;
2794 }
2795 scale -= 1;
2796 }
2797 return scale;
2798}
2799
2800static const wl_cursor *gwl_seat_cursor_find_from_shape(GWL_Seat *seat,
2801 const GHOST_TStandardCursor shape,
2802 const char **r_cursor_name)
2803{
2804 /* Caller must lock `server_mutex`. */
2805 GWL_Cursor *cursor = &seat->cursor;
2806 wl_cursor *wl_cursor = nullptr;
2807
2808 const char *cursor_name = ghost_wl_cursors.names[shape];
2809 if (cursor_name[0] != '\0') {
2810 if (!cursor->wl.theme) {
2811 /* The cursor wl_surface hasn't entered an output yet. Initialize theme with scale 1. */
2812 cursor->wl.theme = wl_cursor_theme_load(
2813 (cursor->theme_name.empty() ? nullptr : cursor->theme_name.c_str()),
2814 cursor->theme_size,
2815 seat->system->wl_shm_get());
2816 }
2817
2818 if (cursor->wl.theme) {
2819 wl_cursor = wl_cursor_theme_get_cursor(cursor->wl.theme, cursor_name);
2820 if (!wl_cursor) {
2821 GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl);
2822 }
2823 }
2824 }
2825
2826 if (r_cursor_name && wl_cursor) {
2827 *r_cursor_name = cursor_name;
2828 }
2829 return wl_cursor;
2830}
2831
2839{
2840 const GWL_Cursor *cursor = &seat->cursor;
2841
2842 if (seat->wl.pointer) {
2843 const int scale = cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale;
2844 const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
2845 const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
2846 wl_pointer_set_cursor(
2847 seat->wl.pointer, seat->pointer.serial, cursor->wl.surface_cursor, hotspot_x, hotspot_y);
2848 }
2849
2850 if (!seat->wp.tablet_tools.empty()) {
2851 const int scale = cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale;
2852 const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
2853 const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
2854 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2855 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
2856 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
2857 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
2858 seat->tablet.serial,
2859 tablet_tool->wl.surface_cursor,
2860 hotspot_x,
2861 hotspot_y);
2862#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK
2863 wl_surface_commit(tablet_tool->wl.surface_cursor);
2864#endif
2865 }
2866 }
2867
2869}
2870
2878{
2880
2881 wl_pointer_set_cursor(seat->wl.pointer, seat->pointer.serial, nullptr, 0, 0);
2882 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2883 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, seat->tablet.serial, nullptr, 0, 0);
2884 }
2885}
2886
2887static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
2888 const wl_cursor_image *wl_image,
2889 wl_buffer *buffer)
2890{
2891 const GWL_Cursor *cursor = &seat->cursor;
2892 const bool visible = (cursor->visible && cursor->is_hardware);
2893
2894 /* This is a requirement of WAYLAND, when this isn't the case,
2895 * it causes Blender's window to close intermittently. */
2896 if (seat->wl.pointer) {
2898 wl_image, cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale);
2899 const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
2900 const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
2901 cursor_buffer_set_surface_impl(wl_image, buffer, cursor->wl.surface_cursor, scale);
2902 wl_pointer_set_cursor(seat->wl.pointer,
2903 seat->pointer.serial,
2904 visible ? cursor->wl.surface_cursor : nullptr,
2905 hotspot_x,
2906 hotspot_y);
2907 }
2908
2909 /* Set the cursor for all tablet tools as well. */
2910 if (!seat->wp.tablet_tools.empty()) {
2912 wl_image, cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale);
2913 const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
2914 const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
2915 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2916 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
2917 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
2918 cursor_buffer_set_surface_impl(wl_image, buffer, tablet_tool->wl.surface_cursor, scale);
2919 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
2920 seat->tablet.serial,
2921 visible ? tablet_tool->wl.surface_cursor : nullptr,
2922 hotspot_x,
2923 hotspot_y);
2924 }
2925 }
2926}
2927
2929{
2930 const GWL_Cursor *cursor = &seat->cursor;
2932 gwl_seat_cursor_buffer_set(seat, &cursor->wl.image, cursor->wl.buffer);
2934}
2935
2941
2943 const bool visible,
2944 const bool is_hardware,
2945 const enum eCursorSetMode set_mode)
2946{
2947 GWL_Cursor *cursor = &seat->cursor;
2948 const bool was_visible = cursor->is_hardware && cursor->visible;
2949 const bool use_visible = is_hardware && visible;
2950
2951 if (set_mode == CURSOR_VISIBLE_ALWAYS_SET) {
2952 /* Pass. */
2953 }
2954 else if (set_mode == CURSOR_VISIBLE_ONLY_SHOW) {
2955 if (!use_visible) {
2956 return;
2957 }
2958 }
2959 else if (set_mode == CURSOR_VISIBLE_ONLY_HIDE) {
2960 if (use_visible) {
2961 return;
2962 }
2963 }
2964
2965 if (use_visible) {
2966 if (!was_visible) {
2968 }
2969 }
2970 else {
2971 if (was_visible) {
2973 }
2974 }
2975 cursor->visible = visible;
2976 cursor->is_hardware = is_hardware;
2977}
2978
2980
2981/* -------------------------------------------------------------------- */
2984
2985#ifdef USE_EVENT_BACKGROUND_THREAD
2986
2988{
2989 const wl_cursor *wl_cursor = seat->cursor.wl.theme_cursor;
2990 if (!wl_cursor) {
2991 return false;
2992 }
2993 /* NOTE: return true to stress test animated cursor,
2994 * to ensure (otherwise rare) issues are triggered more frequently. */
2995 // return true;
2996
2997 return wl_cursor->image_count > 1;
2998}
2999
3001{
3002 /* Caller must lock `server_mutex`. */
3003 GHOST_ASSERT(seat->cursor.anim_handle == nullptr, "Must be cleared");
3004
3005 /* Callback for updating the cursor animation. */
3006 auto cursor_anim_frame_step_fn =
3007 [](GWL_Seat *seat, GWL_Cursor_AnimHandle *anim_handle, int delay) {
3008 /* It's possible the `wl_cursor` is reloaded while the cursor is animating.
3009 * Don't access outside the lock, that's why the `delay` is passed in. */
3010 std::mutex *server_mutex = seat->system->server_mutex;
3011 int frame = 0;
3012 while (!anim_handle->exit_pending.load()) {
3013 std::this_thread::sleep_for(std::chrono::milliseconds(delay));
3014 if (!anim_handle->exit_pending.load()) {
3015 std::lock_guard lock_server_guard{*server_mutex};
3016 if (!anim_handle->exit_pending.load()) {
3017 const wl_cursor *wl_cursor = seat->cursor.wl.theme_cursor;
3018 frame = (frame + 1) % wl_cursor->image_count;
3019 wl_cursor_image *image = wl_cursor->images[frame];
3020 wl_buffer *buffer = wl_cursor_image_get_buffer(image);
3021 gwl_seat_cursor_buffer_set(seat, image, buffer);
3022 delay = wl_cursor->images[frame]->delay;
3023 /* Without this the cursor won't update when other processes are occupied. */
3025 }
3026 }
3027 }
3028 delete anim_handle;
3029 };
3030
3031 /* Allocate so this can be set before the thread begins. */
3033 seat->cursor.anim_handle = anim_handle;
3034
3035 const int delay = seat->cursor.wl.theme_cursor->images[0]->delay;
3036 std::thread cursor_anim_thread(cursor_anim_frame_step_fn, seat, anim_handle, delay);
3037 /* Application logic should take priority. */
3038 thread_set_min_priority(cursor_anim_thread);
3039 cursor_anim_thread.detach();
3040}
3041
3043{
3044 if (gwl_seat_cursor_anim_check(seat)) {
3046 }
3047}
3048
3050{
3051 GWL_Cursor *cursor = &seat->cursor;
3052 if (cursor->anim_handle) {
3053 GWL_Cursor_AnimHandle *anim_handle = cursor->anim_handle;
3054 cursor->anim_handle = nullptr;
3055 anim_handle->exit_pending.store(true);
3056 }
3057}
3058
3064
3065#else
3066
3067/* Unfortunately cursor animation requires a background thread. */
3068[[maybe_unused]] static bool gwl_seat_cursor_anim_check(GWL_Seat * /*seat*/)
3069{
3070 return false;
3071}
3072[[maybe_unused]] static void gwl_seat_cursor_anim_begin(GWL_Seat * /*seat*/) {}
3073[[maybe_unused]] static void gwl_seat_cursor_anim_begin_if_needed(GWL_Seat * /*seat*/) {}
3074[[maybe_unused]] static void gwl_seat_cursor_anim_end(GWL_Seat * /*seat*/) {}
3075[[maybe_unused]] static void gwl_seat_cursor_anim_reset(GWL_Seat * /*seat*/) {}
3076
3077#endif /* !USE_EVENT_BACKGROUND_THREAD */
3078
3080
3081/* -------------------------------------------------------------------- */
3088
3089static CLG_LogRef LOG_WL_KEYBOARD_DEPRESSED_STATE = {"ghost.wl.keyboard.depressed"};
3090#define LOG (&LOG_WL_KEYBOARD_DEPRESSED_STATE)
3091
3093{
3094 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3095 seat->key_depressed.mods[i] = 0;
3096 }
3097}
3098
3100 const GHOST_TKey gkey,
3101 const GHOST_TEventType etype)
3102{
3103 if (GHOST_KEY_MODIFIER_CHECK(gkey)) {
3104 const int index = GHOST_KEY_MODIFIER_TO_INDEX(gkey);
3105 int16_t &value = seat->key_depressed.mods[index];
3106 if (etype == GHOST_kEventKeyUp) {
3107 value -= 1;
3108 if (UNLIKELY(value < 0)) {
3109 CLOG_WARN(LOG, "modifier (%d) has negative keys held (%d)!", index, value);
3110 value = 0;
3111 }
3112 }
3113 else {
3114 value += 1;
3115 }
3116 }
3117}
3118
3120 GWL_Seat *seat,
3121 GHOST_IWindow *win,
3122 const uint64_t event_ms,
3123 const GWL_KeyboardDepressedState &key_depressed_prev)
3124{
3125 /* Separate key up and down into separate passes so key down events always come after key up.
3126 * Do this so users of GHOST can use the last pressed or released modifier to check
3127 * if the modifier is held instead of counting modifiers pressed as is done here,
3128 * this isn't perfect but works well enough in practice. */
3129 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3130 for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d < 0; d++) {
3133 new GHOST_EventKey(event_ms, GHOST_kEventKeyUp, win, gkey, false));
3134
3135 CLOG_INFO(LOG, 2, "modifier (%d) up", i);
3136 }
3137 }
3138
3139 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3140 for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d > 0; d--) {
3143 new GHOST_EventKey(event_ms, GHOST_kEventKeyDown, win, gkey, false));
3144 CLOG_INFO(LOG, 2, "modifier (%d) down", i);
3145 }
3146 }
3147}
3148
3149#undef LOG
3150
3152
3153/* -------------------------------------------------------------------- */
3159
3160static CLG_LogRef LOG_WL_RELATIVE_POINTER = {"ghost.wl.handle.relative_pointer"};
3161#define LOG (&LOG_WL_RELATIVE_POINTER)
3162
3168 const wl_fixed_t xy[2],
3169 const uint64_t event_ms)
3170{
3171 seat->pointer.xy[0] = xy[0];
3172 seat->pointer.xy[1] = xy[1];
3173
3174#ifdef USE_GNOME_CONFINE_HACK
3175 if (seat->use_pointer_software_confine) {
3177 win->getClientBounds(bounds);
3178 /* Needed or the cursor is considered outside the window and doesn't restore the location. */
3179 bounds.m_r -= 1;
3180 bounds.m_b -= 1;
3181 bounds.m_l = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_l));
3182 bounds.m_t = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_t));
3183 bounds.m_r = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_r));
3184 bounds.m_b = win->wl_fixed_from_window(wl_fixed_from_int(bounds.m_b));
3185 bounds.clampPoint(UNPACK2(seat->pointer.xy));
3186 }
3187#endif
3188 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
3190 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
3191}
3192
3194 void *data,
3195 zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/,
3196 const uint32_t utime_hi,
3197 const uint32_t utime_lo,
3198 const wl_fixed_t dx,
3199 const wl_fixed_t dy,
3200 const wl_fixed_t /*dx_unaccel*/,
3201 const wl_fixed_t /*dy_unaccel*/)
3202{
3203 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3204 const uint64_t time = ghost_wl_ms_from_utime_pair(utime_hi, utime_lo);
3205 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3206
3207 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
3208 CLOG_INFO(LOG, 2, "relative_motion");
3209 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3210 const wl_fixed_t xy_next[2] = {
3211 seat->pointer.xy[0] + win->wl_fixed_from_window(dx),
3212 seat->pointer.xy[1] + win->wl_fixed_from_window(dy),
3213 };
3214 relative_pointer_handle_relative_motion_impl(seat, win, xy_next, event_ms);
3215 }
3216 else {
3217 CLOG_INFO(LOG, 2, "relative_motion (skipped)");
3218 }
3219}
3220
3221static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
3223};
3224
3225#undef LOG
3226
3228
3229/* -------------------------------------------------------------------- */
3232
3233static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"};
3234#define LOG (&LOG_WL_DATA_SOURCE)
3235
3236static void dnd_events(const GWL_Seat *const seat,
3237 const GHOST_TEventType event,
3238 const uint64_t event_ms)
3239{
3240 /* NOTE: `seat->data_offer_dnd_mutex` must already be locked. */
3241 if (wl_surface *wl_surface_focus = seat->wl.surface_window_focus_dnd) {
3242 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3243 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->data_offer_dnd->dnd.xy)};
3244 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order_type); i++) {
3247 new GHOST_EventDragnDrop(event_ms, event, type, win, UNPACK2(event_xy), nullptr));
3248 }
3249 }
3250}
3251
3253 const char *mime_receive,
3254 std::mutex *mutex,
3255 const bool nil_terminate,
3256 size_t *r_len)
3257{
3258 int pipefd[2];
3259 const bool pipefd_ok = pipe(pipefd) == 0;
3260 if (pipefd_ok) {
3261 wl_data_offer_receive(data_offer->wl.id, mime_receive, pipefd[1]);
3262 close(pipefd[1]);
3263 }
3264 else {
3265 CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno));
3266 }
3267
3268 if (mutex) {
3269 mutex->unlock();
3270 }
3271 /* NOTE: Regarding `data_offer`:
3272 * - In the case of the clipboard - unlocking the `mutex` means, it may be feed from now on.
3273 * - In the case of drag & drop - the caller owns & no locking is needed
3274 * (locking is performed while transferring the ownership).
3275 */
3276 char *buf = nullptr;
3277 if (pipefd_ok) {
3278 buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len);
3279 if (buf == nullptr) {
3280 CLOG_WARN(LOG, "unable to pipe into buffer: %s", std::strerror(errno));
3281 }
3282 close(pipefd[0]);
3283 }
3284 else {
3285 *r_len = 0;
3286 }
3287 return buf;
3288}
3289
3291 const char *mime_receive,
3292 std::mutex *mutex,
3293 const bool nil_terminate,
3294 size_t *r_len)
3295{
3296 int pipefd[2];
3297 const bool pipefd_ok = pipe(pipefd) == 0;
3298 if (pipefd_ok) {
3299 zwp_primary_selection_offer_v1_receive(data_offer->wp.id, mime_receive, pipefd[1]);
3300 close(pipefd[1]);
3301 }
3302 else {
3303 CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno));
3304 }
3305
3306 if (mutex) {
3307 mutex->unlock();
3308 }
3309 /* WARNING: `data_offer` may be freed from now on. */
3310 char *buf = nullptr;
3311 if (pipefd_ok) {
3312 buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len);
3313 if (buf == nullptr) {
3314 CLOG_WARN(LOG, "unable to pipe into buffer: %s", std::strerror(errno));
3315 }
3316 close(pipefd[0]);
3317 }
3318 return buf;
3319}
3320
3327static void data_source_handle_target(void * /*data*/,
3328 wl_data_source * /*wl_data_source*/,
3329 const char * /*mime_type*/)
3330{
3331 CLOG_INFO(LOG, 2, "target");
3332}
3333
3335 wl_data_source * /*wl_data_source*/,
3336 const char * /*mime_type*/,
3337 const int32_t fd)
3338{
3339 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3340
3341 CLOG_INFO(LOG, 2, "send");
3342
3343 auto write_file_fn = [](GWL_Seat *seat, const int fd) {
3344 if (UNLIKELY(write(fd,
3346 seat->data_source->buffer_out.data_size) < 0))
3347 {
3348 CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno));
3349 }
3350 close(fd);
3351 seat->data_source_mutex.unlock();
3352 };
3353
3354 seat->data_source_mutex.lock();
3355 std::thread write_thread(write_file_fn, seat, fd);
3356 write_thread.detach();
3357}
3358
3359static void data_source_handle_cancelled(void *data, wl_data_source *wl_data_source)
3360{
3361 CLOG_INFO(LOG, 2, "cancelled");
3362 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3363 GWL_DataSource *data_source = seat->data_source;
3364 if (seat->data_source->wl.source == wl_data_source) {
3365 data_source->wl.source = nullptr;
3366 }
3367
3368 wl_data_source_destroy(wl_data_source);
3369}
3370
3378static void data_source_handle_dnd_drop_performed(void * /*data*/,
3379 wl_data_source * /*wl_data_source*/)
3380{
3381 CLOG_INFO(LOG, 2, "dnd_drop_performed");
3382}
3383
3391static void data_source_handle_dnd_finished(void * /*data*/, wl_data_source * /*wl_data_source*/)
3392{
3393 CLOG_INFO(LOG, 2, "dnd_finished");
3394}
3395
3403static void data_source_handle_action(void * /*data*/,
3404 wl_data_source * /*wl_data_source*/,
3405 const uint32_t dnd_action)
3406{
3407 CLOG_INFO(LOG, 2, "handle_action (dnd_action=%u)", dnd_action);
3408}
3409
3410static const wl_data_source_listener data_source_listener = {
3411 /*target*/ data_source_handle_target,
3412 /*send*/ data_source_handle_send,
3413 /*cancelled*/ data_source_handle_cancelled,
3414 /*dnd_drop_performed*/ data_source_handle_dnd_drop_performed,
3415 /*dnd_finished*/ data_source_handle_dnd_finished,
3416 /*action*/ data_source_handle_action,
3417};
3418
3419#undef LOG
3420
3422
3423/* -------------------------------------------------------------------- */
3426
3427static CLG_LogRef LOG_WL_DATA_OFFER = {"ghost.wl.handle.data_offer"};
3428#define LOG (&LOG_WL_DATA_OFFER)
3429
3431 wl_data_offer * /*wl_data_offer*/,
3432 const char *mime_type)
3433{
3434 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
3435 CLOG_INFO(LOG, 2, "offer (mime_type=%s)", mime_type);
3436 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3437 data_offer->types.insert(mime_type);
3438}
3439
3441 wl_data_offer * /*wl_data_offer*/,
3442 const uint32_t source_actions)
3443{
3444 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
3445 CLOG_INFO(LOG, 2, "source_actions (%u)", source_actions);
3446 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3447 data_offer->dnd.source_actions = (enum wl_data_device_manager_dnd_action)source_actions;
3448}
3449
3451 wl_data_offer * /*wl_data_offer*/,
3452 const uint32_t dnd_action)
3453{
3454 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
3455 CLOG_INFO(LOG, 2, "actions (%u)", dnd_action);
3456 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3457 data_offer->dnd.action = (enum wl_data_device_manager_dnd_action)dnd_action;
3458}
3459
3460static const wl_data_offer_listener data_offer_listener = {
3461 /*offer*/ data_offer_handle_offer,
3462 /*source_actions*/ data_offer_handle_source_actions,
3463 /*action*/ data_offer_handle_action,
3464};
3465
3466#undef LOG
3467
3469
3470/* -------------------------------------------------------------------- */
3473
3474static CLG_LogRef LOG_WL_DATA_DEVICE = {"ghost.wl.handle.data_device"};
3475#define LOG (&LOG_WL_DATA_DEVICE)
3476
3477static void data_device_handle_data_offer(void * /*data*/,
3478 wl_data_device * /*wl_data_device*/,
3479 wl_data_offer *id)
3480{
3481 CLOG_INFO(LOG, 2, "data_offer");
3482
3483 /* The ownership of data-offer isn't so obvious:
3484 * At this point it's not known if this will be used for drag & drop or selection.
3485 *
3486 * The API docs state that the following callbacks run immediately after this callback:
3487 * - #wl_data_device_listener::enter (for drag & drop).
3488 * - #wl_data_device_listener::selection (for copy & paste).
3489 *
3490 * In the case of GHOST, this means they will be assigned to either:
3491 * - #GWL_Seat::data_offer_dnd
3492 * - #GWL_Seat::data_offer_copy_paste
3493 */
3494 GWL_DataOffer *data_offer = new GWL_DataOffer;
3495 data_offer->wl.id = id;
3496 wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
3497}
3498
3500 wl_data_device * /*wl_data_device*/,
3501 const uint32_t serial,
3503 const wl_fixed_t x,
3504 const wl_fixed_t y,
3505 wl_data_offer *id)
3506{
3507 /* Always clear the current data-offer no matter what else happens. */
3508 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3509 const uint64_t event_ms = seat->system->getMilliSeconds();
3510
3511 std::lock_guard lock{seat->data_offer_dnd_mutex};
3512 if (seat->data_offer_dnd) {
3513 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
3514 delete seat->data_offer_dnd;
3515 seat->data_offer_dnd = nullptr;
3516 }
3517 /* Clearing complete. */
3518
3519 /* Handle the new offer. */
3520 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id));
3522 CLOG_INFO(LOG, 2, "enter (skipped)");
3523 wl_data_offer_destroy(data_offer->wl.id);
3524 delete data_offer;
3525 return;
3526 }
3527 CLOG_INFO(LOG, 2, "enter");
3528
3529 /* Transfer ownership of the `data_offer`. */
3530 seat->data_offer_dnd = data_offer;
3531
3532 data_offer->dnd.xy[0] = x;
3533 data_offer->dnd.xy[1] = y;
3534
3535 wl_data_offer_set_actions(id,
3536 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
3537 WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
3538 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
3539
3540 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) {
3541 const char *type = ghost_wl_mime_preference_order[i];
3542 wl_data_offer_accept(id, serial, type);
3543 }
3544
3546
3547 seat->system->seat_active_set(seat);
3548
3549 dnd_events(seat, GHOST_kEventDraggingEntered, event_ms);
3550}
3551
3552static void data_device_handle_leave(void *data, wl_data_device * /*wl_data_device*/)
3553{
3554 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3555 const uint64_t event_ms = seat->system->getMilliSeconds();
3556
3557 std::lock_guard lock{seat->data_offer_dnd_mutex};
3558 /* The user may have only dragged over the window decorations. */
3559 if (seat->data_offer_dnd == nullptr) {
3560 return;
3561 }
3562 CLOG_INFO(LOG, 2, "leave");
3563
3564 dnd_events(seat, GHOST_kEventDraggingExited, event_ms);
3565 seat->wl.surface_window_focus_dnd = nullptr;
3566
3567 if (seat->data_offer_dnd) {
3568 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
3569 delete seat->data_offer_dnd;
3570 seat->data_offer_dnd = nullptr;
3571 }
3572}
3573
3575 wl_data_device * /*wl_data_device*/,
3576 const uint32_t time,
3577 const wl_fixed_t x,
3578 const wl_fixed_t y)
3579{
3580 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3581 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3582
3583 std::lock_guard lock{seat->data_offer_dnd_mutex};
3584 /* The user may have only dragged over the window decorations. */
3585 if (seat->data_offer_dnd == nullptr) {
3586 return;
3587 }
3588
3589 CLOG_INFO(LOG, 2, "motion");
3590
3591 seat->data_offer_dnd->dnd.xy[0] = x;
3592 seat->data_offer_dnd->dnd.xy[1] = y;
3593
3594 dnd_events(seat, GHOST_kEventDraggingUpdated, event_ms);
3595}
3596
3597static void data_device_handle_drop(void *data, wl_data_device * /*wl_data_device*/)
3598{
3599 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3600 std::lock_guard lock{seat->data_offer_dnd_mutex};
3601
3602 /* No need to check this for null (as other callbacks do).
3603 * because the data-offer has not been accepted (actions set... etc). */
3604 GWL_DataOffer *data_offer = seat->data_offer_dnd;
3605
3606 /* Take ownership of `data_offer` to prevent a double-free, see: #128766.
3607 * The thread this function spawns is responsible for freeing it. */
3608 seat->data_offer_dnd = nullptr;
3609
3610 /* Use a blank string for `mime_receive` to prevent crashes, although could also be `nullptr`.
3611 * Failure to set this to a known type just means the file won't have any special handling.
3612 * GHOST still generates a dropped file event.
3613 * NOTE: this string can be compared with `mime_text_plain`, `mime_text_uri` etc...
3614 * as the this always points to the same values. */
3615 const char *mime_receive = "";
3616 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) {
3617 const char *type = ghost_wl_mime_preference_order[i];
3618 if (data_offer->types.count(type)) {
3619 mime_receive = type;
3620 break;
3621 }
3622 }
3623
3624 CLOG_INFO(LOG, 2, "drop mime_recieve=%s", mime_receive);
3625
3626 auto read_drop_data_fn = [](GWL_Seat *const seat,
3627 GWL_DataOffer *data_offer,
3628 wl_surface *wl_surface_window,
3629 const char *mime_receive) {
3630 const uint64_t event_ms = seat->system->getMilliSeconds();
3631 const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)};
3632
3633 const bool nil_terminate = (mime_receive != ghost_wl_mime_text_uri_list);
3634 size_t data_buf_len = 0;
3635 const char *data_buf = read_buffer_from_data_offer(
3636 data_offer, mime_receive, nullptr, nil_terminate, &data_buf_len);
3637
3638 CLOG_INFO(LOG, 2, "read_drop_data mime_receive=%s, data_len=%zu", mime_receive, data_buf_len);
3639
3640 wl_data_offer_finish(data_offer->wl.id);
3641 wl_data_offer_destroy(data_offer->wl.id);
3642
3643 delete data_offer;
3644 data_offer = nullptr;
3645
3646 /* Don't generate a drop event if the data could not be read,
3647 * an error will have been logged. */
3648 if (data_buf != nullptr) {
3650 void *ghost_dnd_data = nullptr;
3651
3652 /* Failure to receive drop data. */
3653 if (mime_receive == ghost_wl_mime_text_uri_list) {
3654 std::vector<std::string_view> uris = gwl_clipboard_uri_ranges(data_buf, data_buf_len);
3655
3656 GHOST_TStringArray *flist = static_cast<GHOST_TStringArray *>(
3657 malloc(sizeof(GHOST_TStringArray)));
3658 flist->count = int(uris.size());
3659 flist->strings = static_cast<uint8_t **>(malloc(uris.size() * sizeof(uint8_t *)));
3660 for (size_t i = 0; i < uris.size(); i++) {
3661 flist->strings[i] = reinterpret_cast<uint8_t *>(
3662 GHOST_URL_decode_alloc(uris[i].data(), uris[i].size()));
3663 }
3664
3665 CLOG_INFO(LOG, 2, "read_drop_data file_count=%d", flist->count);
3666 ghost_dnd_type = GHOST_kDragnDropTypeFilenames;
3667 ghost_dnd_data = flist;
3668 }
3669 else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) {
3670 ghost_dnd_type = GHOST_kDragnDropTypeString;
3671 ghost_dnd_data = (void *)data_buf; /* Move ownership to the event. */
3672 data_buf = nullptr;
3673 }
3674
3675 if (ghost_dnd_type != GHOST_kDragnDropTypeUnknown) {
3676 GHOST_SystemWayland *const system = seat->system;
3677 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window);
3678 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)};
3679
3680 system->pushEvent_maybe_pending(new GHOST_EventDragnDrop(event_ms,
3682 ghost_dnd_type,
3683 win,
3684 UNPACK2(event_xy),
3685 ghost_dnd_data));
3686
3688 }
3689 else {
3690 CLOG_INFO(LOG, 2, "read_drop_data, unhandled!");
3691 }
3692
3693 free(const_cast<char *>(data_buf));
3694 }
3695 };
3696
3697 /* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the
3698 * leave callback (#data_device_handle_leave) will clear the value once this function starts. */
3699 std::thread read_thread(
3700 read_drop_data_fn, seat, data_offer, seat->wl.surface_window_focus_dnd, mime_receive);
3701 read_thread.detach();
3702}
3703
3705 wl_data_device * /*wl_data_device*/,
3706 wl_data_offer *id)
3707{
3708 /* Always clear the current data-offer no matter what else happens. */
3709 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3710 std::lock_guard lock{seat->data_offer_copy_paste_mutex};
3711 if (seat->data_offer_copy_paste) {
3712 wl_data_offer_destroy(seat->data_offer_copy_paste->wl.id);
3713 delete seat->data_offer_copy_paste;
3714 seat->data_offer_copy_paste = nullptr;
3715 seat->data_offer_copy_paste_has_image = std::nullopt;
3716 }
3717 /* Clearing complete. */
3718
3719 /* Handle the new offer. */
3720 if (id == nullptr) {
3721 CLOG_INFO(LOG, 2, "selection: (skipped)");
3722 return;
3723 }
3724
3725 CLOG_INFO(LOG, 2, "selection");
3726 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id));
3727 /* Transfer ownership of the `data_offer`. */
3728 seat->data_offer_copy_paste = data_offer;
3729 seat->data_offer_copy_paste_has_image = std::nullopt;
3730}
3731
3732static const wl_data_device_listener data_device_listener = {
3733 /*data_offer*/ data_device_handle_data_offer,
3734 /*enter*/ data_device_handle_enter,
3735 /*leave*/ data_device_handle_leave,
3736 /*motion*/ data_device_handle_motion,
3737 /*drop*/ data_device_handle_drop,
3738 /*selection*/ data_device_handle_selection,
3739};
3740
3741#undef LOG
3742
3744
3745/* -------------------------------------------------------------------- */
3748
3749static CLG_LogRef LOG_WL_CURSOR_BUFFER = {"ghost.wl.handle.cursor_buffer"};
3750#define LOG (&LOG_WL_CURSOR_BUFFER)
3751
3752static void cursor_buffer_handle_release(void *data, wl_buffer *wl_buffer)
3753{
3754 CLOG_INFO(LOG, 2, "release");
3755
3756 GWL_Cursor *cursor = static_cast<GWL_Cursor *>(data);
3757 wl_buffer_destroy(wl_buffer);
3758
3759 if (wl_buffer == cursor->wl.buffer) {
3760 /* The mapped buffer was from a custom cursor. */
3761 cursor->wl.buffer = nullptr;
3762 }
3763}
3764
3765static const wl_buffer_listener cursor_buffer_listener = {
3766 /*release*/ cursor_buffer_handle_release,
3767};
3768
3769#undef LOG
3770
3772
3773/* -------------------------------------------------------------------- */
3776
3777static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"};
3778#define LOG (&LOG_WL_CURSOR_SURFACE)
3779
3781 wl_shm *shm,
3782 GWL_SeatStatePointer *seat_state_pointer,
3783 wl_surface *wl_surface_cursor)
3784{
3785 int scale = 0;
3786 for (const GWL_Output *output : seat_state_pointer->outputs) {
3787 int output_scale_floor = output->scale;
3788
3789 /* It's important to round down in the case of fractional scale,
3790 * otherwise the cursor can be scaled down to be unusably small.
3791 * This is especially a problem when:
3792 * - The cursor theme has one size (24px for the default cursor).
3793 * - The fractional scaling is set just above 1 (typically 125%).
3794 *
3795 * In this case the `output->scale` is rounded up to 2 and a larger cursor is requested.
3796 * It's assumed a large cursor is available but that's not always the case.
3797 * When only a smaller cursor is available it's still assumed to be large,
3798 * fractional scaling causes the cursor to be scaled down making it ~10px. see #105895. */
3799 if (output_scale_floor > 1 && output->has_scale_fractional) {
3800 output_scale_floor = std::max(1, output->scale_fractional / FRACTIONAL_DENOMINATOR);
3801 }
3802
3803 scale = std::max(output_scale_floor, scale);
3804 }
3805
3806 if (scale > 0 && seat_state_pointer->theme_scale != scale) {
3807 seat_state_pointer->theme_scale = scale;
3808 if (!cursor.is_custom) {
3809 if (wl_surface_cursor) {
3810 wl_surface_set_buffer_scale(wl_surface_cursor, scale);
3811 }
3812 }
3814 cursor.wl.theme = wl_cursor_theme_load(
3815 (cursor.theme_name.empty() ? nullptr : cursor.theme_name.c_str()),
3816 scale * cursor.theme_size,
3817 shm);
3818 if (cursor.wl.theme_cursor) {
3820 cursor.wl.theme_cursor_name);
3821 }
3822
3823 return true;
3824 }
3825 return false;
3826}
3827
3828static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_output *wl_output)
3829{
3830 if (!ghost_wl_output_own(wl_output)) {
3831 CLOG_INFO(LOG, 2, "handle_enter (skipped)");
3832 return;
3833 }
3834 CLOG_INFO(LOG, 2, "handle_enter");
3835
3836 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3838 seat, wl_surface);
3839 const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
3840 seat_state_pointer->outputs.insert(reg_output);
3841 update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface);
3842}
3843
3844static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output)
3845{
3846 if (!(wl_output && ghost_wl_output_own(wl_output))) {
3847 CLOG_INFO(LOG, 2, "handle_leave (skipped)");
3848 return;
3849 }
3850 CLOG_INFO(LOG, 2, "handle_leave");
3851
3852 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3854 seat, wl_surface);
3855 const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
3856 seat_state_pointer->outputs.erase(reg_output);
3857 update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface);
3858}
3859
3861 wl_surface * /*wl_surface*/,
3862 int32_t factor)
3863{
3864 /* Only available in interface version 6. */
3865 CLOG_INFO(LOG, 2, "handle_preferred_buffer_scale (factor=%d)", factor);
3866}
3867
3868#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
3869 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
3870static void cursor_surface_handle_preferred_buffer_transform(void * /*data*/,
3871 wl_surface * /*wl_surface*/,
3872 uint32_t transform)
3873{
3874 /* Only available in interface version 6. */
3875 CLOG_INFO(LOG, 2, "handle_preferred_buffer_transform (transform=%u)", transform);
3876}
3877#endif /* WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION && \
3878 * WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION */
3879
3883#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
3884 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
3885 /*preferred_buffer_scale*/ cursor_surface_handle_preferred_buffer_scale,
3886 /*preferred_buffer_transform*/ cursor_surface_handle_preferred_buffer_transform,
3887#endif
3888};
3889
3890#undef LOG
3891
3893
3894/* -------------------------------------------------------------------- */
3897
3898static CLG_LogRef LOG_WL_POINTER = {"ghost.wl.handle.pointer"};
3899#define LOG (&LOG_WL_POINTER)
3900
3901static void pointer_handle_enter(void *data,
3902 wl_pointer * /*wl_pointer*/,
3903 const uint32_t serial,
3905 const wl_fixed_t surface_x,
3906 const wl_fixed_t surface_y)
3907{
3908 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3909 const uint64_t event_ms = seat->system->getMilliSeconds();
3910
3911 /* Null when just destroyed. */
3913 CLOG_INFO(LOG, 2, "enter (skipped)");
3914 return;
3915 }
3916 CLOG_INFO(LOG, 2, "enter");
3917
3919
3920 seat->cursor_source_serial = serial;
3921 seat->pointer.serial = serial;
3922 seat->pointer.xy[0] = surface_x;
3923 seat->pointer.xy[1] = surface_y;
3924
3925 /* Resetting scroll events is likely unnecessary,
3926 * do this to avoid any possible problems as it's harmless. */
3928
3929 seat->pointer.wl.surface_window = wl_surface;
3930
3931 seat->system->seat_active_set(seat);
3932 win->cursor_shape_refresh();
3933
3934 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
3936 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
3937}
3938
3939static void pointer_handle_leave(void *data,
3940 wl_pointer * /*wl_pointer*/,
3941 const uint32_t /*serial*/,
3943{
3944 /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
3945 static_cast<GWL_Seat *>(data)->pointer.wl.surface_window = nullptr;
3947 CLOG_INFO(LOG, 2, "leave (skipped)");
3948 return;
3949 }
3950 CLOG_INFO(LOG, 2, "leave");
3951}
3952
3953static void pointer_handle_motion(void *data,
3954 wl_pointer * /*wl_pointer*/,
3955 const uint32_t time,
3956 const wl_fixed_t surface_x,
3957 const wl_fixed_t surface_y)
3958{
3959 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3960 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3961
3962 seat->pointer.xy[0] = surface_x;
3963 seat->pointer.xy[1] = surface_y;
3964
3965 CLOG_INFO(LOG, 2, "motion");
3966
3969}
3970
3971static void pointer_handle_button(void *data,
3972 wl_pointer * /*wl_pointer*/,
3973 const uint32_t serial,
3974 const uint32_t time,
3975 const uint32_t button,
3976 const uint32_t state)
3977{
3978 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3979
3980 CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state);
3981
3982 /* Always set the serial, even if the button event is not sent. */
3983 seat->data_source_serial = serial;
3984
3985 int button_release;
3986 switch (state) {
3987 case WL_POINTER_BUTTON_STATE_RELEASED:
3988 button_release = 1;
3989 break;
3990 case WL_POINTER_BUTTON_STATE_PRESSED:
3991 button_release = 0;
3992 break;
3993 default: {
3994 return;
3995 }
3996 }
3997
3998 const uint32_t button_index = button - BTN_RANGE_MIN;
3999 if (button_index >= (BTN_RANGE_MAX - BTN_RANGE_MIN)) {
4000 return;
4001 }
4002
4004 int(GWL_Pointer_EventTypes::Button0_Down) + ((button_index * 2) + button_release));
4005
4006 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4008}
4009
4010static void pointer_handle_axis(void *data,
4011 wl_pointer * /*wl_pointer*/,
4012 const uint32_t time,
4013 const uint32_t axis,
4014 const wl_fixed_t value)
4015{
4016 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4017 seat->pointer_scroll.event_ms = seat->system->ms_from_input_time(time);
4018 seat->pointer_scroll.has_event_ms = true;
4019
4020 /* NOTE: this is used for touch based scrolling - or other input that doesn't scroll with
4021 * discrete "steps". This allows supporting smooth-scrolling without "touch" gesture support. */
4022 CLOG_INFO(LOG, 2, "axis (axis=%u, value=%d)", axis, value);
4023 const int index = pointer_axis_as_index(axis);
4024 if (UNLIKELY(index == -1)) {
4025 return;
4026 }
4027 seat->pointer_scroll.smooth_xy[index] = value;
4028
4030}
4031
4032static void pointer_handle_frame(void *data, wl_pointer * /*wl_pointer*/)
4033{
4034 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4035
4036 CLOG_INFO(LOG, 2, "frame");
4037
4038 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4039 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
4040 for (int ty_index = 0; ty_index < seat->pointer_events.frame_pending.frame_types_num;
4041 ty_index++)
4042 {
4044 const uint64_t event_ms = seat->pointer_events.frame_pending.frame_event_ms[ty_index];
4045 switch (ty) {
4046 /* Use motion for pressure and tilt as there are no explicit event types for these. */
4048 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4050 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
4051 break;
4052 }
4055
4056 /* The scroll data is "interpreted" before generating the events,
4057 * this is needed because data is accumulated
4058 * with support for handling smooth scroll as discrete events (for example). */
4059
4060 /* Most scroll events have no time, use `ps.event_ms` instead.
4061 * This is assigned for the scroll events that do set a time.
4062 * In practice the values tends to be set but fall back to the current time. */
4063 GHOST_ASSERT(event_ms == 0, "Scroll events are not expected to have a time!");
4064 /* Handle value120 to discrete steps first. */
4065 if (ps.discrete120_xy[0] || ps.discrete120_xy[1]) {
4066 for (int i = 0; i < 2; i++) {
4068 ps.discrete120_xy[i] = 0;
4069 /* The values will have been normalized so 120 represents a single click-step. */
4070 ps.discrete_xy[i] = ps.discrete120_xy_accum[i] / 120;
4071 ps.discrete120_xy_accum[i] -= ps.discrete_xy[i] * 120;
4072 }
4073 }
4074
4075 /* Multiple wheel events may have been generated and it's not known which.
4076 * The logic here handles prioritizing how they should be handled. */
4077 if (ps.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) {
4078 /* We never want mouse wheel events to be treated as smooth scrolling as this
4079 * causes mouse wheel scroll to orbit the view, see #120587.
4080 * Although it could be supported if the event system would forward
4081 * the source of the scroll action (a wheel or touch device). */
4082 ps.smooth_xy[0] = 0;
4083 ps.smooth_xy[1] = 0;
4084 }
4085 else if (ps.axis_source == WL_POINTER_AXIS_SOURCE_FINGER) {
4089 /* If discrete steps have been sent, use them as-is. */
4090 if ((ps.discrete_xy[0] == 0) && (ps.discrete_xy[1] == 0)) {
4091 /* Convert smooth to discrete. */
4092 for (int i = 0; i < 2; i++) {
4093 smooth_as_discrete.smooth_xy_accum[i] += ps.smooth_xy[i];
4094 if (std::abs(smooth_as_discrete.smooth_xy_accum[i]) >= smooth_as_discrete_steps)
4095 {
4096 ps.discrete_xy[i] = smooth_as_discrete.smooth_xy_accum[i] /
4098 smooth_as_discrete.smooth_xy_accum[i] -= ps.discrete_xy[i] *
4100 }
4101 }
4102 }
4103 ps.smooth_xy[0] = 0;
4104 ps.smooth_xy[1] = 0;
4105 }
4106 }
4107
4108 /* Both discrete and smooth events may be set at once, never generate events for both
4109 * as this will be handling the same event in to different ways.
4110 * Prioritize discrete axis events for the mouse wheel, otherwise smooth scroll. */
4111 if (ps.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) {
4112 if (ps.discrete_xy[0]) {
4113 ps.smooth_xy[0] = 0;
4114 }
4115 if (ps.discrete_xy[1]) {
4116 ps.smooth_xy[1] = 0;
4117 }
4118 }
4119 else {
4120 if (ps.smooth_xy[0]) {
4121 ps.discrete_xy[0] = 0;
4122 }
4123 if (ps.smooth_xy[1]) {
4124 ps.discrete_xy[1] = 0;
4125 }
4126 }
4127
4128 /* Done evaluating scroll input, generate the events. */
4129 if (ps.discrete_xy[0] || ps.discrete_xy[1]) {
4130 if (ps.discrete_xy[0]) {
4132 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4133 win,
4135 ps.discrete_xy[0]));
4136 }
4137 if (ps.discrete_xy[1]) {
4139 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4140 win,
4142 -ps.discrete_xy[1]));
4143 }
4144 ps.discrete_xy[0] = 0;
4145 ps.discrete_xy[1] = 0;
4146 }
4147
4148 if (ps.smooth_xy[0] || ps.smooth_xy[1]) {
4149 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4151 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4152 win,
4154 UNPACK2(event_xy),
4155 /* NOTE: scaling the delta doesn't seem necessary.
4156 * NOTE: inverting delta gives correct results, see: QTBUG-85767.
4157 * NOTE: the preference to invert scrolling (in GNOME at least)
4158 * has already been applied so there is no need to read this preference. */
4159 -wl_fixed_to_int(ps.smooth_xy[0]),
4160 -wl_fixed_to_int(ps.smooth_xy[1]),
4161 /* NOTE: GHOST does not support per-axis inversion.
4162 * Assume inversion is used or not. */
4163 ps.inverted_xy[0] || ps.inverted_xy[1]));
4164
4165 ps.smooth_xy[0] = 0;
4166 ps.smooth_xy[1] = 0;
4167 }
4168
4169 ps.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
4170 ps.inverted_xy[0] = false;
4171 ps.inverted_xy[1] = false;
4172
4173 ps.event_ms = 0;
4174 ps.has_event_ms = false;
4175
4176 break;
4177 }
4178#ifdef NDEBUG
4179 default:
4180#else /* Warn when any events aren't handled (in debug builds). */
4195#endif
4196 {
4197 const int button_enum_offset = int(ty) - int(GWL_Pointer_EventTypes::Button0_Down);
4198 const int button_index = button_enum_offset / 2;
4199 const bool button_down = (button_index * 2) == button_enum_offset;
4200 const GHOST_TButton ebutton = gwl_pointer_events_ebutton[button_index];
4201 const GHOST_TEventType etype = button_down ? GHOST_kEventButtonDown :
4203 seat->pointer.buttons.set(ebutton, button_down);
4205 new GHOST_EventButton(event_ms, etype, win, ebutton, GHOST_TABLET_DATA_NONE));
4206 break;
4207 }
4208 }
4209 }
4210 }
4211
4213}
4215 wl_pointer * /*wl_pointer*/,
4216 uint32_t axis_source)
4217{
4218 CLOG_INFO(LOG, 2, "axis_source (axis_source=%u)", axis_source);
4219 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4220 seat->pointer_scroll.axis_source = (enum wl_pointer_axis_source)axis_source;
4221}
4223 wl_pointer * /*wl_pointer*/,
4224 uint32_t time,
4225 uint32_t axis)
4226{
4227 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4228 seat->pointer_scroll.event_ms = seat->system->ms_from_input_time(time);
4229 seat->pointer_scroll.has_event_ms = true;
4230
4232 /* Reset the scroll steps when the touch event ends.
4233 * Done so the user doesn't accidentally bump smooth scroll input a small number of steps
4234 * causing an unexpected discrete step caused by a very small amount of smooth-scrolling. */
4237 smooth_as_discrete.smooth_xy_accum[0] = 0;
4238 smooth_as_discrete.smooth_xy_accum[1] = 0;
4239 }
4240
4241 CLOG_INFO(LOG, 2, "axis_stop (axis=%u)", axis);
4242}
4244 wl_pointer * /*wl_pointer*/,
4245 uint32_t axis,
4246 int32_t discrete)
4247{
4248 /* NOTE: a discrete axis are typically mouse wheel events.
4249 * The non-discrete version of this function is used for touch-pad. */
4250 CLOG_INFO(LOG, 2, "axis_discrete (axis=%u, discrete=%d)", axis, discrete);
4251 const int index = pointer_axis_as_index(axis);
4252 if (UNLIKELY(index == -1)) {
4253 return;
4254 }
4255 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4256 seat->pointer_scroll.discrete_xy[index] = discrete;
4257
4259}
4261 wl_pointer * /*wl_pointer*/,
4262 uint32_t axis,
4263 int32_t value120)
4264{
4265 /* Only available in interface version 8. */
4266 CLOG_INFO(LOG, 2, "axis_value120 (axis=%u, value120=%d)", axis, value120);
4267 const int index = pointer_axis_as_index(axis);
4268 if (UNLIKELY(index == -1)) {
4269 return;
4270 }
4271 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4272 seat->pointer_scroll.discrete120_xy[index] = value120;
4273
4275}
4276#ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM /* Requires WAYLAND 1.22 or newer. */
4277static void pointer_handle_axis_relative_direction(void *data,
4278 wl_pointer * /*wl_pointer*/,
4279 uint32_t axis,
4280 uint32_t direction)
4281{
4282 /* Only available in interface version 9. */
4283 CLOG_INFO(LOG, 2, "axis_relative_direction (axis=%u, direction=%u)", axis, direction);
4284 const int index = pointer_axis_as_index(axis);
4285 if (UNLIKELY(index == -1)) {
4286 return;
4287 }
4288 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4289 seat->pointer_scroll.inverted_xy[index] = (direction ==
4290 WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED);
4291}
4292#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */
4293
4294static const wl_pointer_listener pointer_listener = {
4295 /*enter*/ pointer_handle_enter,
4296 /*leave*/ pointer_handle_leave,
4297 /*motion*/ pointer_handle_motion,
4298 /*button*/ pointer_handle_button,
4299 /*axis*/ pointer_handle_axis,
4300 /*frame*/ pointer_handle_frame,
4301 /*axis_source*/ pointer_handle_axis_source,
4302 /*axis_stop*/ pointer_handle_axis_stop,
4303 /*axis_discrete*/ pointer_handle_axis_discrete,
4304 /*axis_value120*/ pointer_handle_axis_value120,
4305#ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM
4306 /*axis_relative_direction*/ pointer_handle_axis_relative_direction,
4307#endif
4308};
4309
4310#undef LOG
4311
4313
4314/* -------------------------------------------------------------------- */
4317
4318#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
4319static CLG_LogRef LOG_WL_POINTER_GESTURE_HOLD = {"ghost.wl.handle.pointer_gesture.hold"};
4320# define LOG (&LOG_WL_POINTER_GESTURE_HOLD)
4321
4322static void gesture_hold_handle_begin(
4323 void * /*data*/,
4324 zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/,
4325 uint32_t /*serial*/,
4326 uint32_t /*time*/,
4327 wl_surface * /*surface*/,
4328 uint32_t fingers)
4329{
4330 CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers);
4331}
4332
4333static void gesture_hold_handle_end(void * /*data*/,
4334 zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/,
4335 uint32_t /*serial*/,
4336 uint32_t /*time*/,
4337 int32_t cancelled)
4338{
4339 CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled);
4340}
4341
4342static const zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = {
4343 /*begin*/ gesture_hold_handle_begin,
4344 /*end*/ gesture_hold_handle_end,
4345};
4346
4347# undef LOG
4348#endif /* ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE */
4349
4351
4352/* -------------------------------------------------------------------- */
4355
4356#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
4357static CLG_LogRef LOG_WL_POINTER_GESTURE_PINCH = {"ghost.wl.handle.pointer_gesture.pinch"};
4358# define LOG (&LOG_WL_POINTER_GESTURE_PINCH)
4359
4360static void gesture_pinch_handle_begin(void *data,
4361 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4362 uint32_t /*serial*/,
4363 uint32_t time,
4364 wl_surface * /*surface*/,
4365 uint32_t fingers)
4366{
4367 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4368 (void)seat->system->ms_from_input_time(time); /* Only update internal time. */
4369
4370 CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers);
4371
4372 /* Reset defaults. */
4374
4375 const GHOST_WindowWayland *win = nullptr;
4376 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4377 win = ghost_wl_surface_user_data(wl_surface_focus);
4378 }
4379
4380 /* NOTE(@ideasman42): Scale factors match Blender's operators & default preferences.
4381 * For these values to work correctly, operator logic will need to be changed not to scale input
4382 * by the region size (as with 3D view zoom) or preference for 3D view orbit sensitivity.
4383 *
4384 * By working "correctly" I mean that a rotation action where the users fingers rotate to
4385 * opposite locations should always rotate the viewport 180d, since users will expect the
4386 * physical location of their fingers to match the viewport.
4387 * Similarly with zoom, the scale value from the pinch action can be mapped to a zoom level
4388 * although unlike rotation, an inexact mapping is less noticeable.
4389 * Users may even prefer the zoom level to be scaled - which could be a preference. */
4390 seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1);
4391 /* The value 300 matches a value used in clip & image zoom operators.
4392 * It seems OK for the 3D view too. */
4394 /* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation,
4395 * although preferences can scale the sensitivity (which would be skipped ideally). */
4397
4398 if (win) {
4399 /* NOTE(@ideasman42): Blender's use of trackpad coordinates is inconsistent and needs work.
4400 * This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
4401 * Some operators scale by the UI scale, some don't.
4402 * Even though the window scale is correct, it doesn't account for the UI scale preference
4403 * (which GHOST doesn't know about).
4404 *
4405 * If support for this were all that was needed it could be handled in GHOST,
4406 * however as the operators are not even using coordinates compatible with each other,
4407 * it would be better to resolve this by passing rotation & zoom levels directly,
4408 * instead of attempting to handle them as cursor coordinates. */
4409 const GWL_WindowScaleParams &scale_params = win->scale_params_get();
4411 scale_params, seat->pointer_gesture_pinch.scale.factor);
4413 scale_params, seat->pointer_gesture_pinch.rotation.factor);
4414 }
4415}
4416
4417static void gesture_pinch_handle_update(void *data,
4418 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4419 uint32_t time,
4420 wl_fixed_t dx,
4421 wl_fixed_t dy,
4422 wl_fixed_t scale,
4423 wl_fixed_t rotation)
4424{
4425 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4426 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4427
4428 CLOG_INFO(LOG,
4429 2,
4430 "update (dx=%.3f, dy=%.3f, scale=%.3f, rotation=%.3f)",
4431 wl_fixed_to_double(dx),
4432 wl_fixed_to_double(dy),
4433 wl_fixed_to_double(scale),
4434 wl_fixed_to_double(rotation));
4435
4436 GHOST_WindowWayland *win = nullptr;
4437
4438 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4439 win = ghost_wl_surface_user_data(wl_surface_focus);
4440 }
4441
4442 /* Scale defaults to `wl_fixed_from_int(1)` which may change while pinching.
4443 * This needs to be converted to a delta. */
4444 const wl_fixed_t scale_delta = scale - seat->pointer_gesture_pinch.scale.value;
4445 const int scale_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta(
4446 &seat->pointer_gesture_pinch.scale, scale_delta);
4447
4448 /* Rotation in degrees, unlike scale this is a delta. */
4449 const int rotation_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta(
4450 &seat->pointer_gesture_pinch.rotation, rotation);
4451
4452 if (win) {
4453 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4454 if (scale_as_delta_px) {
4456 win,
4458 event_xy[0],
4459 event_xy[1],
4460 scale_as_delta_px,
4461 0,
4462 false));
4463 }
4464
4465 if (rotation_as_delta_px) {
4467 win,
4469 event_xy[0],
4470 event_xy[1],
4471 rotation_as_delta_px,
4472 0,
4473 false));
4474 }
4475 }
4476}
4477
4478static void gesture_pinch_handle_end(void *data,
4479 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4480 uint32_t /*serial*/,
4481 uint32_t time,
4482 int32_t cancelled)
4483{
4484 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4485 (void)seat->system->ms_from_input_time(time); /* Only update internal time. */
4486
4487 CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled);
4488}
4489
4490static const zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = {
4491 /*begin*/ gesture_pinch_handle_begin,
4492 /*update*/ gesture_pinch_handle_update,
4493 /*end*/ gesture_pinch_handle_end,
4494};
4495
4496# undef LOG
4497#endif /* ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE */
4498
4500
4501/* -------------------------------------------------------------------- */
4509
4510#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
4511static CLG_LogRef LOG_WL_POINTER_GESTURE_SWIPE = {"ghost.wl.handle.pointer_gesture.swipe"};
4512# define LOG (&LOG_WL_POINTER_GESTURE_SWIPE)
4513
4514static void gesture_swipe_handle_begin(
4515 void * /*data*/,
4516 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4517 uint32_t /*serial*/,
4518 uint32_t /*time*/,
4519 wl_surface * /*surface*/,
4520 uint32_t fingers)
4521{
4522 CLOG_INFO(LOG, 2, "begin (fingers=%u)", fingers);
4523}
4524
4525static void gesture_swipe_handle_update(
4526 void * /*data*/,
4527 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4528 uint32_t /*time*/,
4529 wl_fixed_t dx,
4530 wl_fixed_t dy)
4531{
4532 CLOG_INFO(LOG, 2, "update (dx=%.3f, dy=%.3f)", wl_fixed_to_double(dx), wl_fixed_to_double(dy));
4533}
4534
4535static void gesture_swipe_handle_end(
4536 void * /*data*/,
4537 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4538 uint32_t /*serial*/,
4539 uint32_t /*time*/,
4540 int32_t cancelled)
4541{
4542 CLOG_INFO(LOG, 2, "end (cancelled=%i)", cancelled);
4543}
4544
4545static const zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener = {
4546 /*begin*/ gesture_swipe_handle_begin,
4547 /*update*/ gesture_swipe_handle_update,
4548 /*end*/ gesture_swipe_handle_end,
4549};
4550
4551# undef LOG
4552#endif /* ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE */
4553
4555
4556/* -------------------------------------------------------------------- */
4563
4564static CLG_LogRef LOG_WL_TOUCH = {"ghost.wl.handle.touch"};
4565#define LOG (&LOG_WL_TOUCH)
4566
4567static void touch_seat_handle_down(void * /*data*/,
4568 wl_touch * /*wl_touch*/,
4569 uint32_t /*serial*/,
4570 uint32_t /*time*/,
4571 wl_surface * /*wl_surface*/,
4572 int32_t /*id*/,
4573 wl_fixed_t /*x*/,
4574 wl_fixed_t /*y*/)
4575{
4576 CLOG_INFO(LOG, 2, "down");
4577}
4578
4579static void touch_seat_handle_up(void * /*data*/,
4580 wl_touch * /*wl_touch*/,
4581 uint32_t /*serial*/,
4582 uint32_t /*time*/,
4583 int32_t /*id*/)
4584{
4585 CLOG_INFO(LOG, 2, "up");
4586}
4587
4588static void touch_seat_handle_motion(void * /*data*/,
4589 wl_touch * /*wl_touch*/,
4590 uint32_t /*time*/,
4591 int32_t /*id*/,
4592 wl_fixed_t /*x*/,
4593 wl_fixed_t /*y*/)
4594{
4595 CLOG_INFO(LOG, 2, "motion");
4596}
4597
4598static void touch_seat_handle_frame(void * /*data*/, wl_touch * /*wl_touch*/)
4599{
4600 CLOG_INFO(LOG, 2, "frame");
4601}
4602
4603static void touch_seat_handle_cancel(void * /*data*/, wl_touch * /*wl_touch*/)
4604{
4605
4606 CLOG_INFO(LOG, 2, "cancel");
4607}
4608
4609static void touch_seat_handle_shape(void * /*data*/,
4610 wl_touch * /*touch*/,
4611 int32_t /*id*/,
4612 wl_fixed_t /*major*/,
4613 wl_fixed_t /*minor*/)
4614{
4615 CLOG_INFO(LOG, 2, "shape");
4616}
4617
4618static void touch_seat_handle_orientation(void * /*data*/,
4619 wl_touch * /*touch*/,
4620 int32_t /*id*/,
4621 wl_fixed_t /*orientation*/)
4622{
4623 CLOG_INFO(LOG, 2, "orientation");
4624}
4625
4626static const wl_touch_listener touch_seat_listener = {
4627 /*down*/ touch_seat_handle_down,
4628 /*up*/ touch_seat_handle_up,
4629 /*motion*/ touch_seat_handle_motion,
4630 /*frame*/ touch_seat_handle_frame,
4631 /*cancel*/ touch_seat_handle_cancel,
4632 /*shape*/ touch_seat_handle_shape,
4633 /*orientation*/ touch_seat_handle_orientation,
4634};
4635
4636#undef LOG
4637
4639
4640/* -------------------------------------------------------------------- */
4643
4644static CLG_LogRef LOG_WL_TABLET_TOOL = {"ghost.wl.handle.tablet_tool"};
4645#define LOG (&LOG_WL_TABLET_TOOL)
4646
4648 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4649 const uint32_t tool_type)
4650{
4651 CLOG_INFO(LOG, 2, "type (type=%u)", tool_type);
4652
4653 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4654
4655 tablet_tool->data.Active = tablet_tool_map_type((enum zwp_tablet_tool_v2_type)tool_type);
4656}
4657
4658static void tablet_tool_handle_hardware_serial(void * /*data*/,
4659 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4660 const uint32_t /*hardware_serial_hi*/,
4661 const uint32_t /*hardware_serial_lo*/)
4662{
4663 CLOG_INFO(LOG, 2, "hardware_serial");
4664}
4665
4666static void tablet_tool_handle_hardware_id_wacom(void * /*data*/,
4667 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4668 const uint32_t /*hardware_id_hi*/,
4669 const uint32_t /*hardware_id_lo*/)
4670{
4671 CLOG_INFO(LOG, 2, "hardware_id_wacom");
4672}
4673
4674static void tablet_tool_handle_capability(void * /*data*/,
4675 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4676 const uint32_t capability)
4677{
4678 CLOG_INFO(LOG,
4679 2,
4680 "capability (tilt=%d, distance=%d, rotation=%d, slider=%d, wheel=%d)",
4681 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_TILT) != 0,
4682 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE) != 0,
4683 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION) != 0,
4684 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER) != 0,
4685 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL) != 0);
4686}
4687
4688static void tablet_tool_handle_done(void * /*data*/, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4689{
4690 CLOG_INFO(LOG, 2, "done");
4691}
4692static void tablet_tool_handle_removed(void *data, zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
4693{
4694 CLOG_INFO(LOG, 2, "removed");
4695
4696 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4697 GWL_Seat *seat = tablet_tool->seat;
4698
4699 if (tablet_tool->wl.surface_cursor) {
4700 wl_surface_destroy(tablet_tool->wl.surface_cursor);
4701 }
4702 seat->wp.tablet_tools.erase(zwp_tablet_tool_v2);
4703
4704 delete tablet_tool;
4705}
4707 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4708 const uint32_t serial,
4709 zwp_tablet_v2 * /*tablet*/,
4711{
4713 CLOG_INFO(LOG, 2, "proximity_in (skipped)");
4714 return;
4715 }
4716 CLOG_INFO(LOG, 2, "proximity_in");
4717
4718 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4719 tablet_tool->proximity = true;
4720
4721 GWL_Seat *seat = tablet_tool->seat;
4722 seat->cursor_source_serial = serial;
4724 seat->tablet.serial = serial;
4725
4726 seat->data_source_serial = serial;
4727
4728 seat->system->seat_active_set(seat);
4729
4731 win->cursor_shape_refresh();
4732
4733 /* Update #GHOST_TabletData. */
4734 GHOST_TabletData &td = tablet_tool->data;
4735 /* Reset, to avoid using stale tilt/pressure. */
4736 td.Xtilt = 0.0f;
4737 td.Ytilt = 0.0f;
4738 /* In case pressure isn't supported. */
4739 td.Pressure = 1.0f;
4740}
4742 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4743{
4744 CLOG_INFO(LOG, 2, "proximity_out");
4745 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4746 /* Defer clearing the wl_surface until the frame is handled.
4747 * Without this, the frame can not access the wl_surface. */
4748 tablet_tool->proximity = false;
4749}
4750
4752 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4753 const uint32_t serial)
4754{
4755 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4756 GWL_Seat *seat = tablet_tool->seat;
4757
4758 CLOG_INFO(LOG, 2, "down");
4759
4760 seat->data_source_serial = serial;
4761
4763}
4764
4765static void tablet_tool_handle_up(void *data, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4766{
4767 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4768
4769 CLOG_INFO(LOG, 2, "up");
4770
4772}
4773
4775 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4776 const wl_fixed_t x,
4777 const wl_fixed_t y)
4778{
4779 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4780
4781 CLOG_INFO(LOG, 2, "motion");
4782
4783 tablet_tool->xy[0] = x;
4784 tablet_tool->xy[1] = y;
4785 tablet_tool->has_xy = true;
4786
4788}
4789
4791 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4792 const uint32_t pressure)
4793{
4794 const float pressure_unit = float(pressure) / 65535;
4795 CLOG_INFO(LOG, 2, "pressure (%.4f)", pressure_unit);
4796
4797 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4798 GHOST_TabletData &td = tablet_tool->data;
4799 td.Pressure = pressure_unit;
4800
4802}
4803
4804static void tablet_tool_handle_distance(void * /*data*/,
4805 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4806 const uint32_t distance)
4807{
4808 CLOG_INFO(LOG, 2, "distance (distance=%u)", distance);
4809}
4810
4812 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4813 const wl_fixed_t tilt_x,
4814 const wl_fixed_t tilt_y)
4815{
4816 /* Map X tilt to `-1.0 (left)..1.0 (right)`.
4817 * Map Y tilt to `-1.0 (away from user)..1.0 (toward user)`. */
4818 const float tilt_unit[2] = {
4819 float(wl_fixed_to_double(tilt_x) / 90.0f),
4820 float(wl_fixed_to_double(tilt_y) / 90.0f),
4821 };
4822 CLOG_INFO(LOG, 2, "tilt (x=%.4f, y=%.4f)", UNPACK2(tilt_unit));
4823 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4824 GHOST_TabletData &td = tablet_tool->data;
4825 td.Xtilt = std::clamp(tilt_unit[0], -1.0f, 1.0f);
4826 td.Ytilt = std::clamp(tilt_unit[1], -1.0f, 1.0f);
4827
4829}
4830
4831static void tablet_tool_handle_rotation(void * /*data*/,
4832 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4833 const wl_fixed_t degrees)
4834{
4835 CLOG_INFO(LOG, 2, "rotation (degrees=%.4f)", wl_fixed_to_double(degrees));
4836}
4837
4838static void tablet_tool_handle_slider(void * /*data*/,
4839 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4840 const int32_t position)
4841{
4842 CLOG_INFO(LOG, 2, "slider (position=%d)", position);
4843}
4845 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4846 const wl_fixed_t /*degrees*/,
4847 const int32_t clicks)
4848{
4849 if (clicks == 0) {
4850 return;
4851 }
4852
4853 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4854
4855 CLOG_INFO(LOG, 2, "wheel (clicks=%d)", clicks);
4856
4857 tablet_tool->frame_pending.wheel.clicks = clicks;
4858
4860}
4861
4863 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4864 const uint32_t serial,
4865 const uint32_t button,
4866 const uint32_t state)
4867{
4868 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4869 GWL_Seat *seat = tablet_tool->seat;
4870
4871 CLOG_INFO(LOG, 2, "button (button=%u, state=%u)", button, state);
4872
4873 bool is_press = false;
4874 switch (state) {
4875 case WL_POINTER_BUTTON_STATE_RELEASED:
4876 is_press = false;
4877 break;
4878 case WL_POINTER_BUTTON_STATE_PRESSED:
4879 is_press = true;
4880 break;
4881 }
4882
4883 seat->data_source_serial = serial;
4884
4886 switch (button) {
4887 case BTN_STYLUS: {
4890 break;
4891 }
4892 case BTN_STYLUS2: {
4895 break;
4896 }
4897 case BTN_STYLUS3: {
4900 break;
4901 }
4902 }
4903
4905 gwl_tablet_tool_frame_event_add(tablet_tool, ty);
4906 }
4907}
4909 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4910 const uint32_t time)
4911{
4912 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4913 GWL_Seat *seat = tablet_tool->seat;
4914 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4915
4916 CLOG_INFO(LOG, 2, "frame");
4917
4918 /* No need to check the surfaces origin, it's already known to be owned by GHOST. */
4919 if (wl_surface *wl_surface_focus = seat->tablet.wl.surface_window) {
4920 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
4921 bool has_motion = false;
4922
4923 for (int ty_index = 0; ty_index < tablet_tool->frame_pending.frame_types_num; ty_index++) {
4924 const GWL_TabletTool_EventTypes ty = tablet_tool->frame_pending.frame_types[ty_index];
4925 switch (ty) {
4926 /* Use motion for pressure and tilt as there are no explicit event types for these. */
4930 /* Only one motion event per frame. */
4931 if (has_motion) {
4932 break;
4933 }
4934 /* Can happen when there is pressure/tilt without motion. */
4935 if (tablet_tool->has_xy == false) {
4936 break;
4937 }
4938
4939 seat->tablet.xy[0] = tablet_tool->xy[0];
4940 seat->tablet.xy[1] = tablet_tool->xy[1];
4941
4942 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, tablet_tool->xy)};
4944 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), tablet_tool->data));
4945 has_motion = true;
4946 break;
4947 }
4948#ifdef NDEBUG
4949 default:
4950#else /* Warn when any events aren't handled (in debug builds). */
4959#endif
4960 {
4961 const int button_enum_offset = int(ty) - int(GWL_TabletTool_EventTypes::Stylus0_Down);
4962 const int button_index = button_enum_offset / 2;
4963 const bool button_down = (button_index * 2) == button_enum_offset;
4964 const GHOST_TButton ebutton = gwl_tablet_tool_ebutton[button_index];
4965 const GHOST_TEventType etype = button_down ? GHOST_kEventButtonDown :
4967 seat->tablet.buttons.set(ebutton, button_down);
4969 new GHOST_EventButton(event_ms, etype, win, ebutton, tablet_tool->data));
4970 break;
4971 }
4974 new GHOST_EventWheel(event_ms,
4975 win,
4977 -tablet_tool->frame_pending.wheel.clicks));
4978 break;
4979 }
4980 }
4981 }
4982
4983 if (tablet_tool->proximity == false) {
4984 win->cursor_shape_refresh();
4985 }
4986 }
4987
4988 if (tablet_tool->proximity == false) {
4989 seat->tablet.wl.surface_window = nullptr;
4990 }
4991
4993}
4994
4995static const zwp_tablet_tool_v2_listener tablet_tool_listner = {
4996 /*type*/ tablet_tool_handle_type,
4997 /*hardware_serial*/ tablet_tool_handle_hardware_serial,
4998 /*hardware_id_wacom*/ tablet_tool_handle_hardware_id_wacom,
4999 /*capability*/ tablet_tool_handle_capability,
5000 /*done*/ tablet_tool_handle_done,
5001 /*removed*/ tablet_tool_handle_removed,
5002 /*proximity_in*/ tablet_tool_handle_proximity_in,
5003 /*proximity_out*/ tablet_tool_handle_proximity_out,
5004 /*down*/ tablet_tool_handle_down,
5005 /*up*/ tablet_tool_handle_up,
5006 /*motion*/ tablet_tool_handle_motion,
5007 /*pressure*/ tablet_tool_handle_pressure,
5008 /*distance*/ tablet_tool_handle_distance,
5009 /*tilt*/ tablet_tool_handle_tilt,
5010 /*rotation*/ tablet_tool_handle_rotation,
5011 /*slider*/ tablet_tool_handle_slider,
5012 /*wheel*/ tablet_tool_handle_wheel,
5013 /*button*/ tablet_tool_handle_button,
5014 /*frame*/ tablet_tool_handle_frame,
5015};
5016
5017#undef LOG
5018
5020
5021/* -------------------------------------------------------------------- */
5024
5025static CLG_LogRef LOG_WL_TABLET_SEAT = {"ghost.wl.handle.tablet_seat"};
5026#define LOG (&LOG_WL_TABLET_SEAT)
5027
5028static void tablet_seat_handle_tablet_added(void * /*data*/,
5029 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5030 zwp_tablet_v2 *id)
5031{
5032 CLOG_INFO(LOG, 2, "tablet_added (id=%p)", id);
5033}
5034
5036 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5037 zwp_tablet_tool_v2 *id)
5038{
5039 CLOG_INFO(LOG, 2, "tool_added (id=%p)", id);
5040
5041 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5042 GWL_TabletTool *tablet_tool = new GWL_TabletTool();
5043 tablet_tool->seat = seat;
5044
5045 /* Every tool has its own cursor wl_surface. */
5046 tablet_tool->wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
5048
5049 wl_surface_add_listener(
5050 tablet_tool->wl.surface_cursor, &cursor_surface_listener, static_cast<void *>(seat));
5051
5052 zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listner, tablet_tool);
5053
5054 seat->wp.tablet_tools.insert(id);
5055}
5056
5057static void tablet_seat_handle_pad_added(void * /*data*/,
5058 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5059 zwp_tablet_pad_v2 *id)
5060{
5061 CLOG_INFO(LOG, 2, "pad_added (id=%p)", id);
5062}
5063
5064static const zwp_tablet_seat_v2_listener tablet_seat_listener = {
5065 /*tablet_added*/ tablet_seat_handle_tablet_added,
5066 /*tool_added*/ tablet_seat_handle_tool_added,
5067 /*pad_added*/ tablet_seat_handle_pad_added,
5068};
5069
5070#undef LOG
5071
5073
5074/* -------------------------------------------------------------------- */
5077
5078static CLG_LogRef LOG_WL_KEYBOARD = {"ghost.wl.handle.keyboard"};
5079#define LOG (&LOG_WL_KEYBOARD)
5080
5082 wl_keyboard * /*wl_keyboard*/,
5083 const uint32_t format,
5084 const int32_t fd,
5085 const uint32_t size)
5086{
5087 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5088
5089 if ((!data) || (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)) {
5090 CLOG_INFO(LOG, 2, "keymap (no data or wrong version)");
5091 close(fd);
5092 return;
5093 }
5094
5095 char *map_str = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0));
5096 if (map_str == MAP_FAILED) {
5097 close(fd);
5098 CLOG_INFO(LOG, 2, "keymap mmap failed: %s", std::strerror(errno));
5099 return;
5100 }
5101
5102 xkb_keymap *keymap = xkb_keymap_new_from_string(
5103 seat->xkb.context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
5104 munmap(map_str, size);
5105 close(fd);
5106
5107 if (!keymap) {
5108 CLOG_INFO(LOG, 2, "keymap (not found)");
5109 return;
5110 }
5111
5112 CLOG_INFO(LOG, 2, "keymap");
5113
5114 /* Reset in case there was a previous non-zero active layout for the last key-map.
5115 * Note that this is set later by `wl_keyboard_listener::modifiers`, it's possible that handling
5116 * the first modifier will run #xkb_state_update_mask again (if the active layout is non-zero)
5117 * however as this is only done when the layout changed, it's harmless.
5118 * With a single layout - in practice the active layout will be zero. */
5119 seat->xkb.layout_active = 0;
5120
5121 if (seat->xkb.compose_state) {
5122 xkb_compose_state_reset(seat->xkb.compose_state);
5123 }
5124 else if (seat->xkb.compose_table) {
5125 seat->xkb.compose_state = xkb_compose_state_new(seat->xkb.compose_table,
5126 XKB_COMPOSE_STATE_NO_FLAGS);
5127 }
5128
5129 /* In practice we can assume `xkb_state_new` always succeeds. */
5130 xkb_state_unref(seat->xkb.state);
5131 seat->xkb.state = xkb_state_new(keymap);
5132
5133 xkb_state_unref(seat->xkb.state_empty);
5134 seat->xkb.state_empty = xkb_state_new(keymap);
5135
5136 for (int i = 0; i < MOD_INDEX_NUM; i++) {
5137 const GWL_ModifierInfo &mod_info = g_modifier_info_table[i];
5138 seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id);
5139 }
5140 seat->xkb_keymap_mod_index_mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
5141 seat->xkb_keymap_mod_index_numlock = xkb_keymap_mod_get_index(keymap, "NumLock");
5142
5143 xkb_state_unref(seat->xkb.state_empty_with_shift);
5144 seat->xkb.state_empty_with_shift = nullptr;
5145 if (seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT] != XKB_MOD_INVALID) {
5146 seat->xkb.state_empty_with_shift = xkb_state_new(keymap);
5147 }
5148
5149 xkb_state_unref(seat->xkb.state_empty_with_numlock);
5150 seat->xkb.state_empty_with_numlock = nullptr;
5151 if ((seat->xkb_keymap_mod_index_mod2 != XKB_MOD_INVALID) &&
5152 (seat->xkb_keymap_mod_index_numlock != XKB_MOD_INVALID))
5153 {
5154 seat->xkb.state_empty_with_numlock = xkb_state_new(keymap);
5155 }
5156
5158
5159#ifdef USE_NON_LATIN_KB_WORKAROUND
5160 seat->xkb_use_non_latin_workaround = false;
5161 if (seat->xkb.state_empty_with_shift) {
5162 seat->xkb_use_non_latin_workaround = true;
5163 for (xkb_keycode_t key_code = KEY_1 + EVDEV_OFFSET; key_code <= KEY_0 + EVDEV_OFFSET;
5164 key_code++)
5165 {
5166 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(seat->xkb.state_empty_with_shift,
5167 key_code);
5168 if (!(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9)) {
5169 seat->xkb_use_non_latin_workaround = false;
5170 break;
5171 }
5172 }
5173 }
5174#endif
5175
5177
5178 xkb_keymap_unref(keymap);
5179}
5180
5186static void keyboard_handle_enter(void *data,
5187 wl_keyboard * /*wl_keyboard*/,
5188 const uint32_t serial,
5190 wl_array *keys)
5191{
5192 /* Null when just destroyed. */
5194 CLOG_INFO(LOG, 2, "enter (skipped)");
5195 return;
5196 }
5197 CLOG_INFO(LOG, 2, "enter");
5198
5199 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5201
5202 seat->keyboard.serial = serial;
5203 seat->keyboard.wl.surface_window = wl_surface;
5204
5205 seat->system->seat_active_set(seat);
5206
5207 /* If there are any keys held when activating the window,
5208 * modifiers will be compared against the seat state,
5209 * only enabling modifiers that were previously disabled. */
5210 GWL_KeyboardDepressedState key_depressed_prev = seat->key_depressed;
5212
5213 /* Keep track of the last held repeating key, start the repeat timer if one exists. */
5214 struct {
5215 uint32_t key = std::numeric_limits<uint32_t>::max();
5216 xkb_keysym_t sym = 0;
5217 } repeat;
5218
5219 uint32_t *key;
5220 WL_ARRAY_FOR_EACH (key, keys) {
5221 const xkb_keycode_t key_code = *key + EVDEV_OFFSET;
5222 CLOG_INFO(LOG, 2, "enter (key_held=%d)", int(key_code));
5223 const xkb_keysym_t sym = xkb_state_key_get_one_sym(seat->xkb.state, key_code);
5224 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, *key);
5225 if (gkey != GHOST_kKeyUnknown) {
5227 }
5228
5229 if (xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code)) {
5230 repeat.key = *key;
5231 repeat.sym = sym;
5232 }
5233 }
5234
5235 /* Caller has no time-stamp, set from system. */
5236 const uint64_t event_ms = seat->system->getMilliSeconds();
5237 keyboard_depressed_state_push_events_from_change(seat, win, event_ms, key_depressed_prev);
5238
5239 if ((repeat.key != std::numeric_limits<uint32_t>::max()) && (seat->key_repeat.rate > 0)) {
5240 /* Since the key has been held, immediately send a press event.
5241 * This also ensures the key will be registered as pressed, see #117896. */
5242#ifdef USE_EVENT_BACKGROUND_THREAD
5243 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5244#endif
5245 /* Should have been cleared on leave, set here just in case. */
5246 if (UNLIKELY(seat->key_repeat.timer)) {
5248 }
5249
5250 const xkb_keycode_t key_code = repeat.key + EVDEV_OFFSET;
5251 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(repeat.sym, repeat.key);
5252
5253 GWL_KeyRepeatPlayload *key_repeat_payload = new GWL_KeyRepeatPlayload();
5254 key_repeat_payload->seat = seat;
5255 key_repeat_payload->key_code = key_code;
5256 key_repeat_payload->key_data.gkey = gkey;
5257
5258 gwl_seat_key_repeat_timer_add(seat, gwl_seat_key_repeat_timer_fn, key_repeat_payload, false);
5259 /* Ensure there is a press event on enter so this is known to be held before any mouse
5260 * button events which may use a key-binding that depends on this key being held. */
5262 }
5263}
5264
5270static void keyboard_handle_leave(void *data,
5271 wl_keyboard * /*wl_keyboard*/,
5272 const uint32_t /*serial*/,
5274{
5276 CLOG_INFO(LOG, 2, "leave (skipped)");
5277 return;
5278 }
5279 CLOG_INFO(LOG, 2, "leave");
5280
5281 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5282 seat->keyboard.wl.surface_window = nullptr;
5283
5284 {
5285#ifdef USE_EVENT_BACKGROUND_THREAD
5286 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5287#endif
5288 /* Losing focus must stop repeating text. */
5289 if (seat->key_repeat.timer) {
5291 }
5292 }
5293}
5294
5300 xkb_state *xkb_state_empty,
5301 xkb_state *xkb_state_empty_with_numlock,
5302 xkb_state *xkb_state_empty_with_shift,
5303 const bool xkb_use_non_latin_workaround,
5304 const xkb_keycode_t key)
5305{
5306 /* Use an empty keyboard state to access key symbol without modifiers. */
5307 xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state_empty, key);
5308
5309 /* NOTE(@ideasman42): Only perform the number-locked lookup as a fallback
5310 * when a number-pad key has been pressed. This is important as some key-maps use number lock
5311 * for switching other layers (in particular `de(neo_qwertz)` turns on layer-4), see: #96170.
5312 * Alternative solutions could be to inspect the layout however this could get involved
5313 * and turning on the number-lock is only needed for a limited set of keys. */
5314
5315 /* Accounts for key-pad keys typically swapped for numbers when number-lock is enabled:
5316 * `Home Left Up Right Down Prior Page_Up Next Page_Dow End Begin Insert Delete`. */
5317 if (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete) {
5318 if (xkb_state_empty_with_numlock) {
5319 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key);
5320 if (sym_test != XKB_KEY_NoSymbol) {
5321 sym = sym_test;
5322 }
5323 }
5324 }
5325 else {
5326#ifdef USE_NON_LATIN_KB_WORKAROUND
5327 if (key >= (KEY_1 + EVDEV_OFFSET) && key <= (KEY_0 + EVDEV_OFFSET)) {
5328 if (xkb_state_empty_with_shift && xkb_use_non_latin_workaround) {
5329 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_shift, key);
5330 if (sym_test != XKB_KEY_NoSymbol) {
5331 /* Should never happen as enabling `xkb_use_non_latin_workaround` checks this. */
5332 GHOST_ASSERT(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9, "Unexpected key");
5333 sym = sym_test;
5334 }
5335 }
5336 }
5337#else
5338 (void)xkb_state_empty_with_shift;
5339 (void)xkb_use_non_latin_workaround;
5340#endif
5341 }
5342
5343 return sym;
5344}
5345
5347 xkb_compose_state *compose_state,
5348 xkb_state *state,
5349 const xkb_keycode_t key,
5350 char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)])
5351{
5352 const xkb_keysym_t sym = xkb_state_key_get_one_sym(state, key);
5353 const xkb_compose_feed_result result = xkb_compose_state_feed(compose_state, sym);
5354 bool handled = false;
5355
5356 if (result == XKB_COMPOSE_FEED_ACCEPTED) {
5357 switch (xkb_compose_state_get_status(compose_state)) {
5358 case XKB_COMPOSE_NOTHING: {
5359 break;
5360 }
5361 case XKB_COMPOSE_COMPOSING: {
5362 r_utf8_buf[0] = '\0';
5363 handled = true;
5364 break;
5365 }
5366 case XKB_COMPOSE_COMPOSED: {
5367 char utf8_buf_compose[sizeof(GHOST_TEventKeyData::utf8_buf) + 1] = {'\0'};
5368 const int utf8_buf_compose_len = xkb_compose_state_get_utf8(
5369 compose_state, utf8_buf_compose, sizeof(utf8_buf_compose));
5370 if (utf8_buf_compose_len > 0) {
5371 if (utf8_buf_compose_len > sizeof(GHOST_TEventKeyData::utf8_buf)) {
5372 /* TODO(@ideasman42): keyboard events in GHOST only support a single character.
5373 *
5374 * - In the case XKB compose enters multiple code-points only the first will be used.
5375 *
5376 * - Besides supporting multiple characters per key input,
5377 * one possible solution would be to generate an IME event.
5378 *
5379 * - In practice I'm not sure how common these are.
5380 * So far no bugs have been reported about this.
5381 */
5382 CLOG_WARN(LOG, "key (compose_size=%d) exceeds the maximum size", utf8_buf_compose_len);
5383 }
5384 memcpy(r_utf8_buf, utf8_buf_compose, sizeof(GHOST_TEventKeyData::utf8_buf));
5385 handled = true;
5386 }
5387 break;
5388 }
5389 case XKB_COMPOSE_CANCELLED: {
5390 /* NOTE(@ideasman42): QT & GTK ignore these events as well as not inputting any text
5391 * so `<Compose><Backspace>` for example causes a cancel and *not* back-space.
5392 * This isn't supported under GHOST at the moment.
5393 * The key-event could also be ignored but this means tracking held state of
5394 * keys wont work properly, so don't do any input and pass in the key-symbol. */
5395 r_utf8_buf[0] = '\0';
5396 handled = true;
5397 break;
5398 }
5399 }
5400 }
5401 return handled;
5402}
5403
5408{
5409 GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller much check for timer");
5410 delete static_cast<GWL_KeyRepeatPlayload *>(seat->key_repeat.timer->getUserData());
5411
5413}
5414
5422static void keyboard_handle_key_repeat_reset(GWL_Seat *seat, const bool use_delay)
5423{
5424 GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller much check for timer");
5425 GHOST_TimerProcPtr key_repeat_fn = seat->key_repeat.timer->getTimerProc();
5427
5429 gwl_seat_key_repeat_timer_add(seat, key_repeat_fn, payload, use_delay);
5430}
5431
5432static void keyboard_handle_key(void *data,
5433 wl_keyboard * /*wl_keyboard*/,
5434 const uint32_t serial,
5435 const uint32_t time,
5436 const uint32_t key,
5437 const uint32_t state)
5438{
5439 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5440 const uint64_t event_ms = seat->system->ms_from_input_time(time);
5441
5442 const xkb_keycode_t key_code = key + EVDEV_OFFSET;
5443
5444 const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers(
5445 seat->xkb.state_empty,
5450#else
5451 false,
5452#endif
5453 key_code);
5454 if (sym == XKB_KEY_NoSymbol) {
5455 CLOG_INFO(LOG, 2, "key (code=%d, state=%u, no symbol, skipped)", int(key_code), state);
5456 return;
5457 }
5458 CLOG_INFO(LOG, 2, "key (code=%d, state=%u)", int(key_code), state);
5459
5461 switch (state) {
5462 case WL_KEYBOARD_KEY_STATE_RELEASED:
5463 etype = GHOST_kEventKeyUp;
5464 break;
5465 case WL_KEYBOARD_KEY_STATE_PRESSED:
5466 etype = GHOST_kEventKeyDown;
5467 break;
5468 }
5469
5470#ifdef USE_EVENT_BACKGROUND_THREAD
5471 /* Any access to `seat->key_repeat.timer` must lock. */
5472 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5473#endif
5474
5475 GWL_KeyRepeatPlayload *key_repeat_payload = nullptr;
5476
5477 /* Delete previous timer. */
5478 if (seat->key_repeat.timer) {
5479 enum { NOP = 1, RESET, CANCEL } timer_action = NOP;
5480 key_repeat_payload = static_cast<GWL_KeyRepeatPlayload *>(
5481 seat->key_repeat.timer->getUserData());
5482
5483 if (seat->key_repeat.rate == 0) {
5484 /* Repeat was disabled (unlikely but possible). */
5485 timer_action = CANCEL;
5486 }
5487 else if (key_code == key_repeat_payload->key_code) {
5488 /* Releasing the key that was held always cancels. */
5489 timer_action = CANCEL;
5490 }
5491 else if (xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code)) {
5492 if (etype == GHOST_kEventKeyDown) {
5493 /* Any other key-down always cancels (and may start its own repeat timer). */
5494 timer_action = CANCEL;
5495 }
5496 else {
5497 /* Key-up from keys that were not repeating cause the repeat timer to pause.
5498 *
5499 * NOTE(@ideasman42): This behavior isn't universal, some text input systems will
5500 * stop the repeat entirely. Choose to pause repeat instead as this is what GTK/WIN32 do,
5501 * and it fits better for keyboard input that isn't related to text entry. */
5502 timer_action = RESET;
5503 }
5504 }
5505
5506 switch (timer_action) {
5507 case NOP: {
5508 /* Don't add a new timer, leave the existing timer owning this `key_repeat_payload`. */
5509 key_repeat_payload = nullptr;
5510 break;
5511 }
5512 case RESET: {
5513 /* The payload will be added again. */
5515 break;
5516 }
5517 case CANCEL: {
5518 delete key_repeat_payload;
5519 key_repeat_payload = nullptr;
5521 break;
5522 }
5523 }
5524 }
5525
5526 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, key);
5527
5528 char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
5529 if (etype == GHOST_kEventKeyDown) {
5530 /* Handle key-compose (dead-keys). */
5531 if (seat->xkb.compose_state &&
5533 seat->xkb.compose_state, seat->xkb.state, key_code, utf8_buf))
5534 {
5535 /* `utf8_buf` has been filled by a compose action. */
5536 }
5537 else {
5538 xkb_state_key_get_utf8(seat->xkb.state, key_code, utf8_buf, sizeof(utf8_buf));
5539 }
5540 }
5541
5542 seat->data_source_serial = serial;
5543
5544 keyboard_depressed_state_key_event(seat, gkey, etype);
5545
5546 if (wl_surface *wl_surface_focus = seat->keyboard.wl.surface_window) {
5547 GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
5549 new GHOST_EventKey(event_ms, etype, win, gkey, false, utf8_buf));
5550 }
5551
5552 /* An existing payload means the key repeat timer is reset and will be added again. */
5553 if (key_repeat_payload == nullptr) {
5554 /* Start timer for repeating key, if applicable. */
5555 if ((seat->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) &&
5556 xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code))
5557 {
5558 key_repeat_payload = new GWL_KeyRepeatPlayload();
5559 key_repeat_payload->seat = seat;
5560 key_repeat_payload->key_code = key_code;
5561 key_repeat_payload->key_data.gkey = gkey;
5562 }
5563 }
5564
5565 if (key_repeat_payload) {
5566 gwl_seat_key_repeat_timer_add(seat, gwl_seat_key_repeat_timer_fn, key_repeat_payload, true);
5567 }
5568}
5569
5571 wl_keyboard * /*wl_keyboard*/,
5572 const uint32_t serial,
5573 const uint32_t mods_depressed,
5574 const uint32_t mods_latched,
5575 const uint32_t mods_locked,
5576 const uint32_t group)
5577{
5578 CLOG_INFO(LOG,
5579 2,
5580 "modifiers (depressed=%u, latched=%u, locked=%u, group=%u)",
5581 mods_depressed,
5582 mods_latched,
5583 mods_locked,
5584 group);
5585
5586 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5587 xkb_state_update_mask(seat->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
5588
5589 /* Account for the active layout changing within the same key-map,
5590 * needed so modifiers are detected from the expected layout, see: #115160. */
5591 if (group != seat->xkb.layout_active) {
5592 seat->xkb.layout_active = group;
5594 }
5595
5596 /* A modifier changed so reset the timer,
5597 * see comment in #keyboard_handle_key regarding this behavior. */
5598 {
5599#ifdef USE_EVENT_BACKGROUND_THREAD
5600 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5601#endif
5602 if (seat->key_repeat.timer) {
5604 }
5605 }
5606
5607 seat->data_source_serial = serial;
5608}
5609
5611 wl_keyboard * /*wl_keyboard*/,
5612 const int32_t rate,
5613 const int32_t delay)
5614{
5615 CLOG_INFO(LOG, 2, "info (rate=%d, delay=%d)", rate, delay);
5616
5617 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5618 seat->key_repeat.rate = rate;
5619 seat->key_repeat.delay = delay;
5620
5621 {
5622#ifdef USE_EVENT_BACKGROUND_THREAD
5623 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5624#endif
5625 /* Unlikely possible this setting changes while repeating. */
5626 if (seat->key_repeat.timer) {
5628 }
5629 }
5630}
5631
5632static const wl_keyboard_listener keyboard_listener = {
5633 /*keymap*/ keyboard_handle_keymap,
5634 /*enter*/ keyboard_handle_enter,
5635 /*leave*/ keyboard_handle_leave,
5636 /*key*/ keyboard_handle_key,
5637 /*modifiers*/ keyboard_handle_modifiers,
5638 /*repeat_info*/ keyboard_handle_repeat_info,
5639};
5640
5641#undef LOG
5642
5644
5645/* -------------------------------------------------------------------- */
5648
5649static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER = {"ghost.wl.handle.primary_selection_offer"};
5650#define LOG (&LOG_WL_PRIMARY_SELECTION_OFFER)
5651
5653 zwp_primary_selection_offer_v1 *id,
5654 const char *type)
5655{
5656 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
5658 if (data_offer->wp.id != id) {
5659 CLOG_INFO(LOG, 2, "offer: %p: offer for unknown selection %p of %s (skipped)", data, id, type);
5660 return;
5661 }
5662
5663 data_offer->types.insert(std::string(type));
5664}
5665
5666static const zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
5668};
5669
5670#undef LOG
5671
5673
5674/* -------------------------------------------------------------------- */
5677
5678static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE = {"ghost.wl.handle.primary_selection_device"};
5679#define LOG (&LOG_WL_PRIMARY_SELECTION_DEVICE)
5680
5682 void * /*data*/,
5683 zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
5684 zwp_primary_selection_offer_v1 *id)
5685{
5686 CLOG_INFO(LOG, 2, "data_offer");
5687
5689 data_offer->wp.id = id;
5690 zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, data_offer);
5691}
5692
5694 void *data,
5695 zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
5696 zwp_primary_selection_offer_v1 *id)
5697{
5698 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5699
5700 std::lock_guard lock{primary->data_offer_mutex};
5701
5702 /* Delete old data offer. */
5703 if (primary->data_offer != nullptr) {
5705 }
5706
5707 if (id == nullptr) {
5708 CLOG_INFO(LOG, 2, "selection: (skipped)");
5709 return;
5710 }
5711 CLOG_INFO(LOG, 2, "selection");
5712 /* Transfer ownership of the `data_offer`. */
5714 zwp_primary_selection_offer_v1_get_user_data(id));
5715 primary->data_offer = data_offer;
5716}
5717
5718static const zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
5721};
5722
5723#undef LOG
5724
5726
5727/* -------------------------------------------------------------------- */
5730
5731static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE = {"ghost.wl.handle.primary_selection_source"};
5732#define LOG (&LOG_WL_PRIMARY_SELECTION_SOURCE)
5733
5735 zwp_primary_selection_source_v1 * /*source*/,
5736 const char * /*mime_type*/,
5737 int32_t fd)
5738{
5739 CLOG_INFO(LOG, 2, "send");
5740
5741 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5742
5743 auto write_file_fn = [](GWL_PrimarySelection *primary, const int fd) {
5744 if (UNLIKELY(write(fd,
5745 primary->data_source->buffer_out.data,
5746 primary->data_source->buffer_out.data_size) < 0))
5747 {
5748 CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno));
5749 }
5750 close(fd);
5751 primary->data_source_mutex.unlock();
5752 };
5753
5754 primary->data_source_mutex.lock();
5755 std::thread write_thread(write_file_fn, primary, fd);
5756 write_thread.detach();
5757}
5758
5759static void primary_selection_source_cancelled(void *data, zwp_primary_selection_source_v1 *source)
5760{
5761 CLOG_INFO(LOG, 2, "cancelled");
5762
5763 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5764
5765 if (source == primary->data_source->wp.source) {
5767 }
5768}
5769
5770static const zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
5773};
5774
5775#undef LOG
5776
5778
5779/* -------------------------------------------------------------------- */
5782
5783#ifdef WITH_INPUT_IME
5784
5785class GHOST_EventIME : public GHOST_Event {
5786 protected:
5787 GHOST_TEventImeData event_ime_data;
5788
5789 public:
5796 GHOST_EventIME(uint64_t msec,
5797 GHOST_TEventType type,
5798 GHOST_IWindow *window,
5799 GHOST_TEventImeData *customdata)
5800 : GHOST_Event(msec, type, window)
5801 {
5802 /* Make sure that we keep a copy of the IME input. Otherwise it might get lost
5803 * because we overwrite it before it can be read in Blender. (See #137346). */
5804 this->event_ime_data = *customdata;
5805 this->m_data = &this->event_ime_data;
5806 }
5807};
5808
5809static CLG_LogRef LOG_WL_TEXT_INPUT = {"ghost.wl.handle.text_input"};
5810# define LOG (&LOG_WL_TEXT_INPUT)
5811
5812static void text_input_handle_enter(void *data,
5813 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5814 wl_surface *surface)
5815{
5816 /* Can be null when closing a window, see: #141777. */
5818 return;
5819 }
5820 CLOG_INFO(LOG, 2, "enter");
5821 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5822 seat->ime.surface_window = surface;
5823 /* If text input is enabled, should call `enable` after receive `enter` event.
5824 * This support switch input method during input, otherwise input method will not work. */
5825 if (seat->ime.is_enabled) {
5826 zwp_text_input_v3_enable(seat->wp.text_input);
5827 zwp_text_input_v3_commit(seat->wp.text_input);
5828 }
5829}
5830
5831static void text_input_handle_leave(void *data,
5832 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5833 wl_surface *surface)
5834{
5835 /* Can be null when closing a window. */
5837 return;
5838 }
5839 CLOG_INFO(LOG, 2, "leave");
5840 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5841 if (seat->ime.surface_window == surface) {
5842 seat->ime.surface_window = nullptr;
5843 }
5844 /* Always call `disable` after receive `leave` event. */
5845 zwp_text_input_v3_disable(seat->wp.text_input);
5846 zwp_text_input_v3_commit(seat->wp.text_input);
5847}
5848
5849static void text_input_handle_preedit_string(void *data,
5850 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5851 const char *text,
5852 int32_t cursor_begin,
5853 int32_t cursor_end)
5854{
5855 CLOG_INFO(LOG,
5856 2,
5857 "preedit_string (text=\"%s\", cursor_begin=%d, cursor_end=%d)",
5858 text ? text : "<null>",
5859 cursor_begin,
5860 cursor_end);
5861
5862 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5863 if (seat->ime.has_preedit == false) {
5864 /* Starting IME input. */
5865 gwl_seat_ime_full_reset(seat);
5866 }
5867
5868 seat->ime.composite_is_null = (text == nullptr);
5869 if (!seat->ime.composite_is_null) {
5870 seat->ime.event_ime_data.composite = text;
5871
5872 seat->ime.event_ime_data.cursor_position = cursor_begin;
5873 seat->ime.event_ime_data.target_start = cursor_begin;
5874 seat->ime.event_ime_data.target_end = cursor_end;
5875 }
5876
5877 seat->ime.has_preedit_string_callback = true;
5878}
5879
5880static void text_input_handle_commit_string(void *data,
5881 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5882 const char *text)
5883{
5884 CLOG_INFO(LOG, 2, "commit_string (text=\"%s\")", text ? text : "<null>");
5885
5886 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5887 seat->ime.result_is_null = (text == nullptr);
5888 seat->ime.event_ime_data.result = text ? text : "";
5889 seat->ime.event_ime_data.cursor_position = seat->ime.event_ime_data.result.size();
5890
5891 seat->ime.has_commit_string_callback = true;
5892}
5893
5894static void text_input_handle_delete_surrounding_text(void * /*data*/,
5895 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5896 uint32_t before_length,
5897 uint32_t after_length)
5898{
5899 CLOG_INFO(LOG,
5900 2,
5901 "delete_surrounding_text (before_length=%u, after_length=%u)",
5902 before_length,
5903 after_length);
5904
5905 /* NOTE: Currently unused, do we care about this event?
5906 * SDL ignores this event. */
5907}
5908
5909static void text_input_handle_done(void *data,
5910 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5911 uint32_t /*serial*/)
5912{
5913 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5914 GHOST_SystemWayland *system = seat->system;
5915 const uint64_t event_ms = seat->system->getMilliSeconds();
5916
5917 CLOG_INFO(LOG, 2, "done");
5918
5919 GHOST_WindowWayland *win = seat->ime.surface_window ?
5920 ghost_wl_surface_user_data(seat->ime.surface_window) :
5921 nullptr;
5922 if (seat->ime.has_commit_string_callback) {
5923 if (seat->ime.has_preedit) {
5924 const bool is_end = seat->ime.composite_is_null;
5925 if (is_end) {
5926 seat->ime.has_preedit = false;
5927 /* `commit_string` (end). */
5928 system->pushEvent_maybe_pending(new GHOST_EventIME(
5929 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
5930 system->pushEvent_maybe_pending(new GHOST_EventIME(
5931 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
5932 }
5933 else {
5934 /* `commit_string` (continues). */
5935 system->pushEvent_maybe_pending(new GHOST_EventIME(
5936 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
5937 }
5938 }
5939 else {
5940 /* `commit_string` ran with no active IME popup, start & end to insert text. */
5941 system->pushEvent_maybe_pending(new GHOST_EventIME(
5942 event_ms, GHOST_kEventImeCompositionStart, win, &seat->ime.event_ime_data));
5943 system->pushEvent_maybe_pending(new GHOST_EventIME(
5944 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
5945 system->pushEvent_maybe_pending(new GHOST_EventIME(
5946 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
5947 }
5948
5949 if (seat->ime.has_preedit == false) {
5950 gwl_seat_ime_preedit_reset(seat);
5951 }
5952 }
5953 else if (seat->ime.has_preedit_string_callback) {
5954 const bool is_end = seat->ime.composite_is_null;
5955 if (is_end) {
5956 /* `preedit_string` (end). */
5957 seat->ime.has_preedit = false;
5958 system->pushEvent_maybe_pending(new GHOST_EventIME(
5959 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
5960 }
5961 else {
5962 const bool is_start = seat->ime.has_preedit == false;
5963 /* `preedit_string` (start or continue). */
5964 seat->ime.has_preedit = true;
5965 system->pushEvent_maybe_pending(new GHOST_EventIME(
5966 event_ms,
5968 win,
5969 &seat->ime.event_ime_data));
5970 }
5971 }
5972
5973 seat->ime.has_preedit_string_callback = false;
5974 seat->ime.has_commit_string_callback = false;
5975}
5976
5977static zwp_text_input_v3_listener text_input_listener = {
5978 /*enter*/ text_input_handle_enter,
5979 /*leave*/ text_input_handle_leave,
5980 /*preedit_string*/ text_input_handle_preedit_string,
5981 /*commit_string*/ text_input_handle_commit_string,
5982 /*delete_surrounding_text*/ text_input_handle_delete_surrounding_text,
5983 /*done*/ text_input_handle_done,
5984};
5985
5986# undef LOG
5987
5988#endif /* WITH_INPUT_IME. */
5989
5991
5992/* -------------------------------------------------------------------- */
5995
5996static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"};
5997#define LOG (&LOG_WL_SEAT)
5998
5999static bool gwl_seat_capability_pointer_multitouch_check(const GWL_Seat *seat, const bool fallback)
6000{
6001 const zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6002 if (pointer_gestures == nullptr) {
6003 return fallback;
6004 }
6005
6006 bool found = false;
6007#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6008 if (seat->wp.pointer_gesture_hold) {
6009 return true;
6010 }
6011 found = true;
6012#endif
6013#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6014 if (seat->wp.pointer_gesture_pinch) {
6015 return true;
6016 }
6017 found = true;
6018#endif
6019#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6020 if (seat->wp.pointer_gesture_swipe) {
6021 return true;
6022 }
6023 found = true;
6024#endif
6025 if (seat->use_pointer_scroll_smooth_as_discrete == false) {
6026 return true;
6027 }
6028
6029 if (found == false) {
6030 return fallback;
6031 }
6032 return false;
6033}
6034
6036{
6037 /* Smooth to discrete handling. */
6040
6041 zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6042 if (pointer_gestures == nullptr) {
6043 return;
6044 }
6045
6046 const uint pointer_gestures_version = zwp_pointer_gestures_v1_get_version(pointer_gestures);
6047#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6048 if (pointer_gestures_version >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION)
6049 { /* Hold gesture. */
6050 zwp_pointer_gesture_hold_v1 *gesture = zwp_pointer_gestures_v1_get_hold_gesture(
6051 pointer_gestures, seat->wl.pointer);
6052 zwp_pointer_gesture_hold_v1_set_user_data(gesture, seat);
6053 zwp_pointer_gesture_hold_v1_add_listener(gesture, &gesture_hold_listener, seat);
6054 seat->wp.pointer_gesture_hold = gesture;
6055 }
6056#endif
6057#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6058 { /* Pinch gesture. */
6059 zwp_pointer_gesture_pinch_v1 *gesture = zwp_pointer_gestures_v1_get_pinch_gesture(
6060 pointer_gestures, seat->wl.pointer);
6061 zwp_pointer_gesture_pinch_v1_set_user_data(gesture, seat);
6062 zwp_pointer_gesture_pinch_v1_add_listener(gesture, &gesture_pinch_listener, seat);
6063 seat->wp.pointer_gesture_pinch = gesture;
6064 }
6065#endif
6066#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6067 { /* Swipe gesture. */
6068 zwp_pointer_gesture_swipe_v1 *gesture = zwp_pointer_gestures_v1_get_swipe_gesture(
6069 pointer_gestures, seat->wl.pointer);
6070 zwp_pointer_gesture_swipe_v1_set_user_data(gesture, seat);
6071 zwp_pointer_gesture_swipe_v1_add_listener(gesture, &gesture_swipe_listener, seat);
6072 seat->wp.pointer_gesture_swipe = gesture;
6073 }
6074#endif
6075}
6076
6078{
6079 /* Smooth to discrete handling. */
6082 seat->pointer_scroll.smooth_xy[0] = 0;
6083 seat->pointer_scroll.smooth_xy[1] = 0;
6084
6085 const zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6086 if (pointer_gestures == nullptr) {
6087 return;
6088 }
6089#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6090 { /* Hold gesture. */
6091 zwp_pointer_gesture_hold_v1 **gesture_p = &seat->wp.pointer_gesture_hold;
6092 if (*gesture_p) {
6093 zwp_pointer_gesture_hold_v1_destroy(*gesture_p);
6094 *gesture_p = nullptr;
6095 }
6096 }
6097#endif
6098#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6099 { /* Pinch gesture. */
6100 zwp_pointer_gesture_pinch_v1 **gesture_p = &seat->wp.pointer_gesture_pinch;
6101 if (*gesture_p) {
6102 zwp_pointer_gesture_pinch_v1_destroy(*gesture_p);
6103 *gesture_p = nullptr;
6104 }
6105 }
6106#endif
6107#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6108 { /* Swipe gesture. */
6109 zwp_pointer_gesture_swipe_v1 **gesture_p = &seat->wp.pointer_gesture_swipe;
6110 if (*gesture_p) {
6111 zwp_pointer_gesture_swipe_v1_destroy(*gesture_p);
6112 *gesture_p = nullptr;
6113 }
6114 }
6115#endif
6116}
6117
6119{
6120 if (seat->wl.pointer) {
6121 return;
6122 }
6123 seat->wl.pointer = wl_seat_get_pointer(seat->wl.seat);
6124 seat->cursor.wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
6125 seat->cursor.visible = true;
6126 seat->cursor.wl.buffer = nullptr;
6127 {
6128 /* Use environment variables, falling back to defaults.
6129 * These environment variables are used by enough WAYLAND applications
6130 * that it makes sense to check them (see `Xcursor` man page). */
6131 const char *env;
6132
6133 env = getenv("XCURSOR_THEME");
6134 seat->cursor.theme_name = std::string(env ? env : "");
6135
6136 env = getenv("XCURSOR_SIZE");
6138
6139 if (env && (*env != '\0')) {
6140 char *env_end = nullptr;
6141 /* While clamping is not needed on the WAYLAND side,
6142 * GHOST's internal logic may get confused by negative values, so ensure it's at least 1. */
6143 const long value = strtol(env, &env_end, 10);
6144 if ((*env_end == '\0') && (value > 0)) {
6145 seat->cursor.theme_size = int(value);
6146 }
6147 }
6148 }
6149 wl_pointer_add_listener(seat->wl.pointer, &pointer_listener, seat);
6150
6151 wl_surface_add_listener(seat->cursor.wl.surface_cursor, &cursor_surface_listener, seat);
6153
6155}
6156
6158{
6159 if (!seat->wl.pointer) {
6160 return;
6161 }
6162
6164
6165 if (seat->cursor.wl.surface_cursor) {
6166 wl_surface_destroy(seat->cursor.wl.surface_cursor);
6167 seat->cursor.wl.surface_cursor = nullptr;
6168 }
6169 if (seat->cursor.wl.theme) {
6171 seat->cursor.wl.theme = nullptr;
6172 }
6173
6174 wl_pointer_destroy(seat->wl.pointer);
6175 seat->wl.pointer = nullptr;
6176}
6177
6179{
6180 if (seat->wl.keyboard) {
6181 return;
6182 }
6183 seat->wl.keyboard = wl_seat_get_keyboard(seat->wl.seat);
6184 wl_keyboard_add_listener(seat->wl.keyboard, &keyboard_listener, seat);
6185}
6186
6188{
6189 if (!seat->wl.keyboard) {
6190 return;
6191 }
6192
6193 {
6194#ifdef USE_EVENT_BACKGROUND_THREAD
6195 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
6196#endif
6197 if (seat->key_repeat.timer) {
6199 }
6200 }
6201 wl_keyboard_destroy(seat->wl.keyboard);
6202 seat->wl.keyboard = nullptr;
6203}
6204
6206{
6207 if (seat->wl.touch) {
6208 return;
6209 }
6210 seat->wl.touch = wl_seat_get_touch(seat->wl.seat);
6211 wl_touch_set_user_data(seat->wl.touch, seat);
6212 wl_touch_add_listener(seat->wl.touch, &touch_seat_listener, seat);
6213}
6214
6216{
6217 if (!seat->wl.touch) {
6218 return;
6219 }
6220 wl_touch_destroy(seat->wl.touch);
6221 seat->wl.touch = nullptr;
6222}
6223
6225 /* Only used in an assert. */
6226 [[maybe_unused]] wl_seat *wl_seat,
6227 const uint32_t capabilities)
6228{
6229 CLOG_INFO(LOG,
6230 2,
6231 "capabilities (pointer=%d, keyboard=%d, touch=%d)",
6232 (capabilities & WL_SEAT_CAPABILITY_POINTER) != 0,
6233 (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) != 0,
6234 (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0);
6235
6236 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
6237 GHOST_ASSERT(seat->wl.seat == wl_seat, "Seat mismatch");
6238
6239 if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
6241 }
6242 else {
6244 }
6245
6246 if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
6248 }
6249 else {
6251 }
6252
6253 if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
6255 }
6256 else {
6258 }
6259}
6260
6261static void seat_handle_name(void *data, wl_seat * /*wl_seat*/, const char *name)
6262{
6263 CLOG_INFO(LOG, 2, "name (name=\"%s\")", name);
6264 static_cast<GWL_Seat *>(data)->name = std::string(name);
6265}
6266
6267static const wl_seat_listener seat_listener = {
6268 /*capabilities*/ seat_handle_capabilities,
6269 /*name*/ seat_handle_name,
6270};
6271
6272#undef LOG
6273
6275
6276/* -------------------------------------------------------------------- */
6279
6280static CLG_LogRef LOG_WL_XDG_OUTPUT = {"ghost.wl.handle.xdg_output"};
6281#define LOG (&LOG_WL_XDG_OUTPUT)
6282
6284 zxdg_output_v1 * /*xdg_output*/,
6285 const int32_t x,
6286 const int32_t y)
6287{
6288 CLOG_INFO(LOG, 2, "logical_position [%d, %d]", x, y);
6289
6290 GWL_Output *output = static_cast<GWL_Output *>(data);
6291 output->position_logical[0] = x;
6292 output->position_logical[1] = y;
6293 output->has_position_logical = true;
6294}
6295
6297 zxdg_output_v1 * /*xdg_output*/,
6298 const int32_t width,
6299 const int32_t height)
6300{
6301 CLOG_INFO(LOG, 2, "logical_size [%d, %d]", width, height);
6302
6303 GWL_Output *output = static_cast<GWL_Output *>(data);
6304 if (output->size_native[0] != 0 && output->size_native[1] != 0) {
6305 /* Original comment from SDL. */
6306 /* FIXME(@flibit): GNOME has a bug where the logical size does not account for
6307 * scale, resulting in bogus viewport sizes.
6308 *
6309 * Until this is fixed, validate that _some_ kind of scaling is being
6310 * done (we can't match exactly because fractional scaling can't be
6311 * detected otherwise), then override if necessary. */
6312 int width_native = output->size_native[(output->transform & WL_OUTPUT_TRANSFORM_90) ? 1 : 0];
6313 if ((width_native == width) && (output->scale_fractional == (1 * FRACTIONAL_DENOMINATOR))) {
6314 GHOST_PRINT("xdg_output scale did not match, overriding with wl_output scale\n");
6315
6316#ifdef USE_GNOME_CONFINE_HACK
6317 /* Use a bug in GNOME to check GNOME is in use. If the bug is fixed this won't cause an issue
6318 * as #98793 has been fixed up-stream too, but not in a release at time of writing. */
6320#endif
6321
6322 return;
6323 }
6324 }
6325
6326 output->size_logical[0] = width;
6327 output->size_logical[1] = height;
6328 output->has_size_logical = true;
6329}
6330
6331static void xdg_output_handle_done(void *data, zxdg_output_v1 * /*xdg_output*/)
6332{
6333 CLOG_INFO(LOG, 2, "done");
6334 /* NOTE: `xdg-output.done` events are deprecated and only apply below version 3 of the protocol.
6335 * `wl-output.done` event will be emitted in version 3 or higher. */
6336 GWL_Output *output = static_cast<GWL_Output *>(data);
6337 if (zxdg_output_v1_get_version(output->xdg.output) < 3) {
6338 output_handle_done(data, output->wl.output);
6339 }
6340}
6341
6342static void xdg_output_handle_name(void * /*data*/,
6343 zxdg_output_v1 * /*xdg_output*/,
6344 const char *name)
6345{
6346 CLOG_INFO(LOG, 2, "name (name=\"%s\")", name);
6347}
6348
6349static void xdg_output_handle_description(void * /*data*/,
6350 zxdg_output_v1 * /*xdg_output*/,
6351 const char *description)
6352{
6353 CLOG_INFO(LOG, 2, "description (description=\"%s\")", description);
6354}
6355
6356static const zxdg_output_v1_listener xdg_output_listener = {
6357 /*logical_position*/ xdg_output_handle_logical_position,
6358 /*logical_size*/ xdg_output_handle_logical_size,
6359 /*done*/ xdg_output_handle_done,
6360 /*name*/ xdg_output_handle_name,
6361 /*description*/ xdg_output_handle_description,
6362};
6363
6364#undef LOG
6365
6367
6368/* -------------------------------------------------------------------- */
6371
6372static CLG_LogRef LOG_WL_OUTPUT = {"ghost.wl.handle.output"};
6373#define LOG (&LOG_WL_OUTPUT)
6374
6376 wl_output * /*wl_output*/,
6377 const int32_t /*x*/,
6378 const int32_t /*y*/,
6379 const int32_t physical_width,
6380 const int32_t physical_height,
6381 const int32_t /*subpixel*/,
6382 const char *make,
6383 const char *model,
6384 const int32_t transform)
6385{
6386 CLOG_INFO(LOG,
6387 2,
6388 "geometry (make=\"%s\", model=\"%s\", transform=%d, size=[%d, %d])",
6389 make,
6390 model,
6391 transform,
6392 physical_width,
6393 physical_height);
6394
6395 GWL_Output *output = static_cast<GWL_Output *>(data);
6396 output->transform = transform;
6397 output->make = std::string(make);
6398 output->model = std::string(model);
6399 output->size_mm[0] = physical_width;
6400 output->size_mm[1] = physical_height;
6401}
6402
6403static void output_handle_mode(void *data,
6404 wl_output * /*wl_output*/,
6405 const uint32_t flags,
6406 const int32_t width,
6407 const int32_t height,
6408 const int32_t /*refresh*/)
6409{
6410 if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) {
6411 CLOG_INFO(LOG, 2, "mode (skipped)");
6412 return;
6413 }
6414 CLOG_INFO(LOG, 2, "mode (size=[%d, %d], flags=%u)", width, height, flags);
6415
6416 GWL_Output *output = static_cast<GWL_Output *>(data);
6417 output->size_native[0] = width;
6418 output->size_native[1] = height;
6419}
6420
6429static void output_handle_done(void *data, wl_output * /*wl_output*/)
6430{
6431 CLOG_INFO(LOG, 2, "done");
6432
6433 GWL_Output *output = static_cast<GWL_Output *>(data);
6434 int32_t size_native[2] = {UNPACK2(output->size_native)};
6435 if (output->transform & WL_OUTPUT_TRANSFORM_90) {
6436 std::swap(size_native[0], size_native[1]);
6437 }
6438
6439 /* If `xdg-output` is present, calculate the true scale of the desktop */
6440 if (output->has_size_logical) {
6441
6442 /* NOTE: it's not necessary to divide these values by their greatest-common-denominator
6443 * as even a 64k screen resolution doesn't approach overflowing an `int32_t`. */
6444
6445 GHOST_ASSERT(size_native[0] && output->size_logical[0],
6446 "Screen size values were not set when they were expected to be.");
6447
6448 output->scale_fractional = (size_native[0] * FRACTIONAL_DENOMINATOR) / output->size_logical[0];
6449 output->has_scale_fractional = true;
6450 }
6451}
6452
6453static void output_handle_scale(void *data, wl_output * /*wl_output*/, const int32_t factor)
6454{
6455 CLOG_INFO(LOG, 2, "scale");
6456 GWL_Output *output = static_cast<GWL_Output *>(data);
6457 output->scale = factor;
6458 output->system->output_scale_update(output);
6459}
6460
6461static void output_handle_name(void * /*data*/, wl_output * /*wl_output*/, const char *name)
6462{
6463 /* Only available in interface version 4. */
6464 CLOG_INFO(LOG, 2, "name (%s)", name);
6465}
6466static void output_handle_description(void * /*data*/,
6467 wl_output * /*wl_output*/,
6468 const char *description)
6469{
6470 /* Only available in interface version 4. */
6471 CLOG_INFO(LOG, 2, "description (%s)", description);
6472}
6473
6474static const wl_output_listener output_listener = {
6475 /*geometry*/ output_handle_geometry,
6476 /*mode*/ output_handle_mode,
6477 /*done*/ output_handle_done,
6478 /*scale*/ output_handle_scale,
6479 /*name*/ output_handle_name,
6480 /*description*/ output_handle_description,
6481};
6482
6483#undef LOG
6484
6486
6487/* -------------------------------------------------------------------- */
6490
6491static CLG_LogRef LOG_WL_XDG_WM_BASE = {"ghost.wl.handle.xdg_wm_base"};
6492#define LOG (&LOG_WL_XDG_WM_BASE)
6493
6494static void shell_handle_ping(void * /*data*/, xdg_wm_base *xdg_wm_base, const uint32_t serial)
6495{
6496 CLOG_INFO(LOG, 2, "ping");
6497 xdg_wm_base_pong(xdg_wm_base, serial);
6498}
6499
6500static const xdg_wm_base_listener shell_listener = {
6501 /*ping*/ shell_handle_ping,
6502};
6503
6504#undef LOG
6505
6507
6508/* -------------------------------------------------------------------- */
6511
6512#ifdef WITH_GHOST_WAYLAND_LIBDECOR
6513
6514static CLG_LogRef LOG_WL_LIBDECOR = {"ghost.wl.handle.libdecor"};
6515# define LOG (&LOG_WL_LIBDECOR)
6516
6517static void decor_handle_error(libdecor * /*context*/,
6518 enum libdecor_error error,
6519 const char *message)
6520{
6521 CLOG_INFO(LOG, 2, "error (id=%d, message=%s)", error, message);
6522
6523 (void)(error);
6524 (void)(message);
6525 GHOST_PRINT("decoration error (" << error << "): " << message << std::endl);
6526 exit(EXIT_FAILURE);
6527}
6528
6529static libdecor_interface libdecor_interface = {
6530 decor_handle_error,
6531};
6532
6533# undef LOG
6534
6535#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
6536
6538
6539/* -------------------------------------------------------------------- */
6542
6543static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"};
6544#define LOG (&LOG_WL_REGISTRY)
6545
6546/* #GWL_Display.wl_compositor */
6547
6550{
6551 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 6u);
6552
6553 display->wl.compositor = static_cast<wl_compositor *>(
6554 wl_registry_bind(display->wl.registry, params.name, &wl_compositor_interface, version));
6555 gwl_registry_entry_add(display, params, nullptr);
6556}
6558 void * /*user_data*/,
6559 const bool /*on_exit*/)
6560{
6561 wl_compositor **value_p = &display->wl.compositor;
6562 wl_compositor_destroy(*value_p);
6563 *value_p = nullptr;
6564}
6565
6566/* #GWL_Display.xdg_decor.shell */
6567
6570{
6571 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 6u);
6572
6573 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6574 decor.shell = static_cast<xdg_wm_base *>(
6575 wl_registry_bind(display->wl.registry, params.name, &xdg_wm_base_interface, version));
6576 xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr);
6577 decor.shell_name = params.name;
6578 gwl_registry_entry_add(display, params, nullptr);
6579}
6581 void * /*user_data*/,
6582 const bool /*on_exit*/)
6583{
6584 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6585 xdg_wm_base **value_p = &decor.shell;
6586 uint32_t *name_p = &decor.shell_name;
6587 xdg_wm_base_destroy(*value_p);
6588 *value_p = nullptr;
6589 *name_p = WL_NAME_UNSET;
6590}
6591
6592/* #GWL_Display.xdg_decor.manager */
6593
6596{
6597 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6598
6599 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6600 decor.manager = static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind(
6601 display->wl.registry, params.name, &zxdg_decoration_manager_v1_interface, version));
6602 decor.manager_name = params.name;
6603 gwl_registry_entry_add(display, params, nullptr);
6604}
6606 void * /*user_data*/,
6607 const bool /*on_exit*/)
6608{
6609 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6610 zxdg_decoration_manager_v1 **value_p = &decor.manager;
6611 uint32_t *name_p = &decor.manager_name;
6612 zxdg_decoration_manager_v1_destroy(*value_p);
6613 *value_p = nullptr;
6614 *name_p = WL_NAME_UNSET;
6615}
6616
6617/* #GWL_Display.xdg_output_manager */
6618
6621{
6622 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 2u, 3u);
6623
6624 display->xdg.output_manager = static_cast<zxdg_output_manager_v1 *>(wl_registry_bind(
6625 display->wl.registry, params.name, &zxdg_output_manager_v1_interface, version));
6626 gwl_registry_entry_add(display, params, nullptr);
6627}
6629 void * /*user_data*/,
6630 const bool /*on_exit*/)
6631{
6632 zxdg_output_manager_v1 **value_p = &display->xdg.output_manager;
6633 zxdg_output_manager_v1_destroy(*value_p);
6634 *value_p = nullptr;
6635}
6636
6637/* #GWL_Display.wl_output */
6638
6640{
6641 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 2u, 4u);
6642
6644 output->system = display->system;
6645 output->wl.output = static_cast<wl_output *>(
6646 wl_registry_bind(display->wl.registry, params.name, &wl_output_interface, version));
6647 ghost_wl_output_tag(output->wl.output);
6648 wl_output_set_user_data(output->wl.output, output);
6649
6650 display->outputs.push_back(output);
6651 wl_output_add_listener(output->wl.output, &output_listener, output);
6652 gwl_registry_entry_add(display, params, static_cast<void *>(output));
6653}
6656{
6657 GWL_Output *output = static_cast<GWL_Output *>(params.user_data);
6658 if (display->xdg.output_manager) {
6659 if (output->xdg.output == nullptr) {
6660 output->xdg.output = zxdg_output_manager_v1_get_xdg_output(display->xdg.output_manager,
6661 output->wl.output);
6662 zxdg_output_v1_add_listener(output->xdg.output, &xdg_output_listener, output);
6663 }
6664 }
6665 else {
6666 output->xdg.output = nullptr;
6667 }
6668}
6670 void *user_data,
6671 const bool on_exit)
6672{
6673 /* While windows & cursors hold references to outputs, there is no need to manually remove
6674 * these references as the compositor will remove references via #wl_surface_listener.leave.
6675 *
6676 * WARNING: this is not the case for WLROOTS based compositors which have a (bug?)
6677 * where surface leave events don't run. So `system->output_leave(..)` is needed
6678 * until the issue is resolved in WLROOTS. */
6679 GWL_Output *output = static_cast<GWL_Output *>(user_data);
6680
6681 if (!on_exit) {
6682 /* Needed for WLROOTS, does nothing if surface leave callbacks have already run. */
6683 if (output->system->output_unref(output->wl.output)) {
6684 CLOG_WARN(LOG,
6685 "mis-behaving compositor failed to call \"surface_listener.leave\" "
6686 "window scale may be invalid!");
6687 }
6688 }
6689
6690 if (output->xdg.output) {
6691 zxdg_output_v1_destroy(output->xdg.output);
6692 }
6693 wl_output_destroy(output->wl.output);
6694 std::vector<GWL_Output *>::iterator iter = std::find(
6695 display->outputs.begin(), display->outputs.end(), output);
6696 const int index = (iter != display->outputs.cend()) ?
6697 std::distance(display->outputs.begin(), iter) :
6698 -1;
6699 GHOST_ASSERT(index != -1, "invalid internal state");
6700 /* NOTE: always erase even when `on_exit` because `output->xdg_output` is cleared later. */
6701 display->outputs.erase(display->outputs.begin() + index);
6702 delete output;
6703}
6704
6705/* #GWL_Display.seats */
6706
6708{
6709 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 5u, 9u);
6710
6711 GWL_Seat *seat = new GWL_Seat;
6712 seat->system = display->system;
6713 seat->xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
6714
6715 /* May be null (skip dead-key support in this case). */
6716 seat->xkb.compose_table = xkb_compose_table_new_from_locale(
6717 seat->xkb.context, ghost_wl_locale_from_env_with_default(), XKB_COMPOSE_COMPILE_NO_FLAGS);
6718
6719 seat->data_source = new GWL_DataSource;
6720 seat->wl.seat = static_cast<wl_seat *>(
6721 wl_registry_bind(display->wl.registry, params.name, &wl_seat_interface, version));
6722 display->seats.push_back(seat);
6723 wl_seat_add_listener(seat->wl.seat, &seat_listener, seat);
6724 gwl_registry_entry_add(display, params, static_cast<void *>(seat));
6725
6727}
6730{
6731 GWL_Seat *seat = static_cast<GWL_Seat *>(params.user_data);
6732
6733 /* Register data device per seat for IPC between WAYLAND clients. */
6734 if (display->wl.data_device_manager) {
6735 if (seat->wl.data_device == nullptr) {
6736 seat->wl.data_device = wl_data_device_manager_get_data_device(
6737 display->wl.data_device_manager, seat->wl.seat);
6738 wl_data_device_add_listener(seat->wl.data_device, &data_device_listener, seat);
6739 }
6740 }
6741 else {
6742 seat->wl.data_device = nullptr;
6743 }
6744
6745 if (display->wp.tablet_manager) {
6746 if (seat->wp.tablet_seat == nullptr) {
6747 seat->wp.tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display->wp.tablet_manager,
6748 seat->wl.seat);
6749 zwp_tablet_seat_v2_add_listener(seat->wp.tablet_seat, &tablet_seat_listener, seat);
6750 }
6751 }
6752 else {
6753 seat->wp.tablet_seat = nullptr;
6754 }
6755
6756 if (display->wp.primary_selection_device_manager) {
6757 if (seat->wp.primary_selection_device == nullptr) {
6758 seat->wp.primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
6759 display->wp.primary_selection_device_manager, seat->wl.seat);
6760
6761 zwp_primary_selection_device_v1_add_listener(seat->wp.primary_selection_device,
6763 &seat->primary_selection);
6764 }
6765 }
6766 else {
6767 seat->wp.primary_selection_device = nullptr;
6768 }
6769
6770#ifdef WITH_INPUT_IME
6771 if (display->wp.text_input_manager) {
6772 if (seat->wp.text_input == nullptr) {
6773 seat->wp.text_input = zwp_text_input_manager_v3_get_text_input(
6774 display->wp.text_input_manager, seat->wl.seat);
6775 zwp_text_input_v3_set_user_data(seat->wp.text_input, seat);
6776 zwp_text_input_v3_add_listener(seat->wp.text_input, &text_input_listener, seat);
6777 }
6778 }
6779 else {
6780 seat->wp.text_input = nullptr;
6781 }
6782#endif /* WITH_INPUT_IME */
6783}
6784static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit)
6785{
6786 GWL_Seat *seat = static_cast<GWL_Seat *>(user_data);
6787
6788 /* First handle members that require locking.
6789 * While highly unlikely, it's possible they are being used while this function runs. */
6790 {
6791 std::lock_guard lock{seat->data_source_mutex};
6792 if (seat->data_source) {
6794 if (seat->data_source->wl.source) {
6795 wl_data_source_destroy(seat->data_source->wl.source);
6796 }
6797 delete seat->data_source;
6798 }
6799 }
6800
6801 {
6802 std::lock_guard lock{seat->data_offer_dnd_mutex};
6803 if (seat->data_offer_dnd) {
6804 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
6805 delete seat->data_offer_dnd;
6806 }
6807 }
6808
6809 {
6810 std::lock_guard lock{seat->data_offer_copy_paste_mutex};
6811 if (seat->data_offer_copy_paste) {
6812 wl_data_offer_destroy(seat->data_offer_copy_paste->wl.id);
6813 delete seat->data_offer_copy_paste;
6814 }
6815 }
6816
6817 {
6818 GWL_PrimarySelection *primary = &seat->primary_selection;
6819 std::lock_guard lock{primary->data_offer_mutex};
6821 }
6822
6823 {
6824 GWL_PrimarySelection *primary = &seat->primary_selection;
6825 std::lock_guard lock{primary->data_source_mutex};
6827 }
6828
6829 if (seat->wp.primary_selection_device) {
6830 zwp_primary_selection_device_v1_destroy(seat->wp.primary_selection_device);
6831 }
6832
6833#ifdef WITH_INPUT_IME
6834 if (seat->wp.text_input) {
6835 zwp_text_input_v3_destroy(seat->wp.text_input);
6836 }
6837#endif
6838
6839 if (seat->wl.data_device) {
6840 wl_data_device_release(seat->wl.data_device);
6841 }
6842
6843 if (seat->wp.tablet_seat) {
6844 zwp_tablet_seat_v2_destroy(seat->wp.tablet_seat);
6845 }
6846
6847 if (seat->cursor.custom_data) {
6848 munmap(seat->cursor.custom_data, seat->cursor.custom_data_size);
6849 }
6850
6851 /* Disable all capabilities as a way to free:
6852 * - `seat.wl_pointer` (and related cursor variables).
6853 * - `seat.wl_touch`.
6854 * - `seat.wl_keyboard`.
6855 */
6859
6860 /* Un-referencing checks for nullptr case. */
6861 xkb_state_unref(seat->xkb.state);
6862 xkb_state_unref(seat->xkb.state_empty);
6863 xkb_state_unref(seat->xkb.state_empty_with_shift);
6864 xkb_state_unref(seat->xkb.state_empty_with_numlock);
6865
6866 xkb_compose_state_unref(seat->xkb.compose_state);
6867 xkb_compose_table_unref(seat->xkb.compose_table);
6868
6869 xkb_context_unref(seat->xkb.context);
6870
6871 /* Remove the seat. */
6872 wl_seat_destroy(seat->wl.seat);
6873
6874 std::vector<GWL_Seat *>::iterator iter = std::find(
6875 display->seats.begin(), display->seats.end(), seat);
6876 const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) :
6877 -1;
6878 GHOST_ASSERT(index != -1, "invalid internal state");
6879
6880 if (!on_exit) {
6881 if (display->seats_active_index >= index) {
6882 display->seats_active_index -= 1;
6883 }
6884 display->seats.erase(display->seats.begin() + index);
6885 }
6886 delete seat;
6887}
6888
6889/* #GWL_Display.wl_shm */
6890
6892{
6893 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6894
6895 display->wl.shm = static_cast<wl_shm *>(
6896 wl_registry_bind(display->wl.registry, params.name, &wl_shm_interface, version));
6897 gwl_registry_entry_add(display, params, nullptr);
6898}
6900 void * /*user_data*/,
6901 const bool /*on_exit*/)
6902{
6903 wl_shm **value_p = &display->wl.shm;
6904 wl_shm_destroy(*value_p);
6905 *value_p = nullptr;
6906}
6907
6908/* #GWL_Display.wl_data_device_manager */
6909
6912{
6913 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 3u);
6914
6915 display->wl.data_device_manager = static_cast<wl_data_device_manager *>(wl_registry_bind(
6916 display->wl.registry, params.name, &wl_data_device_manager_interface, version));
6917 gwl_registry_entry_add(display, params, nullptr);
6918}
6920 void * /*user_data*/,
6921 const bool /*on_exit*/)
6922{
6923 wl_data_device_manager **value_p = &display->wl.data_device_manager;
6924 wl_data_device_manager_destroy(*value_p);
6925 *value_p = nullptr;
6926}
6927
6928/* #GWL_Display.wp_tablet_manager */
6929
6932{
6933 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6934
6935 display->wp.tablet_manager = static_cast<zwp_tablet_manager_v2 *>(wl_registry_bind(
6936 display->wl.registry, params.name, &zwp_tablet_manager_v2_interface, version));
6937 gwl_registry_entry_add(display, params, nullptr);
6938}
6940 void * /*user_data*/,
6941 const bool /*on_exit*/)
6942{
6943 zwp_tablet_manager_v2 **value_p = &display->wp.tablet_manager;
6944 zwp_tablet_manager_v2_destroy(*value_p);
6945 *value_p = nullptr;
6946}
6947
6948/* #GWL_Display.wp_relative_pointer_manager */
6949
6952{
6953 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6954
6955 display->wp.relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>(
6956 wl_registry_bind(
6957 display->wl.registry, params.name, &zwp_relative_pointer_manager_v1_interface, version));
6958 gwl_registry_entry_add(display, params, nullptr);
6959}
6961 void * /*user_data*/,
6962 const bool /*on_exit*/)
6963{
6964 zwp_relative_pointer_manager_v1 **value_p = &display->wp.relative_pointer_manager;
6965 zwp_relative_pointer_manager_v1_destroy(*value_p);
6966 *value_p = nullptr;
6967}
6968
6969/* #GWL_Display.wp_pointer_constraints */
6970
6973{
6974 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6975
6976 display->wp.pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind(
6977 display->wl.registry, params.name, &zwp_pointer_constraints_v1_interface, version));
6978 gwl_registry_entry_add(display, params, nullptr);
6979}
6981 void * /*user_data*/,
6982 const bool /*on_exit*/)
6983{
6984 zwp_pointer_constraints_v1 **value_p = &display->wp.pointer_constraints;
6985 zwp_pointer_constraints_v1_destroy(*value_p);
6986 *value_p = nullptr;
6987}
6988
6989/* #GWL_Display.wp_pointer_gestures */
6990
6993{
6994 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 3u);
6995
6996 display->wp.pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>(
6997 wl_registry_bind(display->wl.registry,
6998 params.name,
6999 &zwp_pointer_gestures_v1_interface,
7000 std::min(params.version, version)));
7001 gwl_registry_entry_add(display, params, nullptr);
7002}
7004 void * /*user_data*/,
7005 const bool /*on_exit*/)
7006{
7007 zwp_pointer_gestures_v1 **value_p = &display->wp.pointer_gestures;
7008 zwp_pointer_gestures_v1_destroy(*value_p);
7009 *value_p = nullptr;
7010}
7011
7012/* #GWL_Display.xdg_activation */
7013
7016{
7017 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7018
7019 display->xdg.activation_manager = static_cast<xdg_activation_v1 *>(
7020 wl_registry_bind(display->wl.registry, params.name, &xdg_activation_v1_interface, version));
7021 gwl_registry_entry_add(display, params, nullptr);
7022}
7024 void * /*user_data*/,
7025 const bool /*on_exit*/)
7026{
7027 xdg_activation_v1 **value_p = &display->xdg.activation_manager;
7028 xdg_activation_v1_destroy(*value_p);
7029 *value_p = nullptr;
7030}
7031
7032/* #GWL_Display.wp_fractional_scale_manger */
7033
7036{
7037 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7038
7039 display->wp.fractional_scale_manager = static_cast<wp_fractional_scale_manager_v1 *>(
7040 wl_registry_bind(
7041 display->wl.registry, params.name, &wp_fractional_scale_manager_v1_interface, version));
7042 gwl_registry_entry_add(display, params, nullptr);
7043}
7045 void * /*user_data*/,
7046 const bool /*on_exit*/)
7047{
7048 wp_fractional_scale_manager_v1 **value_p = &display->wp.fractional_scale_manager;
7049 wp_fractional_scale_manager_v1_destroy(*value_p);
7050 *value_p = nullptr;
7051}
7052
7053/* #GWL_Display.wl_viewport */
7054
7057{
7058 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7059
7060 display->wp.viewporter = static_cast<wp_viewporter *>(
7061 wl_registry_bind(display->wl.registry, params.name, &wp_viewporter_interface, version));
7062 gwl_registry_entry_add(display, params, nullptr);
7063}
7065 void * /*user_data*/,
7066 const bool /*on_exit*/)
7067{
7068 wp_viewporter **value_p = &display->wp.viewporter;
7069 wp_viewporter_destroy(*value_p);
7070 *value_p = nullptr;
7071}
7072
7073/* #GWL_Display.wp_primary_selection_device_manager */
7074
7077{
7078 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7079
7081 static_cast<zwp_primary_selection_device_manager_v1 *>(
7082 wl_registry_bind(display->wl.registry,
7083 params.name,
7084 &zwp_primary_selection_device_manager_v1_interface,
7085 version));
7086 gwl_registry_entry_add(display, params, nullptr);
7087}
7089 void * /*user_data*/,
7090 const bool /*on_exit*/)
7091{
7092 zwp_primary_selection_device_manager_v1 **value_p =
7094 zwp_primary_selection_device_manager_v1_destroy(*value_p);
7095 *value_p = nullptr;
7096}
7097
7098#ifdef WITH_INPUT_IME
7099
7100/* #GWL_Display.wp_text_input_manager */
7101
7102static void gwl_registry_wp_text_input_manager_add(GWL_Display *display,
7104{
7105 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7106
7107 display->wp.text_input_manager = static_cast<zwp_text_input_manager_v3 *>(wl_registry_bind(
7108 display->wl.registry, params.name, &zwp_text_input_manager_v3_interface, version));
7109 gwl_registry_entry_add(display, params, nullptr);
7110}
7111static void gwl_registry_wp_text_input_manager_remove(GWL_Display *display,
7112 void * /*user_data*/,
7113 const bool /*on_exit*/)
7114{
7115 zwp_text_input_manager_v3 **value_p = &display->wp.text_input_manager;
7116 zwp_text_input_manager_v3_destroy(*value_p);
7117 *value_p = nullptr;
7118}
7119
7120#endif /* WITH_INPUT_IME */
7121
7133 /* Low level interfaces. */
7134 {
7135 /*interface_p*/ &wl_compositor_interface.name,
7136 /*add_fn*/ gwl_registry_compositor_add,
7137 /*update_fn*/ nullptr,
7138 /*remove_fn*/ gwl_registry_compositor_remove,
7139 },
7140 {
7141 /*interface_p*/ &wl_shm_interface.name,
7142 /*add_fn*/ gwl_registry_wl_shm_add,
7143 /*update_fn*/ nullptr,
7144 /*remove_fn*/ gwl_registry_wl_shm_remove,
7145 },
7146 {
7147 /*interface_p*/ &xdg_wm_base_interface.name,
7149 /*update_fn*/ nullptr,
7150 /*remove_fn*/ gwl_registry_xdg_wm_base_remove,
7151 },
7152 /* Managers. */
7153 {
7154 /*interface_p*/ &zxdg_decoration_manager_v1_interface.name,
7156 /*update_fn*/ nullptr,
7158 },
7159 {
7160 /*interface_p*/ &zxdg_output_manager_v1_interface.name,
7162 /*update_fn*/ nullptr,
7164 },
7165 {
7166 /*interface_p*/ &wl_data_device_manager_interface.name,
7168 /*update_fn*/ nullptr,
7170 },
7171 {
7172 /*interface_p*/ &zwp_primary_selection_device_manager_v1_interface.name,
7174 /*update_fn*/ nullptr,
7176 },
7177 {
7178 /*interface_p*/ &zwp_tablet_manager_v2_interface.name,
7180 /*update_fn*/ nullptr,
7182 },
7183 {
7184 /*interface_p*/ &zwp_relative_pointer_manager_v1_interface.name,
7186 /*update_fn*/ nullptr,
7188 },
7189#ifdef WITH_INPUT_IME
7190 {
7191 /*interface_p*/ &zwp_text_input_manager_v3_interface.name,
7192 /*add_fn*/ gwl_registry_wp_text_input_manager_add,
7193 /*update_fn*/ nullptr,
7194 /*remove_fn*/ gwl_registry_wp_text_input_manager_remove,
7195 },
7196#endif
7197 /* Higher level interfaces. */
7198 {
7199 /*interface_p*/ &zwp_pointer_constraints_v1_interface.name,
7201 /*update_fn*/ nullptr,
7203 },
7204 {
7205 /*interface_p*/ &zwp_pointer_gestures_v1_interface.name,
7207 /*update_fn*/ nullptr,
7209 },
7210 {
7211 /*interface_p*/ &xdg_activation_v1_interface.name,
7213 /*update_fn*/ nullptr,
7215 },
7216 {
7217 /*interface_p*/ &wp_fractional_scale_manager_v1_interface.name,
7219 /*update_fn*/ nullptr,
7221 },
7222 {
7223 /*interface_p*/ &wp_viewporter_interface.name,
7225 /*update_fn*/ nullptr,
7227 },
7228 /* Display outputs. */
7229 {
7230 /*interface_p*/ &wl_output_interface.name,
7231 /*add_fn*/ gwl_registry_wl_output_add,
7232 /*update_fn*/ gwl_registry_wl_output_update,
7233 /*remove_fn*/ gwl_registry_wl_output_remove,
7234 },
7235 /* Seats.
7236 * Keep the seat near the end to ensure other types are created first.
7237 * as the seat creates data based on other interfaces. */
7238 {
7239 /*interface_p*/ &wl_seat_interface.name,
7240 /*add_fn*/ gwl_registry_wl_seat_add,
7241 /*update_fn*/ gwl_registry_wl_seat_update,
7242 /*remove_fn*/ gwl_registry_wl_seat_remove,
7243 },
7244
7245 {nullptr},
7246};
7247
7256
7258{
7259 for (const GWL_RegistryHandler *handler = gwl_registry_handlers; handler->interface_p != nullptr;
7260 handler++)
7261 {
7262 if (STREQ(interface, *handler->interface_p)) {
7263 return int(handler - gwl_registry_handlers);
7264 }
7265 }
7266 return -1;
7267}
7268
7270{
7271 GHOST_ASSERT(uint32_t(interface_slot) < uint32_t(gwl_registry_handler_interface_slot_max()),
7272 "Index out of range");
7273 return &gwl_registry_handlers[interface_slot];
7274}
7275
7276static void global_handle_add(void *data,
7277 [[maybe_unused]] wl_registry *wl_registry,
7278 const uint32_t name,
7279 const char *interface,
7280 const uint32_t version)
7281{
7282 /* Log last since it's useful to know if the interface was handled or not. */
7283 GWL_Display *display = static_cast<GWL_Display *>(data);
7284 GHOST_ASSERT(display->wl.registry == wl_registry, "Registry argument must match!");
7285
7287 bool added = false;
7288
7289 if (interface_slot != -1) {
7290 const GWL_RegistryHandler *handler = &gwl_registry_handlers[interface_slot];
7291 const GWL_RegistryEntry *registry_entry_prev = display->registry_entry;
7292
7293 /* The interface name that is ensured not to be freed. */
7295 params.name = name;
7296 params.interface_slot = interface_slot;
7297 params.version = version;
7298
7299 handler->add_fn(display, params);
7300
7301 added = display->registry_entry != registry_entry_prev;
7302 }
7303
7304 CLOG_INFO(LOG,
7305 2,
7306 "add %s(interface=%s, version=%u, name=%u)",
7307 (interface_slot != -1) ? (added ? "" : "(found but not added)") : "(skipped), ",
7308 interface,
7309 version,
7310 name);
7311
7312 /* Initialization avoids excessive calls by calling update after all have been initialized. */
7313 if (added) {
7314 if (display->registry_skip_update_all == false) {
7315 /* See doc-string for rationale on updating all on add/removal. */
7316 gwl_registry_entry_update_all(display, interface_slot);
7317 }
7318 }
7319}
7320
7330static void global_handle_remove(void *data,
7331 [[maybe_unused]] wl_registry *wl_registry,
7332 const uint32_t name)
7333{
7334 GWL_Display *display = static_cast<GWL_Display *>(data);
7335 GHOST_ASSERT(display->wl.registry == wl_registry, "Registry argument must match!");
7336
7337 int interface_slot = 0;
7338 const bool removed = gwl_registry_entry_remove_by_name(display, name, &interface_slot);
7339
7340 CLOG_INFO(LOG,
7341 2,
7342 "remove (name=%u, interface=%s)",
7343 name,
7344 removed ? *gwl_registry_handlers[interface_slot].interface_p : "(unknown)");
7345
7346 if (removed) {
7347 if (display->registry_skip_update_all == false) {
7348 /* See doc-string for rationale on updating all on add/removal. */
7349 gwl_registry_entry_update_all(display, interface_slot);
7350 }
7351 }
7352}
7353
7354static const wl_registry_listener registry_listener = {
7355 /*global*/ global_handle_add,
7356 /*global_remove*/ global_handle_remove,
7357};
7358
7359#undef LOG
7360
7362
7363/* -------------------------------------------------------------------- */
7366
7367#ifdef USE_EVENT_BACKGROUND_THREAD
7368
7369static void *gwl_display_event_thread_fn(void *display_voidp)
7370{
7371 GWL_Display *display = static_cast<GWL_Display *>(display_voidp);
7372 GHOST_ASSERT(!display->background, "Foreground only");
7373 const int fd = wl_display_get_fd(display->wl.display);
7374 while (display->events_pthread_is_active) {
7375 /* Wait for an event, this thread is dedicated to event handling. */
7377 display->wl.display, fd, display->system->server_mutex) == -1)
7378 {
7379 break;
7380 }
7381 }
7382
7383 /* Wait until the main thread cancels this thread, otherwise this thread may exit
7384 * before cancel is called, causing a crash on exit. */
7385 while (true) {
7386 pause();
7387 }
7388
7389 return nullptr;
7390}
7391
7392/* Event reading thread. */
7394{
7395 GHOST_ASSERT(!display->background, "Foreground only");
7396 GHOST_ASSERT(display->events_pthread == 0, "Only call once");
7398 display->events_pthread_is_active = true;
7399 pthread_create(&display->events_pthread, nullptr, gwl_display_event_thread_fn, display);
7400 /* Application logic should take priority, this only ensures events don't accumulate when busy
7401 * which typically takes a while (5+ seconds of frantic mouse motion for example). */
7403 pthread_detach(display->events_pthread);
7404}
7405
7407{
7408 GHOST_ASSERT(!display->background, "Foreground only");
7409 pthread_cancel(display->events_pthread);
7410}
7411
7412#endif /* USE_EVENT_BACKGROUND_THREAD */
7413
7415
7416/* -------------------------------------------------------------------- */
7421
7423 : GHOST_System(),
7425 server_mutex(new std::mutex),
7426 timer_mutex(new std::mutex),
7427 main_thread_id(std::this_thread::get_id()),
7428#endif
7429 display_(new GWL_Display)
7430{
7433
7434 display_->system = this;
7435 display_->background = background;
7436 /* Connect to the Wayland server. */
7437
7438 display_->wl.display = wl_display_connect(nullptr);
7439 if (!display_->wl.display) {
7440 display_destroy_and_free_all();
7441 throw std::runtime_error("unable to connect to display!");
7442 }
7443
7444 /* This may be removed later if decorations are required, needed as part of registration. */
7445 display_->xdg_decor = new GWL_XDG_Decor_System;
7446
7447 /* Register interfaces. */
7448 {
7449 display_->registry_skip_update_all = true;
7450 wl_registry *registry = wl_display_get_registry(display_->wl.display);
7451 display_->wl.registry = registry;
7452 wl_registry_add_listener(registry, &registry_listener, display_);
7453 /* First round-trip to receive all registry objects. */
7454 wl_display_roundtrip(display_->wl.display);
7455 /* Second round-trip to receive all output events. */
7456 wl_display_roundtrip(display_->wl.display);
7457
7458 /* Account for dependencies between interfaces. */
7459 gwl_registry_entry_update_all(display_, -1);
7460
7461 display_->registry_skip_update_all = false;
7462 }
7463
7464#ifdef WITH_GHOST_WAYLAND_LIBDECOR
7465 bool libdecor_required = false;
7466 {
7467 const char *xdg_current_desktop = [] {
7468 /* Account for VSCode overriding this value (TSK!), see: #133921. */
7469 const char *key = "ORIGINAL_XDG_CURRENT_DESKTOP";
7470 const char *value = getenv(key);
7471 return value ? value : getenv(key + 9);
7472 }();
7473
7474 if (xdg_current_desktop) {
7475 /* See the free-desktop specifications for details on `XDG_CURRENT_DESKTOP`.
7476 * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
7477 */
7478 if (string_elem_split_by_delim(xdg_current_desktop, ':', "GNOME")) {
7479 libdecor_required = true;
7480 }
7481 }
7482 }
7483
7484 if (libdecor_required) {
7485 /* Ignore windowing requirements when running in background mode,
7486 * as it doesn't make sense to fall back to X11 because of windowing functionality
7487 * in background mode, also LIBDECOR is crashing in background mode `blender -b -f 1`
7488 * for example while it could be fixed, requiring the library at all makes no sense. */
7489 if (background) {
7490 libdecor_required = false;
7491 }
7492# ifdef WITH_GHOST_X11
7493 else if (!has_libdecor && !ghost_wayland_is_x11_available()) {
7494 /* Only require LIBDECOR when X11 is available, otherwise there is nothing to fall back to.
7495 * It's better to open without window decorations than failing entirely. */
7496 libdecor_required = false;
7497 }
7498# endif /* WITH_GHOST_X11 */
7499 }
7500
7501 if (libdecor_required) {
7502 gwl_xdg_decor_system_destroy(display_, display_->xdg_decor);
7503 display_->xdg_decor = nullptr;
7504
7505 if (!has_libdecor) {
7506# ifdef WITH_GHOST_X11
7507 /* LIBDECOR was the only reason X11 was used, let the user know they need it installed. */
7508 fprintf(stderr,
7509 "WAYLAND found but libdecor was not, install libdecor for Wayland support, "
7510 "falling back to X11\n");
7511# endif
7512 display_destroy_and_free_all();
7513 throw std::runtime_error("unable to find libdecor!");
7514 }
7515 }
7516 else {
7517 use_libdecor = false;
7518 }
7519#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
7520
7521#ifdef WITH_GHOST_WAYLAND_LIBDECOR
7522 if (use_libdecor) {
7523 display_->libdecor = new GWL_LibDecor_System;
7524 GWL_LibDecor_System &decor = *display_->libdecor;
7525 decor.context = libdecor_new(display_->wl.display, &libdecor_interface);
7526 if (!decor.context) {
7527 display_destroy_and_free_all();
7528 throw std::runtime_error("unable to create window decorations!");
7529 }
7530 }
7531 else
7532#endif
7533 {
7534 const GWL_XDG_Decor_System &decor = *display_->xdg_decor;
7535 if (!decor.shell) {
7536 display_destroy_and_free_all();
7537 throw std::runtime_error("unable to access xdg_shell!");
7538 }
7539 }
7540
7541 /* Without this, the output fractional size from `display->xdg.output_manager` isn't known,
7542 * while this isn't essential, the first window creation uses this for setting the size.
7543 * Supporting both XDG initialized/uninitialized outputs is possible it complicates logic.
7544 * see: #113328 for an example of size on startup issues. */
7545 wl_display_roundtrip(display_->wl.display);
7546
7547#ifdef USE_EVENT_BACKGROUND_THREAD
7548 /* There is no need for an event handling thread in background mode
7549 * because there no polling for user input. */
7550 if (background) {
7551 GHOST_ASSERT(display_->events_pthread_is_active == false, "Expected to be false");
7552 }
7553 else {
7555 }
7556 /* Could be null in background mode, however there are enough
7557 * references to this that it's safer to create it. */
7558 display_->ghost_timer_manager = new GHOST_TimerManager();
7559#endif
7560}
7561
7562void GHOST_SystemWayland::display_destroy_and_free_all()
7563{
7564 gwl_display_destroy(display_);
7565
7566#ifdef USE_EVENT_BACKGROUND_THREAD
7567 delete server_mutex;
7568 delete timer_mutex;
7569#endif
7570}
7571
7573{
7574 display_destroy_and_free_all();
7575}
7576
7578{
7580
7581 if (success) {
7582#ifdef WITH_INPUT_NDOF
7583 m_ndofManager = new GHOST_NDOFManagerUnix(*this);
7584#endif
7585 return GHOST_kSuccess;
7586 }
7587
7588 return GHOST_kFailure;
7589}
7590
7591#undef pushEvent
7592
7594{
7595 bool any_processed = false;
7596
7597#ifdef USE_EVENT_BACKGROUND_THREAD
7598 if (UNLIKELY(has_pending_actions_for_window.exchange(false))) {
7599 std::lock_guard lock_server_guard{*server_mutex};
7600 for (GHOST_IWindow *iwin : getWindowManager()->getWindows()) {
7601 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
7603 }
7604 }
7605
7606 if (!display_->background) {
7607 std::lock_guard lock{display_->events_pending_mutex};
7608 for (const GHOST_IEvent *event : display_->events_pending) {
7609
7610 /* Perform actions that aren't handled in a thread. */
7611 switch (event->getType()) {
7614 break;
7615 }
7618 break;
7619 }
7620 default: {
7621 break;
7622 }
7623 }
7624
7625 pushEvent(event);
7626 }
7627 display_->events_pending.clear();
7628
7629 if (UNLIKELY(display_->events_pending.capacity() > events_pending_default_size)) {
7630 /* Avoid over allocation in the case of occasional delay between processing events
7631 * causing many events to be collected and making this into a large array. */
7632 display_->events_pending.shrink_to_fit();
7633 display_->events_pending.reserve(events_pending_default_size);
7634 }
7635 }
7636#endif /* USE_EVENT_BACKGROUND_THREAD */
7637
7638 {
7639 const uint64_t now = getMilliSeconds();
7640#ifdef USE_EVENT_BACKGROUND_THREAD
7641 {
7642 std::lock_guard lock_timer_guard{*display_->system->timer_mutex};
7643 if (ghost_timer_manager()->fireTimers(now)) {
7644 any_processed = true;
7645 }
7646 }
7647#endif
7648 if (getTimerManager()->fireTimers(now)) {
7649 any_processed = true;
7650 }
7651 }
7652
7653#ifdef WITH_INPUT_NDOF
7654 if (static_cast<GHOST_NDOFManagerUnix *>(m_ndofManager)->processEvents()) {
7655 /* As NDOF bypasses WAYLAND event handling,
7656 * never wait for an event when an NDOF event was found. */
7657 waitForEvent = false;
7658 any_processed = true;
7659 }
7660#endif /* WITH_INPUT_NDOF */
7661
7662 if (waitForEvent) {
7663#ifdef USE_EVENT_BACKGROUND_THREAD
7664 std::lock_guard lock_server_guard{*server_mutex};
7665#endif
7666 if (wl_display_dispatch(display_->wl.display) == -1) {
7667 ghost_wl_display_report_error(display_->wl.display);
7668 }
7669 }
7670 else {
7671#ifdef USE_EVENT_BACKGROUND_THREAD
7672 /* NOTE: this works, but as the events are being read in a thread,
7673 * this could be removed and event handling still works.. */
7674 if (server_mutex->try_lock()) {
7675 if (ghost_wl_display_event_pump(display_->wl.display) == -1) {
7676 ghost_wl_display_report_error(display_->wl.display);
7677 }
7678 server_mutex->unlock();
7679 }
7680#else
7681 if (ghost_wl_display_event_pump(display_->wl.display) == -1) {
7682 ghost_wl_display_report_error(display_->wl.display);
7683 }
7684#endif /* !USE_EVENT_BACKGROUND_THREAD */
7685 }
7686
7687 if (getEventManager()->getNumEvents() > 0) {
7688 any_processed = true;
7689 }
7690
7691 return any_processed;
7692}
7693
7695{
7696 return false;
7697}
7698
7700{
7701#ifdef USE_EVENT_BACKGROUND_THREAD
7702 std::lock_guard lock_server_guard{*server_mutex};
7703#endif
7704
7705 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7706 if (UNLIKELY(!seat)) {
7707 return GHOST_kFailure;
7708 }
7709
7710 /* Only read the underlying `seat->xkb_state` when there is an active window.
7711 * Without this, the following situation occurs:
7712 *
7713 * - A window is activated (before the #wl_keyboard_listener::enter has run).
7714 * - The modifiers from `seat->xkb_state` don't match `seat->key_depressed`.
7715 * - Dummy values are written into `seat->key_depressed` to account for the discrepancy
7716 * (as `seat->xkb_state` is the source of truth), however the number of held modifiers
7717 * is not longer valid (because it's not known from dummy values).
7718 * - #wl_keyboard_listener::enter runs, however the events generated from the state change
7719 * may not match the physically held keys because the dummy values are not accurate.
7720 *
7721 * As this is an edge-case caused by the order of callbacks that run on window activation,
7722 * don't attempt to *fix* the values in `seat->key_depressed` before the keyboard enter
7723 * handler runs. This means the result of `getModifierKeys` may be momentarily incorrect
7724 * however it's corrected once #wl_keyboard_listener::enter runs.
7725 */
7726 const bool is_keyboard_active = seat->keyboard.wl.surface_window != nullptr;
7727 const xkb_mod_mask_t state = is_keyboard_active ?
7728 xkb_state_serialize_mods(seat->xkb.state,
7729 XKB_STATE_MODS_DEPRESSED) :
7730 0;
7731
7732 /* Use local #GWL_KeyboardDepressedState to check which key is pressed.
7733 * Use XKB as the source of truth, if there is any discrepancy. */
7734 for (int i = 0; i < MOD_INDEX_NUM; i++) {
7735 if (UNLIKELY(seat->xkb_keymap_mod_index[i] == XKB_MOD_INVALID)) {
7736 continue;
7737 }
7738
7739 const GWL_ModifierInfo &mod_info = g_modifier_info_table[i];
7740 /* NOTE(@ideasman42): it's important to write the XKB state back to #GWL_KeyboardDepressedState
7741 * otherwise changes to modifiers in the future wont generate events.
7742 * This can cause modifiers to be stuck when switching between windows in GNOME because
7743 * window activation is handled before the keyboard enter callback runs, see: #107314.
7744 * Now resolved upstream, keep this for GNOME 45 and older releases & misbehaving compositors
7745 * as the workaround doesn't have significant down-sides. */
7746 int16_t &depressed_l = seat->key_depressed.mods[GHOST_KEY_MODIFIER_TO_INDEX(mod_info.key_l)];
7747 int16_t &depressed_r = seat->key_depressed.mods[GHOST_KEY_MODIFIER_TO_INDEX(mod_info.key_r)];
7748 bool val_l = depressed_l > 0;
7749 bool val_r = depressed_r > 0;
7750
7751 if (is_keyboard_active) {
7752 const bool val = (state & (1 << seat->xkb_keymap_mod_index[i])) != 0;
7753 /* This shouldn't be needed, but guard against any possibility of modifiers being stuck.
7754 * Warn so if this happens it can be investigated. */
7755 if (val) {
7756 if (UNLIKELY(!(val_l || val_r))) {
7758 "modifier (%s) state is inconsistent (GHOST held keys do not match XKB)",
7759 mod_info.display_name);
7760
7761 /* Picking the left is arbitrary. */
7762 val_l = true;
7763 depressed_l = 1;
7764 }
7765 }
7766 else {
7767 if (UNLIKELY(val_l || val_r)) {
7769 "modifier (%s) state is inconsistent (GHOST released keys do not match XKB)",
7770 mod_info.display_name);
7771 val_l = false;
7772 val_r = false;
7773 depressed_l = 0;
7774 depressed_r = 0;
7775 }
7776 }
7777 }
7778
7779 keys.set(mod_info.mod_l, val_l);
7780 keys.set(mod_info.mod_r, val_r);
7781 }
7782
7783 return GHOST_kSuccess;
7784}
7785
7787{
7788#ifdef USE_EVENT_BACKGROUND_THREAD
7789 std::lock_guard lock_server_guard{*server_mutex};
7790#endif
7791
7792 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7793 if (UNLIKELY(!seat)) {
7794 return GHOST_kFailure;
7795 }
7796 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
7797 if (!seat_state_pointer) {
7798 return GHOST_kFailure;
7799 }
7800
7801 buttons = seat_state_pointer->buttons;
7802 return GHOST_kSuccess;
7803}
7804
7810 const std::unordered_set<std::string> &data_offer_types)
7811{
7812 const char *ghost_supported_types[] = {ghost_wl_mime_text_utf8, ghost_wl_mime_text_plain};
7813 for (size_t i = 0; i < ARRAY_SIZE(ghost_supported_types); i++) {
7814 if (data_offer_types.count(ghost_supported_types[i])) {
7815 return ghost_supported_types[i];
7816 }
7817 }
7818 return nullptr;
7819}
7820
7822 const bool nil_terminate,
7823 const char *mime_receive_override,
7824 size_t *r_data_len)
7825{
7826 GWL_Seat *seat = gwl_display_seat_active_get(display);
7827 if (UNLIKELY(!seat)) {
7828 return nullptr;
7829 }
7830 GWL_PrimarySelection *primary = &seat->primary_selection;
7831 std::mutex &mutex = primary->data_offer_mutex;
7832
7833 mutex.lock();
7834 bool mutex_locked = true;
7835 char *data = nullptr;
7836
7837 GWL_PrimarySelection_DataOffer *data_offer = primary->data_offer;
7838 if (data_offer != nullptr) {
7839 const char *mime_receive = mime_receive_override ?
7840 mime_receive_override :
7842 GHOST_ASSERT((mime_receive_override == nullptr) ||
7843 data_offer->types.count(mime_receive_override) != 0,
7844 "Mime type override not found in data offer, caller must check");
7845
7846 if (mime_receive) {
7847 /* Receive the clipboard in a thread, performing round-trips while waiting.
7848 * This is needed so pasting contents from our own `primary->data_source` doesn't hang. */
7849 struct ThreadResult {
7850 char *data = nullptr;
7851 size_t data_len = 0;
7852 std::atomic<bool> done = false;
7853 } thread_result;
7854 auto read_clipboard_fn = [](GWL_PrimarySelection_DataOffer *data_offer,
7855 const bool nil_terminate,
7856 const char *mime_receive,
7857 std::mutex *mutex,
7858 ThreadResult *thread_result) {
7859 thread_result->data = read_buffer_from_primary_selection_offer(
7860 data_offer, mime_receive, mutex, nil_terminate, &thread_result->data_len);
7861 thread_result->done = true;
7862 };
7863 std::thread read_thread(
7864 read_clipboard_fn, data_offer, nil_terminate, mime_receive, &mutex, &thread_result);
7865 read_thread.detach();
7866
7867 while (!thread_result.done) {
7869 }
7870 data = thread_result.data;
7871 *r_data_len = thread_result.data_len;
7872
7873 /* Reading the data offer unlocks the mutex. */
7874 mutex_locked = false;
7875 }
7876 }
7877 if (mutex_locked) {
7878 mutex.unlock();
7879 }
7880 return data;
7881}
7882
7883static char *system_clipboard_get(GWL_Display *display,
7884 bool nil_terminate,
7885 const char *mime_receive_override,
7886 size_t *r_data_len)
7887{
7888 GWL_Seat *seat = gwl_display_seat_active_get(display);
7889 if (UNLIKELY(!seat)) {
7890 return nullptr;
7891 }
7892 std::mutex &mutex = seat->data_offer_copy_paste_mutex;
7893
7894 mutex.lock();
7895 bool mutex_locked = true;
7896 char *data = nullptr;
7897
7898 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
7899 if (data_offer != nullptr) {
7900 const char *mime_receive = mime_receive_override ?
7901 mime_receive_override :
7903 GHOST_ASSERT((mime_receive_override == nullptr) ||
7904 data_offer->types.count(mime_receive_override) != 0,
7905 "Mime type override not found in data offer, caller must check");
7906
7907 if (mime_receive) {
7908 /* Receive the clipboard in a thread, performing round-trips while waiting.
7909 * This is needed so pasting contents from our own `seat->data_source` doesn't hang. */
7910 struct ThreadResult {
7911 char *data = nullptr;
7912 size_t data_len = 0;
7913 std::atomic<bool> done = false;
7914 } thread_result;
7915 auto read_clipboard_fn = [](GWL_DataOffer *data_offer,
7916 const bool nil_terminate,
7917 const char *mime_receive,
7918 std::mutex *mutex,
7919 ThreadResult *thread_result) {
7920 thread_result->data = read_buffer_from_data_offer(
7921 data_offer, mime_receive, mutex, nil_terminate, &thread_result->data_len);
7922 thread_result->done = true;
7923 };
7924 std::thread read_thread(
7925 read_clipboard_fn, data_offer, nil_terminate, mime_receive, &mutex, &thread_result);
7926 read_thread.detach();
7927
7928 while (!thread_result.done) {
7930 }
7931 data = thread_result.data;
7932 *r_data_len = thread_result.data_len;
7933
7934 /* Reading the data offer unlocks the mutex. */
7935 mutex_locked = false;
7936 }
7937 }
7938 if (mutex_locked) {
7939 mutex.unlock();
7940 }
7941 return data;
7942}
7943
7944char *GHOST_SystemWayland::getClipboard(bool selection) const
7945{
7946#ifdef USE_EVENT_BACKGROUND_THREAD
7947 std::lock_guard lock_server_guard{*server_mutex};
7948#endif
7949
7950 const bool nil_terminate = true;
7951 char *data = nullptr;
7952 size_t data_len = 0;
7953 if (selection) {
7954 data = system_clipboard_get_primary_selection(display_, nil_terminate, nullptr, &data_len);
7955 }
7956 else {
7957 data = system_clipboard_get(display_, nil_terminate, nullptr, &data_len);
7958 }
7959 return data;
7960}
7961
7962static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer)
7963{
7964 if (!display->wp.primary_selection_device_manager) {
7965 return;
7966 }
7967 GWL_Seat *seat = gwl_display_seat_active_get(display);
7968 if (UNLIKELY(!seat)) {
7969 return;
7970 }
7971 GWL_PrimarySelection *primary = &seat->primary_selection;
7972
7973 std::lock_guard lock{primary->data_source_mutex};
7974
7976
7978 primary->data_source = data_source;
7979
7980 /* Copy buffer. */
7981 gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer);
7982
7983 data_source->wp.source = zwp_primary_selection_device_manager_v1_create_source(
7985
7986 zwp_primary_selection_source_v1_add_listener(
7987 data_source->wp.source, &primary_selection_source_listener, primary);
7988
7989 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) {
7990 zwp_primary_selection_source_v1_offer(data_source->wp.source, ghost_wl_mime_send[i]);
7991 }
7992
7993 if (seat->wp.primary_selection_device) {
7994 zwp_primary_selection_device_v1_set_selection(
7995 seat->wp.primary_selection_device, data_source->wp.source, seat->data_source_serial);
7996 }
7997}
7998
7999static void system_clipboard_put(GWL_Display *display, const char *buffer)
8000{
8001 if (!display->wl.data_device_manager) {
8002 return;
8003 }
8004 GWL_Seat *seat = gwl_display_seat_active_get(display);
8005 if (UNLIKELY(!seat)) {
8006 return;
8007 }
8008 std::lock_guard lock{seat->data_source_mutex};
8009
8010 GWL_DataSource *data_source = seat->data_source;
8011
8012 /* Copy buffer. */
8013 gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer);
8014
8015 data_source->wl.source = wl_data_device_manager_create_data_source(
8016 display->wl.data_device_manager);
8017
8018 wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
8019
8020 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) {
8021 wl_data_source_offer(data_source->wl.source, ghost_wl_mime_send[i]);
8022 }
8023
8024 if (seat->wl.data_device) {
8025 wl_data_device_set_selection(
8026 seat->wl.data_device, data_source->wl.source, seat->data_source_serial);
8027 }
8028}
8029
8030void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const
8031{
8032#ifdef USE_EVENT_BACKGROUND_THREAD
8033 std::lock_guard lock_server_guard{*server_mutex};
8034#endif
8035
8036 if (selection) {
8038 }
8039 else {
8040 system_clipboard_put(display_, buffer);
8041 }
8042}
8043
8044static constexpr const char *ghost_wl_mime_img_png = "image/png";
8045
8047{
8048#ifdef USE_EVENT_BACKGROUND_THREAD
8049 std::lock_guard lock_server_guard{*server_mutex};
8050#endif
8051
8052 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8053 if (UNLIKELY(!seat)) {
8054 return GHOST_kFailure;
8055 }
8056
8057 if (seat->data_offer_copy_paste_has_image.has_value()) {
8058 return *seat->data_offer_copy_paste_has_image;
8059 }
8060
8062
8063 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
8064 if (data_offer) {
8065 if (data_offer->types.count(ghost_wl_mime_img_png)) {
8067 }
8068 else if (data_offer->types.count(ghost_wl_mime_text_uri_list)) {
8069 const bool nil_terminate = true;
8070 size_t data_buf_len = 0;
8071 char *data = system_clipboard_get(
8072 display_, nil_terminate, ghost_wl_mime_text_uri_list, &data_buf_len);
8073
8074 if (data) {
8075 std::vector<std::string_view> uris = gwl_clipboard_uri_ranges(data, data_buf_len);
8076 if (!uris.empty()) {
8077 const std::string_view &uri = uris.front();
8078 char *filepath = GHOST_URL_decode_alloc(uri.data(), uri.size());
8079 if (IMB_test_image(filepath)) {
8081 }
8082 free(filepath);
8083 }
8084 free(data);
8085 }
8086 }
8087 }
8088
8090
8091 return result;
8092}
8093
8094uint *GHOST_SystemWayland::getClipboardImage(int *r_width, int *r_height) const
8095{
8096#ifdef USE_EVENT_BACKGROUND_THREAD
8097 std::lock_guard lock_server_guard{*server_mutex};
8098#endif
8099
8100 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8101 if (UNLIKELY(!seat)) {
8102 return nullptr;
8103 }
8104
8105 uint *rgba = nullptr;
8106
8107 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
8108 if (data_offer) {
8109 ImBuf *ibuf = nullptr;
8110
8111 /* Check if the source offers a supported mime type.
8112 * This check could be skipped, because the paste option is not supposed to be enabled
8113 * otherwise. */
8114 if (data_offer->types.count(ghost_wl_mime_img_png)) {
8115 size_t data_len = 0;
8116 char *data = system_clipboard_get(display_, false, ghost_wl_mime_img_png, &data_len);
8117
8118 if (data) {
8119 /* Generate the image buffer with the received data. */
8121 (const uint8_t *)data, data_len, IB_byte_data, "<clipboard>");
8122 free(data);
8123 }
8124 }
8125 else if (data_offer->types.count(ghost_wl_mime_text_uri_list)) {
8126 const bool nil_terminate = true;
8127 size_t data_len = 0;
8128 char *data = system_clipboard_get(
8129 display_, nil_terminate, ghost_wl_mime_text_uri_list, &data_len);
8130
8131 if (data) {
8132 std::vector<std::string_view> uris = gwl_clipboard_uri_ranges(data, data_len);
8133 if (!uris.empty()) {
8134 const std::string_view &uri = uris.front();
8135 char *filepath = GHOST_URL_decode_alloc(uri.data(), uri.size());
8137 free(filepath);
8138 }
8139 free(data);
8140 }
8141 }
8142
8143 if (ibuf) {
8144 *r_width = ibuf->x;
8145 *r_height = ibuf->y;
8146 const size_t byte_count = size_t(ibuf->x) * size_t(ibuf->y) * 4;
8147 rgba = (uint *)malloc(byte_count);
8148 std::memcpy(rgba, ibuf->byte_buffer.data, byte_count);
8149 IMB_freeImBuf(ibuf);
8150 }
8151 }
8152
8153 return rgba;
8154}
8155
8157{
8158#ifdef USE_EVENT_BACKGROUND_THREAD
8159 std::lock_guard lock_server_guard{*server_mutex};
8160#endif
8161
8162 /* Create a #wl_data_source object. */
8163 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8164 if (UNLIKELY(!seat)) {
8165 return GHOST_kFailure;
8166 }
8167 std::lock_guard lock(seat->data_source_mutex);
8168
8169 GWL_DataSource *data_source = seat->data_source;
8170
8171 /* Load buffer into an #ImBuf and convert to PNG. */
8172 ImBuf *ibuf = IMB_allocFromBuffer(reinterpret_cast<uint8_t *>(rgba), nullptr, width, height, 32);
8173 ibuf->ftype = IMB_FTYPE_PNG;
8174 ibuf->foptions.quality = 15;
8175 if (!IMB_save_image(ibuf, "<memory>", IB_byte_data | IB_mem)) {
8176 IMB_freeImBuf(ibuf);
8177 return GHOST_kFailure;
8178 }
8179
8180 /* Copy #ImBuf encoded_buffer to data source. */
8181 GWL_SimpleBuffer *imgbuffer = &data_source->buffer_out;
8182 gwl_simple_buffer_free_data(imgbuffer);
8183 imgbuffer->data_size = ibuf->encoded_buffer_size;
8184 char *data = static_cast<char *>(malloc(imgbuffer->data_size));
8185 std::memcpy(data, ibuf->encoded_buffer.data, ibuf->encoded_buffer_size);
8186 imgbuffer->data = data;
8187
8188 data_source->wl.source = wl_data_device_manager_create_data_source(
8189 display_->wl.data_device_manager);
8190 wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
8191
8192 /* Advertise the mime types supported. */
8193 wl_data_source_offer(data_source->wl.source, ghost_wl_mime_img_png);
8194
8195 if (seat->wl.data_device) {
8196 wl_data_device_set_selection(
8197 seat->wl.data_device, data_source->wl.source, seat->data_source_serial);
8198 }
8199
8200 IMB_freeImBuf(ibuf);
8201 return GHOST_kSuccess;
8202}
8203
8205{
8206#ifdef USE_EVENT_BACKGROUND_THREAD
8207 std::lock_guard lock_server_guard{*server_mutex};
8208#endif
8209
8210 return display_ ? uint8_t(display_->outputs.size()) : 0;
8211}
8212
8214{
8215 /* Match the timing method used by LIBINPUT, so the result is closer to WAYLAND's time-stamps. */
8216 timespec ts = {0, 0};
8217 clock_gettime(CLOCK_MONOTONIC, &ts);
8218 return (uint64_t(ts.tv_sec) * 1000) + uint64_t(ts.tv_nsec / 1000000);
8219}
8220
8222 const GWL_SeatStatePointer *seat_state_pointer,
8223 const GHOST_WindowWayland *win,
8224 int32_t &x,
8225 int32_t &y)
8226{
8227
8228 if (win->getCursorGrabModeIsWarp()) {
8229 /* As the cursor is restored at the warped location,
8230 * apply warping when requesting the cursor location. */
8231 GHOST_Rect wrap_bounds{};
8232 if (win->getCursorGrabBounds(wrap_bounds) == GHOST_kFailure) {
8233 win->getClientBounds(wrap_bounds);
8234 }
8235 wl_fixed_t xy_wrap[2] = {
8236 seat_state_pointer->xy[0],
8237 seat_state_pointer->xy[1],
8238 };
8239
8240 GHOST_Rect wrap_bounds_scale;
8241
8242 wrap_bounds_scale.m_l = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_l));
8243 wrap_bounds_scale.m_t = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_t));
8244 wrap_bounds_scale.m_r = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_r));
8245 wrap_bounds_scale.m_b = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.m_b));
8246 wrap_bounds_scale.wrapPoint(UNPACK2(xy_wrap), 0, win->getCursorGrabAxis());
8247
8248 x = wl_fixed_to_int(win->wl_fixed_to_window(xy_wrap[0]));
8249 y = wl_fixed_to_int(win->wl_fixed_to_window(xy_wrap[1]));
8250 }
8251 else {
8252 x = wl_fixed_to_int(win->wl_fixed_to_window(seat_state_pointer->xy[0]));
8253 y = wl_fixed_to_int(win->wl_fixed_to_window(seat_state_pointer->xy[1]));
8254 }
8255
8256 return GHOST_kSuccess;
8257}
8258
8261 const int32_t x,
8262 const int32_t y)
8263{
8264 /* NOTE: WAYLAND doesn't support warping the cursor.
8265 * However when grab is enabled, we already simulate a cursor location
8266 * so that can be set to a new location. */
8267 if (!seat->wp.relative_pointer) {
8268 return GHOST_kFailure;
8269 }
8270 const wl_fixed_t xy_next[2]{
8271 win->wl_fixed_from_window(wl_fixed_from_int(x)),
8272 win->wl_fixed_from_window(wl_fixed_from_int(y)),
8273 };
8274
8275 /* As the cursor was "warped" generate an event at the new location. */
8276 const uint64_t event_ms = seat->system->getMilliSeconds();
8277 relative_pointer_handle_relative_motion_impl(seat, win, xy_next, event_ms);
8278
8279 return GHOST_kSuccess;
8280}
8281
8283 int32_t &x,
8284 int32_t &y) const
8285{
8286#ifdef USE_EVENT_BACKGROUND_THREAD
8287 std::lock_guard lock_server_guard{*server_mutex};
8288#endif
8289
8290 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8291 if (UNLIKELY(!seat)) {
8292 return GHOST_kFailure;
8293 }
8294 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
8295 if (!seat_state_pointer || !seat_state_pointer->wl.surface_window) {
8296 return GHOST_kFailure;
8297 }
8298 const GHOST_WindowWayland *win = static_cast<const GHOST_WindowWayland *>(window);
8299 return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y);
8300}
8301
8303 const int32_t x,
8304 const int32_t y)
8305{
8306#ifdef USE_EVENT_BACKGROUND_THREAD
8307 std::lock_guard lock_server_guard{*server_mutex};
8308#endif
8309
8310 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8311 if (UNLIKELY(!seat)) {
8312 return GHOST_kFailure;
8313 }
8314 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(window);
8315 return setCursorPositionClientRelative_impl(seat, win, x, y);
8316}
8317
8319{
8320#ifdef USE_EVENT_BACKGROUND_THREAD
8321 std::lock_guard lock_server_guard{*server_mutex};
8322#endif
8323
8324 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8325 if (UNLIKELY(!seat)) {
8326 return GHOST_kFailure;
8327 }
8328 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
8329 if (!seat_state_pointer) {
8330 return GHOST_kFailure;
8331 }
8332
8333 if (wl_surface *wl_surface_focus = seat_state_pointer->wl.surface_window) {
8334 const GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
8335 return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y);
8336 }
8337 return GHOST_kFailure;
8338}
8339
8341{
8342#ifdef USE_EVENT_BACKGROUND_THREAD
8343 std::lock_guard lock_server_guard{*server_mutex};
8344#endif
8345
8346 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8347 if (UNLIKELY(!seat)) {
8348 return GHOST_kFailure;
8349 }
8350
8351 /* Intentionally different from `getCursorPosition` which supports both tablet & pointer.
8352 * In the case of setting the cursor location, tablets don't support this. */
8353 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
8354 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
8355 return setCursorPositionClientRelative_impl(seat, win, x, y);
8356 }
8357 return GHOST_kFailure;
8358}
8359
8361{
8362#ifdef USE_EVENT_BACKGROUND_THREAD
8363 std::lock_guard lock_server_guard{*server_mutex};
8364#endif
8365 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8366 return seat->cursor.theme_size;
8367}
8368
8369void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
8370{
8371#ifdef USE_EVENT_BACKGROUND_THREAD
8372 std::lock_guard lock_server_guard{*server_mutex};
8373#endif
8374
8375 if (!display_->outputs.empty()) {
8376 /* We assume first output as main. */
8377 const GWL_Output *output = display_->outputs[0];
8378 int32_t size_native[2] = {UNPACK2(output->size_native)};
8379 if (output->transform & WL_OUTPUT_TRANSFORM_90) {
8380 std::swap(size_native[0], size_native[1]);
8381 }
8382 width = uint32_t(size_native[0]);
8383 height = uint32_t(size_native[1]);
8384 }
8385}
8386
8387void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
8388{
8389#ifdef USE_EVENT_BACKGROUND_THREAD
8390 std::lock_guard lock_server_guard{*server_mutex};
8391#endif
8392 if (!display_->outputs.empty()) {
8393 int32_t xy_min[2] = {INT32_MAX, INT32_MAX};
8394 int32_t xy_max[2] = {INT32_MIN, INT32_MIN};
8395
8396 for (const GWL_Output *output : display_->outputs) {
8397 int32_t xy[2] = {0, 0};
8398 int32_t size_native[2] = {UNPACK2(output->size_native)};
8399 if (output->has_position_logical) {
8400 xy[0] = output->position_logical[0];
8401 xy[1] = output->position_logical[1];
8402 }
8403 if (output->transform & WL_OUTPUT_TRANSFORM_90) {
8404 std::swap(size_native[0], size_native[1]);
8405 }
8406 xy_min[0] = std::min(xy_min[0], xy[0]);
8407 xy_min[1] = std::min(xy_min[1], xy[1]);
8408 xy_max[0] = std::max(xy_max[0], xy[0] + size_native[0]);
8409 xy_max[1] = std::max(xy_max[1], xy[1] + size_native[1]);
8410 }
8411
8412 width = xy_max[0] - xy_min[0];
8413 height = xy_max[1] - xy_min[1];
8414 }
8415}
8416
8418{
8419#ifdef USE_EVENT_BACKGROUND_THREAD
8420 std::lock_guard lock_server_guard{*server_mutex};
8421#endif
8422
8423 const bool debug_context = (gpuSettings.flags & GHOST_gpuDebugContext) != 0;
8424
8425 switch (gpuSettings.context_type) {
8426
8427#ifdef WITH_VULKAN_BACKEND
8428 case GHOST_kDrawingContextTypeVulkan: {
8429 /* Create new off-screen surface only for vulkan. */
8430 wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor_get());
8431
8432 GHOST_Context *context = new GHOST_ContextVK(false,
8433 GHOST_kVulkanPlatformWayland,
8434 0,
8435 nullptr,
8436 wl_surface,
8437 display_->wl.display,
8438 nullptr,
8439 1,
8440 2,
8441 debug_context,
8442 gpuSettings.preferred_device);
8443
8444 if (context->initializeDrawingContext()) {
8445 context->setUserData(wl_surface);
8446 return context;
8447 }
8448 delete context;
8449
8450 if (wl_surface) {
8451 wl_surface_destroy(wl_surface);
8452 }
8453 return nullptr;
8454 }
8455#endif /* WITH_VULKAN_BACKEND */
8456
8457#ifdef WITH_OPENGL_BACKEND
8458 case GHOST_kDrawingContextTypeOpenGL: {
8459 /* Create new off-screen window. */
8460 wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor_get());
8461 wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr;
8462
8463 for (int minor = 6; minor >= 3; --minor) {
8464 /* Caller must lock `system->server_mutex`. */
8465 GHOST_Context *context = new GHOST_ContextEGL(
8466 this,
8467 false,
8468 EGLNativeWindowType(egl_window),
8469 EGLNativeDisplayType(display_->wl.display),
8470 EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
8471 4,
8472 minor,
8474 (debug_context ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
8476 EGL_OPENGL_API);
8477
8478 if (context->initializeDrawingContext()) {
8479 wl_surface_set_user_data(wl_surface, egl_window);
8480 context->setUserData(wl_surface);
8481 return context;
8482 }
8483 delete context;
8484 }
8485
8486 GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
8487 if (wl_surface) {
8488 wl_surface_destroy(wl_surface);
8489 }
8490 if (egl_window) {
8491 wl_egl_window_destroy(egl_window);
8492 }
8493 return nullptr;
8494 }
8495#endif /* WITH_OPENGL_BACKEND */
8496
8497 default:
8498 /* Unsupported backend. */
8499 return nullptr;
8500 }
8501}
8502
8504{
8505#ifdef USE_EVENT_BACKGROUND_THREAD
8506 std::lock_guard lock_server_guard{*server_mutex};
8507#endif
8508
8510#ifdef WITH_OPENGL_BACKEND
8511 if (dynamic_cast<GHOST_ContextEGL *>(context)) {
8512 type = GHOST_kDrawingContextTypeOpenGL;
8513 }
8514#endif /* WITH_OPENGL_BACKEND */
8515#ifdef WITH_VULKAN_BACKEND
8516 if (dynamic_cast<GHOST_ContextVK *>(context)) {
8517 type = GHOST_kDrawingContextTypeVulkan;
8518 }
8519#endif /* WITH_VULKAN_BACKEND */
8520
8521 wl_surface *wl_surface = static_cast<struct wl_surface *>(
8522 (static_cast<GHOST_Context *>(context))->getUserData());
8523
8524 /* Delete the context before the window so the context is able to release
8525 * native resources (such as the #EGLSurface) before WAYLAND frees them. */
8526 delete context;
8527
8528#ifdef WITH_OPENGL_BACKEND
8529 if (type == GHOST_kDrawingContextTypeOpenGL) {
8530 wl_egl_window *egl_window = static_cast<wl_egl_window *>(wl_surface_get_user_data(wl_surface));
8531 if (egl_window != nullptr) {
8532 wl_egl_window_destroy(egl_window);
8533 }
8534 }
8535#endif /* WITH_OPENGL_BACKEND */
8536
8537 wl_surface_destroy(wl_surface);
8538
8539 (void)type; /* Maybe unused. */
8540
8541 return GHOST_kSuccess;
8542}
8543
8545 const int32_t left,
8546 const int32_t top,
8547 const uint32_t width,
8548 const uint32_t height,
8550 const GHOST_GPUSettings gpuSettings,
8551 const bool exclusive,
8552 const bool is_dialog,
8553 const GHOST_IWindow *parentWindow)
8554{
8555 /* Globally store pointer to window manager. */
8557 this,
8558 title,
8559 left,
8560 top,
8561 width,
8562 height,
8563 state,
8564 parentWindow,
8565 gpuSettings.context_type,
8566 is_dialog,
8567 ((gpuSettings.flags & GHOST_gpuStereoVisual) != 0),
8568 exclusive,
8569 (gpuSettings.flags & GHOST_gpuDebugContext) != 0,
8570 gpuSettings.preferred_device);
8571
8572 if (window) {
8573 if (window->getValid()) {
8574 m_windowManager->addWindow(window);
8575 m_windowManager->setActiveWindow(window);
8576 const uint64_t event_ms = getMilliSeconds();
8577 pushEvent(new GHOST_Event(event_ms, GHOST_kEventWindowSize, window));
8578 }
8579 else {
8580 delete window;
8581 window = nullptr;
8582 }
8583 }
8584
8585 return window;
8586}
8587
8588static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_software_confine)
8589{
8590 if (mode == GHOST_kGrabWrap) {
8591 return true;
8592 }
8593#ifdef USE_GNOME_CONFINE_HACK
8594 if (mode == GHOST_kGrabNormal) {
8595 if (use_software_confine) {
8596 return true;
8597 }
8598 }
8599#else
8600 (void)use_software_confine;
8601#endif
8602 return false;
8603}
8604
8606{
8607 /* Caller must lock `server_mutex`. */
8608
8609 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8610 if (UNLIKELY(!seat)) {
8611 return GHOST_kFailure;
8612 }
8613
8614 const char *cursor_name = nullptr;
8615 const wl_cursor *wl_cursor = gwl_seat_cursor_find_from_shape(seat, shape, &cursor_name);
8616 if (wl_cursor == nullptr) {
8617 return GHOST_kFailure;
8618 }
8619
8620 GWL_Cursor *cursor = &seat->cursor;
8621 wl_cursor_image *image = wl_cursor->images[0];
8622 wl_buffer *buffer = wl_cursor_image_get_buffer(image);
8623 if (!buffer) {
8624 return GHOST_kFailure;
8625 }
8626
8627 cursor->visible = true;
8628 cursor->is_custom = false;
8629 cursor->wl.buffer = buffer;
8630 cursor->wl.image = *image;
8631 cursor->wl.theme_cursor = wl_cursor;
8632 cursor->wl.theme_cursor_name = cursor_name;
8633
8635
8636 return GHOST_kSuccess;
8637}
8638
8640{
8641 /* No need to lock `server_mutex`. */
8642 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8643 if (UNLIKELY(!seat)) {
8644 return GHOST_kFailure;
8645 }
8646
8647 const wl_cursor *wl_cursor = gwl_seat_cursor_find_from_shape(seat, cursorShape, nullptr);
8648 if (wl_cursor == nullptr) {
8649 return GHOST_kFailure;
8650 }
8651 return GHOST_kSuccess;
8652}
8653
8655 const uint8_t *mask,
8656 const int sizex,
8657 const int sizey,
8658 const int hotX,
8659 const int hotY,
8660 const bool /*canInvertColor*/)
8661{
8662 /* Caller needs to lock `server_mutex`. */
8663 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8664 if (UNLIKELY(!seat)) {
8665 return GHOST_kFailure;
8666 }
8667
8668 GWL_Cursor *cursor = &seat->cursor;
8669 if (cursor->custom_data) {
8670 munmap(cursor->custom_data, cursor->custom_data_size);
8671 cursor->custom_data = nullptr;
8672 cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */
8673 }
8674
8675 const int32_t size_xy[2] = {sizex, sizey};
8676 wl_buffer *buffer = ghost_wl_buffer_create_for_image(display_->wl.shm,
8677 size_xy,
8678 WL_SHM_FORMAT_ARGB8888,
8679 &cursor->custom_data,
8680 &cursor->custom_data_size);
8681 if (buffer == nullptr) {
8682 return GHOST_kFailure;
8683 }
8684
8685 wl_buffer_add_listener(buffer, &cursor_buffer_listener, cursor);
8686
8687 static constexpr uint32_t black = 0xFF000000;
8688 static constexpr uint32_t white = 0xFFFFFFFF;
8689 static constexpr uint32_t transparent = 0x00000000;
8690
8691 uint8_t datab = 0, maskb = 0;
8692
8693 for (int y = 0; y < sizey; ++y) {
8694 uint32_t *pixel = &static_cast<uint32_t *>(cursor->custom_data)[y * sizex];
8695 for (int x = 0; x < sizex; ++x) {
8696 if ((x % 8) == 0) {
8697 datab = *bitmap++;
8698 maskb = *mask++;
8699
8700 /* Reverse bit order. */
8701 datab = uint8_t((datab * 0x0202020202ULL & 0x010884422010ULL) % 1023);
8702 maskb = uint8_t((maskb * 0x0202020202ULL & 0x010884422010ULL) % 1023);
8703 }
8704
8705 if (maskb & 0x80) {
8706 *pixel++ = (datab & 0x80) ? white : black;
8707 }
8708 else {
8709 *pixel++ = (datab & 0x80) ? white : transparent;
8710 }
8711 datab <<= 1;
8712 maskb <<= 1;
8713 }
8714 }
8715
8716 cursor->visible = true;
8717 cursor->is_custom = true;
8718
8719 /* Calculate the cursor size to use based on the theme setting. */
8720 {
8721
8722 /* WARNING: Weak logic, if we can't use vector cursors - ideally the custom cursor
8723 * function would receive multiple sizes which WAYLAND could then switch between
8724 * as it does with themes. The following logic is fairly weak but works perfectly
8725 * when all outputs have the same scale.
8726 *
8727 * There is nothing preventing multiple sized cursors from being passed in,
8728 * it's just a matter of refactoring and adding support to WAYLAND. */
8729
8730 /* Get the lowest scale so in the case of mixed-scale-outputs,
8731 * the cursor will be too big on some of the outputs instead of too small.
8732 *
8733 * Note that getting the min/max scale for all outputs be made into an function
8734 * however it's bad practice because it means the cursor size will be wrong
8735 * when there are multiple outputs with different scale.
8736 * So this is not something to encouraged. */
8737 int output_scale = -1;
8738 for (const GWL_Output *output : display_->outputs) {
8739 output_scale = (output_scale == -1) ? output->scale : std::min(output_scale, output->scale);
8740 }
8741 if (output_scale == -1) {
8742 output_scale = 1;
8743 }
8744
8745 const int custom_size = std::max(sizex, sizey);
8746 const int target_size = seat->cursor.theme_size * output_scale;
8747
8748 cursor->custom_scale = std::max(1, (output_scale * custom_size) / target_size);
8749 /* It would make more sense to adjust the buffer size instead of the scale.
8750 * In practice with custom cursors of 16x16, 24x24 & 32x32 its only likely to cause
8751 * problems with odd-scaling (HI-DPI scale of 300% or 500% for example).
8752 * In these cases the custom cursor will be a little too large. */
8753 while ((cursor->custom_scale > 1) &&
8754 !((sizex % cursor->custom_scale) == 0 && (sizey % cursor->custom_scale) == 0))
8755 {
8756 cursor->custom_scale -= 1;
8757 }
8758 }
8759
8760 cursor->wl.buffer = buffer;
8761 cursor->wl.image.width = uint32_t(sizex);
8762 cursor->wl.image.height = uint32_t(sizey);
8763 cursor->wl.image.hotspot_x = uint32_t(hotX);
8764 cursor->wl.image.hotspot_y = uint32_t(hotY);
8765 cursor->wl.theme_cursor = nullptr;
8766 cursor->wl.theme_cursor_name = nullptr;
8767
8769
8770 return GHOST_kSuccess;
8771}
8772
8774{
8775 /* Caller must lock `server_mutex`. */
8776 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8777 if (UNLIKELY(!seat)) {
8778 return GHOST_kFailure;
8779 }
8780
8781 GWL_Cursor *cursor = &seat->cursor;
8782 if (cursor->custom_data == nullptr) {
8783 return GHOST_kFailure;
8784 }
8785 if (!cursor->is_custom) {
8786 return GHOST_kFailure;
8787 }
8788
8789 bitmap->data_size[0] = cursor->wl.image.width;
8790 bitmap->data_size[1] = cursor->wl.image.height;
8791
8792 bitmap->hot_spot[0] = cursor->wl.image.hotspot_x;
8793 bitmap->hot_spot[1] = cursor->wl.image.hotspot_y;
8794
8795 bitmap->data = static_cast<uint8_t *>(cursor->custom_data);
8796
8797 return GHOST_kSuccess;
8798}
8799
8801{
8802 /* Caller must lock `server_mutex`. */
8803 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8804 if (UNLIKELY(!seat)) {
8805 return GHOST_kFailure;
8806 }
8807
8809 return GHOST_kSuccess;
8810}
8811
8813{
8814 /* It's possible there are no seats, ignore the value in this case. */
8815 GHOST_ASSERT(((gwl_display_seat_active_get(display_) == nullptr) ||
8817 "The trackpad direction was expected to be initialized");
8818
8819 return GHOST_TCapabilityFlag(
8821 ~(
8822 /* WAYLAND doesn't support accessing the window position. */
8824 /* WAYLAND doesn't support setting the cursor position directly,
8825 * this is an intentional choice, forcing us to use a software cursor in this case. */
8827 /* Some drivers don't support front-buffer reading, see: #98462 & #106264.
8828 *
8829 * NOTE(@ideasman42): the EGL flag `EGL_BUFFER_PRESERVED` is intended request support for
8830 * front-buffer reading however in my tests requesting the flag didn't work with AMD,
8831 * and it's not even requirement - so we can't rely on this feature being supported.
8832 *
8833 * Instead of assuming this is not supported, the graphics card driver could be inspected
8834 * (enable for NVIDIA for example), but the advantage in supporting this is minimal.
8835 * In practice it means an off-screen buffer is used to redraw the window for the
8836 * screen-shot and eye-dropper sampling logic, both operations where the overhead
8837 * is negligible. */
8839 /* This WAYLAND back-end has not yet implemented desktop color sample. */
8841 /* This WAYLAND back-end doesn't have support for window decoration styles.
8842 * In all likelihood, this back-end will eventually need to support client-side
8843 * decorations, see #113795. */
8845 /* This flag will eventually be removed. */
8847 0 :
8849}
8850
8852{
8853 /* Caller must lock `server_mutex`. */
8854 const GWL_Seat *seat = gwl_display_seat_active_get(display_);
8855 if (UNLIKELY(!seat)) {
8856 return false;
8857 }
8858
8859#ifdef USE_GNOME_CONFINE_HACK
8860 const bool use_software_confine = seat->use_pointer_software_confine;
8861#else
8862 const bool use_software_confine = false;
8863#endif
8864
8865 return cursor_is_software(mode, use_software_confine);
8866}
8867
8868#ifdef USE_GNOME_CONFINE_HACK
8871{
8872# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
8873 if (use_gnome_confine_hack == false) {
8874 return false;
8875 }
8876# endif
8877 if (mode != GHOST_kGrabNormal) {
8878 return false;
8879 }
8881 if (!win) {
8882 return false;
8883 }
8884
8885# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
8886 if (win->scale_get() <= 1) {
8887 return false;
8888 }
8889# endif
8890 return true;
8891}
8892#endif
8893
8895 const bool use_software_confine)
8896{
8897 /* Initialize all members. */
8898 GWL_SeatStateGrab grab_state;
8899 /* Warping happens to require software cursor which also hides. */
8900 grab_state.use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine;
8901 grab_state.use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false);
8902 return grab_state;
8903}
8904
8906{
8907 if (m_multitouchGestures == use) {
8908 return;
8909 }
8911
8912#ifdef USE_EVENT_BACKGROUND_THREAD
8913 /* Ensure this listeners aren't removed while events are generated. */
8914 std::lock_guard lock_server_guard{*server_mutex};
8915#endif
8916 for (GWL_Seat *seat : display_->seats) {
8917 if (use == gwl_seat_capability_pointer_multitouch_check(seat, use)) {
8918 continue;
8919 }
8920 if (use) {
8922 }
8923 else {
8925 }
8926 }
8927}
8928
8930
8931/* -------------------------------------------------------------------- */
8934
8935static const char *ghost_wl_output_tag_id = "GHOST-output";
8936static const char *ghost_wl_surface_tag_id = "GHOST-window";
8937static const char *ghost_wl_surface_cursor_pointer_tag_id = "GHOST-cursor-pointer";
8938static const char *ghost_wl_surface_cursor_tablet_tag_id = "GHOST-cursor-tablet";
8939
8940bool ghost_wl_output_own(const wl_output *wl_output)
8941{
8942 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_output);
8943 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_output_tag_id;
8944}
8945
8947{
8948 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
8949 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_surface_tag_id;
8950}
8951
8956
8958{
8959 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
8960 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) ==
8962}
8963
8965{
8966 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
8967 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_surface_cursor_tablet_tag_id;
8968}
8969
8970void ghost_wl_output_tag(wl_output *wl_output)
8971{
8972 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_output);
8974}
8975
8977{
8978 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
8980}
8981
8983{
8984 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
8986}
8987
8989{
8990 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
8992}
8993
8995
8996/* -------------------------------------------------------------------- */
9001
9003{
9004 return display_->wl.display;
9005}
9006
9008{
9009 return display_->wl.compositor;
9010}
9011
9012zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_selection_manager_get()
9013{
9014 return display_->wp.primary_selection_device_manager;
9015}
9016
9018{
9019 return display_->xdg.activation_manager;
9020}
9021
9023{
9024 return display_->wp.fractional_scale_manager;
9025}
9027{
9028 return display_->wp.viewporter;
9029}
9030
9032{
9033 return display_->wp.pointer_gestures;
9034}
9035
9036/* This value is expected to match the base name of the `.desktop` file. see #101805.
9037 *
9038 * NOTE: the XDG desktop-entry-spec defines that this should follow the "reverse DNS" convention.
9039 * For example `org.blender.Blender` - however the `.desktop` file distributed with Blender is
9040 * simply called `blender.desktop`, so the it's important to follow that name.
9041 * Other distributions such as SNAP & FLATPAK may need to change this value #101779.
9042 * Currently there isn't a way to configure this, we may want to support that. */
9043static const char *ghost_wl_app_id = (
9044#ifdef WITH_GHOST_WAYLAND_APP_ID
9045 STRINGIFY(WITH_GHOST_WAYLAND_APP_ID)
9046#else
9047 "blender"
9048#endif
9049);
9050
9052{
9053 return ghost_wl_app_id;
9054}
9055
9056#ifdef WITH_GHOST_WAYLAND_LIBDECOR
9057
9058libdecor *GHOST_SystemWayland::libdecor_context_get()
9059{
9060 return display_->libdecor->context;
9061}
9062
9063#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
9064
9066{
9067 return display_->xdg_decor->shell;
9068}
9069
9071{
9072 return display_->xdg_decor->manager;
9073}
9074
9075/* End `xdg_decor`. */
9076
9077const std::vector<GWL_Output *> &GHOST_SystemWayland::outputs_get() const
9078{
9079 return display_->outputs;
9080}
9081
9083{
9084 return display_->wl.shm;
9085}
9086
9087#ifdef USE_EVENT_BACKGROUND_THREAD
9089{
9090 return display_->ghost_timer_manager;
9091}
9092#endif
9093
9095
9096/* -------------------------------------------------------------------- */
9101
9102#ifdef WITH_INPUT_IME
9103
9105 int32_t x,
9106 int32_t y,
9107 int32_t w,
9108 int32_t h,
9109 bool completed) const
9110{
9111 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9112 if (UNLIKELY(!seat)) {
9113 return;
9114 }
9115 if (seat->wp.text_input == nullptr) {
9116 return;
9117 }
9118
9119 /* Prevent a feedback loop because the commits from this function cause
9120 * #zwp_text_input_v3_listener::preedit_string to run again which sends an event,
9121 * refreshing the position, running this function again. */
9122 gwl_seat_ime_result_reset(seat);
9123
9124 /* Don't re-enable if we're already enabled. */
9125 if (seat->ime.is_enabled && completed) {
9126 return;
9127 }
9128
9129 bool force_rect_update = false;
9130 if (seat->ime.is_enabled == false) {
9131 seat->ime.has_preedit = false;
9132 seat->ime.is_enabled = true;
9133
9134 zwp_text_input_v3_enable(seat->wp.text_input);
9135 zwp_text_input_v3_commit(seat->wp.text_input);
9136
9137 /* Now that it's enabled, set the input properties. */
9138 zwp_text_input_v3_set_content_type(seat->wp.text_input,
9139 ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
9140 ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
9141
9142 gwl_seat_ime_rect_reset(seat);
9143 force_rect_update = true;
9144 }
9145
9146 if ((force_rect_update == false) && /* Was just created, always update. */
9147 (seat->ime.rect.x == x) && /* X. */
9148 (seat->ime.rect.y == y) && /* Y. */
9149 (seat->ime.rect.w == w) && /* W. */
9150 (seat->ime.rect.h == h)) /* H. */
9151 {
9152 /* Only re-update the rectangle as needed. */
9153 }
9154 else {
9155 const int rect_x = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(x)));
9156 const int rect_y = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(y)));
9157 const int rect_w = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(w))) + 1;
9158 const int rect_h = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(h))) + 1;
9159
9160 zwp_text_input_v3_set_cursor_rectangle(seat->wp.text_input, rect_x, rect_y, rect_w, rect_h);
9161
9162 zwp_text_input_v3_commit(seat->wp.text_input);
9163
9164 seat->ime.rect.x = x;
9165 seat->ime.rect.y = y;
9166 seat->ime.rect.w = w;
9167 seat->ime.rect.h = h;
9168 }
9169}
9170
9171void GHOST_SystemWayland::ime_end(const GHOST_WindowWayland * /*window*/) const
9172{
9173 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9174 if (UNLIKELY(!seat)) {
9175 return;
9176 }
9177
9178 seat->ime.is_enabled = false;
9179
9180 gwl_seat_ime_rect_reset(seat);
9181
9182 if (seat->wp.text_input == nullptr) {
9183 return;
9184 }
9185
9186 zwp_text_input_v3_disable(seat->wp.text_input);
9187 zwp_text_input_v3_commit(seat->wp.text_input);
9188}
9189
9190#endif /* WITH_INPUT_IME */
9191
9193
9194/* -------------------------------------------------------------------- */
9197
9199{
9200 GHOST_ASSERT(wl_output, "output must not be nullptr");
9201 GHOST_ASSERT(ghost_wl_output_own(wl_output), "output is not owned by GHOST");
9202 GWL_Output *output = static_cast<GWL_Output *>(wl_output_get_user_data(wl_output));
9203 return output;
9204}
9205
9207{
9208 GHOST_ASSERT(wl_surface, "wl_surface must not be nullptr");
9209 GHOST_ASSERT(ghost_wl_surface_own(wl_surface), "wl_surface is not owned by GHOST");
9210 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(
9211 wl_surface_get_user_data(wl_surface));
9212 return win;
9213}
9214
9216
9217/* -------------------------------------------------------------------- */
9222
9223uint64_t GHOST_SystemWayland::ms_from_input_time(const uint32_t timestamp_as_uint)
9224{
9225 /* NOTE(@ideasman42): Return a time compatible with `getMilliSeconds()`,
9226 * this is needed as WAYLAND time-stamps don't have a well defined beginning
9227 * use `timestamp_as_uint` to calculate an offset which is applied to future events.
9228 * This is updated because time may have passed between generating the time-stamp and `now`.
9229 * The method here is used by SDL. */
9230 uint64_t timestamp = uint64_t(timestamp_as_uint);
9231
9232 GWL_DisplayTimeStamp &input_timestamp = display_->input_timestamp;
9233 if (UNLIKELY(timestamp_as_uint < input_timestamp.last)) {
9234 /* NOTE(@ideasman42): Sometimes event times are out of order,
9235 * while this should _never_ happen, it occasionally does:
9236 * - When resizing the window then clicking on the window with GNOME+LIBDECOR.
9237 * - With accepting IME text with GNOME-v45.2 the timestamp is in seconds, see:
9238 * https://gitlab.gnome.org/GNOME/mutter/-/issues/3214
9239 * Accept events must occur within ~25 days, out-of-order time-stamps above this time-frame
9240 * will be treated as a wrapped integer. */
9241 if (input_timestamp.last - timestamp_as_uint > std::numeric_limits<uint32_t>::max() / 2) {
9242 /* Finally check to avoid invalid rollover,
9243 * ensure the rolled over time is closer to "now" than it is currently. */
9244 const uint64_t offset_test = input_timestamp.offset +
9245 uint64_t(std::numeric_limits<uint32_t>::max()) + 1;
9246 const uint64_t now = getMilliSeconds();
9247 if (sub_abs_u64(now, timestamp + offset_test) <
9248 sub_abs_u64(now, timestamp + input_timestamp.offset))
9249 {
9250 /* 32-bit timer rollover, bump the offset. */
9251 input_timestamp.offset = offset_test;
9252 }
9253 }
9254 }
9255 input_timestamp.last = timestamp_as_uint;
9256
9257 if (input_timestamp.exact_match) {
9258 timestamp += input_timestamp.offset;
9259 }
9260 else {
9261 const uint64_t now = getMilliSeconds();
9262 const uint32_t now_as_uint32 = uint32_t(now);
9263 if (now_as_uint32 == timestamp_as_uint) {
9264 input_timestamp.exact_match = true;
9265 /* For systems with up times exceeding 47 days
9266 * it's possible we need to begin with an offset. */
9267 input_timestamp.offset = now - uint64_t(now_as_uint32);
9268 timestamp = now;
9269 }
9270 else {
9271 if (!input_timestamp.offset) {
9272 input_timestamp.offset = (now - timestamp);
9273 }
9274 timestamp += input_timestamp.offset;
9275
9276 if (timestamp > now) {
9277 input_timestamp.offset -= (timestamp - now);
9278 timestamp = now;
9279 }
9280 }
9281 }
9282
9283 return timestamp;
9284}
9285
9287{
9288#ifdef USE_EVENT_BACKGROUND_THREAD
9289 GHOST_ASSERT(!display_->background, "Foreground only");
9290 if (main_thread_id != std::this_thread::get_id()) {
9291 std::lock_guard lock{display_->events_pending_mutex};
9292 display_->events_pending.push_back(event);
9293 return GHOST_kSuccess;
9294 }
9295#endif
9296 return pushEvent(event);
9297}
9298
9300{
9301 gwl_display_seat_active_set(display_, seat);
9302}
9303
9305{
9306 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9307 if (UNLIKELY(!seat)) {
9308 return nullptr;
9309 }
9310
9311 serial = seat->data_source_serial;
9312 return seat->wl.seat;
9313}
9314
9316{
9317 bool changed = false;
9318#define SURFACE_CLEAR_PTR(surface_test) \
9319 if (surface_test == wl_surface) { \
9320 surface_test = nullptr; \
9321 changed = true; \
9322 } \
9323 ((void)0);
9324
9325 /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
9326 for (GWL_Seat *seat : display_->seats) {
9327 SURFACE_CLEAR_PTR(seat->pointer.wl.surface_window);
9329 SURFACE_CLEAR_PTR(seat->keyboard.wl.surface_window);
9331#ifdef WITH_INPUT_IME
9332 SURFACE_CLEAR_PTR(seat->ime.surface_window);
9333#endif
9334 }
9335#undef SURFACE_CLEAR_PTR
9336
9337 return changed;
9338}
9339
9340bool GHOST_SystemWayland::output_unref(wl_output *wl_output)
9341{
9342 bool changed = false;
9343 if (!ghost_wl_output_own(wl_output)) {
9344 return changed;
9345 }
9346
9347 /* NOTE: keep in sync with `output_scale_update`. */
9349 const GHOST_WindowManager *window_manager = getWindowManager();
9350 if (window_manager) {
9351 for (GHOST_IWindow *iwin : window_manager->getWindows()) {
9352 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
9353 if (win->outputs_leave(output)) {
9355 changed = true;
9356 }
9357 }
9358 }
9359 for (GWL_Seat *seat : display_->seats) {
9360 if (seat->pointer.outputs.erase(output)) {
9361 changed = true;
9362 }
9363 if (seat->tablet.outputs.erase(output)) {
9364 changed = true;
9365 }
9366 }
9367 return changed;
9368}
9369
9371{
9372 /* NOTE: keep in sync with `output_unref`. */
9373 const GHOST_WindowManager *window_manager = getWindowManager();
9374 if (window_manager) {
9375 for (GHOST_IWindow *iwin : window_manager->getWindows()) {
9376 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
9377 const std::vector<GWL_Output *> &outputs = win->outputs_get();
9378 if (!(std::find(outputs.begin(), outputs.end(), output) == outputs.cend())) {
9380 }
9381 }
9382 }
9383
9384 for (GWL_Seat *seat : display_->seats) {
9385 if (seat->pointer.outputs.count(output)) {
9387 seat->system->wl_shm_get(),
9388 &seat->pointer,
9389 seat->cursor.wl.surface_cursor);
9390 }
9391
9392 if (seat->tablet.outputs.count(output)) {
9393 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
9394 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
9395 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
9397 seat->system->wl_shm_get(),
9398 &seat->tablet,
9399 tablet_tool->wl.surface_cursor);
9400 }
9401 }
9402 }
9403}
9404
9406 const GHOST_TGrabCursorMode mode_current,
9407 int32_t init_grab_xy[2],
9408 const GHOST_Rect *wrap_bounds,
9409 const GHOST_TAxisFlag wrap_axis,
9411 const GWL_WindowScaleParams &scale_params)
9412{
9413 /* Caller must lock `server_mutex`. */
9414
9415 /* Ignore, if the required protocols are not supported. */
9416 if (UNLIKELY(!display_->wp.relative_pointer_manager || !display_->wp.pointer_constraints)) {
9417 return false;
9418 }
9419
9420 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9421 if (UNLIKELY(!seat)) {
9422 return false;
9423 }
9424 /* No change, success. */
9425 if (mode == mode_current) {
9426 return true;
9427 }
9428
9429#ifdef USE_GNOME_CONFINE_HACK
9430 const bool was_software_confine = seat->use_pointer_software_confine;
9431 const bool use_software_confine = setCursorGrab_use_software_confine(mode, wl_surface);
9432#else
9433 const bool was_software_confine = false;
9434 const bool use_software_confine = false;
9435#endif
9436
9437 const GWL_SeatStateGrab grab_state_prev = seat_grab_state_from_mode(mode_current,
9438 was_software_confine);
9439 const GWL_SeatStateGrab grab_state_next = seat_grab_state_from_mode(mode, use_software_confine);
9440
9441 /* Check for wrap as #GHOST_kCapabilityCursorWarp isn't supported. */
9442 const bool use_visible = !(ELEM(mode, GHOST_kGrabHide, GHOST_kGrabWrap) || use_software_confine);
9443 const bool is_hardware_cursor = !cursor_is_software(mode, use_software_confine);
9444
9445 /* Only hide so the cursor is not made visible before it's location is restored.
9446 * This function is called again at the end of this function which only shows. */
9447 gwl_seat_cursor_visible_set(seat, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_HIDE);
9448
9449 /* Switching from one grab mode to another,
9450 * in this case disable the current locks as it makes logic confusing,
9451 * postpone changing the cursor to avoid flickering. */
9452 if (!grab_state_next.use_lock) {
9453 if (seat->wp.relative_pointer) {
9454 zwp_relative_pointer_v1_destroy(seat->wp.relative_pointer);
9455 seat->wp.relative_pointer = nullptr;
9456 }
9457 if (seat->wp.locked_pointer) {
9458 /* Potentially add a motion event so the application has updated X/Y coordinates. */
9459 wl_fixed_t xy_motion[2] = {0, 0};
9460 bool xy_motion_create_event = false;
9461
9462 /* Request location to restore to. */
9463 if (mode_current == GHOST_kGrabWrap) {
9464 /* Since this call is initiated by Blender, we can be sure the window wasn't closed
9465 * by logic outside this function - as the window was needed to make this call. */
9466 wl_fixed_t xy_next[2] = {UNPACK2(seat->pointer.xy)};
9467
9468 GHOST_Rect bounds_scale;
9469
9470 bounds_scale.m_l = gwl_window_scale_wl_fixed_from(scale_params,
9471 wl_fixed_from_int(wrap_bounds->m_l));
9472 bounds_scale.m_t = gwl_window_scale_wl_fixed_from(scale_params,
9473 wl_fixed_from_int(wrap_bounds->m_t));
9474 bounds_scale.m_r = gwl_window_scale_wl_fixed_from(scale_params,
9475 wl_fixed_from_int(wrap_bounds->m_r));
9476 bounds_scale.m_b = gwl_window_scale_wl_fixed_from(scale_params,
9477 wl_fixed_from_int(wrap_bounds->m_b));
9478
9479 bounds_scale.wrapPoint(UNPACK2(xy_next), 0, wrap_axis);
9480
9481 /* Push an event so the new location is registered. */
9482 if ((xy_next[0] != seat->pointer.xy[0]) || (xy_next[1] != seat->pointer.xy[1])) {
9483 xy_motion[0] = xy_next[0];
9484 xy_motion[1] = xy_next[1];
9485 xy_motion_create_event = true;
9486 }
9487 seat->pointer.xy[0] = xy_next[0];
9488 seat->pointer.xy[1] = xy_next[1];
9489
9490 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer, UNPACK2(xy_next));
9491 wl_surface_commit(wl_surface);
9492 }
9493 else if (mode_current == GHOST_kGrabHide) {
9494 const wl_fixed_t xy_next[2] = {
9495 gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[0])),
9496 gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[1])),
9497 };
9498
9499 if ((init_grab_xy[0] != seat->grab_lock_xy[0]) ||
9500 (init_grab_xy[1] != seat->grab_lock_xy[1]))
9501 {
9502 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer,
9503 UNPACK2(xy_next));
9504 wl_surface_commit(wl_surface);
9505
9506 /* NOTE(@ideasman42): The new cursor position is a hint,
9507 * it's possible the hint is ignored. It doesn't seem like there is a good way to
9508 * know if the hint will be used or not, at least not immediately. */
9509 xy_motion[0] = xy_next[0];
9510 xy_motion[1] = xy_next[1];
9511 xy_motion_create_event = true;
9512 }
9513 else if (grab_state_prev.use_lock) {
9514 /* NOTE(@ideasman42): From WAYLAND's perspective the cursor did not move.
9515 * The application will have received "hidden" events to warped locations.
9516 * So generate event without setting the cursor position hint. */
9517 xy_motion[0] = xy_next[0];
9518 xy_motion[1] = xy_next[1];
9519 xy_motion_create_event = true;
9520 }
9521 }
9522#ifdef USE_GNOME_CONFINE_HACK
9523 else if (mode_current == GHOST_kGrabNormal) {
9524 if (was_software_confine) {
9525 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer,
9526 UNPACK2(seat->pointer.xy));
9527 wl_surface_commit(wl_surface);
9528 }
9529 }
9530#endif
9531
9532 if (xy_motion_create_event) {
9533 /* Caller has no time-stamp. */
9534 const uint64_t event_ms = getMilliSeconds();
9536 event_ms,
9539 wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[0])),
9540 wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[1])),
9542 }
9543
9544 zwp_locked_pointer_v1_destroy(seat->wp.locked_pointer);
9545 seat->wp.locked_pointer = nullptr;
9546 }
9547 }
9548
9549 if (!grab_state_next.use_confine) {
9550 if (seat->wp.confined_pointer) {
9551 zwp_confined_pointer_v1_destroy(seat->wp.confined_pointer);
9552 seat->wp.confined_pointer = nullptr;
9553 }
9554 }
9555
9556 if (mode != GHOST_kGrabDisable) {
9557 if (grab_state_next.use_lock) {
9558 if (!grab_state_prev.use_lock) {
9559 /* As WAYLAND does not support setting the cursor coordinates programmatically,
9560 * #GHOST_kGrabWrap cannot be supported by positioning the cursor directly.
9561 * Instead the cursor is locked in place, using a software cursor that is warped.
9562 * Then WAYLAND's #zwp_locked_pointer_v1_set_cursor_position_hint is used to restore
9563 * the cursor to the warped location. */
9564 seat->wp.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
9565 display_->wp.relative_pointer_manager, seat->wl.pointer);
9566 zwp_relative_pointer_v1_add_listener(
9568 seat->wp.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
9569 display_->wp.pointer_constraints,
9570 wl_surface,
9571 seat->wl.pointer,
9572 nullptr,
9573 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9574 }
9575 if (mode == GHOST_kGrabHide) {
9576 /* Set the initial position to detect any changes when un-grabbing,
9577 * otherwise the unlocked cursor defaults to un-locking in-place. */
9578 init_grab_xy[0] = wl_fixed_to_int(
9579 gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[0]));
9580 init_grab_xy[1] = wl_fixed_to_int(
9581 gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[1]));
9582 seat->grab_lock_xy[0] = init_grab_xy[0];
9583 seat->grab_lock_xy[1] = init_grab_xy[1];
9584 }
9585 }
9586 else if (grab_state_next.use_confine) {
9587 if (!grab_state_prev.use_confine) {
9588 seat->wp.confined_pointer = zwp_pointer_constraints_v1_confine_pointer(
9589 display_->wp.pointer_constraints,
9590 wl_surface,
9591 seat->wl.pointer,
9592 nullptr,
9593 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9594 }
9595 }
9596 }
9597
9598 /* Only show so the cursor is made visible as the last step. */
9599 gwl_seat_cursor_visible_set(seat, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_SHOW);
9600
9601#ifdef USE_GNOME_CONFINE_HACK
9602 seat->use_pointer_software_confine = use_software_confine;
9603#endif
9604
9605 return true;
9606}
9607
9608#ifdef WITH_GHOST_WAYLAND_LIBDECOR
9609bool GHOST_SystemWayland::use_libdecor_runtime()
9610{
9611 return use_libdecor;
9612}
9613#endif
9614
9615#ifdef WITH_GHOST_WAYLAND_DYNLOAD
9616bool ghost_wl_dynload_libraries_init()
9617{
9618# ifdef WITH_GHOST_X11
9619 /* When running in WAYLAND, let the user know when a missing library is the only reason
9620 * WAYLAND could not be used. Otherwise it's not obvious why X11 is used as a fallback.
9621 * Otherwise when X11 is used, reporting WAYLAND library warnings is unwelcome noise. */
9622 bool verbose = getenv("WAYLAND_DISPLAY") != nullptr;
9623# else
9624 bool verbose = true;
9625# endif /* !WITH_GHOST_X11 */
9626
9627 if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */
9628 wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */
9629# ifdef WITH_OPENGL_BACKEND
9630 wayland_dynload_egl_init(verbose) /* `libwayland-egl`. */
9631# else
9632 true
9633# endif
9634 )
9635 {
9636# ifdef WITH_GHOST_WAYLAND_LIBDECOR
9637 has_libdecor = wayland_dynload_libdecor_init(verbose); /* `libdecor-0`. */
9638# endif
9639 return true;
9640 }
9641
9644# ifdef WITH_OPENGL_BACKEND
9646# endif
9647
9648 return false;
9649}
9650
9651void ghost_wl_dynload_libraries_exit()
9652{
9655# ifdef WITH_OPENGL_BACKEND
9657# endif
9658# ifdef WITH_GHOST_WAYLAND_LIBDECOR
9660# endif
9661}
9662
9663#endif /* WITH_GHOST_WAYLAND_DYNLOAD */
9664
KDTree *BLI_kdtree_nd_ new(unsigned int nodes_len_capacity)
Definition kdtree_impl.h:97
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
#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
@ MOD_INDEX_HYPER
@ MOD_INDEX_SHIFT
@ MOD_INDEX_ALT
@ MOD_INDEX_CTRL
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)
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)
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 ghost_wayland_log_handler_background(const char *msg, va_list arg)
static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit)
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)
@ GWL_IOR_NO_RETRY
#define WL_NAME_UNSET
static bool gwl_registry_entry_remove_by_interface_slot(GWL_Display *display, int interface_slot, bool on_exit)
static char * system_clipboard_get_primary_selection(GWL_Display *display, const bool nil_terminate, const char *mime_receive_override, size_t *r_data_len)
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 CLG_LogRef LOG_WL_KEYBOARD
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)
void(*)(GWL_Display *display, const GWL_RegisteryUpdate_Params &params) GWL_RegistryHandler_UpdateFn
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)
#define XKB_VMOD_NAME_HYPER
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 constexpr const char * ghost_wl_mime_text_uri_list
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 char * system_clipboard_get(GWL_Display *display, bool nil_terminate, const char *mime_receive_override, size_t *r_data_len)
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)
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)
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 std::vector< std::string_view > gwl_clipboard_uri_ranges(const char *data_buf, size_t data_buf_len)
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)
void(*)(GWL_Display *display, const GWL_RegisteryAdd_Params &params) GWL_RegistryHandler_AddFn
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 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 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_kEventWheelAxisVertical
@ GHOST_kEventWheelAxisHorizontal
@ GHOST_kTrackpadEventMagnify
@ GHOST_kTrackpadEventRotate
@ GHOST_kTrackpadEventScroll
GHOST_TWindowState
void * GHOST_TUserDataPtr
Definition GHOST_Types.h:78
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_kStandardCursorBlade
@ 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:89
@ GHOST_kCapabilityWindowPosition
Definition GHOST_Types.h:97
@ GHOST_kCapabilityGPUReadFrontBuffer
@ GHOST_kCapabilityCursorWarp
Definition GHOST_Types.h:93
@ GHOST_kCapabilityTrackpadPhysicalDirection
@ GHOST_kCapabilityWindowDecorationStyles
@ 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_kKeyLeftHyper
@ 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_kKeyRightHyper
@ GHOST_kKeyLeftShift
@ GHOST_kKeyMediaLast
@ GHOST_kKeySpace
GHOST_TDrawingContextType
@ GHOST_kDrawingContextTypeNone
GHOST_TModifierKey
@ GHOST_kModifierKeyRightControl
@ GHOST_kModifierKeyLeftControl
@ GHOST_kModifierKeyRightHyper
@ GHOST_kModifierKeyRightAlt
@ GHOST_kModifierKeyRightShift
@ GHOST_kModifierKeyLeftAlt
@ GHOST_kModifierKeyLeftShift
@ GHOST_kModifierKeyLeftOS
@ GHOST_kModifierKeyRightOS
@ GHOST_kModifierKeyLeftHyper
GHOST_TSuccess
Definition GHOST_Types.h:80
@ GHOST_kFailure
Definition GHOST_Types.h:80
@ GHOST_kSuccess
Definition GHOST_Types.h:80
@ GHOST_gpuStereoVisual
Definition GHOST_Types.h:69
@ GHOST_gpuDebugContext
Definition GHOST_Types.h:70
void(* GHOST_TBacktraceFn)(void *file_handle)
Definition GHOST_Types.h:56
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
ImBuf * IMB_load_image_from_filepath(const char *filepath, const int flags, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:204
ImBuf * IMB_load_image_from_memory(const unsigned char *mem, const size_t size, const int flags, const char *descr, const char *filepath=nullptr, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:116
ImBuf * IMB_allocFromBuffer(const uint8_t *byte_buffer, const float *float_buffer, unsigned int w, unsigned int h, unsigned int channels)
void IMB_freeImBuf(ImBuf *ibuf)
bool IMB_save_image(ImBuf *ibuf, const char *filepath, const int flags)
Definition writeimage.cc:20
bool IMB_test_image(const char *filepath)
@ IMB_FTYPE_PNG
@ IB_byte_data
@ IB_mem
volatile int lock
BMesh const char void * data
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
unsigned long long int uint64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
btAlignedObjectArray< btScalar > m_data
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
static int verbose
Definition cineonlib.cc:30
virtual GHOST_TEventType getType() const =0
virtual GHOST_IWindow * getWindow() const =0
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()
uint32_t getCursorPreferredLogicalSize() const override
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_TSuccess removeTimer(GHOST_ITimerTask *timerTask) override
GHOST_EventManager * getEventManager() const
GHOST_WindowManager * getWindowManager() const
bool m_multitouchGestures
GHOST_TSuccess init() override
GHOST_TimerManager * getTimerManager() const
GHOST_ITimerTask * installTimer(uint64_t delay, uint64_t interval, GHOST_TimerProcPtr timerProc, GHOST_TUserDataPtr userData=nullptr) override
GHOST_WindowManager * m_windowManager
GHOST_TSuccess removeTimer(GHOST_TimerTask *timer)
GHOST_TSuccess addTimer(GHOST_TimerTask *timer)
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
static __attribute__((constructor)) void cpu_check()
Definition cpu_check.cc:94
#define str(s)
#define INT32_MAX
#define INT32_MIN
uint pos
ThreadMutex mutex
uint top
#define select(A, B, C)
#define interface
#define printf(...)
#define output
float distance(VecOp< float, D >, VecOp< float, D >) RET
constexpr T degrees(T) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
format
#define LOG(severity)
Definition log.h:32
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static ulong * next
static ulong state[N]
static int left
static void error(const char *str)
static int make(const char *input_file_name, const char *output_file_name)
Definition msgfmt.cc:239
static void add(blender::Map< std::string, std::string > &messages, Message &msg)
Definition msgfmt.cc:222
int context(const bContext *C, const char *member, bContextDataResult *result)
bool is_enabled(const Sculpt &sd, const Object &object, const Brush *br)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
static blender::bke::bNodeSocketTemplate outputs[]
void set(GHOST_TButton mask, bool down)
const uint8_t * data
Definition GHOST_Types.h:63
GHOST_TDrawingContextType context_type
GHOST_GPUDevice preferred_device
void set(GHOST_TModifierKey mask, bool down)
std::string composite
GHOST_TTabletMode Active
std::atomic< bool > exit_pending
const char * names[GHOST_kStandardCursorNumCursors]
struct GWL_Cursor::@050316242345211375236221043351014205236353051251 wl
std::string theme_name
wl_cursor_theme * theme
wl_cursor_image image
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
enum wl_data_device_manager_dnd_action source_actions
struct GWL_DataOffer::@000321357326273027247056345031354174337224053377 wl
enum wl_data_device_manager_dnd_action action
struct GWL_DataOffer::@011247255043237154242043025362334115133317316307 dnd
GWL_SimpleBuffer buffer_out
wl_data_source * source
struct GWL_DataSource::@171373255262027061130125060265037104067374340011 wl
wp_viewporter * viewporter
zwp_primary_selection_device_manager_v1 * primary_selection_device_manager
std::vector< GWL_Seat * > seats
GHOST_TimerManager * ghost_timer_manager
struct GWL_Display::@311217013120347372235111234227065250031142315272 xdg
std::vector< const GHOST_IEvent * > events_pending
std::vector< GWL_Output * > outputs
zwp_pointer_gestures_v1 * pointer_gestures
GWL_DisplayTimeStamp input_timestamp
struct GWL_Display::@332351376246201271020375353224364047225056022363 wl
GWL_XDG_Decor_System * xdg_decor
GWL_RegistryEntry * registry_entry
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
struct GWL_Display::@041375341326273211065375343216100110111213252212 wp
xdg_activation_v1 * activation_manager
wl_data_device_manager * data_device_manager
zwp_tablet_manager_v2 * tablet_manager
GHOST_SystemWayland * system
std::mutex events_pending_mutex
wp_fractional_scale_manager_v1 * fractional_scale_manager
struct GWL_KeyRepeatPlayload::@133074121273233316143122154352324207053125164376 key_data
int16_t mods[GHOST_KEY_MODIFIER_NUM]
GHOST_TModifierKey mod_l
GHOST_TModifierKey mod_r
zwp_primary_selection_offer_v1 * id
std::unordered_set< std::string > types
struct GWL_PrimarySelection_DataOffer::@224052045254066252237315110075221223001265154224 wp
zwp_primary_selection_source_v1 * source
struct GWL_PrimarySelection_DataSource::@214262242275022125172372225345372305347362001205 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::@000213333310320026041147135010277053023061216335 wl
enum wl_pointer_axis_source axis_source
GWL_SeatStatePointerScroll_SmoothAsDiscrete smooth_as_discrete
struct GWL_SeatStatePointer_Events::@227246237361267121111060051345102206175303062350 frame_pending
GWL_Pointer_EventTypes frame_types[GWL_TabletTool_FrameTypes_NUM]
uint64_t frame_event_ms[GWL_TabletTool_FrameTypes_NUM]
struct GWL_SeatStatePointer::@106371335315343070276130104021044224024005363234 wl
std::unordered_set< const GWL_Output * > outputs
wl_keyboard * keyboard
struct GWL_Seat::@035335017147325312156122174053370360242062243043 key_repeat
xkb_context * context
GHOST_SystemWayland * system
GWL_SeatStatePointerScroll pointer_scroll
GWL_KeyboardDepressedState key_depressed
GWL_SeatStatePointer_Events pointer_events
GWL_DataSource * data_source
uint32_t data_source_serial
struct GWL_Seat::@017114216203334356214327031134132054273005244337 wl
std::unordered_set< zwp_tablet_tool_v2 * > tablet_tools
GWL_DataOffer * data_offer_copy_paste
zwp_primary_selection_device_v1 * primary_selection_device
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
std::optional< GHOST_TSuccess > data_offer_copy_paste_has_image
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
struct GWL_Seat::@010013076176246161274337005250343273344166001063 wp
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
struct GWL_Seat::@043243047264252106207033262235132033213071332203 xkb
xkb_mod_index_t xkb_keymap_mod_index_mod2
zwp_locked_pointer_v1 * locked_pointer
GWL_SeatStatePointerGesture_Pinch pointer_gesture_pinch
bool use_pointer_software_confine
struct GWL_TabletTool::@051355305123303047047153267134066107147366164170::@057241033203305174035162223152122261200035160150 wheel
struct GWL_TabletTool::@003266142043023341270324345040024161141156041211 wl
GHOST_TabletData data
struct GWL_TabletTool::@051355305123303047047153267134066107147366164170 frame_pending
wl_surface * surface_cursor
GWL_TabletTool_EventTypes frame_types[GWL_TabletTool_FrameTypes_NUM]
zxdg_decoration_manager_v1 * manager
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
enum eImbFileType ftype
unsigned int encoded_buffer_size
ImBufByteBuffer encoded_buffer
read
i
Definition text_draw.cc:230
wmTimer * timer
uint len
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(...)
void wayland_dynload_egl_exit(void)
#define wl_egl_window_create(...)
#define wl_egl_window_destroy(...)
void wayland_dynload_libdecor_exit(void)
#define libdecor_unref(...)
#define libdecor_new(...)
int xy[2]
Definition wm_draw.cc:174