Blender V5.0
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 <cursor-shape-v1-client-protocol.h>
63#include <fractional-scale-v1-client-protocol.h>
64#include <pointer-constraints-unstable-v1-client-protocol.h>
65#include <pointer-gestures-unstable-v1-client-protocol.h>
66#include <primary-selection-unstable-v1-client-protocol.h>
67#include <relative-pointer-unstable-v1-client-protocol.h>
68#include <tablet-v2-client-protocol.h>
69#include <viewporter-client-protocol.h>
70#include <xdg-activation-v1-client-protocol.h>
71#include <xdg-output-unstable-v1-client-protocol.h>
72#ifdef WITH_INPUT_IME
73# include <text-input-unstable-v3-client-protocol.h>
74#endif
75
76/* Decorations `xdg_decor`. */
77#include <xdg-decoration-unstable-v1-client-protocol.h>
78#include <xdg-shell-client-protocol.h>
79/* End `xdg_decor`. */
80
81#include <fcntl.h>
82#include <sys/mman.h>
83#include <unistd.h>
84
85#include <cstdlib> /* For `exit`. */
86#include <cstring>
87#include <mutex>
88
89#include <pthread.h> /* For setting the thread priority. */
90
91#ifdef HAVE_POLL
92# include <poll.h>
93#endif
94
95/* Logging, use `ghost.wl.*` prefix. */
96#include "CLG_log.h"
97
98#ifdef USE_EVENT_BACKGROUND_THREAD
99# include "GHOST_TimerTask.hh"
100#endif
101
102#ifdef WITH_GHOST_WAYLAND_LIBDECOR
103static bool use_libdecor = true;
104# ifdef WITH_GHOST_WAYLAND_DYNLOAD
105static bool has_libdecor = false;
106# else
107static bool has_libdecor = true;
108# endif
109#endif
110
112
113#include "IMB_imbuf.hh"
114#include "IMB_imbuf_types.hh"
115
116/* -------------------------------------------------------------------- */
119
121
122static void output_handle_done(void *data, wl_output *wl_output);
123
127
129 uint32_t name,
130 int *r_interface_slot);
131static void gwl_registry_entry_remove_all(GWL_Display *display);
132
136static const GWL_RegistryHandler *gwl_registry_handler_from_interface_slot(int interface_slot);
137
139 xkb_compose_state *compose_state,
140 xkb_state *state,
141 const xkb_keycode_t key,
142 char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)]);
143
144#ifdef USE_EVENT_BACKGROUND_THREAD
146
147static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex);
148
150constexpr size_t events_pending_default_size = 4096 / sizeof(void *);
151
152#endif /* USE_EVENT_BACKGROUND_THREAD */
153
156#define pushEvent DONT_USE
157
159
160/* -------------------------------------------------------------------- */
163
171#define USE_GNOME_CONFINE_HACK
176// #define USE_GNOME_CONFINE_HACK_ALWAYS_ON
177
178#ifdef USE_GNOME_CONFINE_HACK
179static bool use_gnome_confine_hack = false;
180#endif
181
187#define USE_KDE_TABLET_HIDDEN_CURSOR_HACK
188
190
191/* -------------------------------------------------------------------- */
196
210// #define USE_VERBOSE_OLD_IFACE_PRINT
211
212#ifdef USE_VERBOSE_OLD_IFACE_PRINT
213# define _VERBOSE_OLD_IFACE_PRINT(params_version, version_max) \
214 ((params_version > version_max) ? \
215 fprintf(stderr, \
216 "%s: version_max=%u, is smaller than run-time version=%u\n", \
217 __func__, \
218 version_max, \
219 params_version) : \
220 0)
221#else
222# define _VERBOSE_OLD_IFACE_PRINT(params_version, version_max) \
223 ((void)(params_version), (version_max))
224#endif
225
226#define GWL_IFACE_VERSION_CLAMP(params_version, version_min, version_max) \
227 ((void)_VERBOSE_OLD_IFACE_PRINT(params_version, version_max), \
228 std::clamp(params_version, version_min, version_max))
229
234#define USE_NON_LATIN_KB_WORKAROUND
235
236#define WL_NAME_UNSET uint32_t(-1)
237
242#define WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy) \
243 wl_fixed_to_int((win)->wl_fixed_to_window((xy)[0])), \
244 wl_fixed_to_int((win)->wl_fixed_to_window((xy)[1])),
245
247
248/* -------------------------------------------------------------------- */
255
259enum {
260 BTN_LEFT = 0x110,
261 BTN_RIGHT = 0x111,
262 BTN_MIDDLE = 0x112,
263 BTN_SIDE = 0x113,
264 BTN_EXTRA = 0x114,
265 BTN_FORWARD = 0x115,
266 BTN_BACK = 0x116
267};
268// #define BTN_TASK 0x117 /* UNUSED. */
269
270#define BTN_RANGE_MIN BTN_LEFT
271#define BTN_RANGE_MAX BTN_BACK
272
281enum {
283 BTN_STYLUS = 0x14b,
285 BTN_STYLUS2 = 0x14c,
287 BTN_STYLUS3 = 0x149,
288};
289
295enum {
303
304#ifdef USE_NON_LATIN_KB_WORKAROUND
305 KEY_1 = 2,
306 KEY_2 = 3,
307 KEY_3 = 4,
308 KEY_4 = 5,
309 KEY_5 = 6,
310 KEY_6 = 7,
311 KEY_7 = 8,
312 KEY_8 = 9,
313 KEY_9 = 10,
314 KEY_0 = 11,
315#endif
316};
317
318/* Only defined in XKB 1.8x and newer, it seems XKB doesn't provide a version define. */
319#ifndef XKB_VMOD_NAME_HYPER
320# define XKB_VMOD_NAME_HYPER "Hyper"
321#endif
322
324
325/* -------------------------------------------------------------------- */
330
331enum {
337};
338#define MOD_INDEX_NUM (MOD_INDEX_HYPER + 1)
339
347
349 /*MOD_INDEX_SHIFT*/
350 {
351 /*display_name*/ "Shift",
352 /*xkb_id*/ XKB_MOD_NAME_SHIFT,
353 /*key_l*/ GHOST_kKeyLeftShift,
354 /*key_r*/ GHOST_kKeyRightShift,
357 },
358 /*MOD_INDEX_ALT*/
359 {
360 /*display_name*/ "Alt",
361 /*xkb_id*/ XKB_MOD_NAME_ALT,
362 /*key_l*/ GHOST_kKeyLeftAlt,
363 /*key_r*/ GHOST_kKeyRightAlt,
366 },
367 /*MOD_INDEX_CTRL*/
368 {
369 /*display_name*/ "Control",
370 /*xkb_id*/ XKB_MOD_NAME_CTRL,
371 /*key_l*/ GHOST_kKeyLeftControl,
372 /*key_r*/ GHOST_kKeyRightControl,
375 },
376 /*MOD_INDEX_OS*/
377 {
378 /*display_name*/ "OS",
379 /*xkb_id*/ XKB_MOD_NAME_LOGO,
380 /*key_l*/ GHOST_kKeyLeftOS,
381 /*key_r*/ GHOST_kKeyRightOS,
382 /*mod_l*/ GHOST_kModifierKeyLeftOS,
384 },
385 /*MOD_INDEX_HYPER*/
386 {
387 /*display_name*/ "Hyper",
388 /*xkb_id*/ XKB_VMOD_NAME_HYPER,
389 /*key_l*/ GHOST_kKeyLeftHyper,
390 /*key_r*/ GHOST_kKeyRightHyper,
393 },
394};
395
397
398/* -------------------------------------------------------------------- */
401
404 const char *data = nullptr;
405 size_t data_size = 0;
406};
407
409{
410 free(const_cast<char *>(buffer->data));
411 buffer->data = nullptr;
412 buffer->data_size = 0;
413}
414
416{
417 free(const_cast<char *>(buffer->data));
418 buffer->data_size = strlen(str);
419 char *data = static_cast<char *>(malloc(buffer->data_size));
420 std::memcpy(data, str, buffer->data_size);
421 buffer->data = data;
422}
423
425
426/* -------------------------------------------------------------------- */
429
434#define EVDEV_OFFSET 8
435
445 wp_cursor_shape_device_v1_shape enum_id = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
446 wp_cursor_shape_device_v1 *device = nullptr;
447};
448
450
452 struct {
453 /* Everything below in this wl struct is used for custom cursor shapes. */
455 wl_buffer *buffer = nullptr;
456 wl_cursor_image image = {0};
457 } wl;
458
460
461 bool visible = false;
468 bool is_hardware = true;
470 bool is_custom = false;
471 void *custom_data = nullptr;
474
477
483 int theme_size = 0;
487 bool use_dark_theme = false;
488};
489
491
492/* -------------------------------------------------------------------- */
495
503
505
506 /* NOTE: Keep buttons last (simplifies switch statement). */
507
508 /* Left mouse button. */
511 /* Middle mouse button. */
514 /* Right mouse button. */
517 /* Mouse button number 4. */
520
521#define GWL_TabletTool_FrameTypes_NUM (int(GWL_TabletTool_EventTypes::Stylus3_Up) + 1)
522};
523
525 GHOST_kButtonMaskLeft, /* `Stylus0_*`. */
526 GHOST_kButtonMaskMiddle, /* `Stylus1_*`. */
527 GHOST_kButtonMaskRight, /* `Stylus2_*`. */
528 GHOST_kButtonMaskButton4, /* `Stylus3_*`. */
529};
530
537
539 struct {
545 } wl;
546
548
549 GWL_Seat *seat = nullptr;
550
552 uint32_t serial = 0;
553
555 bool proximity = false;
556
558
560 int32_t xy[2] = {0};
561 bool has_xy = false;
562
566 struct {
572
573 struct {
577};
578
581{
582 const int ty_mask = 1 << int(ty);
583 /* Motion callback may run multiple times. */
584 if (tablet_tool->frame_pending.frame_types_mask & ty_mask) {
585 return;
586 }
587 tablet_tool->frame_pending.frame_types_mask |= ty_mask;
588 int i = tablet_tool->frame_pending.frame_types_num++;
589 tablet_tool->frame_pending.frame_types[i] = ty;
590}
591
593{
594 tablet_tool->frame_pending.frame_types_num = 0;
595 tablet_tool->frame_pending.frame_types_mask = 0;
596}
597
599
600/* -------------------------------------------------------------------- */
603
608
610 struct {
611 wl_data_offer *id = nullptr;
612 } wl;
613
614 std::unordered_set<std::string> types;
615
616 struct {
623 enum wl_data_device_manager_dnd_action source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
624 enum wl_data_device_manager_dnd_action action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
626 wl_fixed_t xy[2] = {0, 0};
628};
629
631
632/* -------------------------------------------------------------------- */
635
637
639 struct {
640 wl_data_source *source = nullptr;
641 } wl;
642
644};
645
647
648/* -------------------------------------------------------------------- */
651
661 GWL_Seat *seat = nullptr;
662
663 xkb_keycode_t key_code;
664
667
672 struct {
675};
676
679 bool use_lock = false;
680 bool use_confine = false;
681};
682
687
689 struct {
695 } wl;
696
710 wl_fixed_t xy[2] = {0, 0};
711
713 std::unordered_set<const GWL_Output *> outputs;
714
716
722 uint32_t serial = 0;
723
725};
726
729
737
738 /* NOTE: Keep buttons last (simplifies switch statement). */
739
740 /* #BTN_LEFT mouse button. */
743 /* #BTN_RIGHT mouse button. */
746 /* #BTN_MIDDLE mouse button. */
749 /* #BTN_SIDE mouse button. */
752 /* #BTN_EXTRA mouse button. */
755 /* #BTN_FORWARD mouse button. */
758 /* #BTN_BACK mouse button. */
761
762#define GWL_SeatStatePointer_EventTypes_NUM (int(GWL_Pointer_EventTypes::Button6_Up) + 1)
763};
764
766 GHOST_kButtonMaskLeft, /* `Button0_*` / #BTN_LEFT. */
767 GHOST_kButtonMaskRight, /* `Button1_*` / #BTN_RIGHT. */
768 GHOST_kButtonMaskMiddle, /* `Button2_*` / #BTN_MIDDLE. */
769 GHOST_kButtonMaskButton4, /* `Button3_*` / #BTN_SIDE. */
770 GHOST_kButtonMaskButton5, /* `Button4_*` / #BTN_EXTRA. */
771 GHOST_kButtonMaskButton6, /* `Button5_*` / #BTN_FORWARD. */
772 GHOST_kButtonMaskButton7, /* `Button6_*` / #BTN_BACK. */
773};
774
777 "Buttons missing");
778
792
794 const GWL_Pointer_EventTypes ty,
795 const uint64_t event_ms)
796{
797 /* It's a quirk of WAYLAND that most scroll events don't have a time-stamp.
798 * Scroll events use their own time-stamp (see #GWL_SeatStatePointerScroll::event_ms usage).
799 * Ensure the API is used as intended. */
801 GHOST_ASSERT(event_ms == 0, "Scroll events must not have a time-stamp");
802 }
803 else {
804 GHOST_ASSERT(event_ms != 0, "Non-scroll events must have a time-stamp");
805 }
806
807 const int ty_mask = 1 << int(ty);
808 /* Motion callback may run multiple times. */
809 if (pointer_events->frame_pending.frame_types_mask & ty_mask) {
810 return;
811 }
812 pointer_events->frame_pending.frame_types_mask |= ty_mask;
813 int i = pointer_events->frame_pending.frame_types_num++;
814 pointer_events->frame_pending.frame_types[i] = ty;
815 pointer_events->frame_pending.frame_event_ms[i] = event_ms;
816}
817
819{
820 pointer_events->frame_pending.frame_types_num = 0;
821 pointer_events->frame_pending.frame_types_mask = 0;
822}
823
830
831static constexpr int smooth_as_discrete_steps = 2400;
832
839 wl_fixed_t smooth_xy[2] = {0, 0};
841 int32_t discrete_xy[2] = {0, 0};
847 bool inverted_xy[2] = {false, false};
849 enum wl_pointer_axis_source axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
850
855 bool has_event_ms = false;
858
860};
861
873 wl_fixed_t value = 0;
874 wl_fixed_t factor = 1;
875};
876
878 const wl_fixed_t add)
879{
880 const int result_prev = wl_fixed_to_int(sf->value * sf->factor);
881 sf->value += add;
882 const int result_curr = wl_fixed_to_int(sf->value * sf->factor);
883 return result_curr - result_prev;
884}
885
894
899
901 struct {
907 } wl;
908
910 uint32_t serial = 0;
911};
912
921
922#ifdef WITH_GHOST_WAYLAND_LIBDECOR
923struct GWL_LibDecor_System {
924 libdecor *context = nullptr;
925};
926
927static void gwl_libdecor_system_destroy(GWL_LibDecor_System *decor)
928{
929 if (decor->context) {
930 libdecor_unref(decor->context);
931 decor->context = nullptr;
932 }
933 delete decor;
934}
935#endif
936
938 xdg_wm_base *shell = nullptr;
940
941 zxdg_decoration_manager_v1 *manager = nullptr;
943};
944
946{
947 if (decor->manager) {
948 gwl_registry_entry_remove_by_name(display, decor->manager_name, nullptr);
949 GHOST_ASSERT(decor->manager == nullptr, "Internal registry error");
950 }
951 if (decor->shell) {
952 gwl_registry_entry_remove_by_name(display, decor->shell_name, nullptr);
953 GHOST_ASSERT(decor->shell == nullptr, "Internal registry error");
954 }
955 delete decor;
956}
957
959
961 struct {
962 zwp_primary_selection_offer_v1 *id = nullptr;
963 } wp;
964
965 std::unordered_set<std::string> types;
966};
967
969
971 struct {
972 zwp_primary_selection_source_v1 *source = nullptr;
973 } wp;
974
976};
977
987
989{
990 if (primary->data_offer == nullptr) {
991 return;
992 }
993 zwp_primary_selection_offer_v1_destroy(primary->data_offer->wp.id);
994 delete primary->data_offer;
995 primary->data_offer = nullptr;
996}
997
999{
1000 GWL_PrimarySelection_DataSource *data_source = primary->data_source;
1001 if (data_source == nullptr) {
1002 return;
1003 }
1005 if (data_source->wp.source) {
1006 zwp_primary_selection_source_v1_destroy(data_source->wp.source);
1007 }
1008 delete primary->data_source;
1009 primary->data_source = nullptr;
1010}
1011
1012#ifdef WITH_INPUT_IME
1013struct GWL_SeatIME {
1021 wl_surface *surface_window = nullptr;
1022 GHOST_TEventImeData event_ime_data = {
1024 /*result*/ "",
1027 /*composite*/ "",
1028 /*cursor_position*/ -1,
1029 /*target_start*/ -1,
1030 /*target_end*/ -1,
1031 };
1033 bool is_enabled = false;
1038 bool has_preedit = false;
1039
1041 bool result_is_null = false;
1043 bool composite_is_null = false;
1044
1046 bool has_preedit_string_callback = false;
1048 bool has_commit_string_callback = false;
1049
1051 struct {
1052 int x = -1;
1053 int y = -1;
1054 int w = -1;
1055 int h = -1;
1056 } rect;
1057};
1058#endif /* WITH_INPUT_IME */
1059
1060struct GWL_Seat {
1061
1063 struct {
1064 wl_seat *seat = nullptr;
1065 wl_pointer *pointer = nullptr;
1066 wl_touch *touch = nullptr;
1067 wl_keyboard *keyboard = nullptr;
1068
1070 wl_data_device *data_device = nullptr;
1072
1074 struct {
1075 zwp_tablet_seat_v2 *tablet_seat = nullptr;
1076
1077#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
1078 zwp_pointer_gesture_hold_v1 *pointer_gesture_hold = nullptr;
1079#endif
1080#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
1081 zwp_pointer_gesture_pinch_v1 *pointer_gesture_pinch = nullptr;
1082#endif
1083#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
1084 zwp_pointer_gesture_swipe_v1 *pointer_gesture_swipe = nullptr;
1085#endif
1086
1087 zwp_relative_pointer_v1 *relative_pointer = nullptr;
1088 zwp_locked_pointer_v1 *locked_pointer = nullptr;
1089 zwp_confined_pointer_v1 *confined_pointer = nullptr;
1090
1091 zwp_primary_selection_device_v1 *primary_selection_device = nullptr;
1092
1094 std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools;
1095
1096#ifdef WITH_INPUT_IME
1097 zwp_text_input_v3 *text_input = nullptr;
1098#endif
1100
1102 struct {
1103 xkb_context *context = nullptr;
1104
1106 xkb_compose_table *compose_table = nullptr;
1107
1109 xkb_compose_state *compose_state = nullptr;
1110
1111 xkb_state *state = nullptr;
1115 xkb_state *state_empty = nullptr;
1116
1121 xkb_state *state_empty_with_shift = nullptr;
1126 xkb_state *state_empty_with_numlock = nullptr;
1127
1131 xkb_layout_index_t layout_active = 0;
1133
1134#ifdef WITH_INPUT_IME
1135 GWL_SeatIME ime;
1136#endif
1137
1139
1140 std::string name;
1141
1144
1150
1163
1165
1173 struct {
1174 bool is_touching = false;
1175 uint32_t down_id = 0;
1176 bool down_pending = false;
1178 bool up_pending = false;
1180 bool motion_pending = false;
1183
1184#ifdef USE_GNOME_CONFINE_HACK
1186#endif
1188 wl_fixed_t grab_lock_xy[2] = {0, 0};
1189
1191
1192#ifdef USE_NON_LATIN_KB_WORKAROUND
1194#endif
1195
1198
1204 xkb_mod_index_t xkb_keymap_mod_index[MOD_INDEX_NUM] = {XKB_MOD_INVALID};
1205
1206 /* Cache result for other modifiers which aren't stored in `xkb_keymap_mod_index`
1207 * since their depressed state isn't tracked. */
1208
1210 xkb_mod_index_t xkb_keymap_mod_index_mod2 = XKB_MOD_INVALID;
1212 xkb_mod_index_t xkb_keymap_mod_index_numlock = XKB_MOD_INVALID;
1213
1214 struct {
1227
1231
1235
1241 std::optional<GHOST_TSuccess> data_offer_copy_paste_has_image = std::nullopt;
1242
1245
1247
1255};
1256
1258{
1259 if (seat->pointer.serial == seat->cursor_source_serial) {
1260 return &seat->pointer;
1261 }
1262 if (seat->tablet.serial == seat->cursor_source_serial) {
1263 return &seat->tablet;
1264 }
1265 if (seat->touch.serial == seat->cursor_source_serial) {
1266 return &seat->touch;
1267 }
1268 return nullptr;
1269}
1270
1272 GWL_Seat *seat, const wl_surface *wl_surface)
1273{
1275 return &seat->pointer;
1276 }
1278 return &seat->tablet;
1279 }
1280 GHOST_ASSERT(0, "Surface found without pointer/tablet tag");
1281 return nullptr;
1282}
1283
1291{
1292 const xkb_layout_index_t group = seat->xkb.layout_active;
1293 const xkb_mod_index_t mod_shift = seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT];
1294 const xkb_mod_index_t mod_mod2 = seat->xkb_keymap_mod_index_mod2;
1295 const xkb_mod_index_t mod_numlock = seat->xkb_keymap_mod_index_numlock;
1296
1297 /* Handle `state_empty`. */
1298 xkb_state_update_mask(seat->xkb.state_empty, 0, 0, 0, 0, 0, group);
1299
1300 /* Handle `state_empty_with_shift`. */
1301 GHOST_ASSERT((mod_shift == XKB_MOD_INVALID) == (seat->xkb.state_empty_with_shift == nullptr),
1302 "Invalid state for SHIFT");
1303 if (seat->xkb.state_empty_with_shift) {
1304 xkb_state_update_mask(seat->xkb.state_empty_with_shift, 1 << mod_shift, 0, 0, 0, 0, group);
1305 }
1306
1307 /* Handle `state_empty_with_shift`. */
1308 GHOST_ASSERT((mod_mod2 == XKB_MOD_INVALID || mod_numlock == XKB_MOD_INVALID) ==
1309 (seat->xkb.state_empty_with_numlock == nullptr),
1310 "Invalid state for NUMLOCK");
1311 if (seat->xkb.state_empty_with_numlock) {
1312 xkb_state_update_mask(
1313 seat->xkb.state_empty_with_numlock, 1 << mod_mod2, 0, 1 << mod_numlock, 0, 0, group);
1314 }
1315}
1316
1319{
1320 GWL_KeyRepeatPlayload *payload = static_cast<GWL_KeyRepeatPlayload *>(task->getUserData());
1321
1322 GWL_Seat *seat = payload->seat;
1323 wl_surface *wl_surface_focus = seat->keyboard.wl.surface_window;
1324 if (UNLIKELY(wl_surface_focus == nullptr)) {
1325 return;
1326 }
1327
1328 GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
1329 GHOST_SystemWayland *system = seat->system;
1330 const uint64_t event_ms = payload->time_ms_init + time_ms;
1331 /* Calculate this value every time in case modifier keys are pressed. */
1332
1333 char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
1334 if (seat->xkb.compose_state &&
1336 seat->xkb.compose_state, seat->xkb.state, payload->key_code, utf8_buf))
1337 {
1338 /* `utf8_buf` has been filled by a compose action. */
1339 }
1340 else {
1341 xkb_state_key_get_utf8(seat->xkb.state, payload->key_code, utf8_buf, sizeof(utf8_buf));
1342 }
1343
1345 event_ms, GHOST_kEventKeyDown, win, payload->key_data.gkey, true, utf8_buf));
1346}
1347
1356 GHOST_TimerProcPtr key_repeat_fn,
1357 GHOST_TUserDataPtr payload,
1358 const bool use_delay)
1359{
1360 /* Caller is expected to ensure this. */
1361 GHOST_ASSERT(seat->key_repeat.rate > 0, "invalid rate");
1362 GHOST_SystemWayland *system = seat->system;
1363 const uint64_t time_now = system->getMilliSeconds();
1364 const uint64_t time_step = 1000 / seat->key_repeat.rate;
1365 const uint64_t time_start = use_delay ? seat->key_repeat.delay : time_step;
1366
1367 static_cast<GWL_KeyRepeatPlayload *>(payload)->time_ms_init = time_now;
1368
1369#ifdef USE_EVENT_BACKGROUND_THREAD
1371 time_now + time_start, time_step, key_repeat_fn, payload);
1372 seat->key_repeat.timer = timer;
1374#else
1375 seat->key_repeat.timer = system->installTimer(time_start, time_step, key_repeat_fn, payload);
1376#endif
1377}
1378
1383{
1384 GHOST_SystemWayland *system = seat->system;
1385#ifdef USE_EVENT_BACKGROUND_THREAD
1387 static_cast<GHOST_TimerTask *>(seat->key_repeat.timer));
1388#else
1389 system->removeTimer(seat->key_repeat.timer);
1390#endif
1391 seat->key_repeat.timer = nullptr;
1392}
1393
1394#ifdef WITH_INPUT_IME
1395
1396static void gwl_seat_ime_full_reset(GWL_Seat *seat)
1397{
1398 const GWL_SeatIME ime_default{};
1399 /* Preserve the following members since they represent the state of the connection to Wayland.
1400 * or which callbacks have run (which shouldn't be reset). */
1401 wl_surface *surface_window = seat->ime.surface_window;
1402 const bool is_enabled = seat->ime.is_enabled;
1403 const bool has_preedit_string_callback = seat->ime.has_preedit_string_callback;
1404 const bool has_commit_string_callback = seat->ime.has_commit_string_callback;
1405
1406 seat->ime = ime_default;
1407
1408 seat->ime.surface_window = surface_window;
1409 seat->ime.is_enabled = is_enabled;
1410 seat->ime.has_preedit_string_callback = has_preedit_string_callback;
1411 seat->ime.has_commit_string_callback = has_commit_string_callback;
1412}
1413
1414static void gwl_seat_ime_result_reset(GWL_Seat *seat)
1415{
1416 GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
1417 event_ime_data.result.clear();
1418 seat->ime.result_is_null = false;
1419}
1420
1421static void gwl_seat_ime_preedit_reset(GWL_Seat *seat)
1422{
1423
1424 GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data;
1425 event_ime_data.composite.clear();
1426 seat->ime.composite_is_null = false;
1427
1428 event_ime_data.cursor_position = -1;
1429 event_ime_data.target_start = -1;
1430 event_ime_data.target_end = -1;
1431}
1432
1433static void gwl_seat_ime_rect_reset(GWL_Seat *seat)
1434{
1435 seat->ime.rect.x = -1;
1436 seat->ime.rect.y = -1;
1437 seat->ime.rect.w = -1;
1438 seat->ime.rect.h = -1;
1439}
1440
1441#endif /* WITH_INPUT_IME */
1442
1444
1445/* -------------------------------------------------------------------- */
1448
1449struct GWL_RegistryEntry;
1450
1462 bool exact_match = false;
1463 uint32_t last = 0;
1465};
1466
1468
1470 struct {
1471 wl_registry *registry = nullptr;
1473 wl_compositor *compositor = nullptr;
1474 wl_shm *shm = nullptr;
1475
1476 /* Managers. */
1477 wl_data_device_manager *data_device_manager = nullptr;
1479
1481 struct {
1482 /* Managers. */
1483 zwp_tablet_manager_v2 *tablet_manager = nullptr;
1484 zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr;
1485 zwp_primary_selection_device_manager_v1 *primary_selection_device_manager = nullptr;
1486 wp_fractional_scale_manager_v1 *fractional_scale_manager = nullptr;
1487 wp_viewporter *viewporter = nullptr;
1488 zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
1489 zwp_pointer_gestures_v1 *pointer_gestures = nullptr;
1490#ifdef WITH_INPUT_IME
1491 zwp_text_input_manager_v3 *text_input_manager = nullptr;
1492#endif
1493 wp_cursor_shape_manager_v1 *cursor_shape_manager = nullptr;
1495
1497 struct {
1498 /* Managers. */
1499 zxdg_output_manager_v1 *output_manager = nullptr;
1500 xdg_activation_v1 *activation_manager = nullptr;
1502
1504
1506
1512
1515
1516#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1517 GWL_LibDecor_System *libdecor = nullptr;
1518#endif
1520
1526 std::vector<GWL_Output *> outputs;
1527 std::vector<GWL_Seat *> seats;
1540
1549 bool background = false;
1550
1554 bool use_window_frame = false;
1555
1556 /* Threaded event handling. */
1557#ifdef USE_EVENT_BACKGROUND_THREAD
1564 pthread_t events_pthread = 0;
1571
1576 std::vector<const GHOST_IEvent *> events_pending;
1579
1589
1590#endif /* USE_EVENT_BACKGROUND_THREAD */
1591};
1592
1600{
1601#ifdef USE_EVENT_BACKGROUND_THREAD
1602 if (!display->background) {
1603 if (display->events_pthread) {
1605 display->events_pthread_is_active = false;
1606 }
1607 }
1608#endif
1609
1610 /* For typical WAYLAND use this will always be set.
1611 * However when WAYLAND isn't running, this will early-exit and be null. */
1612 if (display->wl.registry) {
1613 wl_registry_destroy(display->wl.registry);
1614 display->wl.registry = nullptr;
1615 }
1616
1617 /* Unregister items in reverse order. */
1619
1620#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1621 if (use_libdecor) {
1622 if (display->libdecor) {
1623 gwl_libdecor_system_destroy(display->libdecor);
1624 display->libdecor = nullptr;
1625 }
1626 }
1627 else
1628#endif
1629 {
1630 if (display->xdg_decor) {
1631 gwl_xdg_decor_system_destroy(display, display->xdg_decor);
1632 display->xdg_decor = nullptr;
1633 }
1634 }
1635
1636#ifdef WITH_OPENGL_BACKEND
1637 if (display->wl.display) {
1638 if (eglGetDisplay) {
1639 ::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl.display)));
1640 }
1641 }
1642#endif
1643
1644#ifdef USE_EVENT_BACKGROUND_THREAD
1645 if (!display->background) {
1646 if (display->events_pthread) {
1648 display->system->server_mutex->unlock();
1649 }
1650 }
1651
1652 /* Important to remove after the seats which may have key repeat timers active. */
1653 if (display->ghost_timer_manager) {
1654 delete display->ghost_timer_manager;
1655 display->ghost_timer_manager = nullptr;
1656 }
1657 /* Pending events may be left unhandled. */
1658 for (const GHOST_IEvent *event : display->events_pending) {
1659 delete event;
1660 }
1661
1662#endif /* USE_EVENT_BACKGROUND_THREAD */
1663
1664 if (display->wl.display) {
1666 }
1667
1668 delete display;
1669}
1670
1671static int gwl_display_seat_index(GWL_Display *display, const GWL_Seat *seat)
1672{
1673 std::vector<GWL_Seat *>::iterator iter = std::find(
1674 display->seats.begin(), display->seats.end(), seat);
1675 const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) :
1676 -1;
1677 GHOST_ASSERT(index != -1, "invalid internal state");
1678 return index;
1679}
1680
1689{
1690 if (UNLIKELY(display->seats.empty())) {
1691 return nullptr;
1692 }
1693 return display->seats[display->seats_active_index];
1694}
1695
1696static bool gwl_display_seat_active_set(GWL_Display *display, const GWL_Seat *seat)
1697{
1698 if (UNLIKELY(display->seats.empty())) {
1699 return false;
1700 }
1701 const int index = gwl_display_seat_index(display, seat);
1702 if (index == display->seats_active_index) {
1703 return false;
1704 }
1705 display->seats_active_index = index;
1706 return true;
1707}
1708
1710
1711/* -------------------------------------------------------------------- */
1714
1716 uint32_t name = 0;
1719 uint32_t version = 0;
1720};
1721
1730using GWL_RegistryHandler_AddFn = void (*)(GWL_Display *display,
1732
1734 uint32_t name = 0;
1737 uint32_t version = 0;
1738
1740 void *user_data = nullptr;
1741};
1742
1751
1762using GWL_RegistryEntry_RemoveFn = void (*)(GWL_Display *display, void *user_data, bool on_exit);
1763
1775
1777
1778/* -------------------------------------------------------------------- */
1781
1793 void *user_data = nullptr;
1801 uint32_t version;
1807};
1808
1811 void *user_data)
1812{
1814
1815 reg->interface_slot = params.interface_slot;
1816 reg->name = params.name;
1817 reg->version = params.version;
1818 reg->user_data = user_data;
1819
1820 reg->next = display->registry_entry;
1821 display->registry_entry = reg;
1822}
1823
1825 uint32_t name,
1826 int *r_interface_slot)
1827{
1828 GWL_RegistryEntry *reg = display->registry_entry;
1829 GWL_RegistryEntry **reg_link_p = &display->registry_entry;
1830 bool found = false;
1831
1832 if (r_interface_slot) {
1833 *r_interface_slot = -1;
1834 }
1835
1836 while (reg) {
1837 if (reg->name == name) {
1838 GWL_RegistryEntry *reg_next = reg->next;
1840 reg->interface_slot);
1841 handler->remove_fn(display, reg->user_data, false);
1842 if (r_interface_slot) {
1843 *r_interface_slot = reg->interface_slot;
1844 }
1845 delete reg;
1846 *reg_link_p = reg_next;
1847 found = true;
1848 break;
1849 }
1850 reg_link_p = &reg->next;
1851 reg = reg->next;
1852 }
1853 return found;
1854}
1855
1857 const int interface_slot,
1858 const bool on_exit)
1859{
1860 GWL_RegistryEntry *reg = display->registry_entry;
1861 GWL_RegistryEntry **reg_link_p = &display->registry_entry;
1862 bool found = false;
1863
1864 while (reg) {
1865 if (reg->interface_slot == interface_slot) {
1866 GWL_RegistryEntry *reg_next = reg->next;
1868 interface_slot);
1869 handler->remove_fn(display, reg->user_data, on_exit);
1870 delete reg;
1871 *reg_link_p = reg_next;
1872 reg = reg_next;
1873 found = true;
1874 continue;
1875 }
1876 reg_link_p = &reg->next;
1877 reg = reg->next;
1878 }
1879 return found;
1880}
1881
1886{
1887 const bool on_exit = true;
1888
1889 /* NOTE(@ideasman42): Free by slot instead of simply looping over
1890 * `display->registry_entry` so the order of freeing is always predictable.
1891 * Otherwise global objects would be feed in the order they are registered.
1892 * While this works in my tests, it could cause difficult to reproduce bugs
1893 * where lesser used compositors or changes to existing compositors could
1894 * crash on exit based on the order of freeing objects is out of our control.
1895 *
1896 * To give a concrete example of how this could fail, it's possible removing
1897 * a tablet interface could reference the pointer interface, or the output interface.
1898 * Even though references between interfaces shouldn't be necessary in most cases
1899 * when `on_exit` is enabled. */
1900 int interface_slot = gwl_registry_handler_interface_slot_max();
1901 while (interface_slot--) {
1902 gwl_registry_entry_remove_by_interface_slot(display, interface_slot, on_exit);
1903 }
1904
1905 GHOST_ASSERT(display->registry_entry == nullptr, "Failed to remove all entries!");
1906 display->registry_entry = nullptr;
1907}
1908
1925static void gwl_registry_entry_update_all(GWL_Display *display, const int interface_slot_exclude)
1926{
1927 GHOST_ASSERT(interface_slot_exclude == -1 || (uint(interface_slot_exclude) <
1929 "Invalid exclude slot");
1930
1931 for (GWL_RegistryEntry *reg = display->registry_entry; reg; reg = reg->next) {
1932 if (reg->interface_slot == interface_slot_exclude) {
1933 continue;
1934 }
1936 reg->interface_slot);
1937 if (handler->update_fn == nullptr) {
1938 continue;
1939 }
1940
1942 params.name = reg->name;
1943 params.interface_slot = reg->interface_slot;
1944 params.version = reg->version;
1945 params.user_data = reg->user_data;
1946
1947 handler->update_fn(display, params);
1948 }
1949}
1950
1952
1953/* -------------------------------------------------------------------- */
1956
1957static uint32_t round_up_uint(const uint32_t x, const uint32_t multiple)
1958{
1959 return ((x + multiple - 1) / multiple) * multiple;
1960}
1961
1962static uint32_t rgba_straight_to_premul(uint32_t rgba_uint)
1963{
1964 uint8_t *rgba = reinterpret_cast<uint8_t *>(&rgba_uint);
1965 const uint32_t alpha = uint32_t(rgba[3]);
1966 rgba[0] = uint8_t(((alpha * rgba[0]) + (0xff / 2)) / 0xff);
1967 rgba[1] = uint8_t(((alpha * rgba[1]) + (0xff / 2)) / 0xff);
1968 rgba[2] = uint8_t(((alpha * rgba[2]) + (0xff / 2)) / 0xff);
1969 return rgba_uint;
1970}
1971
1972static uint32_t rgba_straight_to_premul_inverted(uint32_t rgba_uint)
1973{
1974 uint8_t *rgba = reinterpret_cast<uint8_t *>(&rgba_uint);
1975 const uint32_t alpha = uint32_t(rgba[3]);
1976 rgba[0] = uint8_t(((alpha * (0xff - rgba[0])) + (0xff / 2)) / 0xff);
1977 rgba[1] = uint8_t(((alpha * (0xff - rgba[1])) + (0xff / 2)) / 0xff);
1978 rgba[2] = uint8_t(((alpha * (0xff - rgba[2])) + (0xff / 2)) / 0xff);
1979 return rgba_uint;
1980}
1981
1982#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1983static const char *strchr_or_end(const char *str, const char ch)
1984{
1985 const char *p = str;
1986 while (!ELEM(*p, ch, '\0')) {
1987 p++;
1988 }
1989 return p;
1990}
1991
1992static bool string_elem_split_by_delim(const char *haystack, const char delim, const char *needle)
1993{
1994 /* Local copy of #BLI_string_elem_split_by_delim (would be a bad level call). */
1995
1996 /* May be zero, returns true when an empty span exists. */
1997 const size_t needle_len = strlen(needle);
1998 const char *p = haystack, *p_next;
1999 while (true) {
2000 p_next = strchr_or_end(p, delim);
2001 if ((size_t(p_next - p) == needle_len) && (memcmp(p, needle, needle_len) == 0)) {
2002 return true;
2003 }
2004 if (*p_next == '\0') {
2005 break;
2006 }
2007 p = p_next + 1;
2008 }
2009 return false;
2010}
2011#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
2012
2014{
2015 return a > b ? a - b : b - a;
2016}
2017
2021static uint64_t ghost_wl_ms_from_utime_pair(uint32_t utime_hi, uint32_t utime_lo)
2022{
2023 return ((uint64_t(utime_hi) << 32) | uint64_t(utime_lo)) / 1000;
2024}
2025
2030{
2031 const char *locale = getenv("LC_ALL");
2032 if (!locale || !*locale) {
2033 locale = getenv("LC_CTYPE");
2034 if (!locale || !*locale) {
2035 locale = getenv("LANG");
2036 if (!locale || !*locale) {
2037 locale = "C";
2038 }
2039 }
2040 }
2041 return locale;
2042}
2043
2044static void ghost_wl_display_report_error_from_code(wl_display *display, const int ecode)
2045{
2046 GHOST_ASSERT(ecode, "Error not set!");
2047 if (ELEM(ecode, EPIPE, ECONNRESET)) {
2048 fprintf(stderr, "The Wayland connection broke. Did the Wayland compositor die?\n");
2049 return;
2050 }
2051
2052 if (ecode == EPROTO) {
2053 const wl_interface *interface = nullptr;
2054 const int ecode_proto = wl_display_get_protocol_error(display, &interface, nullptr);
2055 fprintf(stderr,
2056 "The Wayland connection experienced a protocol error %d in interface: %s\n",
2057 ecode_proto,
2058 interface ? interface->name : "<nil>");
2059 const char *env_debug = "WAYLAND_DEBUG";
2060 if (getenv(env_debug) == nullptr) {
2061 fprintf(stderr, "Run with the environment variable \"%s=1\" for details.\n", env_debug);
2062 }
2063 return;
2064 }
2065
2066 fprintf(stderr, "The Wayland connection experienced a fatal error: %s\n", strerror(ecode));
2067}
2068
2070{
2071 int ecode = wl_display_get_error(display);
2072 GHOST_ASSERT(ecode, "Error not set!");
2074
2075 /* NOTE(@ideasman42): The application is running,
2076 * however an error closes all windows and most importantly:
2077 * shuts down the GPU context (loosing all GPU state - shaders, bind codes etc),
2078 * so recovering from this effectively involves restarting.
2079 *
2080 * Keeping the GPU state alive doesn't seem to be supported as windows EGL context must use the
2081 * same connection as the used for all other WAYLAND interactions (see #wl_display_connect).
2082 * So in practice re-connecting to the display server isn't an option.
2083 *
2084 * Exit since leaving the process open will simply flood the output and do nothing.
2085 * Although as the process is in a valid state, auto-save for example is possible, see: #100855.
2086 */
2087 ::exit(-1);
2088}
2089
2091{
2092 const int ecode = wl_display_get_error(display);
2093 if (ecode == 0) {
2094 return false;
2095 }
2097 return true;
2098}
2099
2100#ifdef __GNUC__
2101static void ghost_wayland_log_handler(const char *msg, va_list arg)
2102 __attribute__((format(printf, 1, 0)));
2103static void ghost_wayland_log_handler_background(const char *msg, va_list arg)
2104 __attribute__((format(printf, 1, 0)));
2105#endif
2106
2113static void ghost_wayland_log_handler(const char *msg, va_list arg)
2114{
2115 fprintf(stderr, "GHOST/Wayland: ");
2116 vfprintf(stderr, msg, arg); /* Includes newline. */
2117
2119 if (backtrace_fn) {
2120 backtrace_fn(stderr); /* Includes newline. */
2121 }
2122}
2123
2125static void ghost_wayland_log_handler_background(const char *msg, va_list arg)
2126{
2127 /* This is fine in background mode, we will try to fall back to headless GPU context.
2128 * Happens when render farm process runs without user login session. */
2129 if (strstr(msg, "error: XDG_RUNTIME_DIR not set in the environment") ||
2130 strstr(msg, "error: XDG_RUNTIME_DIR is invalid or not set in the environment"))
2131 {
2132 return;
2133 }
2134 ghost_wayland_log_handler(msg, arg);
2135}
2136
2137#if defined(WITH_GHOST_X11) && defined(WITH_GHOST_WAYLAND_LIBDECOR)
2143static bool ghost_wayland_is_x11_available()
2144{
2145 const char *x11_display = getenv("DISPLAY");
2146 if (x11_display && x11_display[0]) {
2147 return true;
2148 }
2149 return false;
2150}
2151#endif /* WITH_GHOST_X11 && WITH_GHOST_WAYLAND_LIBDECOR */
2152
2153static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym)
2154{
2155
2156 GHOST_TKey gkey;
2157 if (sym >= XKB_KEY_0 && sym <= XKB_KEY_9) {
2158 gkey = GHOST_TKey(sym);
2159 }
2160 else if (sym >= XKB_KEY_KP_0 && sym <= XKB_KEY_KP_9) {
2161 gkey = GHOST_TKey(GHOST_kKeyNumpad0 + sym - XKB_KEY_KP_0);
2162 }
2163 else if (sym >= XKB_KEY_A && sym <= XKB_KEY_Z) {
2164 gkey = GHOST_TKey(sym);
2165 }
2166 else if (sym >= XKB_KEY_a && sym <= XKB_KEY_z) {
2167 gkey = GHOST_TKey(sym - XKB_KEY_a + XKB_KEY_A);
2168 }
2169 else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F24) {
2170 gkey = GHOST_TKey(GHOST_kKeyF1 + sym - XKB_KEY_F1);
2171 }
2172 else {
2173
2174#define GXMAP(k, x, y) \
2175 case x: \
2176 k = y; \
2177 break
2178
2179 switch (sym) {
2180 GXMAP(gkey, XKB_KEY_BackSpace, GHOST_kKeyBackSpace);
2181 GXMAP(gkey, XKB_KEY_Tab, GHOST_kKeyTab);
2182 GXMAP(gkey, XKB_KEY_Linefeed, GHOST_kKeyLinefeed);
2183 GXMAP(gkey, XKB_KEY_Clear, GHOST_kKeyClear);
2184 GXMAP(gkey, XKB_KEY_Return, GHOST_kKeyEnter);
2185
2186 GXMAP(gkey, XKB_KEY_Escape, GHOST_kKeyEsc);
2187 GXMAP(gkey, XKB_KEY_space, GHOST_kKeySpace);
2188 GXMAP(gkey, XKB_KEY_apostrophe, GHOST_kKeyQuote);
2189 GXMAP(gkey, XKB_KEY_comma, GHOST_kKeyComma);
2190 GXMAP(gkey, XKB_KEY_minus, GHOST_kKeyMinus);
2191 GXMAP(gkey, XKB_KEY_plus, GHOST_kKeyPlus);
2192 GXMAP(gkey, XKB_KEY_period, GHOST_kKeyPeriod);
2193 GXMAP(gkey, XKB_KEY_slash, GHOST_kKeySlash);
2194
2195 GXMAP(gkey, XKB_KEY_semicolon, GHOST_kKeySemicolon);
2196 GXMAP(gkey, XKB_KEY_equal, GHOST_kKeyEqual);
2197
2198 GXMAP(gkey, XKB_KEY_bracketleft, GHOST_kKeyLeftBracket);
2199 GXMAP(gkey, XKB_KEY_bracketright, GHOST_kKeyRightBracket);
2200 GXMAP(gkey, XKB_KEY_backslash, GHOST_kKeyBackslash);
2201 GXMAP(gkey, XKB_KEY_grave, GHOST_kKeyAccentGrave);
2202
2203 GXMAP(gkey, XKB_KEY_Shift_L, GHOST_kKeyLeftShift);
2204 GXMAP(gkey, XKB_KEY_Shift_R, GHOST_kKeyRightShift);
2205 GXMAP(gkey, XKB_KEY_Control_L, GHOST_kKeyLeftControl);
2206 GXMAP(gkey, XKB_KEY_Control_R, GHOST_kKeyRightControl);
2207 GXMAP(gkey, XKB_KEY_Alt_L, GHOST_kKeyLeftAlt);
2208 GXMAP(gkey, XKB_KEY_Alt_R, GHOST_kKeyRightAlt);
2209 GXMAP(gkey, XKB_KEY_Super_L, GHOST_kKeyLeftOS);
2210 GXMAP(gkey, XKB_KEY_Super_R, GHOST_kKeyRightOS);
2211 GXMAP(gkey, XKB_KEY_Hyper_L, GHOST_kKeyLeftHyper);
2212 GXMAP(gkey, XKB_KEY_Hyper_R, GHOST_kKeyRightHyper);
2213 GXMAP(gkey, XKB_KEY_Menu, GHOST_kKeyApp);
2214
2215 GXMAP(gkey, XKB_KEY_Caps_Lock, GHOST_kKeyCapsLock);
2216 GXMAP(gkey, XKB_KEY_Num_Lock, GHOST_kKeyNumLock);
2217 GXMAP(gkey, XKB_KEY_Scroll_Lock, GHOST_kKeyScrollLock);
2218
2219 GXMAP(gkey, XKB_KEY_Left, GHOST_kKeyLeftArrow);
2220 GXMAP(gkey, XKB_KEY_Right, GHOST_kKeyRightArrow);
2221 GXMAP(gkey, XKB_KEY_Up, GHOST_kKeyUpArrow);
2222 GXMAP(gkey, XKB_KEY_Down, GHOST_kKeyDownArrow);
2223
2224 GXMAP(gkey, XKB_KEY_Print, GHOST_kKeyPrintScreen);
2225 GXMAP(gkey, XKB_KEY_Pause, GHOST_kKeyPause);
2226
2227 GXMAP(gkey, XKB_KEY_Insert, GHOST_kKeyInsert);
2228 GXMAP(gkey, XKB_KEY_Delete, GHOST_kKeyDelete);
2229 GXMAP(gkey, XKB_KEY_Home, GHOST_kKeyHome);
2230 GXMAP(gkey, XKB_KEY_End, GHOST_kKeyEnd);
2231 GXMAP(gkey, XKB_KEY_Page_Up, GHOST_kKeyUpPage);
2232 GXMAP(gkey, XKB_KEY_Page_Down, GHOST_kKeyDownPage);
2233
2234 GXMAP(gkey, XKB_KEY_KP_Decimal, GHOST_kKeyNumpadPeriod);
2235 GXMAP(gkey, XKB_KEY_KP_Enter, GHOST_kKeyNumpadEnter);
2236 GXMAP(gkey, XKB_KEY_KP_Add, GHOST_kKeyNumpadPlus);
2237 GXMAP(gkey, XKB_KEY_KP_Subtract, GHOST_kKeyNumpadMinus);
2238 GXMAP(gkey, XKB_KEY_KP_Multiply, GHOST_kKeyNumpadAsterisk);
2239 GXMAP(gkey, XKB_KEY_KP_Divide, GHOST_kKeyNumpadSlash);
2240
2241 GXMAP(gkey, XKB_KEY_XF86AudioPlay, GHOST_kKeyMediaPlay);
2242 GXMAP(gkey, XKB_KEY_XF86AudioStop, GHOST_kKeyMediaStop);
2243 GXMAP(gkey, XKB_KEY_XF86AudioPrev, GHOST_kKeyMediaFirst);
2244 GXMAP(gkey, XKB_KEY_XF86AudioNext, GHOST_kKeyMediaLast);
2245
2246 /* Additional keys for non US layouts. */
2247
2248 /* Uses the same physical key as #XKB_KEY_KP_Decimal for QWERTZ layout, see: #102287. */
2249 GXMAP(gkey, XKB_KEY_KP_Separator, GHOST_kKeyNumpadPeriod);
2250 GXMAP(gkey, XKB_KEY_less, GHOST_kKeyGrLess);
2251
2252 default: {
2253 /* Rely on #xkb_map_gkey_or_scan_code to report when no key can be found. */
2254 gkey = GHOST_kKeyUnknown;
2255 }
2256 }
2257#undef GXMAP
2258 }
2259
2260 return gkey;
2261}
2262
2270static GHOST_TKey xkb_map_gkey_or_scan_code(const xkb_keysym_t sym, const uint32_t key)
2271{
2272 GHOST_TKey gkey = xkb_map_gkey(sym);
2273
2274 if (UNLIKELY(gkey == GHOST_kKeyUnknown)) {
2275 /* Fall back to physical location for keys that would otherwise do nothing. */
2276 switch (key) {
2277 case KEY_GRAVE: {
2278 gkey = GHOST_kKeyAccentGrave;
2279 break;
2280 }
2281 case KEY_102ND: {
2282 gkey = GHOST_kKeyGrLess;
2283 break;
2284 }
2285 default: {
2287 /* Key-code. */
2288 "unhandled key: " << std::hex << std::showbase << sym << /* Hex. */
2289 std::dec << " (" << sym << "), " << /* Decimal. */
2290 /* Scan-code. */
2291 "scan-code: " << std::hex << std::showbase << key << /* Hex. */
2292 std::dec << " (" << key << ")" << /* Decimal. */
2293 std::endl);
2294 break;
2295 }
2296 }
2297 }
2298
2299 return gkey;
2300}
2301
2302static int pointer_axis_as_index(const uint32_t axis)
2303{
2304 switch (axis) {
2305 case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {
2306 return 0;
2307 }
2308 case WL_POINTER_AXIS_VERTICAL_SCROLL: {
2309 return 1;
2310 }
2311 default: {
2312 return -1;
2313 }
2314 }
2315}
2316
2317static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type)
2318{
2319 switch (wp_tablet_tool_type) {
2320 case ZWP_TABLET_TOOL_V2_TYPE_ERASER: {
2322 }
2323 case ZWP_TABLET_TOOL_V2_TYPE_PEN:
2324 case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
2325 case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
2326 case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
2327 case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
2328 case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
2329 case ZWP_TABLET_TOOL_V2_TYPE_LENS: {
2331 }
2332 }
2333
2334 GHOST_PRINT("unknown tablet tool: " << wp_tablet_tool_type << std::endl);
2336}
2337
2338static const int default_cursor_size = 24;
2339
2340static constexpr const char *ghost_wl_mime_text_plain = "text/plain";
2341static constexpr const char *ghost_wl_mime_text_utf8 = "text/plain;charset=utf-8";
2342static constexpr const char *ghost_wl_mime_text_uri_list = "text/uri-list";
2343
2349/* Aligned to `ghost_wl_mime_preference_order`. */
2355
2356static const char *ghost_wl_mime_send[] = {
2357 "UTF8_STRING",
2358 "COMPOUND_TEXT",
2359 "TEXT",
2360 "STRING",
2361 "text/plain;charset=utf-8",
2362 "text/plain",
2363};
2364
2369static std::vector<std::string_view> gwl_clipboard_uri_ranges(const char *data_buf,
2370 size_t data_buf_len)
2371{
2372 std::vector<std::string_view> uris;
2373 const char file_proto[] = "file://";
2374 /* NOTE: some applications CRLF (`\r\n`) GTK3 for example & others don't `pcmanfm-qt`.
2375 * So support both, once `\n` is found, strip the preceding `\r` if found. */
2376 const char lf = '\n';
2377
2378 const std::string_view data = std::string_view(data_buf, data_buf_len);
2379
2380 size_t pos = 0;
2381 while (pos != std::string::npos) {
2382 pos = data.find(file_proto, pos);
2383 if (pos == std::string::npos) {
2384 break;
2385 }
2386 const size_t start = pos + sizeof(file_proto) - 1;
2387 pos = data.find(lf, pos);
2388
2389 size_t end = pos;
2390 if (UNLIKELY(end == std::string::npos)) {
2391 /* Note that most well behaved file managers will add a trailing newline,
2392 * Gnome's web browser (44.3) doesn't, so support reading up until the last byte. */
2393 end = data.size();
2394 }
2395 /* Account for 'CRLF' case. */
2396 if (data[end - 1] == '\r') {
2397 end -= 1;
2398 }
2399
2400 std::string_view data_substr = data.substr(start, end - start);
2401 uris.push_back(data_substr);
2402 }
2403 return uris;
2404}
2405
2406#ifdef USE_EVENT_BACKGROUND_THREAD
2407static void pthread_set_min_priority(pthread_t handle)
2408{
2409 int policy;
2410 sched_param sch_params;
2411 if (pthread_getschedparam(handle, &policy, &sch_params) == 0) {
2412 sch_params.sched_priority = sched_get_priority_min(policy);
2413 pthread_setschedparam(handle, policy, &sch_params);
2414 }
2415}
2416
2417#endif /* USE_EVENT_BACKGROUND_THREAD */
2418
2419static int memfd_create_sealed(const char *name)
2420{
2421#ifdef HAVE_MEMFD_CREATE
2422 const int fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
2423 if (fd >= 0) {
2424 fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
2425 }
2426 return fd;
2427#else /* HAVE_MEMFD_CREATE */
2428 char *path = getenv("XDG_RUNTIME_DIR");
2429 if (!path) {
2430 errno = ENOENT;
2431 return -1;
2432 }
2433 char *tmpname;
2434 asprintf(&tmpname, "%s/%s-XXXXXX", path, name);
2435 const int fd = mkostemp(tmpname, O_CLOEXEC);
2436 if (fd >= 0) {
2437 unlink(tmpname);
2438 }
2439 free(tmpname);
2440 return fd;
2441#endif /* !HAVE_MEMFD_CREATE */
2442}
2443
2444enum {
2448};
2449
2450static int file_descriptor_is_io_ready(int fd, const int flags, const int timeout_ms)
2451{
2452 int result;
2453
2454 GHOST_ASSERT(flags & (GWL_IOR_READ | GWL_IOR_WRITE), "X");
2455
2456 /* NOTE: We don't bother to account for elapsed time if we get #EINTR. */
2457 do {
2458#ifdef HAVE_POLL
2459 pollfd info;
2460
2461 info.fd = fd;
2462 info.events = 0;
2463 if (flags & GWL_IOR_READ) {
2464 info.events |= POLLIN | POLLPRI;
2465 }
2466 if (flags & GWL_IOR_WRITE) {
2467 info.events |= POLLOUT;
2468 }
2469 result = poll(&info, 1, timeout_ms);
2470#else
2471 fd_set rfdset, *rfdp = nullptr;
2472 fd_set wfdset, *wfdp = nullptr;
2473 struct timeval tv, *tvp = nullptr;
2474
2475 /* If this assert triggers we'll corrupt memory here */
2476 GHOST_ASSERT(fd >= 0 && fd < FD_SETSIZE, "X");
2477
2478 if (flags & GWL_IOR_READ) {
2479 FD_ZERO(&rfdset);
2480 FD_SET(fd, &rfdset);
2481 rfdp = &rfdset;
2482 }
2483 if (flags & GWL_IOR_WRITE) {
2484 FD_ZERO(&wfdset);
2485 FD_SET(fd, &wfdset);
2486 wfdp = &wfdset;
2487 }
2488
2489 if (timeout_ms >= 0) {
2490 tv.tv_sec = timeout_ms / 1000;
2491 tv.tv_usec = (timeout_ms % 1000) * 1000;
2492 tvp = &tv;
2493 }
2494
2495 result = select(fd + 1, rfdp, wfdp, nullptr, tvp);
2496#endif /* !HAVE_POLL */
2497 } while (result < 0 && errno == EINTR && !(flags & GWL_IOR_NO_RETRY));
2498
2499 return result;
2500}
2501
2503{
2504 /* Based on SDL's `Wayland_PumpEvents`. */
2505 int err;
2506
2507 /* NOTE: Without this, interactions with window borders via LIBDECOR doesn't function. */
2509
2511 /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
2514 {
2516 }
2517 else {
2519 err = 0;
2520 }
2521 }
2522 else {
2524 }
2525 return err;
2526}
2527
2528#ifdef USE_EVENT_BACKGROUND_THREAD
2529
2530static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex)
2531{
2532 const int fd = wl_display_get_fd(wl_display);
2533 int state;
2534 do {
2536 /* Re-check `state` with a lock held, needed to avoid holding the lock. */
2537 if (state == 0) {
2538 server_mutex->lock();
2540 if (state == 0) {
2541 break;
2542 }
2543 }
2544 } while (state == 0);
2545}
2546
2548 const int fd,
2549 std::mutex *server_mutex)
2550{
2551 /* Based on SDL's `Wayland_PumpEvents`. */
2552 server_mutex->lock();
2553 int err = 0;
2555 bool wait_on_fd = false;
2556 /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
2559 }
2560 else {
2562 /* Without this, the thread will loop continuously, 100% CPU. */
2563 wait_on_fd = true;
2564 }
2565
2566 server_mutex->unlock();
2567
2568 if (wait_on_fd) {
2569 /* Important this runs after unlocking. */
2571 }
2572 }
2573 else {
2574 server_mutex->unlock();
2575
2576 /* Wait for input (unlocked, so as not to block other threads). */
2578 /* Re-check `state` with a lock held, needed to avoid holding the lock. */
2579 if (state > 0) {
2580 server_mutex->lock();
2582 if (state > 0) {
2584 }
2585 server_mutex->unlock();
2586 }
2587 }
2588
2589 return err;
2590}
2591#endif /* USE_EVENT_BACKGROUND_THREAD */
2592
2593static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format)
2594{
2595 switch (format) {
2596 case WL_SHM_FORMAT_ARGB8888: {
2597 return 4;
2598 }
2599 default: {
2600 /* Support other formats as needed. */
2601 GHOST_ASSERT(0, "Unexpected format passed in!");
2602 return 4;
2603 }
2604 }
2605}
2606
2614static wl_buffer *ghost_wl_buffer_create_for_image(wl_shm *shm,
2615 const int32_t size_xy[2],
2616 enum wl_shm_format format,
2617 void **r_buffer_data,
2618 size_t *r_buffer_data_size)
2619{
2620 const int fd = memfd_create_sealed("ghost-wl-buffer");
2621 wl_buffer *buffer = nullptr;
2622 if (fd >= 0) {
2623 const int32_t buffer_stride = size_xy[0] * ghost_wl_shm_format_as_size(format);
2624 const int32_t buffer_size = buffer_stride * size_xy[1];
2625 if (posix_fallocate(fd, 0, buffer_size) == 0) {
2626 void *buffer_data = mmap(nullptr, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
2627 if (buffer_data != MAP_FAILED) {
2628 wl_shm_pool *pool = wl_shm_create_pool(shm, fd, buffer_size);
2629 buffer = wl_shm_pool_create_buffer(pool, 0, UNPACK2(size_xy), buffer_stride, format);
2630 wl_shm_pool_destroy(pool);
2631 if (buffer) {
2632 *r_buffer_data = buffer_data;
2633 *r_buffer_data_size = size_t(buffer_size);
2634 }
2635 else {
2636 /* Highly unlikely. */
2637 munmap(buffer_data, buffer_size);
2638 }
2639 }
2640 }
2641 close(fd);
2642 }
2643 return buffer;
2644}
2645
2650static ssize_t read_exhaustive(const int fd, void *data, size_t nbytes)
2651{
2652 ssize_t nbytes_read = read(fd, data, nbytes);
2653 if (nbytes_read > 0) {
2654 while (nbytes_read < nbytes) {
2655 const ssize_t nbytes_extra = read(
2656 fd, static_cast<char *>(data) + nbytes_read, nbytes - nbytes_read);
2657 if (nbytes_extra > 0) {
2658 nbytes_read += nbytes_extra;
2659 }
2660 else {
2661 if (UNLIKELY(nbytes_extra < 0)) {
2662 nbytes_read = nbytes_extra; /* Error. */
2663 }
2664 break;
2665 }
2666 }
2667 }
2668 return nbytes_read;
2669}
2670
2678static char *read_file_as_buffer(const int fd, const bool nil_terminate, size_t *r_len)
2679{
2680 struct ByteChunk {
2681 ByteChunk *next;
2682 char data[4096 - sizeof(ByteChunk *)];
2683 };
2684 bool ok = true;
2685 size_t len = 0;
2686
2687 ByteChunk *chunk_first = static_cast<ByteChunk *>(malloc(sizeof(ByteChunk)));
2688 {
2689 ByteChunk **chunk_link_p = &chunk_first;
2690 ByteChunk *chunk = chunk_first;
2691 while (true) {
2692 if (UNLIKELY(chunk == nullptr)) {
2693 errno = ENOMEM;
2694 ok = false;
2695 break;
2696 }
2697 chunk->next = nullptr;
2698 /* Using `read` causes issues with GNOME, see: #106040). */
2699 const ssize_t len_chunk = read_exhaustive(fd, chunk->data, sizeof(ByteChunk::data));
2700 if (len_chunk <= 0) {
2701 if (UNLIKELY(len_chunk < 0)) {
2702 ok = false;
2703 }
2704 if (chunk == chunk_first) {
2705 chunk_first = nullptr;
2706 }
2707 free(chunk);
2708 break;
2709 }
2710 *chunk_link_p = chunk;
2711 chunk_link_p = &chunk->next;
2712 len += len_chunk;
2713
2714 if (len_chunk != sizeof(ByteChunk::data)) {
2715 break;
2716 }
2717 chunk = static_cast<ByteChunk *>(malloc(sizeof(ByteChunk)));
2718 }
2719 }
2720
2721 char *buf = nullptr;
2722 if (ok) {
2723 buf = static_cast<char *>(malloc(len + (nil_terminate ? 1 : 0)));
2724 if (UNLIKELY(buf == nullptr)) {
2725 errno = ENOMEM;
2726 ok = false;
2727 }
2728 }
2729
2730 if (ok) {
2731 *r_len = len;
2732 if (nil_terminate) {
2733 buf[len] = '\0';
2734 }
2735 }
2736 else {
2737 *r_len = 0;
2738 }
2739
2740 char *buf_stride = buf;
2741 while (chunk_first) {
2742 if (ok) {
2743 const size_t len_chunk = std::min(len, sizeof(chunk_first->data));
2744 memcpy(buf_stride, chunk_first->data, len_chunk);
2745 buf_stride += len_chunk;
2746 len -= len_chunk;
2747 }
2748 ByteChunk *chunk = chunk_first->next;
2749 free(chunk_first);
2750 chunk_first = chunk;
2751 }
2752
2753 return buf;
2754}
2755
2757
2758/* -------------------------------------------------------------------- */
2761
2762static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image,
2763 wl_buffer *buffer,
2765 const int scale)
2766{
2767 const int32_t image_size_x = int32_t(wl_image->width);
2768 const int32_t image_size_y = int32_t(wl_image->height);
2769 GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0,
2770 "The size must be a multiple of the scale!");
2771 (void)image_size_x;
2772 (void)image_size_y;
2773
2774 wl_surface_set_buffer_scale(wl_surface, scale);
2775 wl_surface_attach(wl_surface, buffer, 0, 0);
2776 if (wl_surface_get_version(wl_surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
2777 wl_surface_damage_buffer(wl_surface,
2778 0,
2779 0,
2780 std::numeric_limits<int32_t>::max(),
2781 std::numeric_limits<int32_t>::max());
2782 }
2783 else {
2784 /* Effectively deprecated according to documentation. */
2785 wl_surface_damage(wl_surface,
2786 0,
2787 0,
2788 std::numeric_limits<int32_t>::max(),
2789 std::numeric_limits<int32_t>::max());
2790 }
2791 wl_surface_commit(wl_surface);
2792}
2793
2794static std::optional<wp_cursor_shape_device_v1_shape> gwl_seat_cursor_find_wl_shape_from_ghost(
2795 const GHOST_TStandardCursor shape)
2796{
2797 /* Cases that return `std::nullopt` mean the cursor is not available. */
2798
2799 /* Unused WAYLAND cursors:
2800 *
2801 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS.
2802 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL.
2803 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP.
2804 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE.
2805 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE.
2806 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK.
2807 * #WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE.
2808 */
2809
2810 switch (shape) {
2812 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
2814 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE;
2816 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE;
2818 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU;
2820 return std::nullopt;
2822 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP;
2824 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT;
2826 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
2828 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
2830 return std::nullopt;
2832 return std::nullopt;
2834 return std::nullopt;
2836 return std::nullopt;
2838 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE;
2840 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE;
2842 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE;
2844 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE;
2846 return std::nullopt;
2848 return std::nullopt;
2850 return std::nullopt;
2852 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN;
2854 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT;
2856 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE;
2858 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB;
2860 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING;
2862 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
2864 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL;
2866 return std::nullopt;
2868 return std::nullopt;
2870 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED;
2872 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
2874 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
2876 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE;
2878 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE;
2880 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE;
2882 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE;
2884 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE;
2886 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE;
2888 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE;
2890 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE;
2892 return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY;
2894 return std::nullopt;
2896 return std::nullopt;
2898 return std::nullopt;
2900 return std::nullopt;
2902 return std::nullopt;
2904 return std::nullopt;
2905 }
2906 GHOST_ASSERT(0, "Unhandled GHOST cursor shape!");
2907 /* Shape not found. */
2908 return std::nullopt;
2909}
2910
2918{
2919 int scale = 1;
2920 if (seat->wl.pointer) {
2921 scale = std::max(scale, seat->pointer.buffer_scale);
2922 }
2923 if (seat->wp.tablet_seat) {
2924 scale = std::max(scale, seat->tablet.buffer_scale);
2925 }
2926 return scale;
2927}
2928
2936{
2937 const GWL_Cursor *cursor = &seat->cursor;
2938
2939 if (seat->wl.pointer) {
2940 if (seat->cursor.shape.device) {
2941 wp_cursor_shape_device_v1_set_shape(
2942 seat->cursor.shape.device, seat->pointer.serial, seat->cursor.shape.enum_id);
2943 }
2944 else {
2945 /* TODO: use `seat->pointer.buffer_scale`, give each device it's own buffer. */
2946 const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
2947
2948 const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
2949 const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
2950 wl_pointer_set_cursor(
2951 seat->wl.pointer, seat->pointer.serial, cursor->wl.surface_cursor, hotspot_x, hotspot_y);
2952 }
2953 }
2954
2955 if (!seat->wp.tablet_tools.empty()) {
2956 /* TODO: use `seat->tablet.buffer_scale`, give each device it's own buffer. */
2957 const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
2958 const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
2959 const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
2960 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2961 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
2962 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
2963 if (tablet_tool->shape.device) {
2964 wp_cursor_shape_device_v1_set_shape(
2965 tablet_tool->shape.device, tablet_tool->serial, tablet_tool->shape.enum_id);
2966 }
2967 else {
2968 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
2969 tablet_tool->serial,
2970 tablet_tool->wl.surface_cursor,
2971 hotspot_x,
2972 hotspot_y);
2973#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK
2974 wl_surface_commit(tablet_tool->wl.surface_cursor);
2975#endif
2976 }
2977 }
2978 }
2979}
2980
2988{
2989 wl_pointer_set_cursor(seat->wl.pointer, seat->pointer.serial, nullptr, 0, 0);
2990 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
2991 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
2992 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
2993 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, tablet_tool->serial, nullptr, 0, 0);
2994 }
2995}
2996
2997static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
2998 const wl_cursor_image *wl_image,
2999 wl_buffer *buffer)
3000{
3001 const GWL_Cursor *cursor = &seat->cursor;
3002 const bool visible = (cursor->visible && cursor->is_hardware);
3003
3004 const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
3005
3006 /* This is a requirement of WAYLAND, when this isn't the case,
3007 * it causes Blender's window to close intermittently. */
3008 if (seat->wl.pointer) {
3009 const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
3010 const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
3011 cursor_buffer_set_surface_impl(wl_image, buffer, cursor->wl.surface_cursor, scale);
3012 wl_pointer_set_cursor(seat->wl.pointer,
3013 seat->pointer.serial,
3014 visible ? cursor->wl.surface_cursor : nullptr,
3015 hotspot_x,
3016 hotspot_y);
3017 }
3018
3019 /* Set the cursor for all tablet tools as well. */
3020 if (!seat->wp.tablet_tools.empty()) {
3021 const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
3022 const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
3023 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
3024 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
3025 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
3026 cursor_buffer_set_surface_impl(wl_image, buffer, tablet_tool->wl.surface_cursor, scale);
3027 zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
3028 tablet_tool->serial,
3029 visible ? tablet_tool->wl.surface_cursor : nullptr,
3030 hotspot_x,
3031 hotspot_y);
3032 }
3033 }
3034}
3035
3037{
3038 const GWL_Cursor *cursor = &seat->cursor;
3039 gwl_seat_cursor_buffer_set(seat, &cursor->wl.image, cursor->wl.buffer);
3040}
3041
3047
3049 const bool visible,
3050 const bool is_hardware,
3051 const enum eCursorSetMode set_mode)
3052{
3053 GWL_Cursor *cursor = &seat->cursor;
3054 const bool was_visible = cursor->is_hardware && cursor->visible;
3055 const bool use_visible = is_hardware && visible;
3056
3057 if (set_mode == CURSOR_VISIBLE_ALWAYS_SET) {
3058 /* Pass. */
3059 }
3060 else if (set_mode == CURSOR_VISIBLE_ONLY_SHOW) {
3061 if (!use_visible) {
3062 return;
3063 }
3064 }
3065 else if (set_mode == CURSOR_VISIBLE_ONLY_HIDE) {
3066 if (use_visible) {
3067 return;
3068 }
3069 }
3070
3071 if (use_visible) {
3072 if (!was_visible) {
3074 }
3075 }
3076 else {
3077 if (was_visible) {
3079 }
3080 }
3081 cursor->visible = visible;
3082 cursor->is_hardware = is_hardware;
3083}
3084
3086
3087/* -------------------------------------------------------------------- */
3094
3095static CLG_LogRef LOG_WL_KEYBOARD_DEPRESSED_STATE = {"ghost.wl.keyboard.depressed"};
3096#define LOG (&LOG_WL_KEYBOARD_DEPRESSED_STATE)
3097
3099{
3100 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3101 seat->key_depressed.mods[i] = 0;
3102 }
3103}
3104
3106 const GHOST_TKey gkey,
3107 const GHOST_TEventType etype)
3108{
3109 if (GHOST_KEY_MODIFIER_CHECK(gkey)) {
3110 const int index = GHOST_KEY_MODIFIER_TO_INDEX(gkey);
3111 int16_t &value = seat->key_depressed.mods[index];
3112 if (etype == GHOST_kEventKeyUp) {
3113 value -= 1;
3114 if (UNLIKELY(value < 0)) {
3115 CLOG_WARN(LOG, "modifier (%d) has negative keys held (%d)!", index, value);
3116 value = 0;
3117 }
3118 }
3119 else {
3120 value += 1;
3121 }
3122 }
3123}
3124
3126 GWL_Seat *seat,
3127 GHOST_IWindow *win,
3128 const uint64_t event_ms,
3129 const GWL_KeyboardDepressedState &key_depressed_prev)
3130{
3131 /* Separate key up and down into separate passes so key down events always come after key up.
3132 * Do this so users of GHOST can use the last pressed or released modifier to check
3133 * if the modifier is held instead of counting modifiers pressed as is done here,
3134 * this isn't perfect but works well enough in practice. */
3135 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3136 for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d < 0; d++) {
3139 new GHOST_EventKey(event_ms, GHOST_kEventKeyUp, win, gkey, false));
3140
3141 CLOG_DEBUG(LOG, "modifier (%d) up", i);
3142 }
3143 }
3144
3145 for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
3146 for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d > 0; d--) {
3149 new GHOST_EventKey(event_ms, GHOST_kEventKeyDown, win, gkey, false));
3150 CLOG_DEBUG(LOG, "modifier (%d) down", i);
3151 }
3152 }
3153}
3154
3155#undef LOG
3156
3158
3159/* -------------------------------------------------------------------- */
3165
3166static CLG_LogRef LOG_WL_RELATIVE_POINTER = {"ghost.wl.handle.relative_pointer"};
3167#define LOG (&LOG_WL_RELATIVE_POINTER)
3168
3174 const wl_fixed_t xy[2],
3175 const uint64_t event_ms)
3176{
3177 seat->pointer.xy[0] = xy[0];
3178 seat->pointer.xy[1] = xy[1];
3179
3180#ifdef USE_GNOME_CONFINE_HACK
3181 if (seat->use_pointer_software_confine) {
3183 win->getClientBounds(bounds);
3184 /* Needed or the cursor is considered outside the window and doesn't restore the location. */
3185 bounds.r_ -= 1;
3186 bounds.b_ -= 1;
3187 bounds.l_ = win->wl_fixed_from_window(wl_fixed_from_int(bounds.l_));
3188 bounds.t_ = win->wl_fixed_from_window(wl_fixed_from_int(bounds.t_));
3189 bounds.r_ = win->wl_fixed_from_window(wl_fixed_from_int(bounds.r_));
3190 bounds.b_ = win->wl_fixed_from_window(wl_fixed_from_int(bounds.b_));
3191 bounds.clampPoint(UNPACK2(seat->pointer.xy));
3192 }
3193#endif
3194 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
3196 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
3197}
3198
3200 void *data,
3201 zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/,
3202 const uint32_t utime_hi,
3203 const uint32_t utime_lo,
3204 const wl_fixed_t dx,
3205 const wl_fixed_t dy,
3206 const wl_fixed_t /*dx_unaccel*/,
3207 const wl_fixed_t /*dy_unaccel*/)
3208{
3209 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3210 const uint64_t time = ghost_wl_ms_from_utime_pair(utime_hi, utime_lo);
3211 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3212
3213 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
3214 CLOG_DEBUG(LOG, "relative_motion");
3215 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3216 const wl_fixed_t xy_next[2] = {
3217 seat->pointer.xy[0] + win->wl_fixed_from_window(dx),
3218 seat->pointer.xy[1] + win->wl_fixed_from_window(dy),
3219 };
3220 relative_pointer_handle_relative_motion_impl(seat, win, xy_next, event_ms);
3221 }
3222 else {
3223 CLOG_DEBUG(LOG, "relative_motion (skipped)");
3224 }
3225}
3226
3227static const zwp_relative_pointer_v1_listener relative_pointer_listener = {
3229};
3230
3231#undef LOG
3232
3234
3235/* -------------------------------------------------------------------- */
3238
3239static CLG_LogRef LOG_WL_DATA_SOURCE = {"ghost.wl.handle.data_source"};
3240#define LOG (&LOG_WL_DATA_SOURCE)
3241
3242static void dnd_events(const GWL_Seat *const seat,
3243 const GHOST_TEventType event,
3244 const uint64_t event_ms)
3245{
3246 /* NOTE: `seat->data_offer_dnd_mutex` must already be locked. */
3247 if (wl_surface *wl_surface_focus = seat->wl.surface_window_focus_dnd) {
3248 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3249 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->data_offer_dnd->dnd.xy)};
3250 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order_type); i++) {
3253 new GHOST_EventDragnDrop(event_ms, event, type, win, UNPACK2(event_xy), nullptr));
3254 }
3255 }
3256}
3257
3259 const char *mime_receive,
3260 std::mutex *mutex,
3261 const bool nil_terminate,
3262 size_t *r_len)
3263{
3264 int pipefd[2];
3265 const bool pipefd_ok = pipe(pipefd) == 0;
3266 if (pipefd_ok) {
3267 wl_data_offer_receive(data_offer->wl.id, mime_receive, pipefd[1]);
3268 close(pipefd[1]);
3269 }
3270 else {
3271 CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno));
3272 }
3273
3274 if (mutex) {
3275 mutex->unlock();
3276 }
3277 /* NOTE: Regarding `data_offer`:
3278 * - In the case of the clipboard - unlocking the `mutex` means, it may be feed from now on.
3279 * - In the case of drag & drop - the caller owns & no locking is needed
3280 * (locking is performed while transferring the ownership).
3281 */
3282 char *buf = nullptr;
3283 if (pipefd_ok) {
3284 buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len);
3285 if (buf == nullptr) {
3286 CLOG_WARN(LOG, "unable to pipe into buffer: %s", std::strerror(errno));
3287 }
3288 close(pipefd[0]);
3289 }
3290 else {
3291 *r_len = 0;
3292 }
3293 return buf;
3294}
3295
3297 const char *mime_receive,
3298 std::mutex *mutex,
3299 const bool nil_terminate,
3300 size_t *r_len)
3301{
3302 int pipefd[2];
3303 const bool pipefd_ok = pipe(pipefd) == 0;
3304 if (pipefd_ok) {
3305 zwp_primary_selection_offer_v1_receive(data_offer->wp.id, mime_receive, pipefd[1]);
3306 close(pipefd[1]);
3307 }
3308 else {
3309 CLOG_WARN(LOG, "error creating pipe: %s", std::strerror(errno));
3310 }
3311
3312 if (mutex) {
3313 mutex->unlock();
3314 }
3315 /* WARNING: `data_offer` may be freed from now on. */
3316 char *buf = nullptr;
3317 if (pipefd_ok) {
3318 buf = read_file_as_buffer(pipefd[0], nil_terminate, r_len);
3319 if (buf == nullptr) {
3320 CLOG_WARN(LOG, "unable to pipe into buffer: %s", std::strerror(errno));
3321 }
3322 close(pipefd[0]);
3323 }
3324 return buf;
3325}
3326
3333static void data_source_handle_target(void * /*data*/,
3334 wl_data_source * /*wl_data_source*/,
3335 const char * /*mime_type*/)
3336{
3337 CLOG_DEBUG(LOG, "target");
3338}
3339
3341 wl_data_source * /*wl_data_source*/,
3342 const char * /*mime_type*/,
3343 const int32_t fd)
3344{
3345 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3346
3347 CLOG_DEBUG(LOG, "send");
3348
3349 auto write_file_fn = [](GWL_Seat *seat, const int fd) {
3350 if (UNLIKELY(write(fd,
3352 seat->data_source->buffer_out.data_size) < 0))
3353 {
3354 CLOG_WARN(LOG, "error writing to clipboard: %s", std::strerror(errno));
3355 }
3356 close(fd);
3357 seat->data_source_mutex.unlock();
3358 };
3359
3360 seat->data_source_mutex.lock();
3361 std::thread write_thread(write_file_fn, seat, fd);
3362 write_thread.detach();
3363}
3364
3365static void data_source_handle_cancelled(void *data, wl_data_source *wl_data_source)
3366{
3367 CLOG_DEBUG(LOG, "cancelled");
3368 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3369 GWL_DataSource *data_source = seat->data_source;
3370 if (seat->data_source->wl.source == wl_data_source) {
3371 data_source->wl.source = nullptr;
3372 }
3373
3374 wl_data_source_destroy(wl_data_source);
3375}
3376
3384static void data_source_handle_dnd_drop_performed(void * /*data*/,
3385 wl_data_source * /*wl_data_source*/)
3386{
3387 CLOG_DEBUG(LOG, "dnd_drop_performed");
3388}
3389
3397static void data_source_handle_dnd_finished(void * /*data*/, wl_data_source * /*wl_data_source*/)
3398{
3399 CLOG_DEBUG(LOG, "dnd_finished");
3400}
3401
3409static void data_source_handle_action(void * /*data*/,
3410 wl_data_source * /*wl_data_source*/,
3411 const uint32_t dnd_action)
3412{
3413 CLOG_DEBUG(LOG, "handle_action (dnd_action=%u)", dnd_action);
3414}
3415
3416static const wl_data_source_listener data_source_listener = {
3417 /*target*/ data_source_handle_target,
3418 /*send*/ data_source_handle_send,
3419 /*cancelled*/ data_source_handle_cancelled,
3420 /*dnd_drop_performed*/ data_source_handle_dnd_drop_performed,
3421 /*dnd_finished*/ data_source_handle_dnd_finished,
3422 /*action*/ data_source_handle_action,
3423};
3424
3425#undef LOG
3426
3428
3429/* -------------------------------------------------------------------- */
3432
3433static CLG_LogRef LOG_WL_DATA_OFFER = {"ghost.wl.handle.data_offer"};
3434#define LOG (&LOG_WL_DATA_OFFER)
3435
3437 wl_data_offer * /*wl_data_offer*/,
3438 const char *mime_type)
3439{
3440 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
3441 CLOG_DEBUG(LOG, "offer (mime_type=%s)", mime_type);
3442 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3443 data_offer->types.insert(mime_type);
3444}
3445
3447 wl_data_offer * /*wl_data_offer*/,
3448 const uint32_t source_actions)
3449{
3450 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
3451 CLOG_DEBUG(LOG, "source_actions (%u)", source_actions);
3452 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3453 data_offer->dnd.source_actions = (enum wl_data_device_manager_dnd_action)source_actions;
3454}
3455
3457 wl_data_offer * /*wl_data_offer*/,
3458 const uint32_t dnd_action)
3459{
3460 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
3461 CLOG_DEBUG(LOG, "actions (%u)", dnd_action);
3462 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(data);
3463 data_offer->dnd.action = (enum wl_data_device_manager_dnd_action)dnd_action;
3464}
3465
3466static const wl_data_offer_listener data_offer_listener = {
3467 /*offer*/ data_offer_handle_offer,
3468 /*source_actions*/ data_offer_handle_source_actions,
3469 /*action*/ data_offer_handle_action,
3470};
3471
3472#undef LOG
3473
3475
3476/* -------------------------------------------------------------------- */
3479
3480static CLG_LogRef LOG_WL_DATA_DEVICE = {"ghost.wl.handle.data_device"};
3481#define LOG (&LOG_WL_DATA_DEVICE)
3482
3483static void data_device_handle_data_offer(void * /*data*/,
3484 wl_data_device * /*wl_data_device*/,
3485 wl_data_offer *id)
3486{
3487 CLOG_DEBUG(LOG, "data_offer");
3488
3489 /* The ownership of data-offer isn't so obvious:
3490 * At this point it's not known if this will be used for drag & drop or selection.
3491 *
3492 * The API docs state that the following callbacks run immediately after this callback:
3493 * - #wl_data_device_listener::enter (for drag & drop).
3494 * - #wl_data_device_listener::selection (for copy & paste).
3495 *
3496 * In the case of GHOST, this means they will be assigned to either:
3497 * - #GWL_Seat::data_offer_dnd
3498 * - #GWL_Seat::data_offer_copy_paste
3499 */
3500 GWL_DataOffer *data_offer = new GWL_DataOffer;
3501 data_offer->wl.id = id;
3502 wl_data_offer_add_listener(id, &data_offer_listener, data_offer);
3503}
3504
3506 wl_data_device * /*wl_data_device*/,
3507 const uint32_t serial,
3509 const wl_fixed_t x,
3510 const wl_fixed_t y,
3511 wl_data_offer *id)
3512{
3513 /* Always clear the current data-offer no matter what else happens. */
3514 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3515 const uint64_t event_ms = seat->system->getMilliSeconds();
3516
3517 std::lock_guard lock{seat->data_offer_dnd_mutex};
3518 if (seat->data_offer_dnd) {
3519 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
3520 delete seat->data_offer_dnd;
3521 seat->data_offer_dnd = nullptr;
3522 }
3523 /* Clearing complete. */
3524
3525 /* Handle the new offer. */
3526 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id));
3528 CLOG_DEBUG(LOG, "enter (skipped)");
3529 wl_data_offer_destroy(data_offer->wl.id);
3530 delete data_offer;
3531 return;
3532 }
3533 CLOG_DEBUG(LOG, "enter");
3534
3535 /* Transfer ownership of the `data_offer`. */
3536 seat->data_offer_dnd = data_offer;
3537
3538 data_offer->dnd.xy[0] = x;
3539 data_offer->dnd.xy[1] = y;
3540
3541 wl_data_offer_set_actions(id,
3542 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
3543 WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
3544 WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
3545
3546 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) {
3547 const char *type = ghost_wl_mime_preference_order[i];
3548 wl_data_offer_accept(id, serial, type);
3549 }
3550
3552
3553 seat->system->seat_active_set(seat);
3554
3555 dnd_events(seat, GHOST_kEventDraggingEntered, event_ms);
3556}
3557
3558static void data_device_handle_leave(void *data, wl_data_device * /*wl_data_device*/)
3559{
3560 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3561 const uint64_t event_ms = seat->system->getMilliSeconds();
3562
3563 std::lock_guard lock{seat->data_offer_dnd_mutex};
3564 /* The user may have only dragged over the window decorations. */
3565 if (seat->data_offer_dnd == nullptr) {
3566 return;
3567 }
3568 CLOG_DEBUG(LOG, "leave");
3569
3570 dnd_events(seat, GHOST_kEventDraggingExited, event_ms);
3571 seat->wl.surface_window_focus_dnd = nullptr;
3572
3573 if (seat->data_offer_dnd) {
3574 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
3575 delete seat->data_offer_dnd;
3576 seat->data_offer_dnd = nullptr;
3577 }
3578}
3579
3581 wl_data_device * /*wl_data_device*/,
3582 const uint32_t time,
3583 const wl_fixed_t x,
3584 const wl_fixed_t y)
3585{
3586 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3587 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3588
3589 std::lock_guard lock{seat->data_offer_dnd_mutex};
3590 /* The user may have only dragged over the window decorations. */
3591 if (seat->data_offer_dnd == nullptr) {
3592 return;
3593 }
3594
3595 CLOG_DEBUG(LOG, "motion");
3596
3597 seat->data_offer_dnd->dnd.xy[0] = x;
3598 seat->data_offer_dnd->dnd.xy[1] = y;
3599
3600 dnd_events(seat, GHOST_kEventDraggingUpdated, event_ms);
3601}
3602
3603static void data_device_handle_drop(void *data, wl_data_device * /*wl_data_device*/)
3604{
3605 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3606 std::lock_guard lock{seat->data_offer_dnd_mutex};
3607
3608 /* No need to check this for null (as other callbacks do).
3609 * because the data-offer has not been accepted (actions set... etc). */
3610 GWL_DataOffer *data_offer = seat->data_offer_dnd;
3611
3612 /* Take ownership of `data_offer` to prevent a double-free, see: #128766.
3613 * The thread this function spawns is responsible for freeing it. */
3614 seat->data_offer_dnd = nullptr;
3615
3616 /* Use a blank string for `mime_receive` to prevent crashes, although could also be `nullptr`.
3617 * Failure to set this to a known type just means the file won't have any special handling.
3618 * GHOST still generates a dropped file event.
3619 * NOTE: this string can be compared with `mime_text_plain`, `mime_text_uri` etc...
3620 * as this always points to the same values. */
3621 const char *mime_receive = "";
3622 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order); i++) {
3623 const char *type = ghost_wl_mime_preference_order[i];
3624 if (data_offer->types.count(type)) {
3625 mime_receive = type;
3626 break;
3627 }
3628 }
3629
3630 CLOG_DEBUG(LOG, "drop mime_recieve=%s", mime_receive);
3631
3632 auto read_drop_data_fn = [](GWL_Seat *const seat,
3633 GWL_DataOffer *data_offer,
3634 wl_surface *wl_surface_window,
3635 const char *mime_receive) {
3636 const uint64_t event_ms = seat->system->getMilliSeconds();
3637 const wl_fixed_t xy[2] = {UNPACK2(data_offer->dnd.xy)};
3638
3639 const bool nil_terminate = (mime_receive != ghost_wl_mime_text_uri_list);
3640 size_t data_buf_len = 0;
3641 const char *data_buf = read_buffer_from_data_offer(
3642 data_offer, mime_receive, nullptr, nil_terminate, &data_buf_len);
3643
3644 CLOG_DEBUG(LOG, "read_drop_data mime_receive=%s, data_len=%zu", mime_receive, data_buf_len);
3645
3646 wl_data_offer_finish(data_offer->wl.id);
3647 wl_data_offer_destroy(data_offer->wl.id);
3648
3649 delete data_offer;
3650 data_offer = nullptr;
3651
3652 /* Don't generate a drop event if the data could not be read,
3653 * an error will have been logged. */
3654 if (data_buf != nullptr) {
3656 void *ghost_dnd_data = nullptr;
3657
3658 /* Failure to receive drop data. */
3659 if (mime_receive == ghost_wl_mime_text_uri_list) {
3660 std::vector<std::string_view> uris = gwl_clipboard_uri_ranges(data_buf, data_buf_len);
3661
3662 GHOST_TStringArray *flist = static_cast<GHOST_TStringArray *>(
3663 malloc(sizeof(GHOST_TStringArray)));
3664 flist->count = int(uris.size());
3665 flist->strings = static_cast<uint8_t **>(malloc(uris.size() * sizeof(uint8_t *)));
3666 for (size_t i = 0; i < uris.size(); i++) {
3667 flist->strings[i] = reinterpret_cast<uint8_t *>(
3668 GHOST_URL_decode_alloc(uris[i].data(), uris[i].size()));
3669 }
3670
3671 CLOG_DEBUG(LOG, "read_drop_data file_count=%d", flist->count);
3672 ghost_dnd_type = GHOST_kDragnDropTypeFilenames;
3673 ghost_dnd_data = flist;
3674 }
3675 else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) {
3676 ghost_dnd_type = GHOST_kDragnDropTypeString;
3677 ghost_dnd_data = (void *)data_buf; /* Move ownership to the event. */
3678 data_buf = nullptr;
3679 }
3680
3681 if (ghost_dnd_type != GHOST_kDragnDropTypeUnknown) {
3682 GHOST_SystemWayland *const system = seat->system;
3683 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_window);
3684 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)};
3685
3686 system->pushEvent_maybe_pending(new GHOST_EventDragnDrop(event_ms,
3688 ghost_dnd_type,
3689 win,
3690 UNPACK2(event_xy),
3691 ghost_dnd_data));
3692
3694 }
3695 else {
3696 CLOG_DEBUG(LOG, "read_drop_data, unhandled!");
3697 }
3698
3699 free(const_cast<char *>(data_buf));
3700 }
3701 };
3702
3703 /* Pass in `seat->wl_surface_window_focus_dnd` instead of accessing it from `seat` since the
3704 * leave callback (#data_device_handle_leave) will clear the value once this function starts. */
3705 std::thread read_thread(
3706 read_drop_data_fn, seat, data_offer, seat->wl.surface_window_focus_dnd, mime_receive);
3707 read_thread.detach();
3708}
3709
3711 wl_data_device * /*wl_data_device*/,
3712 wl_data_offer *id)
3713{
3714 /* Always clear the current data-offer no matter what else happens. */
3715 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3716 std::lock_guard lock{seat->data_offer_copy_paste_mutex};
3717 if (seat->data_offer_copy_paste) {
3718 wl_data_offer_destroy(seat->data_offer_copy_paste->wl.id);
3719 delete seat->data_offer_copy_paste;
3720 seat->data_offer_copy_paste = nullptr;
3721 seat->data_offer_copy_paste_has_image = std::nullopt;
3722 }
3723 /* Clearing complete. */
3724
3725 /* Handle the new offer. */
3726 if (id == nullptr) {
3727 CLOG_DEBUG(LOG, "selection: (skipped)");
3728 return;
3729 }
3730
3731 CLOG_DEBUG(LOG, "selection");
3732 GWL_DataOffer *data_offer = static_cast<GWL_DataOffer *>(wl_data_offer_get_user_data(id));
3733 /* Transfer ownership of the `data_offer`. */
3734 seat->data_offer_copy_paste = data_offer;
3735 seat->data_offer_copy_paste_has_image = std::nullopt;
3736}
3737
3738static const wl_data_device_listener data_device_listener = {
3739 /*data_offer*/ data_device_handle_data_offer,
3740 /*enter*/ data_device_handle_enter,
3741 /*leave*/ data_device_handle_leave,
3742 /*motion*/ data_device_handle_motion,
3743 /*drop*/ data_device_handle_drop,
3744 /*selection*/ data_device_handle_selection,
3745};
3746
3747#undef LOG
3748
3750
3751/* -------------------------------------------------------------------- */
3754
3755static CLG_LogRef LOG_WL_CURSOR_BUFFER = {"ghost.wl.handle.cursor_buffer"};
3756#define LOG (&LOG_WL_CURSOR_BUFFER)
3757
3758static void cursor_buffer_handle_release(void *data, wl_buffer *wl_buffer)
3759{
3760 CLOG_DEBUG(LOG, "release");
3761
3762 GWL_Cursor *cursor = static_cast<GWL_Cursor *>(data);
3763 wl_buffer_destroy(wl_buffer);
3764
3765 if (wl_buffer == cursor->wl.buffer) {
3766 /* The mapped buffer was from a custom cursor. */
3767 cursor->wl.buffer = nullptr;
3768 }
3769}
3770
3771static const wl_buffer_listener cursor_buffer_listener = {
3772 /*release*/ cursor_buffer_handle_release,
3773};
3774
3775#undef LOG
3776
3778
3779/* -------------------------------------------------------------------- */
3782
3783static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"};
3784#define LOG (&LOG_WL_CURSOR_SURFACE)
3785
3787 GWL_Cursor &cursor,
3788 GWL_SeatStatePointer *seat_state_pointer)
3789{
3790 /* NOTE: currently there is no special handling for fractional scaling.
3791 * This could be supported however the default behavior of generating a cursor
3792 * rounded up to the nearest integer scaling works fairly well. */
3793 int scale = 0;
3794 for (const GWL_Output *output : seat_state_pointer->outputs) {
3795 scale = std::max(scale, output->scale);
3796 }
3797 if (scale > 0 && seat_state_pointer->buffer_scale != scale) {
3798 seat_state_pointer->buffer_scale = scale;
3799 }
3800
3802
3803 if (scale > 0 && cursor.custom_cursor_scale != scale) {
3804 cursor.custom_cursor_scale = scale;
3805 if (cursor.is_custom) {
3806 wl_surface *wl_surface_focus = seat_state_pointer->wl.surface_window;
3807 if (wl_surface_focus) {
3808 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
3809 if (win) {
3810 win->cursor_shape_refresh();
3811 }
3812 }
3813 }
3814 return true;
3815 }
3816 return false;
3817}
3818
3819static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_output *wl_output)
3820{
3821 if (!ghost_wl_output_own(wl_output)) {
3822 CLOG_DEBUG(LOG, "handle_enter (skipped)");
3823 return;
3824 }
3825 CLOG_DEBUG(LOG, "handle_enter");
3826
3827 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3829 seat, wl_surface);
3830 const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
3831 seat_state_pointer->outputs.insert(reg_output);
3832 update_cursor_scale(seat, seat->cursor, seat_state_pointer);
3833}
3834
3835static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output)
3836{
3837 if (!(wl_output && ghost_wl_output_own(wl_output))) {
3838 CLOG_DEBUG(LOG, "handle_leave (skipped)");
3839 return;
3840 }
3841 CLOG_DEBUG(LOG, "handle_leave");
3842
3843 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3845 seat, wl_surface);
3846 const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
3847 seat_state_pointer->outputs.erase(reg_output);
3848 update_cursor_scale(seat, seat->cursor, seat_state_pointer);
3849}
3850
3852 wl_surface * /*wl_surface*/,
3853 const int32_t factor)
3854{
3855 /* Only available in interface version 6. */
3856 CLOG_DEBUG(LOG, "handle_preferred_buffer_scale (factor=%d)", factor);
3857}
3858
3859#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
3860 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
3861static void cursor_surface_handle_preferred_buffer_transform(void * /*data*/,
3862 wl_surface * /*wl_surface*/,
3863 uint32_t transform)
3864{
3865 /* Only available in interface version 6. */
3866 CLOG_DEBUG(LOG, "handle_preferred_buffer_transform (transform=%u)", transform);
3867}
3868#endif /* WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION && \
3869 * WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION */
3870
3874#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
3875 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
3876 /*preferred_buffer_scale*/ cursor_surface_handle_preferred_buffer_scale,
3877 /*preferred_buffer_transform*/ cursor_surface_handle_preferred_buffer_transform,
3878#endif
3879};
3880
3881#undef LOG
3882
3884
3885/* -------------------------------------------------------------------- */
3888
3889static CLG_LogRef LOG_WL_POINTER = {"ghost.wl.handle.pointer"};
3890#define LOG (&LOG_WL_POINTER)
3891
3892static void pointer_handle_enter(void *data,
3893 wl_pointer * /*wl_pointer*/,
3894 const uint32_t serial,
3896 const wl_fixed_t surface_x,
3897 const wl_fixed_t surface_y)
3898{
3899 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3900 const uint64_t event_ms = seat->system->getMilliSeconds();
3901
3902 /* Null when just destroyed. */
3904 CLOG_DEBUG(LOG, "enter (skipped)");
3905 return;
3906 }
3907 CLOG_DEBUG(LOG, "enter");
3908
3910
3911 seat->cursor_source_serial = serial;
3912 seat->pointer.serial = serial;
3913 seat->pointer.xy[0] = surface_x;
3914 seat->pointer.xy[1] = surface_y;
3915
3916 /* Resetting scroll events is likely unnecessary,
3917 * do this to avoid any possible problems as it's harmless. */
3919
3920 seat->pointer.wl.surface_window = wl_surface;
3921
3922 seat->system->seat_active_set(seat);
3923 win->cursor_shape_refresh();
3924
3925 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
3927 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
3928}
3929
3930static void pointer_handle_leave(void *data,
3931 wl_pointer * /*wl_pointer*/,
3932 const uint32_t /*serial*/,
3934{
3935 /* First clear the `pointer.wl_surface`, since the window won't exist when closing the window. */
3936 static_cast<GWL_Seat *>(data)->pointer.wl.surface_window = nullptr;
3938 CLOG_DEBUG(LOG, "leave (skipped)");
3939 return;
3940 }
3941 CLOG_DEBUG(LOG, "leave");
3942}
3943
3944static void pointer_handle_motion(void *data,
3945 wl_pointer * /*wl_pointer*/,
3946 const uint32_t time,
3947 const wl_fixed_t surface_x,
3948 const wl_fixed_t surface_y)
3949{
3950 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3951 const uint64_t event_ms = seat->system->ms_from_input_time(time);
3952
3953 seat->pointer.xy[0] = surface_x;
3954 seat->pointer.xy[1] = surface_y;
3955
3956 CLOG_DEBUG(LOG, "motion");
3957
3960}
3961
3962static void pointer_handle_button(void *data,
3963 wl_pointer * /*wl_pointer*/,
3964 const uint32_t serial,
3965 const uint32_t time,
3966 const uint32_t button,
3967 const uint32_t state)
3968{
3969 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
3970
3971 CLOG_DEBUG(LOG, "button (button=%u, state=%u)", button, state);
3972
3973 /* Always set the serial, even if the button event is not sent. */
3974 seat->data_source_serial = serial;
3975
3976 int button_release;
3977 switch (state) {
3978 case WL_POINTER_BUTTON_STATE_RELEASED: {
3979 button_release = 1;
3980 break;
3981 }
3982 case WL_POINTER_BUTTON_STATE_PRESSED: {
3983 button_release = 0;
3984 break;
3985 }
3986 default: {
3987 return;
3988 }
3989 }
3990
3991 const uint32_t button_index = button - BTN_RANGE_MIN;
3992 if (button_index >= (BTN_RANGE_MAX - BTN_RANGE_MIN)) {
3993 return;
3994 }
3995
3997 int(GWL_Pointer_EventTypes::Button0_Down) + ((button_index * 2) + button_release));
3998
3999 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4001}
4002
4003static void pointer_handle_axis(void *data,
4004 wl_pointer * /*wl_pointer*/,
4005 const uint32_t time,
4006 const uint32_t axis,
4007 const wl_fixed_t value)
4008{
4009 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4010 seat->pointer_scroll.event_ms = seat->system->ms_from_input_time(time);
4011 seat->pointer_scroll.has_event_ms = true;
4012
4013 /* NOTE: this is used for touch based scrolling - or other input that doesn't scroll with
4014 * discrete "steps". This allows supporting smooth-scrolling without "touch" gesture support. */
4015 CLOG_DEBUG(LOG, "axis (axis=%u, value=%d)", axis, value);
4016 const int index = pointer_axis_as_index(axis);
4017 if (UNLIKELY(index == -1)) {
4018 return;
4019 }
4020 seat->pointer_scroll.smooth_xy[index] = value;
4021
4023}
4024
4025static void pointer_handle_frame(void *data, wl_pointer * /*wl_pointer*/)
4026{
4027 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4028
4029 CLOG_DEBUG(LOG, "frame");
4030
4031 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4032 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
4033 for (int ty_index = 0; ty_index < seat->pointer_events.frame_pending.frame_types_num;
4034 ty_index++)
4035 {
4037 const uint64_t event_ms = seat->pointer_events.frame_pending.frame_event_ms[ty_index];
4038 switch (ty) {
4039 /* Use motion for pressure and tilt as there are no explicit event types for these. */
4041 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4043 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), GHOST_TABLET_DATA_NONE));
4044 break;
4045 }
4048
4049 /* The scroll data is "interpreted" before generating the events,
4050 * this is needed because data is accumulated
4051 * with support for handling smooth scroll as discrete events (for example). */
4052
4053 /* Most scroll events have no time, use `ps.event_ms` instead.
4054 * This is assigned for the scroll events that do set a time.
4055 * In practice the values tends to be set but fall back to the current time. */
4056 GHOST_ASSERT(event_ms == 0, "Scroll events are not expected to have a time!");
4057 /* Handle value120 to discrete steps first. */
4058 if (ps.discrete120_xy[0] || ps.discrete120_xy[1]) {
4059 for (int i = 0; i < 2; i++) {
4061 ps.discrete120_xy[i] = 0;
4062 /* The values will have been normalized so 120 represents a single click-step. */
4063 ps.discrete_xy[i] = ps.discrete120_xy_accum[i] / 120;
4064 ps.discrete120_xy_accum[i] -= ps.discrete_xy[i] * 120;
4065 }
4066 }
4067
4068 /* Multiple wheel events may have been generated and it's not known which.
4069 * The logic here handles prioritizing how they should be handled. */
4070 if (ps.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) {
4071 /* We never want mouse wheel events to be treated as smooth scrolling as this
4072 * causes mouse wheel scroll to orbit the view, see #120587.
4073 * Although it could be supported if the event system would forward
4074 * the source of the scroll action (a wheel or touch device). */
4075 ps.smooth_xy[0] = 0;
4076 ps.smooth_xy[1] = 0;
4077 }
4078 else if (ps.axis_source == WL_POINTER_AXIS_SOURCE_FINGER) {
4082 /* If discrete steps have been sent, use them as-is. */
4083 if ((ps.discrete_xy[0] == 0) && (ps.discrete_xy[1] == 0)) {
4084 /* Convert smooth to discrete. */
4085 for (int i = 0; i < 2; i++) {
4086 smooth_as_discrete.smooth_xy_accum[i] += ps.smooth_xy[i];
4087 if (std::abs(smooth_as_discrete.smooth_xy_accum[i]) >= smooth_as_discrete_steps)
4088 {
4089 ps.discrete_xy[i] = smooth_as_discrete.smooth_xy_accum[i] /
4091 smooth_as_discrete.smooth_xy_accum[i] -= ps.discrete_xy[i] *
4093 }
4094 }
4095 }
4096 ps.smooth_xy[0] = 0;
4097 ps.smooth_xy[1] = 0;
4098 }
4099 }
4100
4101 /* Both discrete and smooth events may be set at once, never generate events for both
4102 * as this will be handling the same event in to different ways.
4103 * Prioritize discrete axis events for the mouse wheel, otherwise smooth scroll. */
4104 if (ps.axis_source == WL_POINTER_AXIS_SOURCE_WHEEL) {
4105 if (ps.discrete_xy[0]) {
4106 ps.smooth_xy[0] = 0;
4107 }
4108 if (ps.discrete_xy[1]) {
4109 ps.smooth_xy[1] = 0;
4110 }
4111 }
4112 else {
4113 if (ps.smooth_xy[0]) {
4114 ps.discrete_xy[0] = 0;
4115 }
4116 if (ps.smooth_xy[1]) {
4117 ps.discrete_xy[1] = 0;
4118 }
4119 }
4120
4121 /* Done evaluating scroll input, generate the events. */
4122 if (ps.discrete_xy[0] || ps.discrete_xy[1]) {
4123 if (ps.discrete_xy[0]) {
4125 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4126 win,
4128 ps.discrete_xy[0]));
4129 }
4130 if (ps.discrete_xy[1]) {
4132 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4133 win,
4135 -ps.discrete_xy[1]));
4136 }
4137 ps.discrete_xy[0] = 0;
4138 ps.discrete_xy[1] = 0;
4139 }
4140
4141 if (ps.smooth_xy[0] || ps.smooth_xy[1]) {
4142 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4144 ps.has_event_ms ? ps.event_ms : seat->system->getMilliSeconds(),
4145 win,
4147 UNPACK2(event_xy),
4148 /* NOTE: scaling the delta doesn't seem necessary.
4149 * NOTE: inverting delta gives correct results, see: QTBUG-85767.
4150 * NOTE: the preference to invert scrolling (in GNOME at least)
4151 * has already been applied so there is no need to read this preference. */
4152 -wl_fixed_to_int(ps.smooth_xy[0]),
4153 -wl_fixed_to_int(ps.smooth_xy[1]),
4154 /* NOTE: GHOST does not support per-axis inversion.
4155 * Assume inversion is used or not. */
4156 ps.inverted_xy[0] || ps.inverted_xy[1]));
4157
4158 ps.smooth_xy[0] = 0;
4159 ps.smooth_xy[1] = 0;
4160 }
4161
4162 ps.axis_source = WL_POINTER_AXIS_SOURCE_WHEEL;
4163 ps.inverted_xy[0] = false;
4164 ps.inverted_xy[1] = false;
4165
4166 ps.event_ms = 0;
4167 ps.has_event_ms = false;
4168
4169 break;
4170 }
4171#ifdef NDEBUG
4172 default:
4173#else /* Warn when any events aren't handled (in debug builds). */
4188#endif
4189 {
4190 const int button_enum_offset = int(ty) - int(GWL_Pointer_EventTypes::Button0_Down);
4191 const int button_index = button_enum_offset / 2;
4192 const bool button_down = (button_index * 2) == button_enum_offset;
4193 const GHOST_TButton ebutton = gwl_pointer_events_ebutton[button_index];
4194 const GHOST_TEventType etype = button_down ? GHOST_kEventButtonDown :
4196 seat->pointer.buttons.set(ebutton, button_down);
4198 new GHOST_EventButton(event_ms, etype, win, ebutton, GHOST_TABLET_DATA_NONE));
4199 break;
4200 }
4201 }
4202 }
4203 }
4204
4206}
4208 wl_pointer * /*wl_pointer*/,
4209 const uint32_t axis_source)
4210{
4211 CLOG_DEBUG(LOG, "axis_source (axis_source=%u)", axis_source);
4212 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4213 seat->pointer_scroll.axis_source = (enum wl_pointer_axis_source)axis_source;
4214}
4216 wl_pointer * /*wl_pointer*/,
4217 const uint32_t time,
4218 const uint32_t axis)
4219{
4220 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4221 seat->pointer_scroll.event_ms = seat->system->ms_from_input_time(time);
4222 seat->pointer_scroll.has_event_ms = true;
4223
4225 /* Reset the scroll steps when the touch event ends.
4226 * Done so the user doesn't accidentally bump smooth scroll input a small number of steps
4227 * causing an unexpected discrete step caused by a very small amount of smooth-scrolling. */
4230 smooth_as_discrete.smooth_xy_accum[0] = 0;
4231 smooth_as_discrete.smooth_xy_accum[1] = 0;
4232 }
4233
4234 CLOG_DEBUG(LOG, "axis_stop (axis=%u)", axis);
4235}
4237 wl_pointer * /*wl_pointer*/,
4238 const uint32_t axis,
4239 const int32_t discrete)
4240{
4241 /* NOTE: a discrete axis are typically mouse wheel events.
4242 * The non-discrete version of this function is used for touch-pad. */
4243 CLOG_DEBUG(LOG, "axis_discrete (axis=%u, discrete=%d)", axis, discrete);
4244 const int index = pointer_axis_as_index(axis);
4245 if (UNLIKELY(index == -1)) {
4246 return;
4247 }
4248 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4249 seat->pointer_scroll.discrete_xy[index] = discrete;
4250
4252}
4254 wl_pointer * /*wl_pointer*/,
4255 const uint32_t axis,
4256 const int32_t value120)
4257{
4258 /* Only available in interface version 8. */
4259 CLOG_DEBUG(LOG, "axis_value120 (axis=%u, value120=%d)", axis, value120);
4260 const int index = pointer_axis_as_index(axis);
4261 if (UNLIKELY(index == -1)) {
4262 return;
4263 }
4264 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4265 seat->pointer_scroll.discrete120_xy[index] = value120;
4266
4268}
4269#ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM /* Requires WAYLAND 1.22 or newer. */
4270static void pointer_handle_axis_relative_direction(void *data,
4271 wl_pointer * /*wl_pointer*/,
4272 const uint32_t axis,
4273 const uint32_t direction)
4274{
4275 /* Only available in interface version 9. */
4276 CLOG_DEBUG(LOG, "axis_relative_direction (axis=%u, direction=%u)", axis, direction);
4277 const int index = pointer_axis_as_index(axis);
4278 if (UNLIKELY(index == -1)) {
4279 return;
4280 }
4281 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4282 seat->pointer_scroll.inverted_xy[index] = (direction ==
4283 WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED);
4284}
4285#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */
4286
4287static const wl_pointer_listener pointer_listener = {
4288 /*enter*/ pointer_handle_enter,
4289 /*leave*/ pointer_handle_leave,
4290 /*motion*/ pointer_handle_motion,
4291 /*button*/ pointer_handle_button,
4292 /*axis*/ pointer_handle_axis,
4293 /*frame*/ pointer_handle_frame,
4294 /*axis_source*/ pointer_handle_axis_source,
4295 /*axis_stop*/ pointer_handle_axis_stop,
4296 /*axis_discrete*/ pointer_handle_axis_discrete,
4297 /*axis_value120*/ pointer_handle_axis_value120,
4298#ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM
4299 /*axis_relative_direction*/ pointer_handle_axis_relative_direction,
4300#endif
4301};
4302
4303#undef LOG
4304
4306
4307/* -------------------------------------------------------------------- */
4310
4311#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
4312static CLG_LogRef LOG_WL_POINTER_GESTURE_HOLD = {"ghost.wl.handle.pointer_gesture.hold"};
4313# define LOG (&LOG_WL_POINTER_GESTURE_HOLD)
4314
4315static void gesture_hold_handle_begin(
4316 void * /*data*/,
4317 zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/,
4318 const uint32_t /*serial*/,
4319 const uint32_t /*time*/,
4320 wl_surface * /*surface*/,
4321 const uint32_t fingers)
4322{
4323 CLOG_DEBUG(LOG, "begin (fingers=%u)", fingers);
4324}
4325
4326static void gesture_hold_handle_end(void * /*data*/,
4327 zwp_pointer_gesture_hold_v1 * /*zwp_pointer_gesture_hold_v1*/,
4328 const uint32_t /*serial*/,
4329 const uint32_t /*time*/,
4330 const int32_t cancelled)
4331{
4332 CLOG_DEBUG(LOG, "end (cancelled=%i)", cancelled);
4333}
4334
4335static const zwp_pointer_gesture_hold_v1_listener gesture_hold_listener = {
4336 /*begin*/ gesture_hold_handle_begin,
4337 /*end*/ gesture_hold_handle_end,
4338};
4339
4340# undef LOG
4341#endif /* ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE */
4342
4344
4345/* -------------------------------------------------------------------- */
4348
4349#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
4350static CLG_LogRef LOG_WL_POINTER_GESTURE_PINCH = {"ghost.wl.handle.pointer_gesture.pinch"};
4351# define LOG (&LOG_WL_POINTER_GESTURE_PINCH)
4352
4353static void gesture_pinch_handle_begin(void *data,
4354 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4355 const uint32_t /*serial*/,
4356 const uint32_t time,
4357 wl_surface * /*surface*/,
4358 const uint32_t fingers)
4359{
4360 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4361 (void)seat->system->ms_from_input_time(time); /* Only update internal time. */
4362
4363 CLOG_DEBUG(LOG, "begin (fingers=%u)", fingers);
4364
4365 /* Reset defaults. */
4367
4368 const GHOST_WindowWayland *win = nullptr;
4369 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4370 win = ghost_wl_surface_user_data(wl_surface_focus);
4371 }
4372
4373 /* NOTE(@ideasman42): Scale factors match Blender's operators & default preferences.
4374 * For these values to work correctly, operator logic will need to be changed not to scale input
4375 * by the region size (as with 3D view zoom) or preference for 3D view orbit sensitivity.
4376 *
4377 * By working "correctly" I mean that a rotation action where the users fingers rotate to
4378 * opposite locations should always rotate the viewport 180d, since users will expect the
4379 * physical location of their fingers to match the viewport.
4380 * Similarly with zoom, the scale value from the pinch action can be mapped to a zoom level
4381 * although unlike rotation, an inexact mapping is less noticeable.
4382 * Users may even prefer the zoom level to be scaled - which could be a preference. */
4383 seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1);
4384 /* The value 300 matches a value used in clip & image zoom operators.
4385 * It seems OK for the 3D view too. */
4387 /* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation,
4388 * although preferences can scale the sensitivity (which would be skipped ideally). */
4390
4391 if (win) {
4392 /* NOTE(@ideasman42): Blender's use of trackpad coordinates is inconsistent and needs work.
4393 * This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
4394 * Some operators scale by the UI scale, some don't.
4395 * Even though the window scale is correct, it doesn't account for the UI scale preference
4396 * (which GHOST doesn't know about).
4397 *
4398 * If support for this were all that was needed it could be handled in GHOST,
4399 * however as the operators are not even using coordinates compatible with each other,
4400 * it would be better to resolve this by passing rotation & zoom levels directly,
4401 * instead of attempting to handle them as cursor coordinates. */
4402 const GWL_WindowScaleParams &scale_params = win->scale_params_get();
4404 scale_params, seat->pointer_gesture_pinch.scale.factor);
4406 scale_params, seat->pointer_gesture_pinch.rotation.factor);
4407 }
4408}
4409
4410static void gesture_pinch_handle_update(void *data,
4411 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4412 const uint32_t time,
4413 const wl_fixed_t dx,
4414 const wl_fixed_t dy,
4415 const wl_fixed_t scale,
4416 const wl_fixed_t rotation)
4417{
4418 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4419 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4420
4422 "update (dx=%.3f, dy=%.3f, scale=%.3f, rotation=%.3f)",
4423 wl_fixed_to_double(dx),
4424 wl_fixed_to_double(dy),
4425 wl_fixed_to_double(scale),
4426 wl_fixed_to_double(rotation));
4427
4428 GHOST_WindowWayland *win = nullptr;
4429
4430 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
4431 win = ghost_wl_surface_user_data(wl_surface_focus);
4432 }
4433
4434 /* Scale defaults to `wl_fixed_from_int(1)` which may change while pinching.
4435 * This needs to be converted to a delta. */
4436 const wl_fixed_t scale_delta = scale - seat->pointer_gesture_pinch.scale.value;
4437 const int scale_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta(
4438 &seat->pointer_gesture_pinch.scale, scale_delta);
4439
4440 /* Rotation in degrees, unlike scale this is a delta. */
4441 const int rotation_as_delta_px = gwl_scaled_fixed_t_add_and_calc_rounded_delta(
4442 &seat->pointer_gesture_pinch.rotation, rotation);
4443
4444 if (win) {
4445 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)};
4446 if (scale_as_delta_px) {
4448 win,
4450 event_xy[0],
4451 event_xy[1],
4452 scale_as_delta_px,
4453 0,
4454 false));
4455 }
4456
4457 if (rotation_as_delta_px) {
4459 win,
4461 event_xy[0],
4462 event_xy[1],
4463 rotation_as_delta_px,
4464 0,
4465 false));
4466 }
4467 }
4468}
4469
4470static void gesture_pinch_handle_end(void *data,
4471 zwp_pointer_gesture_pinch_v1 * /*pinch*/,
4472 const uint32_t /*serial*/,
4473 const uint32_t time,
4474 const int32_t cancelled)
4475{
4476 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4477 (void)seat->system->ms_from_input_time(time); /* Only update internal time. */
4478
4479 CLOG_DEBUG(LOG, "end (cancelled=%i)", cancelled);
4480}
4481
4482static const zwp_pointer_gesture_pinch_v1_listener gesture_pinch_listener = {
4483 /*begin*/ gesture_pinch_handle_begin,
4484 /*update*/ gesture_pinch_handle_update,
4485 /*end*/ gesture_pinch_handle_end,
4486};
4487
4488# undef LOG
4489#endif /* ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE */
4490
4492
4493/* -------------------------------------------------------------------- */
4501
4502#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
4503static CLG_LogRef LOG_WL_POINTER_GESTURE_SWIPE = {"ghost.wl.handle.pointer_gesture.swipe"};
4504# define LOG (&LOG_WL_POINTER_GESTURE_SWIPE)
4505
4506static void gesture_swipe_handle_begin(
4507 void * /*data*/,
4508 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4509 uint32_t /*serial*/,
4510 uint32_t /*time*/,
4511 wl_surface * /*surface*/,
4512 uint32_t fingers)
4513{
4514 CLOG_DEBUG(LOG, "begin (fingers=%u)", fingers);
4515}
4516
4517static void gesture_swipe_handle_update(
4518 void * /*data*/,
4519 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4520 uint32_t /*time*/,
4521 wl_fixed_t dx,
4522 wl_fixed_t dy)
4523{
4524 CLOG_DEBUG(LOG, "update (dx=%.3f, dy=%.3f)", wl_fixed_to_double(dx), wl_fixed_to_double(dy));
4525}
4526
4527static void gesture_swipe_handle_end(
4528 void * /*data*/,
4529 zwp_pointer_gesture_swipe_v1 * /*zwp_pointer_gesture_swipe_v1*/,
4530 uint32_t /*serial*/,
4531 uint32_t /*time*/,
4532 int32_t cancelled)
4533{
4534 CLOG_DEBUG(LOG, "end (cancelled=%i)", cancelled);
4535}
4536
4537static const zwp_pointer_gesture_swipe_v1_listener gesture_swipe_listener = {
4538 /*begin*/ gesture_swipe_handle_begin,
4539 /*update*/ gesture_swipe_handle_update,
4540 /*end*/ gesture_swipe_handle_end,
4541};
4542
4543# undef LOG
4544#endif /* ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE */
4545
4547
4548/* -------------------------------------------------------------------- */
4559
4560static CLG_LogRef LOG_WL_TOUCH = {"ghost.wl.handle.touch"};
4561#define LOG (&LOG_WL_TOUCH)
4562
4564 wl_touch * /*touch*/,
4565 const uint32_t serial,
4566 const uint32_t time,
4567 wl_surface *surface,
4568 const int32_t id,
4569 const wl_fixed_t x,
4570 const wl_fixed_t y)
4571{
4572 /* Touching down is equivalent to moving a pointer and holding its left mouse button. */
4573
4574 CLOG_DEBUG(LOG, "down");
4575 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4576
4577 /* Null when just destroyed. */
4579 CLOG_DEBUG(LOG, "down (skipped on empty surface)");
4580 return;
4581 }
4582
4583 /* Only track one point at a time.
4584 * At some point *full* touch supported could be. */
4585 if (seat->touch_state.is_touching) {
4586 return;
4587 }
4588
4589 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4590
4591 /* Set generic pointer state. */
4592 seat->touch.xy[0] = x;
4593 seat->touch.xy[1] = y;
4594 seat->touch.serial = serial;
4595 seat->touch.wl.surface_window = surface;
4596
4597 /* Set the active pointer. */
4598 seat->cursor_source_serial = serial;
4599
4600 /* Set touch-tracking state. */
4601 seat->touch_state.is_touching = true;
4602 seat->touch_state.down_id = id;
4603 seat->touch_state.motion_pending = true;
4604 seat->touch_state.motion_event_time_ms = event_ms;
4605 seat->touch_state.down_pending = true;
4606 seat->touch_state.down_event_time_ms = event_ms;
4607
4608 /* Signal the window manager to update the cursor shape
4609 * into whatever shape it considers correct for the touchscreen's pointer. */
4610 GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->touch.wl.surface_window);
4611 win->cursor_shape_refresh();
4612}
4613
4614static void touch_seat_handle_up(void *data,
4615 wl_touch * /*touch*/,
4616 const uint32_t /*serial*/,
4617 const uint32_t time,
4618 const int32_t id)
4619{
4620 CLOG_DEBUG(LOG, "up");
4621 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4622
4623 /* Only track one contact point at a time. */
4624 if (seat->touch_state.down_id != id) {
4625 return;
4626 }
4627
4628 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4629 seat->touch_state.is_touching = false;
4630 seat->touch_state.up_pending = true;
4631 seat->touch_state.up_event_time_ms = event_ms;
4632}
4633
4635 wl_touch * /*touch*/,
4636 const uint32_t time,
4637 const int32_t id,
4638 const wl_fixed_t x,
4639 const wl_fixed_t y)
4640{
4641 CLOG_DEBUG(LOG, "motion");
4642 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4643
4644 /* Only track one contact point at a time. */
4645 if (seat->touch_state.down_id != id) {
4646 return;
4647 }
4648 if (seat->touch_state.is_touching == false) {
4649 return;
4650 }
4651
4652 const uint64_t event_ms = seat->system->ms_from_input_time(time);
4653 seat->touch.xy[0] = x;
4654 seat->touch.xy[1] = y;
4655
4656 seat->touch_state.motion_event_time_ms = event_ms;
4657 seat->touch_state.motion_pending = true;
4658}
4659
4660static void touch_seat_handle_frame(void *data, wl_touch * /*touch*/)
4661{
4662 CLOG_DEBUG(LOG, "frame");
4663 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
4664 if (wl_surface *wl_surface_focus = seat->touch.wl.surface_window) {
4665 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
4666
4667 GHOST_Event *touch_events[3];
4668 int touch_events_num = 0;
4669
4670 /* For a finger move, generate a cursor move. */
4671 if (seat->touch_state.motion_pending == true) {
4672 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->touch.xy)};
4673 touch_events[touch_events_num++] = new GHOST_EventCursor(
4676 win,
4677 UNPACK2(event_xy),
4679
4680 seat->touch_state.motion_pending = false;
4682 }
4683
4684 /* For a finger press, generate left-mouse button press. */
4685 if (seat->touch_state.down_pending == true) {
4686 seat->touch.buttons.set(GHOST_kButtonMaskLeft, true);
4687
4688 touch_events[touch_events_num++] = new GHOST_EventButton(
4691 win,
4694
4695 seat->touch_state.down_pending = false;
4697 }
4698
4699 /* For a finger release, generate a left-mouse button release. */
4700 if (seat->touch_state.up_pending == true) {
4701 seat->touch.buttons.set(GHOST_kButtonMaskLeft, false);
4702
4703 touch_events[touch_events_num++] = new GHOST_EventButton(seat->touch_state.up_event_time_ms,
4705 win,
4708
4709 seat->touch_state.up_pending = false;
4710 seat->touch_state.up_event_time_ms = 0;
4711 seat->touch.wl.surface_window = nullptr;
4712 }
4713
4714 GHOST_ASSERT(touch_events_num <= ARRAY_SIZE(touch_events), "Buffer overflow");
4715
4716 /* Ensure events are ordered in time. */
4717 if (UNLIKELY(touch_events_num > 1)) {
4718 std::sort(touch_events,
4719 touch_events + touch_events_num,
4720 [](const GHOST_Event *event_a, const GHOST_Event *event_b) -> bool {
4721 return event_a->getTime() < event_b->getTime();
4722 });
4723 }
4724
4725 for (int i = 0; i < touch_events_num; i++) {
4726 seat->system->pushEvent_maybe_pending(touch_events[i]);
4727 }
4728 }
4729}
4730
4731static void touch_seat_handle_cancel(void * /*data*/, wl_touch * /*wl_touch*/)
4732{
4733
4734 CLOG_DEBUG(LOG, "cancel");
4735}
4736
4737static void touch_seat_handle_shape(void * /*data*/,
4738 wl_touch * /*touch*/,
4739 const int32_t /*id*/,
4740 const wl_fixed_t /*major*/,
4741 const wl_fixed_t /*minor*/)
4742{
4743 CLOG_DEBUG(LOG, "shape");
4744}
4745
4746static void touch_seat_handle_orientation(void * /*data*/,
4747 wl_touch * /*touch*/,
4748 const int32_t /*id*/,
4749 const wl_fixed_t /*orientation*/)
4750{
4751 CLOG_DEBUG(LOG, "orientation");
4752}
4753
4754static const wl_touch_listener touch_seat_listener = {
4755 /*down*/ touch_seat_handle_down,
4756 /*up*/ touch_seat_handle_up,
4757 /*motion*/ touch_seat_handle_motion,
4758 /*frame*/ touch_seat_handle_frame,
4759 /*cancel*/ touch_seat_handle_cancel,
4760 /*shape*/ touch_seat_handle_shape,
4761 /*orientation*/ touch_seat_handle_orientation,
4762};
4763
4764#undef LOG
4765
4767
4768/* -------------------------------------------------------------------- */
4771
4772static CLG_LogRef LOG_WL_TABLET_TOOL = {"ghost.wl.handle.tablet_tool"};
4773#define LOG (&LOG_WL_TABLET_TOOL)
4774
4776 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4777 const uint32_t tool_type)
4778{
4779 CLOG_DEBUG(LOG, "type (type=%u)", tool_type);
4780
4781 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4782
4783 tablet_tool->data.Active = tablet_tool_map_type((enum zwp_tablet_tool_v2_type)tool_type);
4784}
4785
4786static void tablet_tool_handle_hardware_serial(void * /*data*/,
4787 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4788 const uint32_t /*hardware_serial_hi*/,
4789 const uint32_t /*hardware_serial_lo*/)
4790{
4791 CLOG_DEBUG(LOG, "hardware_serial");
4792}
4793
4794static void tablet_tool_handle_hardware_id_wacom(void * /*data*/,
4795 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4796 const uint32_t /*hardware_id_hi*/,
4797 const uint32_t /*hardware_id_lo*/)
4798{
4799 CLOG_DEBUG(LOG, "hardware_id_wacom");
4800}
4801
4802static void tablet_tool_handle_capability(void * /*data*/,
4803 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4804 const uint32_t capability)
4805{
4807 "capability (tilt=%d, distance=%d, rotation=%d, slider=%d, wheel=%d)",
4808 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_TILT) != 0,
4809 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE) != 0,
4810 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION) != 0,
4811 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER) != 0,
4812 (capability & ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL) != 0);
4813}
4814
4815static void tablet_tool_handle_done(void * /*data*/, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4816{
4817 CLOG_DEBUG(LOG, "done");
4818}
4819static void tablet_tool_handle_removed(void *data, zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
4820{
4821 CLOG_DEBUG(LOG, "removed");
4822
4823 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4824 GWL_Seat *seat = tablet_tool->seat;
4825
4826 if (tablet_tool->wl.surface_cursor) {
4827 wl_surface_destroy(tablet_tool->wl.surface_cursor);
4828 }
4829 if (tablet_tool->shape.device) {
4830 wp_cursor_shape_device_v1_destroy(tablet_tool->shape.device);
4831 }
4832 seat->wp.tablet_tools.erase(zwp_tablet_tool_v2);
4833
4834 delete tablet_tool;
4835}
4837 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4838 const uint32_t serial,
4839 zwp_tablet_v2 * /*tablet*/,
4841{
4843 CLOG_DEBUG(LOG, "proximity_in (skipped)");
4844 return;
4845 }
4846 CLOG_DEBUG(LOG, "proximity_in");
4847
4848 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4849 tablet_tool->proximity = true;
4850 tablet_tool->serial = serial;
4851
4852 GWL_Seat *seat = tablet_tool->seat;
4853 seat->cursor_source_serial = serial;
4855 seat->tablet.serial = serial;
4856
4857 seat->data_source_serial = serial;
4858
4859 seat->system->seat_active_set(seat);
4860
4862 win->cursor_shape_refresh();
4863
4864 /* Update #GHOST_TabletData. */
4865 GHOST_TabletData &td = tablet_tool->data;
4866 /* Reset, to avoid using stale tilt/pressure. */
4867 td.Xtilt = 0.0f;
4868 td.Ytilt = 0.0f;
4869 /* In case pressure isn't supported. */
4870 td.Pressure = 1.0f;
4871}
4873 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4874{
4875 CLOG_DEBUG(LOG, "proximity_out");
4876 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4877 /* Defer clearing the wl_surface until the frame is handled.
4878 * Without this, the frame can not access the wl_surface. */
4879 tablet_tool->proximity = false;
4880 tablet_tool->serial = 0;
4881}
4882
4884 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4885 const uint32_t serial)
4886{
4887 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4888 GWL_Seat *seat = tablet_tool->seat;
4889
4890 CLOG_DEBUG(LOG, "down");
4891
4892 seat->data_source_serial = serial;
4893
4895}
4896
4897static void tablet_tool_handle_up(void *data, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
4898{
4899 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4900
4901 CLOG_DEBUG(LOG, "up");
4902
4904}
4905
4907 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4908 const wl_fixed_t x,
4909 const wl_fixed_t y)
4910{
4911 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4912
4913 CLOG_DEBUG(LOG, "motion");
4914
4915 tablet_tool->xy[0] = x;
4916 tablet_tool->xy[1] = y;
4917 tablet_tool->has_xy = true;
4918
4920}
4921
4923 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4924 const uint32_t pressure)
4925{
4926 const float pressure_unit = float(pressure) / 65535;
4927 CLOG_DEBUG(LOG, "pressure (%.4f)", pressure_unit);
4928
4929 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4930 GHOST_TabletData &td = tablet_tool->data;
4931 td.Pressure = pressure_unit;
4932
4934}
4935
4936static void tablet_tool_handle_distance(void * /*data*/,
4937 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4938 const uint32_t distance)
4939{
4940 CLOG_DEBUG(LOG, "distance (distance=%u)", distance);
4941}
4942
4944 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4945 const wl_fixed_t tilt_x,
4946 const wl_fixed_t tilt_y)
4947{
4948 /* Map X tilt to `-1.0 (left)..1.0 (right)`.
4949 * Map Y tilt to `-1.0 (away from user)..1.0 (toward user)`. */
4950 const float tilt_unit[2] = {
4951 float(wl_fixed_to_double(tilt_x) / 90.0f),
4952 float(wl_fixed_to_double(tilt_y) / 90.0f),
4953 };
4954 CLOG_DEBUG(LOG, "tilt (x=%.4f, y=%.4f)", UNPACK2(tilt_unit));
4955 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4956 GHOST_TabletData &td = tablet_tool->data;
4957 td.Xtilt = std::clamp(tilt_unit[0], -1.0f, 1.0f);
4958 td.Ytilt = std::clamp(tilt_unit[1], -1.0f, 1.0f);
4959
4961}
4962
4963static void tablet_tool_handle_rotation(void * /*data*/,
4964 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4965 const wl_fixed_t degrees)
4966{
4967 CLOG_DEBUG(LOG, "rotation (degrees=%.4f)", wl_fixed_to_double(degrees));
4968}
4969
4970static void tablet_tool_handle_slider(void * /*data*/,
4971 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4972 const int32_t position)
4973{
4974 CLOG_DEBUG(LOG, "slider (position=%d)", position);
4975}
4977 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4978 const wl_fixed_t /*degrees*/,
4979 const int32_t clicks)
4980{
4981 if (clicks == 0) {
4982 return;
4983 }
4984
4985 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
4986
4987 CLOG_DEBUG(LOG, "wheel (clicks=%d)", clicks);
4988
4989 tablet_tool->frame_pending.wheel.clicks = clicks;
4990
4992}
4993
4995 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
4996 const uint32_t serial,
4997 const uint32_t button,
4998 const uint32_t state)
4999{
5000 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
5001 GWL_Seat *seat = tablet_tool->seat;
5002
5003 CLOG_DEBUG(LOG, "button (button=%u, state=%u)", button, state);
5004
5005 bool is_press = false;
5006 switch (state) {
5007 case WL_POINTER_BUTTON_STATE_RELEASED: {
5008 is_press = false;
5009 break;
5010 }
5011 case WL_POINTER_BUTTON_STATE_PRESSED: {
5012 is_press = true;
5013 break;
5014 }
5015 }
5016
5017 seat->data_source_serial = serial;
5018
5020 switch (button) {
5021 case BTN_STYLUS: {
5024 break;
5025 }
5026 case BTN_STYLUS2: {
5029 break;
5030 }
5031 case BTN_STYLUS3: {
5034 break;
5035 }
5036 }
5037
5039 gwl_tablet_tool_frame_event_add(tablet_tool, ty);
5040 }
5041}
5043 zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/,
5044 const uint32_t time)
5045{
5046 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(data);
5047 GWL_Seat *seat = tablet_tool->seat;
5048 const uint64_t event_ms = seat->system->ms_from_input_time(time);
5049
5050 CLOG_DEBUG(LOG, "frame");
5051
5052 /* No need to check the surfaces origin, it's already known to be owned by GHOST. */
5053 if (wl_surface *wl_surface_focus = seat->tablet.wl.surface_window) {
5054 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
5055 bool has_motion = false;
5056
5057 for (int ty_index = 0; ty_index < tablet_tool->frame_pending.frame_types_num; ty_index++) {
5058 const GWL_TabletTool_EventTypes ty = tablet_tool->frame_pending.frame_types[ty_index];
5059 switch (ty) {
5060 /* Use motion for pressure and tilt as there are no explicit event types for these. */
5064 /* Only one motion event per frame. */
5065 if (has_motion) {
5066 break;
5067 }
5068 /* Can happen when there is pressure/tilt without motion. */
5069 if (tablet_tool->has_xy == false) {
5070 break;
5071 }
5072
5073 seat->tablet.xy[0] = tablet_tool->xy[0];
5074 seat->tablet.xy[1] = tablet_tool->xy[1];
5075
5076 const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, tablet_tool->xy)};
5078 event_ms, GHOST_kEventCursorMove, win, UNPACK2(event_xy), tablet_tool->data));
5079 has_motion = true;
5080 break;
5081 }
5082#ifdef NDEBUG
5083 default:
5084#else /* Warn when any events aren't handled (in debug builds). */
5093#endif
5094 {
5095 const int button_enum_offset = int(ty) - int(GWL_TabletTool_EventTypes::Stylus0_Down);
5096 const int button_index = button_enum_offset / 2;
5097 const bool button_down = (button_index * 2) == button_enum_offset;
5098 const GHOST_TButton ebutton = gwl_tablet_tool_ebutton[button_index];
5099 const GHOST_TEventType etype = button_down ? GHOST_kEventButtonDown :
5101 seat->tablet.buttons.set(ebutton, button_down);
5103 new GHOST_EventButton(event_ms, etype, win, ebutton, tablet_tool->data));
5104 break;
5105 }
5108 new GHOST_EventWheel(event_ms,
5109 win,
5111 -tablet_tool->frame_pending.wheel.clicks));
5112 break;
5113 }
5114 }
5115 }
5116
5117 if (tablet_tool->proximity == false) {
5118 win->cursor_shape_refresh();
5119 }
5120 }
5121
5122 if (tablet_tool->proximity == false) {
5123 seat->tablet.wl.surface_window = nullptr;
5124 }
5125
5127}
5128
5129static const zwp_tablet_tool_v2_listener tablet_tool_listner = {
5130 /*type*/ tablet_tool_handle_type,
5131 /*hardware_serial*/ tablet_tool_handle_hardware_serial,
5132 /*hardware_id_wacom*/ tablet_tool_handle_hardware_id_wacom,
5133 /*capability*/ tablet_tool_handle_capability,
5134 /*done*/ tablet_tool_handle_done,
5135 /*removed*/ tablet_tool_handle_removed,
5136 /*proximity_in*/ tablet_tool_handle_proximity_in,
5137 /*proximity_out*/ tablet_tool_handle_proximity_out,
5138 /*down*/ tablet_tool_handle_down,
5139 /*up*/ tablet_tool_handle_up,
5140 /*motion*/ tablet_tool_handle_motion,
5141 /*pressure*/ tablet_tool_handle_pressure,
5142 /*distance*/ tablet_tool_handle_distance,
5143 /*tilt*/ tablet_tool_handle_tilt,
5144 /*rotation*/ tablet_tool_handle_rotation,
5145 /*slider*/ tablet_tool_handle_slider,
5146 /*wheel*/ tablet_tool_handle_wheel,
5147 /*button*/ tablet_tool_handle_button,
5148 /*frame*/ tablet_tool_handle_frame,
5149};
5150
5151#undef LOG
5152
5154
5155/* -------------------------------------------------------------------- */
5158
5159static CLG_LogRef LOG_WL_TABLET_SEAT = {"ghost.wl.handle.tablet_seat"};
5160#define LOG (&LOG_WL_TABLET_SEAT)
5161
5162static void tablet_seat_handle_tablet_added(void * /*data*/,
5163 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5164 zwp_tablet_v2 *id)
5165{
5166 CLOG_DEBUG(LOG, "tablet_added (id=%p)", id);
5167}
5168
5170 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5171 zwp_tablet_tool_v2 *id)
5172{
5173 CLOG_DEBUG(LOG, "tool_added (id=%p)", id);
5174
5175 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5176 GWL_TabletTool *tablet_tool = new GWL_TabletTool();
5177 tablet_tool->seat = seat;
5178
5179 /* Every tool has its own cursor wl_surface. */
5180 tablet_tool->wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
5182
5183 wl_surface_add_listener(
5184 tablet_tool->wl.surface_cursor, &cursor_surface_listener, static_cast<void *>(seat));
5185
5186 zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listner, tablet_tool);
5187
5188 seat->wp.tablet_tools.insert(id);
5189}
5190
5191static void tablet_seat_handle_pad_added(void * /*data*/,
5192 zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/,
5193 zwp_tablet_pad_v2 *id)
5194{
5195 CLOG_DEBUG(LOG, "pad_added (id=%p)", id);
5196}
5197
5198static const zwp_tablet_seat_v2_listener tablet_seat_listener = {
5199 /*tablet_added*/ tablet_seat_handle_tablet_added,
5200 /*tool_added*/ tablet_seat_handle_tool_added,
5201 /*pad_added*/ tablet_seat_handle_pad_added,
5202};
5203
5204#undef LOG
5205
5207
5208/* -------------------------------------------------------------------- */
5211
5212static CLG_LogRef LOG_WL_KEYBOARD = {"ghost.wl.handle.keyboard"};
5213#define LOG (&LOG_WL_KEYBOARD)
5214
5216 wl_keyboard * /*wl_keyboard*/,
5217 const uint32_t format,
5218 const int32_t fd,
5219 const uint32_t size)
5220{
5221 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5222
5223 if ((!data) || (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)) {
5224 CLOG_DEBUG(LOG, "keymap (no data or wrong version)");
5225 close(fd);
5226 return;
5227 }
5228
5229 char *map_str = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0));
5230 if (map_str == MAP_FAILED) {
5231 close(fd);
5232 CLOG_DEBUG(LOG, "keymap mmap failed: %s", std::strerror(errno));
5233 return;
5234 }
5235
5236 xkb_keymap *keymap = xkb_keymap_new_from_string(
5237 seat->xkb.context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
5238 munmap(map_str, size);
5239 close(fd);
5240
5241 if (!keymap) {
5242 CLOG_DEBUG(LOG, "keymap (not found)");
5243 return;
5244 }
5245
5246 CLOG_DEBUG(LOG, "keymap");
5247
5248 /* Reset in case there was a previous non-zero active layout for the last key-map.
5249 * Note that this is set later by `wl_keyboard_listener::modifiers`, it's possible that handling
5250 * the first modifier will run #xkb_state_update_mask again (if the active layout is non-zero)
5251 * however as this is only done when the layout changed, it's harmless.
5252 * With a single layout - in practice the active layout will be zero. */
5253 seat->xkb.layout_active = 0;
5254
5255 if (seat->xkb.compose_state) {
5256 xkb_compose_state_reset(seat->xkb.compose_state);
5257 }
5258 else if (seat->xkb.compose_table) {
5259 seat->xkb.compose_state = xkb_compose_state_new(seat->xkb.compose_table,
5260 XKB_COMPOSE_STATE_NO_FLAGS);
5261 }
5262
5263 /* In practice we can assume `xkb_state_new` always succeeds. */
5264 xkb_state_unref(seat->xkb.state);
5265 seat->xkb.state = xkb_state_new(keymap);
5266
5267 xkb_state_unref(seat->xkb.state_empty);
5268 seat->xkb.state_empty = xkb_state_new(keymap);
5269
5270 for (int i = 0; i < MOD_INDEX_NUM; i++) {
5271 const GWL_ModifierInfo &mod_info = g_modifier_info_table[i];
5272 seat->xkb_keymap_mod_index[i] = xkb_keymap_mod_get_index(keymap, mod_info.xkb_id);
5273 }
5274 seat->xkb_keymap_mod_index_mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
5275 seat->xkb_keymap_mod_index_numlock = xkb_keymap_mod_get_index(keymap, "NumLock");
5276
5277 xkb_state_unref(seat->xkb.state_empty_with_shift);
5278 seat->xkb.state_empty_with_shift = nullptr;
5279 if (seat->xkb_keymap_mod_index[MOD_INDEX_SHIFT] != XKB_MOD_INVALID) {
5280 seat->xkb.state_empty_with_shift = xkb_state_new(keymap);
5281 }
5282
5283 xkb_state_unref(seat->xkb.state_empty_with_numlock);
5284 seat->xkb.state_empty_with_numlock = nullptr;
5285 if ((seat->xkb_keymap_mod_index_mod2 != XKB_MOD_INVALID) &&
5286 (seat->xkb_keymap_mod_index_numlock != XKB_MOD_INVALID))
5287 {
5288 seat->xkb.state_empty_with_numlock = xkb_state_new(keymap);
5289 }
5290
5292
5293#ifdef USE_NON_LATIN_KB_WORKAROUND
5294 seat->xkb_use_non_latin_workaround = false;
5295 if (seat->xkb.state_empty_with_shift) {
5296 seat->xkb_use_non_latin_workaround = true;
5297 for (xkb_keycode_t key_code = KEY_1 + EVDEV_OFFSET; key_code <= KEY_0 + EVDEV_OFFSET;
5298 key_code++)
5299 {
5300 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(seat->xkb.state_empty_with_shift,
5301 key_code);
5302 if (!(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9)) {
5303 seat->xkb_use_non_latin_workaround = false;
5304 break;
5305 }
5306 }
5307 }
5308#endif
5309
5311
5312 xkb_keymap_unref(keymap);
5313}
5314
5320static void keyboard_handle_enter(void *data,
5321 wl_keyboard * /*wl_keyboard*/,
5322 const uint32_t serial,
5324 wl_array *keys)
5325{
5326 /* Null when just destroyed. */
5328 CLOG_DEBUG(LOG, "enter (skipped)");
5329 return;
5330 }
5331 CLOG_DEBUG(LOG, "enter");
5332
5333 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5335
5336 seat->keyboard.serial = serial;
5337 seat->keyboard.wl.surface_window = wl_surface;
5338
5339 seat->system->seat_active_set(seat);
5340
5341 /* If there are any keys held when activating the window,
5342 * modifiers will be compared against the seat state,
5343 * only enabling modifiers that were previously disabled. */
5344 GWL_KeyboardDepressedState key_depressed_prev = seat->key_depressed;
5346
5347 /* Keep track of the last held repeating key, start the repeat timer if one exists. */
5348 struct {
5349 uint32_t key = std::numeric_limits<uint32_t>::max();
5350 xkb_keysym_t sym = 0;
5351 } repeat;
5352
5353 uint32_t *key;
5354 WL_ARRAY_FOR_EACH (key, keys) {
5355 const xkb_keycode_t key_code = *key + EVDEV_OFFSET;
5356 CLOG_DEBUG(LOG, "enter (key_held=%d)", int(key_code));
5357 const xkb_keysym_t sym = xkb_state_key_get_one_sym(seat->xkb.state, key_code);
5358 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, *key);
5359 if (gkey != GHOST_kKeyUnknown) {
5361 }
5362
5363 if (xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code)) {
5364 repeat.key = *key;
5365 repeat.sym = sym;
5366 }
5367 }
5368
5369 /* Caller has no time-stamp, set from system. */
5370 const uint64_t event_ms = seat->system->getMilliSeconds();
5371 keyboard_depressed_state_push_events_from_change(seat, win, event_ms, key_depressed_prev);
5372
5373 if ((repeat.key != std::numeric_limits<uint32_t>::max()) && (seat->key_repeat.rate > 0)) {
5374 /* Since the key has been held, immediately send a press event.
5375 * This also ensures the key will be registered as pressed, see #117896. */
5376#ifdef USE_EVENT_BACKGROUND_THREAD
5377 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5378#endif
5379 /* Should have been cleared on leave, set here just in case. */
5380 if (UNLIKELY(seat->key_repeat.timer)) {
5382 }
5383
5384 const xkb_keycode_t key_code = repeat.key + EVDEV_OFFSET;
5385 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(repeat.sym, repeat.key);
5386
5387 GWL_KeyRepeatPlayload *key_repeat_payload = new GWL_KeyRepeatPlayload();
5388 key_repeat_payload->seat = seat;
5389 key_repeat_payload->key_code = key_code;
5390 key_repeat_payload->key_data.gkey = gkey;
5391
5392 gwl_seat_key_repeat_timer_add(seat, gwl_seat_key_repeat_timer_fn, key_repeat_payload, false);
5393 /* Ensure there is a press event on enter so this is known to be held before any mouse
5394 * button events which may use a key-binding that depends on this key being held. */
5396 }
5397}
5398
5404static void keyboard_handle_leave(void *data,
5405 wl_keyboard * /*wl_keyboard*/,
5406 const uint32_t /*serial*/,
5408{
5410 CLOG_DEBUG(LOG, "leave (skipped)");
5411 return;
5412 }
5413 CLOG_DEBUG(LOG, "leave");
5414
5415 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5416 seat->keyboard.wl.surface_window = nullptr;
5417
5418 {
5419#ifdef USE_EVENT_BACKGROUND_THREAD
5420 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5421#endif
5422 /* Losing focus must stop repeating text. */
5423 if (seat->key_repeat.timer) {
5425 }
5426 }
5427}
5428
5434 xkb_state *xkb_state_empty,
5435 xkb_state *xkb_state_empty_with_numlock,
5436 xkb_state *xkb_state_empty_with_shift,
5437 const bool xkb_use_non_latin_workaround,
5438 const xkb_keycode_t key)
5439{
5440 /* Use an empty keyboard state to access key symbol without modifiers. */
5441 xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state_empty, key);
5442
5443 /* NOTE(@ideasman42): Only perform the number-locked lookup as a fallback
5444 * when a number-pad key has been pressed. This is important as some key-maps use number lock
5445 * for switching other layers (in particular `de(neo_qwertz)` turns on layer-4), see: #96170.
5446 * Alternative solutions could be to inspect the layout however this could get involved
5447 * and turning on the number-lock is only needed for a limited set of keys. */
5448
5449 /* Accounts for key-pad keys typically swapped for numbers when number-lock is enabled:
5450 * `Home Left Up Right Down Prior Page_Up Next Page_Dow End Begin Insert Delete`. */
5451 if (sym >= XKB_KEY_KP_Home && sym <= XKB_KEY_KP_Delete) {
5452 if (xkb_state_empty_with_numlock) {
5453 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_numlock, key);
5454 if (sym_test != XKB_KEY_NoSymbol) {
5455 sym = sym_test;
5456 }
5457 }
5458 }
5459 else {
5460#ifdef USE_NON_LATIN_KB_WORKAROUND
5461 if (key >= (KEY_1 + EVDEV_OFFSET) && key <= (KEY_0 + EVDEV_OFFSET)) {
5462 if (xkb_state_empty_with_shift && xkb_use_non_latin_workaround) {
5463 const xkb_keysym_t sym_test = xkb_state_key_get_one_sym(xkb_state_empty_with_shift, key);
5464 if (sym_test != XKB_KEY_NoSymbol) {
5465 /* Should never happen as enabling `xkb_use_non_latin_workaround` checks this. */
5466 GHOST_ASSERT(sym_test >= XKB_KEY_0 && sym_test <= XKB_KEY_9, "Unexpected key");
5467 sym = sym_test;
5468 }
5469 }
5470 }
5471#else
5472 (void)xkb_state_empty_with_shift;
5473 (void)xkb_use_non_latin_workaround;
5474#endif
5475 }
5476
5477 return sym;
5478}
5479
5481 xkb_compose_state *compose_state,
5482 xkb_state *state,
5483 const xkb_keycode_t key,
5484 char r_utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)])
5485{
5486 const xkb_keysym_t sym = xkb_state_key_get_one_sym(state, key);
5487 const xkb_compose_feed_result result = xkb_compose_state_feed(compose_state, sym);
5488 bool handled = false;
5489
5490 if (result == XKB_COMPOSE_FEED_ACCEPTED) {
5491 switch (xkb_compose_state_get_status(compose_state)) {
5492 case XKB_COMPOSE_NOTHING: {
5493 break;
5494 }
5495 case XKB_COMPOSE_COMPOSING: {
5496 r_utf8_buf[0] = '\0';
5497 handled = true;
5498 break;
5499 }
5500 case XKB_COMPOSE_COMPOSED: {
5501 char utf8_buf_compose[sizeof(GHOST_TEventKeyData::utf8_buf) + 1] = {'\0'};
5502 const int utf8_buf_compose_len = xkb_compose_state_get_utf8(
5503 compose_state, utf8_buf_compose, sizeof(utf8_buf_compose));
5504 if (utf8_buf_compose_len > 0) {
5505 if (utf8_buf_compose_len > sizeof(GHOST_TEventKeyData::utf8_buf)) {
5506 /* TODO(@ideasman42): keyboard events in GHOST only support a single character.
5507 *
5508 * - In the case XKB compose enters multiple code-points only the first will be used.
5509 *
5510 * - Besides supporting multiple characters per key input,
5511 * one possible solution would be to generate an IME event.
5512 *
5513 * - In practice I'm not sure how common these are.
5514 * So far no bugs have been reported about this.
5515 */
5516 CLOG_WARN(LOG, "key (compose_size=%d) exceeds the maximum size", utf8_buf_compose_len);
5517 }
5518 memcpy(r_utf8_buf, utf8_buf_compose, sizeof(GHOST_TEventKeyData::utf8_buf));
5519 handled = true;
5520 }
5521 break;
5522 }
5523 case XKB_COMPOSE_CANCELLED: {
5524 /* NOTE(@ideasman42): QT & GTK ignore these events as well as not inputting any text
5525 * so `<Compose><Backspace>` for example causes a cancel and *not* back-space.
5526 * This isn't supported under GHOST at the moment.
5527 * The key-event could also be ignored but this means tracking held state of
5528 * keys wont work properly, so don't do any input and pass in the key-symbol. */
5529 r_utf8_buf[0] = '\0';
5530 handled = true;
5531 break;
5532 }
5533 }
5534 }
5535 return handled;
5536}
5537
5542{
5543 GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller must check for timer");
5544 delete static_cast<GWL_KeyRepeatPlayload *>(seat->key_repeat.timer->getUserData());
5545
5547}
5548
5556static void keyboard_handle_key_repeat_reset(GWL_Seat *seat, const bool use_delay)
5557{
5558 GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller must check for timer");
5559 GHOST_TimerProcPtr key_repeat_fn = seat->key_repeat.timer->getTimerProc();
5561
5563 gwl_seat_key_repeat_timer_add(seat, key_repeat_fn, payload, use_delay);
5564}
5565
5566static void keyboard_handle_key(void *data,
5567 wl_keyboard * /*wl_keyboard*/,
5568 const uint32_t serial,
5569 const uint32_t time,
5570 const uint32_t key,
5571 const uint32_t state)
5572{
5573 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5574 const uint64_t event_ms = seat->system->ms_from_input_time(time);
5575
5576 const xkb_keycode_t key_code = key + EVDEV_OFFSET;
5577
5578 const xkb_keysym_t sym = xkb_state_key_get_one_sym_without_modifiers(
5579 seat->xkb.state_empty,
5584#else
5585 false,
5586#endif
5587 key_code);
5588 if (sym == XKB_KEY_NoSymbol) {
5589 CLOG_DEBUG(LOG, "key (code=%d, state=%u, no symbol, skipped)", int(key_code), state);
5590 return;
5591 }
5592 CLOG_DEBUG(LOG, "key (code=%d, state=%u)", int(key_code), state);
5593
5595 bool is_repeat = false;
5596 switch (state) {
5597 case WL_KEYBOARD_KEY_STATE_RELEASED: {
5598 etype = GHOST_kEventKeyUp;
5599 break;
5600 }
5601#ifdef WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION
5602 case WL_KEYBOARD_KEY_STATE_REPEATED: {
5603 /* Server side key repeat. */
5604 is_repeat = true;
5605 [[fallthrough]];
5606 }
5607#endif
5608 case WL_KEYBOARD_KEY_STATE_PRESSED: {
5609 etype = GHOST_kEventKeyDown;
5610 break;
5611 }
5612 }
5613
5614#ifdef USE_EVENT_BACKGROUND_THREAD
5615 /* Any access to `seat->key_repeat.timer` must lock. */
5616 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5617#endif
5618
5619 GWL_KeyRepeatPlayload *key_repeat_payload = nullptr;
5620
5621 /* Delete previous timer. */
5622 if (seat->key_repeat.timer) {
5623 enum { NOP = 1, RESET, CANCEL } timer_action = NOP;
5624 key_repeat_payload = static_cast<GWL_KeyRepeatPlayload *>(
5625 seat->key_repeat.timer->getUserData());
5626
5627 if (seat->key_repeat.rate == 0) {
5628 /* Repeat was disabled (unlikely but possible). */
5629 timer_action = CANCEL;
5630 }
5631 else if (key_code == key_repeat_payload->key_code) {
5632 /* Releasing the key that was held always cancels. */
5633 timer_action = CANCEL;
5634 }
5635 else if (xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code)) {
5636 if (etype == GHOST_kEventKeyDown) {
5637 /* Any other key-down always cancels (and may start its own repeat timer). */
5638 timer_action = CANCEL;
5639 }
5640 else {
5641 /* Key-up from keys that were not repeating cause the repeat timer to pause.
5642 *
5643 * NOTE(@ideasman42): This behavior isn't universal, some text input systems will
5644 * stop the repeat entirely. Choose to pause repeat instead as this is what GTK/WIN32 do,
5645 * and it fits better for keyboard input that isn't related to text entry. */
5646 timer_action = RESET;
5647 }
5648 }
5649
5650 switch (timer_action) {
5651 case NOP: {
5652 /* Don't add a new timer, leave the existing timer owning this `key_repeat_payload`. */
5653 key_repeat_payload = nullptr;
5654 break;
5655 }
5656 case RESET: {
5657 /* The payload will be added again. */
5659 break;
5660 }
5661 case CANCEL: {
5662 delete key_repeat_payload;
5663 key_repeat_payload = nullptr;
5665 break;
5666 }
5667 }
5668 }
5669
5670 const GHOST_TKey gkey = xkb_map_gkey_or_scan_code(sym, key);
5671
5672 char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
5673 if (etype == GHOST_kEventKeyDown) {
5674 /* Handle key-compose (dead-keys). */
5675 if (seat->xkb.compose_state &&
5677 seat->xkb.compose_state, seat->xkb.state, key_code, utf8_buf))
5678 {
5679 /* `utf8_buf` has been filled by a compose action. */
5680 }
5681 else {
5682 xkb_state_key_get_utf8(seat->xkb.state, key_code, utf8_buf, sizeof(utf8_buf));
5683 }
5684 }
5685
5686 seat->data_source_serial = serial;
5687
5688 keyboard_depressed_state_key_event(seat, gkey, etype);
5689
5690 if (wl_surface *wl_surface_focus = seat->keyboard.wl.surface_window) {
5691 GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
5693 new GHOST_EventKey(event_ms, etype, win, gkey, is_repeat, utf8_buf));
5694 }
5695
5696 /* An existing payload means the key repeat timer is reset and will be added again. */
5697 if (key_repeat_payload == nullptr) {
5698 /* Start timer for repeating key, if applicable. */
5699 if ((seat->key_repeat.rate > 0) && (etype == GHOST_kEventKeyDown) &&
5700 xkb_keymap_key_repeats(xkb_state_get_keymap(seat->xkb.state), key_code))
5701 {
5702 key_repeat_payload = new GWL_KeyRepeatPlayload();
5703 key_repeat_payload->seat = seat;
5704 key_repeat_payload->key_code = key_code;
5705 key_repeat_payload->key_data.gkey = gkey;
5706 }
5707 }
5708
5709 if (key_repeat_payload) {
5710 gwl_seat_key_repeat_timer_add(seat, gwl_seat_key_repeat_timer_fn, key_repeat_payload, true);
5711 }
5712}
5713
5715 wl_keyboard * /*wl_keyboard*/,
5716 const uint32_t serial,
5717 const uint32_t mods_depressed,
5718 const uint32_t mods_latched,
5719 const uint32_t mods_locked,
5720 const uint32_t group)
5721{
5723 "modifiers (depressed=%u, latched=%u, locked=%u, group=%u)",
5724 mods_depressed,
5725 mods_latched,
5726 mods_locked,
5727 group);
5728
5729 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5730 xkb_state_update_mask(seat->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
5731
5732 /* Account for the active layout changing within the same key-map,
5733 * needed so modifiers are detected from the expected layout, see: #115160. */
5734 if (group != seat->xkb.layout_active) {
5735 seat->xkb.layout_active = group;
5737 }
5738
5739 /* A modifier changed so reset the timer,
5740 * see comment in #keyboard_handle_key regarding this behavior. */
5741 {
5742#ifdef USE_EVENT_BACKGROUND_THREAD
5743 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5744#endif
5745 if (seat->key_repeat.timer) {
5747 }
5748 }
5749
5750 seat->data_source_serial = serial;
5751}
5752
5754 wl_keyboard * /*wl_keyboard*/,
5755 const int32_t rate,
5756 const int32_t delay)
5757{
5758 CLOG_DEBUG(LOG, "info (rate=%d, delay=%d)", rate, delay);
5759
5760 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5761 seat->key_repeat.rate = rate;
5762 seat->key_repeat.delay = delay;
5763
5764 {
5765#ifdef USE_EVENT_BACKGROUND_THREAD
5766 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
5767#endif
5768 /* Unlikely possible this setting changes while repeating. */
5769 if (seat->key_repeat.timer) {
5770 if (rate > 0) {
5772 }
5773 else {
5774 /* A zero rate disables. */
5776 }
5777 }
5778 }
5779}
5780
5781static const wl_keyboard_listener keyboard_listener = {
5782 /*keymap*/ keyboard_handle_keymap,
5783 /*enter*/ keyboard_handle_enter,
5784 /*leave*/ keyboard_handle_leave,
5785 /*key*/ keyboard_handle_key,
5786 /*modifiers*/ keyboard_handle_modifiers,
5787 /*repeat_info*/ keyboard_handle_repeat_info,
5788};
5789
5790#undef LOG
5791
5793
5794/* -------------------------------------------------------------------- */
5797
5798static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER = {"ghost.wl.handle.primary_selection_offer"};
5799#define LOG (&LOG_WL_PRIMARY_SELECTION_OFFER)
5800
5802 zwp_primary_selection_offer_v1 *id,
5803 const char *type)
5804{
5805 /* NOTE: locking isn't needed as the #GWL_DataOffer wont have been assigned to the #GWL_Seat. */
5807 if (data_offer->wp.id != id) {
5808 CLOG_DEBUG(LOG, "offer: %p: offer for unknown selection %p of %s (skipped)", data, id, type);
5809 return;
5810 }
5811
5812 data_offer->types.insert(std::string(type));
5813}
5814
5815static const zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
5817};
5818
5819#undef LOG
5820
5822
5823/* -------------------------------------------------------------------- */
5826
5827static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE = {"ghost.wl.handle.primary_selection_device"};
5828#define LOG (&LOG_WL_PRIMARY_SELECTION_DEVICE)
5829
5831 void * /*data*/,
5832 zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
5833 zwp_primary_selection_offer_v1 *id)
5834{
5835 CLOG_DEBUG(LOG, "data_offer");
5836
5838 data_offer->wp.id = id;
5839 zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, data_offer);
5840}
5841
5843 void *data,
5844 zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
5845 zwp_primary_selection_offer_v1 *id)
5846{
5847 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5848
5849 std::lock_guard lock{primary->data_offer_mutex};
5850
5851 /* Delete old data offer. */
5852 if (primary->data_offer != nullptr) {
5854 }
5855
5856 if (id == nullptr) {
5857 CLOG_DEBUG(LOG, "selection: (skipped)");
5858 return;
5859 }
5860 CLOG_DEBUG(LOG, "selection");
5861 /* Transfer ownership of the `data_offer`. */
5863 zwp_primary_selection_offer_v1_get_user_data(id));
5864 primary->data_offer = data_offer;
5865}
5866
5867static const zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
5870};
5871
5872#undef LOG
5873
5875
5876/* -------------------------------------------------------------------- */
5879
5880static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE = {"ghost.wl.handle.primary_selection_source"};
5881#define LOG (&LOG_WL_PRIMARY_SELECTION_SOURCE)
5882
5884 zwp_primary_selection_source_v1 * /*source*/,
5885 const char * /*mime_type*/,
5886 const int32_t fd)
5887{
5888 CLOG_DEBUG(LOG, "send");
5889
5890 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5891
5892 auto write_file_fn = [](GWL_PrimarySelection *primary, const int fd) {
5893 if (UNLIKELY(write(fd,
5894 primary->data_source->buffer_out.data,
5895 primary->data_source->buffer_out.data_size) < 0))
5896 {
5897 CLOG_WARN(LOG, "error writing to primary clipboard: %s", std::strerror(errno));
5898 }
5899 close(fd);
5900 primary->data_source_mutex.unlock();
5901 };
5902
5903 primary->data_source_mutex.lock();
5904 std::thread write_thread(write_file_fn, primary, fd);
5905 write_thread.detach();
5906}
5907
5908static void primary_selection_source_cancelled(void *data, zwp_primary_selection_source_v1 *source)
5909{
5910 CLOG_DEBUG(LOG, "cancelled");
5911
5912 GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
5913
5914 if (source == primary->data_source->wp.source) {
5916 }
5917}
5918
5919static const zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
5922};
5923
5924#undef LOG
5925
5927
5928/* -------------------------------------------------------------------- */
5931
5932#ifdef WITH_INPUT_IME
5933
5934class GHOST_EventIME : public GHOST_Event {
5935 protected:
5936 GHOST_TEventImeData event_ime_data;
5937
5938 public:
5945 GHOST_EventIME(uint64_t msec,
5946 GHOST_TEventType type,
5947 GHOST_IWindow *window,
5948 const GHOST_TEventImeData *customdata)
5949 : GHOST_Event(msec, type, window)
5950 {
5951 /* Make sure that we keep a copy of the IME input. Otherwise it might get lost
5952 * because we overwrite it before it can be read in Blender. (See #137346). */
5953 this->event_ime_data = *customdata;
5954 this->data_ = &this->event_ime_data;
5955 }
5956};
5957
5958static CLG_LogRef LOG_WL_TEXT_INPUT = {"ghost.wl.handle.text_input"};
5959# define LOG (&LOG_WL_TEXT_INPUT)
5960
5961static void text_input_handle_enter(void *data,
5962 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5963 wl_surface *surface)
5964{
5965 /* Can be null when closing a window, see: #141777. */
5967 return;
5968 }
5969 CLOG_DEBUG(LOG, "enter");
5970 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5971 seat->ime.surface_window = surface;
5972 /* If text input is enabled, should call `enable` after receive `enter` event.
5973 * This support switch input method during input, otherwise input method will not work. */
5974 if (seat->ime.is_enabled) {
5975 zwp_text_input_v3_enable(seat->wp.text_input);
5976 zwp_text_input_v3_commit(seat->wp.text_input);
5977 }
5978}
5979
5980static void text_input_handle_leave(void *data,
5981 zwp_text_input_v3 * /*zwp_text_input_v3*/,
5982 wl_surface *surface)
5983{
5984 /* Can be null when closing a window. */
5986 return;
5987 }
5988 CLOG_DEBUG(LOG, "leave");
5989 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
5990 if (seat->ime.surface_window == surface) {
5991 seat->ime.surface_window = nullptr;
5992 }
5993 /* Always call `disable` after receive `leave` event. */
5994 zwp_text_input_v3_disable(seat->wp.text_input);
5995 zwp_text_input_v3_commit(seat->wp.text_input);
5996}
5997
5998static void text_input_handle_preedit_string(void *data,
5999 zwp_text_input_v3 * /*zwp_text_input_v3*/,
6000 const char *text,
6001 const int32_t cursor_begin,
6002 const int32_t cursor_end)
6003{
6005 "preedit_string (text=\"%s\", cursor_begin=%d, cursor_end=%d)",
6006 text ? text : "<null>",
6007 cursor_begin,
6008 cursor_end);
6009
6010 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
6011 if (seat->ime.has_preedit == false) {
6012 /* Starting IME input. */
6013 gwl_seat_ime_full_reset(seat);
6014 }
6015
6016 seat->ime.composite_is_null = (text == nullptr);
6017 if (!seat->ime.composite_is_null) {
6018 seat->ime.event_ime_data.composite = text;
6019
6020 seat->ime.event_ime_data.cursor_position = cursor_begin;
6021 seat->ime.event_ime_data.target_start = cursor_begin;
6022 seat->ime.event_ime_data.target_end = cursor_end;
6023 }
6024
6025 seat->ime.has_preedit_string_callback = true;
6026}
6027
6028static void text_input_handle_commit_string(void *data,
6029 zwp_text_input_v3 * /*zwp_text_input_v3*/,
6030 const char *text)
6031{
6032 CLOG_DEBUG(LOG, "commit_string (text=\"%s\")", text ? text : "<null>");
6033
6034 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
6035 seat->ime.result_is_null = (text == nullptr);
6036 seat->ime.event_ime_data.result = text ? text : "";
6037 seat->ime.event_ime_data.cursor_position = seat->ime.event_ime_data.result.size();
6038
6039 seat->ime.has_commit_string_callback = true;
6040}
6041
6042static void text_input_handle_delete_surrounding_text(void * /*data*/,
6043 zwp_text_input_v3 * /*zwp_text_input_v3*/,
6044 const uint32_t before_length,
6045 const uint32_t after_length)
6046{
6048 "delete_surrounding_text (before_length=%u, after_length=%u)",
6049 before_length,
6050 after_length);
6051
6052 /* NOTE: Currently unused, do we care about this event?
6053 * SDL ignores this event. */
6054}
6055
6056static void text_input_handle_done(void *data,
6057 zwp_text_input_v3 * /*zwp_text_input_v3*/,
6058 const uint32_t /*serial*/)
6059{
6060 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
6061 GHOST_SystemWayland *system = seat->system;
6062 const uint64_t event_ms = seat->system->getMilliSeconds();
6063
6064 CLOG_DEBUG(LOG, "done");
6065
6066 GHOST_WindowWayland *win = seat->ime.surface_window ?
6067 ghost_wl_surface_user_data(seat->ime.surface_window) :
6068 nullptr;
6069 if (seat->ime.has_commit_string_callback) {
6070 if (seat->ime.has_preedit) {
6071 const bool is_end = seat->ime.composite_is_null;
6072 if (is_end) {
6073 seat->ime.has_preedit = false;
6074 /* `commit_string` (end). */
6075 system->pushEvent_maybe_pending(new GHOST_EventIME(
6076 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
6077 system->pushEvent_maybe_pending(new GHOST_EventIME(
6078 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
6079 }
6080 else {
6081 /* `commit_string` (continues). */
6082 system->pushEvent_maybe_pending(new GHOST_EventIME(
6083 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
6084 }
6085 }
6086 else {
6087 /* `commit_string` ran with no active IME popup, start & end to insert text. */
6088 system->pushEvent_maybe_pending(new GHOST_EventIME(
6089 event_ms, GHOST_kEventImeCompositionStart, win, &seat->ime.event_ime_data));
6090 system->pushEvent_maybe_pending(new GHOST_EventIME(
6091 event_ms, GHOST_kEventImeComposition, win, &seat->ime.event_ime_data));
6092 system->pushEvent_maybe_pending(new GHOST_EventIME(
6093 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
6094 }
6095
6096 if (seat->ime.has_preedit == false) {
6097 gwl_seat_ime_preedit_reset(seat);
6098 }
6099 }
6100 else if (seat->ime.has_preedit_string_callback) {
6101 const bool is_end = seat->ime.composite_is_null;
6102 if (is_end) {
6103 /* `preedit_string` (end). */
6104 seat->ime.has_preedit = false;
6105 system->pushEvent_maybe_pending(new GHOST_EventIME(
6106 event_ms, GHOST_kEventImeCompositionEnd, win, &seat->ime.event_ime_data));
6107 }
6108 else {
6109 const bool is_start = seat->ime.has_preedit == false;
6110 /* `preedit_string` (start or continue). */
6111 seat->ime.has_preedit = true;
6112 system->pushEvent_maybe_pending(new GHOST_EventIME(
6113 event_ms,
6115 win,
6116 &seat->ime.event_ime_data));
6117 }
6118 }
6119
6120 seat->ime.has_preedit_string_callback = false;
6121 seat->ime.has_commit_string_callback = false;
6122}
6123
6124static zwp_text_input_v3_listener text_input_listener = {
6125 /*enter*/ text_input_handle_enter,
6126 /*leave*/ text_input_handle_leave,
6127 /*preedit_string*/ text_input_handle_preedit_string,
6128 /*commit_string*/ text_input_handle_commit_string,
6129 /*delete_surrounding_text*/ text_input_handle_delete_surrounding_text,
6130 /*done*/ text_input_handle_done,
6131};
6132
6133# undef LOG
6134
6135#endif /* WITH_INPUT_IME. */
6136
6138
6139/* -------------------------------------------------------------------- */
6142
6143static CLG_LogRef LOG_WL_SEAT = {"ghost.wl.handle.seat"};
6144#define LOG (&LOG_WL_SEAT)
6145
6146static bool gwl_seat_capability_pointer_multitouch_check(const GWL_Seat *seat, const bool fallback)
6147{
6148 const zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6149 if (pointer_gestures == nullptr) {
6150 return fallback;
6151 }
6152
6153 bool found = false;
6154#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6155 if (seat->wp.pointer_gesture_hold) {
6156 return true;
6157 }
6158 found = true;
6159#endif
6160#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6161 if (seat->wp.pointer_gesture_pinch) {
6162 return true;
6163 }
6164 found = true;
6165#endif
6166#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6167 if (seat->wp.pointer_gesture_swipe) {
6168 return true;
6169 }
6170 found = true;
6171#endif
6172 if (seat->use_pointer_scroll_smooth_as_discrete == false) {
6173 return true;
6174 }
6175
6176 if (found == false) {
6177 return fallback;
6178 }
6179 return false;
6180}
6181
6183{
6184 /* Smooth to discrete handling. */
6187
6188 zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6189 if (pointer_gestures == nullptr) {
6190 return;
6191 }
6192
6193 const uint pointer_gestures_version = zwp_pointer_gestures_v1_get_version(pointer_gestures);
6194#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6195 if (pointer_gestures_version >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION)
6196 { /* Hold gesture. */
6197 zwp_pointer_gesture_hold_v1 *gesture = zwp_pointer_gestures_v1_get_hold_gesture(
6198 pointer_gestures, seat->wl.pointer);
6199 zwp_pointer_gesture_hold_v1_set_user_data(gesture, seat);
6200 zwp_pointer_gesture_hold_v1_add_listener(gesture, &gesture_hold_listener, seat);
6201 seat->wp.pointer_gesture_hold = gesture;
6202 }
6203#endif
6204#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6205 { /* Pinch gesture. */
6206 zwp_pointer_gesture_pinch_v1 *gesture = zwp_pointer_gestures_v1_get_pinch_gesture(
6207 pointer_gestures, seat->wl.pointer);
6208 zwp_pointer_gesture_pinch_v1_set_user_data(gesture, seat);
6209 zwp_pointer_gesture_pinch_v1_add_listener(gesture, &gesture_pinch_listener, seat);
6210 seat->wp.pointer_gesture_pinch = gesture;
6211 }
6212#endif
6213#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6214 { /* Swipe gesture. */
6215 zwp_pointer_gesture_swipe_v1 *gesture = zwp_pointer_gestures_v1_get_swipe_gesture(
6216 pointer_gestures, seat->wl.pointer);
6217 zwp_pointer_gesture_swipe_v1_set_user_data(gesture, seat);
6218 zwp_pointer_gesture_swipe_v1_add_listener(gesture, &gesture_swipe_listener, seat);
6219 seat->wp.pointer_gesture_swipe = gesture;
6220 }
6221#endif
6222}
6223
6225{
6226 /* Smooth to discrete handling. */
6229 seat->pointer_scroll.smooth_xy[0] = 0;
6230 seat->pointer_scroll.smooth_xy[1] = 0;
6231
6232 const zwp_pointer_gestures_v1 *pointer_gestures = seat->system->wp_pointer_gestures_get();
6233 if (pointer_gestures == nullptr) {
6234 return;
6235 }
6236#ifdef ZWP_POINTER_GESTURE_HOLD_V1_INTERFACE
6237 { /* Hold gesture. */
6238 zwp_pointer_gesture_hold_v1 **gesture_p = &seat->wp.pointer_gesture_hold;
6239 if (*gesture_p) {
6240 zwp_pointer_gesture_hold_v1_destroy(*gesture_p);
6241 *gesture_p = nullptr;
6242 }
6243 }
6244#endif
6245#ifdef ZWP_POINTER_GESTURE_PINCH_V1_INTERFACE
6246 { /* Pinch gesture. */
6247 zwp_pointer_gesture_pinch_v1 **gesture_p = &seat->wp.pointer_gesture_pinch;
6248 if (*gesture_p) {
6249 zwp_pointer_gesture_pinch_v1_destroy(*gesture_p);
6250 *gesture_p = nullptr;
6251 }
6252 }
6253#endif
6254#ifdef ZWP_POINTER_GESTURE_SWIPE_V1_INTERFACE
6255 { /* Swipe gesture. */
6256 zwp_pointer_gesture_swipe_v1 **gesture_p = &seat->wp.pointer_gesture_swipe;
6257 if (*gesture_p) {
6258 zwp_pointer_gesture_swipe_v1_destroy(*gesture_p);
6259 *gesture_p = nullptr;
6260 }
6261 }
6262#endif
6263}
6264
6266{
6267 if (seat->wl.pointer) {
6268 return;
6269 }
6270 seat->wl.pointer = wl_seat_get_pointer(seat->wl.seat);
6271 seat->cursor.wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
6272 seat->cursor.visible = true;
6273 seat->cursor.wl.buffer = nullptr;
6274 wl_pointer_add_listener(seat->wl.pointer, &pointer_listener, seat);
6275
6276 wl_surface_add_listener(seat->cursor.wl.surface_cursor, &cursor_surface_listener, seat);
6278
6280 {
6281 /* Use environment variables, falling back to defaults.
6282 * These environment variables are used by enough WAYLAND applications
6283 * that it makes sense to check them (see `Xcursor` man page). */
6284 const char *env;
6285 env = getenv("XCURSOR_SIZE");
6287
6288 if (env && (*env != '\0')) {
6289 char *env_end = nullptr;
6290 /* While clamping is not needed on the WAYLAND side,
6291 * GHOST's internal logic may get confused by negative values, so ensure it's at least 1. */
6292 const long value = strtol(env, &env_end, 10);
6293 if ((*env_end == '\0') && (value > 0)) {
6294 seat->cursor.theme_size = int(value);
6295 }
6296 }
6297
6298 /* TODO: detect this from the system.
6299 * We *could* have weak support based on checking for known themes. */
6300 seat->cursor.use_dark_theme = true;
6301 }
6302}
6303
6305{
6306 if (!seat->wl.pointer) {
6307 return;
6308 }
6309
6310 if (seat->cursor.shape.device) {
6311 wp_cursor_shape_device_v1_destroy(seat->cursor.shape.device);
6312 seat->cursor.shape.device = nullptr;
6313 }
6314
6316
6317 if (seat->cursor.wl.surface_cursor) {
6318 wl_surface_destroy(seat->cursor.wl.surface_cursor);
6319 seat->cursor.wl.surface_cursor = nullptr;
6320 }
6321
6322 wl_pointer_destroy(seat->wl.pointer);
6323 seat->wl.pointer = nullptr;
6324}
6325
6327{
6328 if (seat->wl.keyboard) {
6329 return;
6330 }
6331 seat->wl.keyboard = wl_seat_get_keyboard(seat->wl.seat);
6332 wl_keyboard_add_listener(seat->wl.keyboard, &keyboard_listener, seat);
6333}
6334
6336{
6337 if (!seat->wl.keyboard) {
6338 return;
6339 }
6340
6341 {
6342#ifdef USE_EVENT_BACKGROUND_THREAD
6343 std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
6344#endif
6345 if (seat->key_repeat.timer) {
6347 }
6348 }
6349 wl_keyboard_destroy(seat->wl.keyboard);
6350 seat->wl.keyboard = nullptr;
6351}
6352
6354{
6355 if (seat->wl.touch) {
6356 return;
6357 }
6358 seat->wl.touch = wl_seat_get_touch(seat->wl.seat);
6359 wl_touch_set_user_data(seat->wl.touch, seat);
6360 wl_touch_add_listener(seat->wl.touch, &touch_seat_listener, seat);
6361}
6362
6364{
6365 if (!seat->wl.touch) {
6366 return;
6367 }
6368 wl_touch_destroy(seat->wl.touch);
6369 seat->wl.touch = nullptr;
6370}
6371
6373 /* Only used in an assert. */
6374 [[maybe_unused]] wl_seat *wl_seat,
6375 const uint32_t capabilities)
6376{
6378 "capabilities (pointer=%d, keyboard=%d, touch=%d)",
6379 (capabilities & WL_SEAT_CAPABILITY_POINTER) != 0,
6380 (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) != 0,
6381 (capabilities & WL_SEAT_CAPABILITY_TOUCH) != 0);
6382
6383 GWL_Seat *seat = static_cast<GWL_Seat *>(data);
6384 GHOST_ASSERT(seat->wl.seat == wl_seat, "Seat mismatch");
6385
6386 if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
6388 }
6389 else {
6391 }
6392
6393 if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
6395 }
6396 else {
6398 }
6399
6400 if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
6402 }
6403 else {
6405 }
6406}
6407
6408static void seat_handle_name(void *data, wl_seat * /*wl_seat*/, const char *name)
6409{
6410 CLOG_DEBUG(LOG, "name (name=\"%s\")", name);
6411 static_cast<GWL_Seat *>(data)->name = std::string(name);
6412}
6413
6414static const wl_seat_listener seat_listener = {
6415 /*capabilities*/ seat_handle_capabilities,
6416 /*name*/ seat_handle_name,
6417};
6418
6419#undef LOG
6420
6422
6423/* -------------------------------------------------------------------- */
6426
6427static CLG_LogRef LOG_WL_XDG_OUTPUT = {"ghost.wl.handle.xdg_output"};
6428#define LOG (&LOG_WL_XDG_OUTPUT)
6429
6431 zxdg_output_v1 * /*xdg_output*/,
6432 const int32_t x,
6433 const int32_t y)
6434{
6435 CLOG_DEBUG(LOG, "logical_position [%d, %d]", x, y);
6436
6437 GWL_Output *output = static_cast<GWL_Output *>(data);
6438 output->position_logical[0] = x;
6439 output->position_logical[1] = y;
6440 output->has_position_logical = true;
6441}
6442
6444 zxdg_output_v1 * /*xdg_output*/,
6445 const int32_t width,
6446 const int32_t height)
6447{
6448 CLOG_DEBUG(LOG, "logical_size [%d, %d]", width, height);
6449
6450 GWL_Output *output = static_cast<GWL_Output *>(data);
6451 if (output->size_native[0] != 0 && output->size_native[1] != 0) {
6452 /* Original comment from SDL. */
6453 /* FIXME(@flibit): GNOME has a bug where the logical size does not account for
6454 * scale, resulting in bogus viewport sizes.
6455 *
6456 * Until this is fixed, validate that _some_ kind of scaling is being
6457 * done (we can't match exactly because fractional scaling can't be
6458 * detected otherwise), then override if necessary. */
6459 int width_native = output->size_native[(output->transform & WL_OUTPUT_TRANSFORM_90) ? 1 : 0];
6460 if ((width_native == width) && (output->scale_fractional == (1 * FRACTIONAL_DENOMINATOR))) {
6461 GHOST_PRINT("xdg_output scale did not match, overriding with wl_output scale\n");
6462
6463#ifdef USE_GNOME_CONFINE_HACK
6464 /* Use a bug in GNOME to check GNOME is in use. If the bug is fixed this won't cause an issue
6465 * as #98793 has been fixed up-stream too, but not in a release at time of writing. */
6467#endif
6468
6469 return;
6470 }
6471 }
6472
6473 output->size_logical[0] = width;
6474 output->size_logical[1] = height;
6475 output->has_size_logical = true;
6476}
6477
6478static void xdg_output_handle_done(void *data, zxdg_output_v1 * /*xdg_output*/)
6479{
6480 CLOG_DEBUG(LOG, "done");
6481 /* NOTE: `xdg-output.done` events are deprecated and only apply below version 3 of the protocol.
6482 * `wl-output.done` event will be emitted in version 3 or higher. */
6483 GWL_Output *output = static_cast<GWL_Output *>(data);
6484 if (zxdg_output_v1_get_version(output->xdg.output) < 3) {
6485 output_handle_done(data, output->wl.output);
6486 }
6487}
6488
6489static void xdg_output_handle_name(void * /*data*/,
6490 zxdg_output_v1 * /*xdg_output*/,
6491 const char *name)
6492{
6493 CLOG_DEBUG(LOG, "name (name=\"%s\")", name);
6494}
6495
6496static void xdg_output_handle_description(void * /*data*/,
6497 zxdg_output_v1 * /*xdg_output*/,
6498 const char *description)
6499{
6500 CLOG_DEBUG(LOG, "description (description=\"%s\")", description);
6501}
6502
6503static const zxdg_output_v1_listener xdg_output_listener = {
6504 /*logical_position*/ xdg_output_handle_logical_position,
6505 /*logical_size*/ xdg_output_handle_logical_size,
6506 /*done*/ xdg_output_handle_done,
6507 /*name*/ xdg_output_handle_name,
6508 /*description*/ xdg_output_handle_description,
6509};
6510
6511#undef LOG
6512
6514
6515/* -------------------------------------------------------------------- */
6518
6519static CLG_LogRef LOG_WL_OUTPUT = {"ghost.wl.handle.output"};
6520#define LOG (&LOG_WL_OUTPUT)
6521
6523 wl_output * /*wl_output*/,
6524 const int32_t /*x*/,
6525 const int32_t /*y*/,
6526 const int32_t physical_width,
6527 const int32_t physical_height,
6528 const int32_t /*subpixel*/,
6529 const char *make,
6530 const char *model,
6531 const int32_t transform)
6532{
6534 "geometry (make=\"%s\", model=\"%s\", transform=%d, size=[%d, %d])",
6535 make,
6536 model,
6537 transform,
6538 physical_width,
6539 physical_height);
6540
6541 GWL_Output *output = static_cast<GWL_Output *>(data);
6542 output->transform = transform;
6543 output->make = std::string(make);
6544 output->model = std::string(model);
6545 output->size_mm[0] = physical_width;
6546 output->size_mm[1] = physical_height;
6547}
6548
6549static void output_handle_mode(void *data,
6550 wl_output * /*wl_output*/,
6551 const uint32_t flags,
6552 const int32_t width,
6553 const int32_t height,
6554 const int32_t /*refresh*/)
6555{
6556 if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) {
6557 CLOG_DEBUG(LOG, "mode (skipped)");
6558 return;
6559 }
6560 CLOG_DEBUG(LOG, "mode (size=[%d, %d], flags=%u)", width, height, flags);
6561
6562 GWL_Output *output = static_cast<GWL_Output *>(data);
6563 output->size_native[0] = width;
6564 output->size_native[1] = height;
6565}
6566
6575static void output_handle_done(void *data, wl_output * /*wl_output*/)
6576{
6577 CLOG_DEBUG(LOG, "done");
6578
6579 GWL_Output *output = static_cast<GWL_Output *>(data);
6580 int32_t size_native[2] = {UNPACK2(output->size_native)};
6581 if (output->transform & WL_OUTPUT_TRANSFORM_90) {
6582 std::swap(size_native[0], size_native[1]);
6583 }
6584
6585 /* If `xdg-output` is present, calculate the true scale of the desktop */
6586 if (output->has_size_logical) {
6587
6588 /* NOTE: it's not necessary to divide these values by their greatest-common-denominator
6589 * as even a 64k screen resolution doesn't approach overflowing an `int32_t`. */
6590
6591 GHOST_ASSERT(size_native[0] && output->size_logical[0],
6592 "Screen size values were not set when they were expected to be.");
6593
6594 output->scale_fractional = (size_native[0] * FRACTIONAL_DENOMINATOR) / output->size_logical[0];
6595 output->has_scale_fractional = true;
6596 }
6597}
6598
6599static void output_handle_scale(void *data, wl_output * /*wl_output*/, const int32_t factor)
6600{
6601 CLOG_DEBUG(LOG, "scale");
6602 GWL_Output *output = static_cast<GWL_Output *>(data);
6603 output->scale = factor;
6604 output->system->output_scale_update(output);
6605}
6606
6607static void output_handle_name(void * /*data*/, wl_output * /*wl_output*/, const char *name)
6608{
6609 /* Only available in interface version 4. */
6610 CLOG_DEBUG(LOG, "name (%s)", name);
6611}
6612static void output_handle_description(void * /*data*/,
6613 wl_output * /*wl_output*/,
6614 const char *description)
6615{
6616 /* Only available in interface version 4. */
6617 CLOG_DEBUG(LOG, "description (%s)", description);
6618}
6619
6620static const wl_output_listener output_listener = {
6621 /*geometry*/ output_handle_geometry,
6622 /*mode*/ output_handle_mode,
6623 /*done*/ output_handle_done,
6624 /*scale*/ output_handle_scale,
6625 /*name*/ output_handle_name,
6626 /*description*/ output_handle_description,
6627};
6628
6629#undef LOG
6630
6632
6633/* -------------------------------------------------------------------- */
6636
6637static CLG_LogRef LOG_WL_XDG_WM_BASE = {"ghost.wl.handle.xdg_wm_base"};
6638#define LOG (&LOG_WL_XDG_WM_BASE)
6639
6640static void shell_handle_ping(void * /*data*/, xdg_wm_base *xdg_wm_base, const uint32_t serial)
6641{
6642 CLOG_DEBUG(LOG, "ping");
6643 xdg_wm_base_pong(xdg_wm_base, serial);
6644}
6645
6646static const xdg_wm_base_listener shell_listener = {
6647 /*ping*/ shell_handle_ping,
6648};
6649
6650#undef LOG
6651
6653
6654/* -------------------------------------------------------------------- */
6657
6658#ifdef WITH_GHOST_WAYLAND_LIBDECOR
6659
6660static CLG_LogRef LOG_WL_LIBDECOR = {"ghost.wl.handle.libdecor"};
6661# define LOG (&LOG_WL_LIBDECOR)
6662
6663static void decor_handle_error(libdecor * /*context*/,
6664 enum libdecor_error error,
6665 const char *message)
6666{
6667 CLOG_DEBUG(LOG, "error (id=%d, message=%s)", error, message);
6668
6669 (void)(error);
6670 (void)(message);
6671 GHOST_PRINT("decoration error (" << error << "): " << message << std::endl);
6672 exit(EXIT_FAILURE);
6673}
6674
6675static libdecor_interface libdecor_interface = {
6676 decor_handle_error,
6677};
6678
6679# undef LOG
6680
6681#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
6682
6684
6685/* -------------------------------------------------------------------- */
6688
6689static CLG_LogRef LOG_WL_REGISTRY = {"ghost.wl.handle.registry"};
6690#define LOG (&LOG_WL_REGISTRY)
6691
6692/* #GWL_Display.wl_compositor */
6693
6696{
6697 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 6u);
6698
6699 display->wl.compositor = static_cast<wl_compositor *>(
6700 wl_registry_bind(display->wl.registry, params.name, &wl_compositor_interface, version));
6701 gwl_registry_entry_add(display, params, nullptr);
6702}
6704 void * /*user_data*/,
6705 const bool /*on_exit*/)
6706{
6707 wl_compositor **value_p = &display->wl.compositor;
6708 wl_compositor_destroy(*value_p);
6709 *value_p = nullptr;
6710}
6711
6712/* #GWL_Display.xdg_decor.shell */
6713
6716{
6717 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 6u);
6718
6719 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6720 decor.shell = static_cast<xdg_wm_base *>(
6721 wl_registry_bind(display->wl.registry, params.name, &xdg_wm_base_interface, version));
6722 xdg_wm_base_add_listener(decor.shell, &shell_listener, nullptr);
6723 decor.shell_name = params.name;
6724 gwl_registry_entry_add(display, params, nullptr);
6725}
6727 void * /*user_data*/,
6728 const bool /*on_exit*/)
6729{
6730 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6731 xdg_wm_base **value_p = &decor.shell;
6732 uint32_t *name_p = &decor.shell_name;
6733 xdg_wm_base_destroy(*value_p);
6734 *value_p = nullptr;
6735 *name_p = WL_NAME_UNSET;
6736}
6737
6738/* #GWL_Display.xdg_decor.manager */
6739
6742{
6743 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
6744
6745 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6746 decor.manager = static_cast<zxdg_decoration_manager_v1 *>(wl_registry_bind(
6747 display->wl.registry, params.name, &zxdg_decoration_manager_v1_interface, version));
6748 decor.manager_name = params.name;
6749 gwl_registry_entry_add(display, params, nullptr);
6750}
6752 void * /*user_data*/,
6753 const bool /*on_exit*/)
6754{
6755 GWL_XDG_Decor_System &decor = *display->xdg_decor;
6756 zxdg_decoration_manager_v1 **value_p = &decor.manager;
6757 uint32_t *name_p = &decor.manager_name;
6758 zxdg_decoration_manager_v1_destroy(*value_p);
6759 *value_p = nullptr;
6760 *name_p = WL_NAME_UNSET;
6761}
6762
6763/* #GWL_Display.xdg_output_manager */
6764
6767{
6768 /* NOTE(@ideasman42): only version 3 and over is well supported.
6769 * Support version 1 so GNOME-32 (on the build-bots Rocky8) can be used.
6770 * Use `zxdg_output_v1_get_version` to ensure feature support at runtime. */
6771 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 3u);
6772
6773 display->xdg.output_manager = static_cast<zxdg_output_manager_v1 *>(wl_registry_bind(
6774 display->wl.registry, params.name, &zxdg_output_manager_v1_interface, version));
6775 gwl_registry_entry_add(display, params, nullptr);
6776}
6778 void * /*user_data*/,
6779 const bool /*on_exit*/)
6780{
6781 zxdg_output_manager_v1 **value_p = &display->xdg.output_manager;
6782 zxdg_output_manager_v1_destroy(*value_p);
6783 *value_p = nullptr;
6784}
6785
6786/* #GWL_Display.wl_output */
6787
6789{
6790 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 2u, 4u);
6791
6793 output->system = display->system;
6794 output->wl.output = static_cast<wl_output *>(
6795 wl_registry_bind(display->wl.registry, params.name, &wl_output_interface, version));
6796 ghost_wl_output_tag(output->wl.output);
6797 wl_output_set_user_data(output->wl.output, output);
6798
6799 display->outputs.push_back(output);
6800 wl_output_add_listener(output->wl.output, &output_listener, output);
6801 gwl_registry_entry_add(display, params, static_cast<void *>(output));
6802}
6805{
6806 GWL_Output *output = static_cast<GWL_Output *>(params.user_data);
6807 if (display->xdg.output_manager) {
6808 if (output->xdg.output == nullptr) {
6809 output->xdg.output = zxdg_output_manager_v1_get_xdg_output(display->xdg.output_manager,
6810 output->wl.output);
6811 zxdg_output_v1_add_listener(output->xdg.output, &xdg_output_listener, output);
6812 }
6813 }
6814 else {
6815 output->xdg.output = nullptr;
6816 }
6817}
6819 void *user_data,
6820 const bool on_exit)
6821{
6822 /* While windows & cursors hold references to outputs, there is no need to manually remove
6823 * these references as the compositor will remove references via #wl_surface_listener.leave.
6824 *
6825 * WARNING: this is not the case for WLROOTS based compositors which have a (bug?)
6826 * where surface leave events don't run. So `system->output_leave(..)` is needed
6827 * until the issue is resolved in WLROOTS. */
6828 GWL_Output *output = static_cast<GWL_Output *>(user_data);
6829
6830 if (!on_exit) {
6831 /* Needed for WLROOTS, does nothing if surface leave callbacks have already run. */
6832 if (output->system->output_unref(output->wl.output)) {
6833 CLOG_WARN(LOG,
6834 "mis-behaving compositor failed to call \"surface_listener.leave\" "
6835 "window scale may be invalid!");
6836 }
6837 }
6838
6839 if (output->xdg.output) {
6840 zxdg_output_v1_destroy(output->xdg.output);
6841 }
6842 wl_output_destroy(output->wl.output);
6843 std::vector<GWL_Output *>::iterator iter = std::find(
6844 display->outputs.begin(), display->outputs.end(), output);
6845 const int index = (iter != display->outputs.cend()) ?
6846 std::distance(display->outputs.begin(), iter) :
6847 -1;
6848 GHOST_ASSERT(index != -1, "invalid internal state");
6849 /* NOTE: always erase even when `on_exit` because `output->xdg_output` is cleared later. */
6850 display->outputs.erase(display->outputs.begin() + index);
6851 delete output;
6852}
6853
6854/* #GWL_Display.seats */
6855
6857{
6858 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 5u, 9u);
6859
6860 GWL_Seat *seat = new GWL_Seat;
6861 seat->system = display->system;
6862 seat->xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
6863
6864 /* May be null (skip dead-key support in this case). */
6865 seat->xkb.compose_table = xkb_compose_table_new_from_locale(
6866 seat->xkb.context, ghost_wl_locale_from_env_with_default(), XKB_COMPOSE_COMPILE_NO_FLAGS);
6867
6868 seat->data_source = new GWL_DataSource;
6869 seat->wl.seat = static_cast<wl_seat *>(
6870 wl_registry_bind(display->wl.registry, params.name, &wl_seat_interface, version));
6871 display->seats.push_back(seat);
6872 wl_seat_add_listener(seat->wl.seat, &seat_listener, seat);
6873 gwl_registry_entry_add(display, params, static_cast<void *>(seat));
6874
6876}
6879{
6880 GWL_Seat *seat = static_cast<GWL_Seat *>(params.user_data);
6881
6882 /* Register data device per seat for IPC between WAYLAND clients. */
6883 if (display->wl.data_device_manager) {
6884 if (seat->wl.data_device == nullptr) {
6885 seat->wl.data_device = wl_data_device_manager_get_data_device(
6886 display->wl.data_device_manager, seat->wl.seat);
6887 wl_data_device_add_listener(seat->wl.data_device, &data_device_listener, seat);
6888 }
6889 }
6890 else {
6891 seat->wl.data_device = nullptr;
6892 }
6893
6894 if (display->wp.tablet_manager) {
6895 if (seat->wp.tablet_seat == nullptr) {
6896 seat->wp.tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(display->wp.tablet_manager,
6897 seat->wl.seat);
6898 zwp_tablet_seat_v2_add_listener(seat->wp.tablet_seat, &tablet_seat_listener, seat);
6899 }
6900 }
6901 else {
6902 seat->wp.tablet_seat = nullptr;
6903 }
6904
6905 if (display->wp.primary_selection_device_manager) {
6906 if (seat->wp.primary_selection_device == nullptr) {
6907 seat->wp.primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
6908 display->wp.primary_selection_device_manager, seat->wl.seat);
6909
6910 zwp_primary_selection_device_v1_add_listener(seat->wp.primary_selection_device,
6912 &seat->primary_selection);
6913 }
6914 }
6915 else {
6916 seat->wp.primary_selection_device = nullptr;
6917 }
6918
6919#ifdef WITH_INPUT_IME
6920 if (display->wp.text_input_manager) {
6921 if (seat->wp.text_input == nullptr) {
6922 seat->wp.text_input = zwp_text_input_manager_v3_get_text_input(
6923 display->wp.text_input_manager, seat->wl.seat);
6924 zwp_text_input_v3_set_user_data(seat->wp.text_input, seat);
6925 zwp_text_input_v3_add_listener(seat->wp.text_input, &text_input_listener, seat);
6926 }
6927 }
6928 else {
6929 seat->wp.text_input = nullptr;
6930 }
6931#endif /* WITH_INPUT_IME */
6932}
6933static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit)
6934{
6935 GWL_Seat *seat = static_cast<GWL_Seat *>(user_data);
6936
6937 /* First handle members that require locking.
6938 * While highly unlikely, it's possible they are being used while this function runs. */
6939 {
6940 std::lock_guard lock{seat->data_source_mutex};
6941 if (seat->data_source) {
6943 if (seat->data_source->wl.source) {
6944 wl_data_source_destroy(seat->data_source->wl.source);
6945 }
6946 delete seat->data_source;
6947 }
6948 }
6949
6950 {
6951 std::lock_guard lock{seat->data_offer_dnd_mutex};
6952 if (seat->data_offer_dnd) {
6953 wl_data_offer_destroy(seat->data_offer_dnd->wl.id);
6954 delete seat->data_offer_dnd;
6955 }
6956 }
6957
6958 {
6959 std::lock_guard lock{seat->data_offer_copy_paste_mutex};
6960 if (seat->data_offer_copy_paste) {
6961 wl_data_offer_destroy(seat->data_offer_copy_paste->wl.id);
6962 delete seat->data_offer_copy_paste;
6963 }
6964 }
6965
6966 {
6967 GWL_PrimarySelection *primary = &seat->primary_selection;
6968 std::lock_guard lock{primary->data_offer_mutex};
6970 }
6971
6972 {
6973 GWL_PrimarySelection *primary = &seat->primary_selection;
6974 std::lock_guard lock{primary->data_source_mutex};
6976 }
6977
6978 if (seat->wp.primary_selection_device) {
6979 zwp_primary_selection_device_v1_destroy(seat->wp.primary_selection_device);
6980 }
6981
6982#ifdef WITH_INPUT_IME
6983 if (seat->wp.text_input) {
6984 zwp_text_input_v3_destroy(seat->wp.text_input);
6985 }
6986#endif
6987
6988 if (seat->wl.data_device) {
6989 wl_data_device_release(seat->wl.data_device);
6990 }
6991
6992 if (seat->wp.tablet_seat) {
6993 zwp_tablet_seat_v2_destroy(seat->wp.tablet_seat);
6994 }
6995
6996 /* Disable all capabilities as a way to free:
6997 * - `seat.wl_pointer` (and related cursor variables).
6998 * - `seat.wl_touch`.
6999 * - `seat.wl_keyboard`.
7000 */
7004
7005 /* Run after tablet & input devices have been disabled
7006 * to ensure the buffer from a *visible* cursor never destroyed.
7007 *
7008 * Note that most compositors will have already released the buffer,
7009 * in that case this will have been set to null.
7010 * However this isn't guaranteed, see: #145557. */
7011 if (seat->cursor.wl.buffer) {
7012 wl_buffer_destroy(seat->cursor.wl.buffer);
7013 }
7014
7015 if (seat->cursor.custom_data) {
7016 munmap(seat->cursor.custom_data, seat->cursor.custom_data_size);
7017 }
7018
7019 /* Un-referencing checks for nullptr case. */
7020 xkb_state_unref(seat->xkb.state);
7021 xkb_state_unref(seat->xkb.state_empty);
7022 xkb_state_unref(seat->xkb.state_empty_with_shift);
7023 xkb_state_unref(seat->xkb.state_empty_with_numlock);
7024
7025 xkb_compose_state_unref(seat->xkb.compose_state);
7026 xkb_compose_table_unref(seat->xkb.compose_table);
7027
7028 xkb_context_unref(seat->xkb.context);
7029
7030 /* Remove the seat. */
7031 wl_seat_destroy(seat->wl.seat);
7032
7033 std::vector<GWL_Seat *>::iterator iter = std::find(
7034 display->seats.begin(), display->seats.end(), seat);
7035 const int index = (iter != display->seats.cend()) ? std::distance(display->seats.begin(), iter) :
7036 -1;
7037 GHOST_ASSERT(index != -1, "invalid internal state");
7038
7039 if (!on_exit) {
7040 if (display->seats_active_index >= index) {
7041 display->seats_active_index -= 1;
7042 }
7043 display->seats.erase(display->seats.begin() + index);
7044 }
7045 delete seat;
7046}
7047
7048/* #GWL_Display.wl_shm */
7049
7051{
7052 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7053
7054 display->wl.shm = static_cast<wl_shm *>(
7055 wl_registry_bind(display->wl.registry, params.name, &wl_shm_interface, version));
7056 gwl_registry_entry_add(display, params, nullptr);
7057}
7059 void * /*user_data*/,
7060 const bool /*on_exit*/)
7061{
7062 wl_shm **value_p = &display->wl.shm;
7063 wl_shm_destroy(*value_p);
7064 *value_p = nullptr;
7065}
7066
7067/* #GWL_Display.wl_data_device_manager */
7068
7071{
7072 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 3u);
7073
7074 display->wl.data_device_manager = static_cast<wl_data_device_manager *>(wl_registry_bind(
7075 display->wl.registry, params.name, &wl_data_device_manager_interface, version));
7076 gwl_registry_entry_add(display, params, nullptr);
7077}
7079 void * /*user_data*/,
7080 const bool /*on_exit*/)
7081{
7082 wl_data_device_manager **value_p = &display->wl.data_device_manager;
7083 wl_data_device_manager_destroy(*value_p);
7084 *value_p = nullptr;
7085}
7086
7087/* #GWL_Display.wp_tablet_manager */
7088
7091{
7092 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7093
7094 display->wp.tablet_manager = static_cast<zwp_tablet_manager_v2 *>(wl_registry_bind(
7095 display->wl.registry, params.name, &zwp_tablet_manager_v2_interface, version));
7096 gwl_registry_entry_add(display, params, nullptr);
7097}
7099 void * /*user_data*/,
7100 const bool /*on_exit*/)
7101{
7102 zwp_tablet_manager_v2 **value_p = &display->wp.tablet_manager;
7103 zwp_tablet_manager_v2_destroy(*value_p);
7104 *value_p = nullptr;
7105}
7106
7107/* #GWL_Display.wp_relative_pointer_manager */
7108
7111{
7112 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7113
7114 display->wp.relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>(
7115 wl_registry_bind(
7116 display->wl.registry, params.name, &zwp_relative_pointer_manager_v1_interface, version));
7117 gwl_registry_entry_add(display, params, nullptr);
7118}
7120 void * /*user_data*/,
7121 const bool /*on_exit*/)
7122{
7123 zwp_relative_pointer_manager_v1 **value_p = &display->wp.relative_pointer_manager;
7124 zwp_relative_pointer_manager_v1_destroy(*value_p);
7125 *value_p = nullptr;
7126}
7127
7128/* #GWL_Display.wp_pointer_constraints */
7129
7132{
7133 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7134
7135 display->wp.pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(wl_registry_bind(
7136 display->wl.registry, params.name, &zwp_pointer_constraints_v1_interface, version));
7137 gwl_registry_entry_add(display, params, nullptr);
7138}
7140 void * /*user_data*/,
7141 const bool /*on_exit*/)
7142{
7143 zwp_pointer_constraints_v1 **value_p = &display->wp.pointer_constraints;
7144 zwp_pointer_constraints_v1_destroy(*value_p);
7145 *value_p = nullptr;
7146}
7147
7148/* #GWL_Display.wp_pointer_gestures */
7149
7152{
7153 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 3u, 3u);
7154
7155 display->wp.pointer_gestures = static_cast<zwp_pointer_gestures_v1 *>(
7156 wl_registry_bind(display->wl.registry,
7157 params.name,
7158 &zwp_pointer_gestures_v1_interface,
7159 std::min(params.version, version)));
7160 gwl_registry_entry_add(display, params, nullptr);
7161}
7163 void * /*user_data*/,
7164 const bool /*on_exit*/)
7165{
7166 zwp_pointer_gestures_v1 **value_p = &display->wp.pointer_gestures;
7167 zwp_pointer_gestures_v1_destroy(*value_p);
7168 *value_p = nullptr;
7169}
7170
7171/* #GWL_Display.xdg_activation */
7172
7175{
7176 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7177
7178 display->xdg.activation_manager = static_cast<xdg_activation_v1 *>(
7179 wl_registry_bind(display->wl.registry, params.name, &xdg_activation_v1_interface, version));
7180 gwl_registry_entry_add(display, params, nullptr);
7181}
7183 void * /*user_data*/,
7184 const bool /*on_exit*/)
7185{
7186 xdg_activation_v1 **value_p = &display->xdg.activation_manager;
7187 xdg_activation_v1_destroy(*value_p);
7188 *value_p = nullptr;
7189}
7190
7191/* #GWL_Display.wp_fractional_scale_manger */
7192
7195{
7196 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7197
7198 display->wp.fractional_scale_manager = static_cast<wp_fractional_scale_manager_v1 *>(
7199 wl_registry_bind(
7200 display->wl.registry, params.name, &wp_fractional_scale_manager_v1_interface, version));
7201 gwl_registry_entry_add(display, params, nullptr);
7202}
7204 void * /*user_data*/,
7205 const bool /*on_exit*/)
7206{
7207 wp_fractional_scale_manager_v1 **value_p = &display->wp.fractional_scale_manager;
7208 wp_fractional_scale_manager_v1_destroy(*value_p);
7209 *value_p = nullptr;
7210}
7211
7212/* #GWL_Display.wl_viewport */
7213
7216{
7217 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7218
7219 display->wp.viewporter = static_cast<wp_viewporter *>(
7220 wl_registry_bind(display->wl.registry, params.name, &wp_viewporter_interface, version));
7221 gwl_registry_entry_add(display, params, nullptr);
7222}
7224 void * /*user_data*/,
7225 const bool /*on_exit*/)
7226{
7227 wp_viewporter **value_p = &display->wp.viewporter;
7228 wp_viewporter_destroy(*value_p);
7229 *value_p = nullptr;
7230}
7231
7232/* #GWL_Display.wp_primary_selection_device_manager */
7233
7236{
7237 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7238
7240 static_cast<zwp_primary_selection_device_manager_v1 *>(
7241 wl_registry_bind(display->wl.registry,
7242 params.name,
7243 &zwp_primary_selection_device_manager_v1_interface,
7244 version));
7245 gwl_registry_entry_add(display, params, nullptr);
7246}
7248 void * /*user_data*/,
7249 const bool /*on_exit*/)
7250{
7251 zwp_primary_selection_device_manager_v1 **value_p =
7253 zwp_primary_selection_device_manager_v1_destroy(*value_p);
7254 *value_p = nullptr;
7255}
7256
7257#ifdef WITH_INPUT_IME
7258
7259/* #GWL_Display.wp_text_input_manager */
7260
7261static void gwl_registry_wp_text_input_manager_add(GWL_Display *display,
7263{
7264 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7265
7266 display->wp.text_input_manager = static_cast<zwp_text_input_manager_v3 *>(wl_registry_bind(
7267 display->wl.registry, params.name, &zwp_text_input_manager_v3_interface, version));
7268 gwl_registry_entry_add(display, params, nullptr);
7269}
7270static void gwl_registry_wp_text_input_manager_remove(GWL_Display *display,
7271 void * /*user_data*/,
7272 const bool /*on_exit*/)
7273{
7274 zwp_text_input_manager_v3 **value_p = &display->wp.text_input_manager;
7275 zwp_text_input_manager_v3_destroy(*value_p);
7276 *value_p = nullptr;
7277}
7278
7279#endif /* WITH_INPUT_IME */
7280
7281/* #GWL_Display.wp_cursor_shape_manager */
7282
7285{
7286 const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
7287
7288 display->wp.cursor_shape_manager = static_cast<wp_cursor_shape_manager_v1 *>(wl_registry_bind(
7289 display->wl.registry, params.name, &wp_cursor_shape_manager_v1_interface, version));
7290 gwl_registry_entry_add(display, params, nullptr);
7291}
7293 void * /*user_data*/,
7294 const bool /*on_exit*/)
7295{
7296 wp_cursor_shape_manager_v1 **value_p = &display->wp.cursor_shape_manager;
7297 wp_cursor_shape_manager_v1_destroy(*value_p);
7298 *value_p = nullptr;
7299}
7300
7312 /* Low level interfaces. */
7313 {
7314 /*interface_p*/ &wl_compositor_interface.name,
7315 /*add_fn*/ gwl_registry_compositor_add,
7316 /*update_fn*/ nullptr,
7317 /*remove_fn*/ gwl_registry_compositor_remove,
7318 },
7319 {
7320 /*interface_p*/ &wl_shm_interface.name,
7321 /*add_fn*/ gwl_registry_wl_shm_add,
7322 /*update_fn*/ nullptr,
7323 /*remove_fn*/ gwl_registry_wl_shm_remove,
7324 },
7325 {
7326 /*interface_p*/ &xdg_wm_base_interface.name,
7328 /*update_fn*/ nullptr,
7329 /*remove_fn*/ gwl_registry_xdg_wm_base_remove,
7330 },
7331 /* Managers. */
7332 {
7333 /*interface_p*/ &zxdg_decoration_manager_v1_interface.name,
7335 /*update_fn*/ nullptr,
7337 },
7338 {
7339 /*interface_p*/ &zxdg_output_manager_v1_interface.name,
7341 /*update_fn*/ nullptr,
7343 },
7344 {
7345 /*interface_p*/ &wl_data_device_manager_interface.name,
7347 /*update_fn*/ nullptr,
7349 },
7350 {
7351 /*interface_p*/ &zwp_primary_selection_device_manager_v1_interface.name,
7353 /*update_fn*/ nullptr,
7355 },
7356 {
7357 /*interface_p*/ &zwp_tablet_manager_v2_interface.name,
7359 /*update_fn*/ nullptr,
7361 },
7362 {
7363 /*interface_p*/ &zwp_relative_pointer_manager_v1_interface.name,
7365 /*update_fn*/ nullptr,
7367 },
7368#ifdef WITH_INPUT_IME
7369 {
7370 /*interface_p*/ &zwp_text_input_manager_v3_interface.name,
7371 /*add_fn*/ gwl_registry_wp_text_input_manager_add,
7372 /*update_fn*/ nullptr,
7373 /*remove_fn*/ gwl_registry_wp_text_input_manager_remove,
7374 },
7375#endif
7376 {
7377 /*interface_p*/ &wp_cursor_shape_manager_v1_interface.name,
7379 /*update_fn*/ nullptr,
7381 },
7382 /* Higher level interfaces. */
7383 {
7384 /*interface_p*/ &zwp_pointer_constraints_v1_interface.name,
7386 /*update_fn*/ nullptr,
7388 },
7389 {
7390 /*interface_p*/ &zwp_pointer_gestures_v1_interface.name,
7392 /*update_fn*/ nullptr,
7394 },
7395 {
7396 /*interface_p*/ &xdg_activation_v1_interface.name,
7398 /*update_fn*/ nullptr,
7400 },
7401 {
7402 /*interface_p*/ &wp_fractional_scale_manager_v1_interface.name,
7404 /*update_fn*/ nullptr,
7406 },
7407 {
7408 /*interface_p*/ &wp_viewporter_interface.name,
7410 /*update_fn*/ nullptr,
7412 },
7413 /* Display outputs. */
7414 {
7415 /*interface_p*/ &wl_output_interface.name,
7416 /*add_fn*/ gwl_registry_wl_output_add,
7417 /*update_fn*/ gwl_registry_wl_output_update,
7418 /*remove_fn*/ gwl_registry_wl_output_remove,
7419 },
7420 /* Seats.
7421 * Keep the seat near the end to ensure other types are created first.
7422 * as the seat creates data based on other interfaces. */
7423 {
7424 /*interface_p*/ &wl_seat_interface.name,
7425 /*add_fn*/ gwl_registry_wl_seat_add,
7426 /*update_fn*/ gwl_registry_wl_seat_update,
7427 /*remove_fn*/ gwl_registry_wl_seat_remove,
7428 },
7429
7430 {nullptr},
7431};
7432
7441
7443{
7444 for (const GWL_RegistryHandler *handler = gwl_registry_handlers; handler->interface_p != nullptr;
7445 handler++)
7446 {
7447 if (STREQ(interface, *handler->interface_p)) {
7448 return int(handler - gwl_registry_handlers);
7449 }
7450 }
7451 return -1;
7452}
7453
7455{
7456 GHOST_ASSERT(uint32_t(interface_slot) < uint32_t(gwl_registry_handler_interface_slot_max()),
7457 "Index out of range");
7458 return &gwl_registry_handlers[interface_slot];
7459}
7460
7461static void global_handle_add(void *data,
7462 [[maybe_unused]] wl_registry *wl_registry,
7463 const uint32_t name,
7464 const char *interface,
7465 const uint32_t version)
7466{
7467 /* Log last since it's useful to know if the interface was handled or not. */
7468 GWL_Display *display = static_cast<GWL_Display *>(data);
7469 GHOST_ASSERT(display->wl.registry == wl_registry, "Registry argument must match!");
7470
7472 bool added = false;
7473
7474 if (interface_slot != -1) {
7475 const GWL_RegistryHandler *handler = &gwl_registry_handlers[interface_slot];
7476 const GWL_RegistryEntry *registry_entry_prev = display->registry_entry;
7477
7478 /* The interface name that is ensured not to be freed. */
7480 params.name = name;
7481 params.interface_slot = interface_slot;
7482 params.version = version;
7483
7484 handler->add_fn(display, params);
7485
7486 added = display->registry_entry != registry_entry_prev;
7487 }
7488
7490 "add %s(interface=%s, version=%u, name=%u)",
7491 (interface_slot != -1) ? (added ? "" : "(found but not added)") : "(skipped), ",
7492 interface,
7493 version,
7494 name);
7495
7496 /* Initialization avoids excessive calls by calling update after all have been initialized. */
7497 if (added) {
7498 if (display->registry_skip_update_all == false) {
7499 /* See doc-string for rationale on updating all on add/removal. */
7500 gwl_registry_entry_update_all(display, interface_slot);
7501 }
7502 }
7503}
7504
7514static void global_handle_remove(void *data,
7515 [[maybe_unused]] wl_registry *wl_registry,
7516 const uint32_t name)
7517{
7518 GWL_Display *display = static_cast<GWL_Display *>(data);
7519 GHOST_ASSERT(display->wl.registry == wl_registry, "Registry argument must match!");
7520
7521 int interface_slot = 0;
7522 const bool removed = gwl_registry_entry_remove_by_name(display, name, &interface_slot);
7523
7525 "remove (name=%u, interface=%s)",
7526 name,
7527 removed ? *gwl_registry_handlers[interface_slot].interface_p : "(unknown)");
7528
7529 if (removed) {
7530 if (display->registry_skip_update_all == false) {
7531 /* See doc-string for rationale on updating all on add/removal. */
7532 gwl_registry_entry_update_all(display, interface_slot);
7533 }
7534 }
7535}
7536
7537static const wl_registry_listener registry_listener = {
7538 /*global*/ global_handle_add,
7539 /*global_remove*/ global_handle_remove,
7540};
7541
7542#undef LOG
7543
7545
7546/* -------------------------------------------------------------------- */
7549
7550#ifdef USE_EVENT_BACKGROUND_THREAD
7551
7552static void *gwl_display_event_thread_fn(void *display_voidp)
7553{
7554 GWL_Display *display = static_cast<GWL_Display *>(display_voidp);
7555 GHOST_ASSERT(!display->background, "Foreground only");
7556 const int fd = wl_display_get_fd(display->wl.display);
7557 while (display->events_pthread_is_active) {
7558 /* Wait for an event, this thread is dedicated to event handling. */
7560 display->wl.display, fd, display->system->server_mutex) == -1)
7561 {
7562 break;
7563 }
7564 }
7565
7566 /* Wait until the main thread cancels this thread, otherwise this thread may exit
7567 * before cancel is called, causing a crash on exit. */
7568 while (true) {
7569 pause();
7570 }
7571
7572 return nullptr;
7573}
7574
7575/* Event reading thread. */
7577{
7578 GHOST_ASSERT(!display->background, "Foreground only");
7579 GHOST_ASSERT(display->events_pthread == 0, "Only call once");
7581 display->events_pthread_is_active = true;
7582 pthread_create(&display->events_pthread, nullptr, gwl_display_event_thread_fn, display);
7583 /* Application logic should take priority, this only ensures events don't accumulate when busy
7584 * which typically takes a while (5+ seconds of frantic mouse motion for example). */
7586 pthread_detach(display->events_pthread);
7587}
7588
7590{
7591 GHOST_ASSERT(!display->background, "Foreground only");
7592 pthread_cancel(display->events_pthread);
7593}
7594
7595#endif /* USE_EVENT_BACKGROUND_THREAD */
7596
7598
7599/* -------------------------------------------------------------------- */
7604
7606 : GHOST_System(),
7608 server_mutex(new std::mutex),
7609 timer_mutex(new std::mutex),
7610 main_thread_id(std::this_thread::get_id()),
7611#endif
7612 display_(new GWL_Display)
7613{
7616
7617 const bool use_window_frame = GHOST_ISystem::getUseWindowFrame();
7618
7619 display_->system = this;
7620 display_->background = background;
7621 display_->use_window_frame = use_window_frame;
7622
7623 /* Connect to the Wayland server. */
7624
7625 display_->wl.display = wl_display_connect(nullptr);
7626 if (!display_->wl.display) {
7627 display_destroy_and_free_all();
7628 throw std::runtime_error("unable to connect to display!");
7629 }
7630
7631 /* This may be removed later if decorations are required, needed as part of registration. */
7632 display_->xdg_decor = new GWL_XDG_Decor_System;
7633
7634 /* Register interfaces. */
7635 {
7636 display_->registry_skip_update_all = true;
7637 wl_registry *registry = wl_display_get_registry(display_->wl.display);
7638 display_->wl.registry = registry;
7639 wl_registry_add_listener(registry, &registry_listener, display_);
7640 /* First round-trip to receive all registry objects. */
7641 wl_display_roundtrip(display_->wl.display);
7642 /* Second round-trip to receive all output events. */
7643 wl_display_roundtrip(display_->wl.display);
7644
7645 /* Account for dependencies between interfaces. */
7646 gwl_registry_entry_update_all(display_, -1);
7647
7648 display_->registry_skip_update_all = false;
7649 }
7650
7651#ifdef WITH_GHOST_WAYLAND_LIBDECOR
7652 bool libdecor_required = false;
7653 if (use_window_frame) {
7654 const char *xdg_current_desktop = [] {
7655 /* Account for VSCode overriding this value (TSK!), see: #133921. */
7656 const char *key = "ORIGINAL_XDG_CURRENT_DESKTOP";
7657 const char *value = getenv(key);
7658 return value ? value : getenv(key + 9);
7659 }();
7660
7661 if (xdg_current_desktop) {
7662 /* See the free-desktop specifications for details on `XDG_CURRENT_DESKTOP`.
7663 * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
7664 */
7665 if (string_elem_split_by_delim(xdg_current_desktop, ':', "GNOME")) {
7666 libdecor_required = true;
7667 }
7668 }
7669 }
7670
7671 if (libdecor_required) {
7672 /* Ignore windowing requirements when running in background mode,
7673 * as it doesn't make sense to fall back to X11 because of windowing functionality
7674 * in background mode, also LIBDECOR is crashing in background mode `blender -b -f 1`
7675 * for example while it could be fixed, requiring the library at all makes no sense. */
7676 if (background) {
7677 libdecor_required = false;
7678 }
7679# ifdef WITH_GHOST_X11
7680 else if (!has_libdecor && !ghost_wayland_is_x11_available()) {
7681 /* Only require LIBDECOR when X11 is available, otherwise there is nothing to fall back to.
7682 * It's better to open without window decorations than failing entirely. */
7683 libdecor_required = false;
7684 }
7685# endif /* WITH_GHOST_X11 */
7686 }
7687
7688 if (libdecor_required) {
7689 gwl_xdg_decor_system_destroy(display_, display_->xdg_decor);
7690 display_->xdg_decor = nullptr;
7691
7692 if (!has_libdecor) {
7693# ifdef WITH_GHOST_X11
7694 /* LIBDECOR was the only reason X11 was used, let the user know they need it installed. */
7695 fprintf(stderr,
7696 "WAYLAND found but libdecor was not, install libdecor for Wayland support, "
7697 "falling back to X11\n");
7698# endif
7699 display_destroy_and_free_all();
7700 throw std::runtime_error("unable to find libdecor!");
7701 }
7702 }
7703 else {
7704 use_libdecor = false;
7705 }
7706#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
7707
7708#ifdef WITH_GHOST_WAYLAND_LIBDECOR
7709 if (use_libdecor) {
7710 display_->libdecor = new GWL_LibDecor_System;
7711 GWL_LibDecor_System &decor = *display_->libdecor;
7712 decor.context = libdecor_new(display_->wl.display, &libdecor_interface);
7713 if (!decor.context) {
7714 display_destroy_and_free_all();
7715 throw std::runtime_error("unable to create window decorations!");
7716 }
7717 }
7718 else
7719#endif
7720 {
7721 const GWL_XDG_Decor_System &decor = *display_->xdg_decor;
7722 if (!decor.shell) {
7723 display_destroy_and_free_all();
7724 throw std::runtime_error("unable to access xdg_shell!");
7725 }
7726 }
7727
7728 /* Without this, the output fractional size from `display->xdg.output_manager` isn't known,
7729 * while this isn't essential, the first window creation uses this for setting the size.
7730 * Supporting both XDG initialized/uninitialized outputs is possible it complicates logic.
7731 * see: #113328 for an example of size on startup issues. */
7732 wl_display_roundtrip(display_->wl.display);
7733
7734#ifdef USE_EVENT_BACKGROUND_THREAD
7735 /* There is no need for an event handling thread in background mode
7736 * because there no polling for user input. */
7737 if (background) {
7738 GHOST_ASSERT(display_->events_pthread_is_active == false, "Expected to be false");
7739 }
7740 else {
7742 }
7743 /* Could be null in background mode, however there are enough
7744 * references to the timer-manager that it's safer to create it. */
7745 display_->ghost_timer_manager = new GHOST_TimerManager();
7746#endif
7747}
7748
7749void GHOST_SystemWayland::display_destroy_and_free_all()
7750{
7751 gwl_display_destroy(display_);
7752
7753#ifdef USE_EVENT_BACKGROUND_THREAD
7754 delete server_mutex;
7755 delete timer_mutex;
7756#endif
7757}
7758
7760{
7761 display_destroy_and_free_all();
7762}
7763
7765{
7767
7768 if (success) {
7769#ifdef WITH_INPUT_NDOF
7770 ndof_manager_ = new GHOST_NDOFManagerUnix(*this);
7771#endif
7772 return GHOST_kSuccess;
7773 }
7774
7775 return GHOST_kFailure;
7776}
7777
7778#undef pushEvent
7779
7781{
7782 bool any_processed = false;
7783
7784#ifdef USE_EVENT_BACKGROUND_THREAD
7785 if (UNLIKELY(has_pending_actions_for_window.exchange(false))) {
7786 std::lock_guard lock_server_guard{*server_mutex};
7787 for (GHOST_IWindow *iwin : getWindowManager()->getWindows()) {
7788 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
7790 }
7791 }
7792
7793 if (!display_->background) {
7794 std::lock_guard lock{display_->events_pending_mutex};
7795 for (const GHOST_IEvent *event : display_->events_pending) {
7796
7797 /* Perform actions that aren't handled in a thread. */
7798 switch (event->getType()) {
7801 break;
7802 }
7805 break;
7806 }
7807 default: {
7808 break;
7809 }
7810 }
7811
7812 pushEvent(event);
7813 }
7814 display_->events_pending.clear();
7815
7816 if (UNLIKELY(display_->events_pending.capacity() > events_pending_default_size)) {
7817 /* Avoid over allocation in the case of occasional delay between processing events
7818 * causing many events to be collected and making this into a large array. */
7819 display_->events_pending.shrink_to_fit();
7820 display_->events_pending.reserve(events_pending_default_size);
7821 }
7822 }
7823#endif /* USE_EVENT_BACKGROUND_THREAD */
7824
7825 {
7826 const uint64_t now = getMilliSeconds();
7827#ifdef USE_EVENT_BACKGROUND_THREAD
7828 {
7829 std::lock_guard lock_timer_guard{*display_->system->timer_mutex};
7830 if (ghost_timer_manager()->fireTimers(now)) {
7831 any_processed = true;
7832 }
7833 }
7834#endif
7835 if (getTimerManager()->fireTimers(now)) {
7836 any_processed = true;
7837 }
7838 }
7839
7840#ifdef WITH_INPUT_NDOF
7841 if (static_cast<GHOST_NDOFManagerUnix *>(ndof_manager_)->processEvents()) {
7842 /* As NDOF bypasses WAYLAND event handling,
7843 * never wait for an event when an NDOF event was found. */
7844 waitForEvent = false;
7845 any_processed = true;
7846 }
7847#endif /* WITH_INPUT_NDOF */
7848
7849 if (waitForEvent) {
7850#ifdef USE_EVENT_BACKGROUND_THREAD
7851 std::lock_guard lock_server_guard{*server_mutex};
7852#endif
7853 if (wl_display_dispatch(display_->wl.display) == -1) {
7854 ghost_wl_display_report_error(display_->wl.display);
7855 }
7856 }
7857 else {
7858#ifdef USE_EVENT_BACKGROUND_THREAD
7859 /* NOTE: this works, but as the events are being read in a thread,
7860 * this could be removed and event handling still works.. */
7861 if (server_mutex->try_lock()) {
7862 if (ghost_wl_display_event_pump(display_->wl.display) == -1) {
7863 ghost_wl_display_report_error(display_->wl.display);
7864 }
7865 server_mutex->unlock();
7866 }
7867#else
7868 if (ghost_wl_display_event_pump(display_->wl.display) == -1) {
7869 ghost_wl_display_report_error(display_->wl.display);
7870 }
7871#endif /* !USE_EVENT_BACKGROUND_THREAD */
7872 }
7873
7874 if (getEventManager()->getNumEvents() > 0) {
7875 any_processed = true;
7876 }
7877
7878 return any_processed;
7879}
7880
7882{
7883 return false;
7884}
7885
7887{
7888#ifdef USE_EVENT_BACKGROUND_THREAD
7889 std::lock_guard lock_server_guard{*server_mutex};
7890#endif
7891
7892 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7893 if (UNLIKELY(!seat)) {
7894 return GHOST_kFailure;
7895 }
7896
7897 /* Only read the underlying `seat->xkb_state` when there is an active window.
7898 * Without this, the following situation occurs:
7899 *
7900 * - A window is activated (before the #wl_keyboard_listener::enter has run).
7901 * - The modifiers from `seat->xkb_state` don't match `seat->key_depressed`.
7902 * - Dummy values are written into `seat->key_depressed` to account for the discrepancy
7903 * (as `seat->xkb_state` is the source of truth), however the number of held modifiers
7904 * is not longer valid (because it's not known from dummy values).
7905 * - #wl_keyboard_listener::enter runs, however the events generated from the state change
7906 * may not match the physically held keys because the dummy values are not accurate.
7907 *
7908 * As this is an edge-case caused by the order of callbacks that run on window activation,
7909 * don't attempt to *fix* the values in `seat->key_depressed` before the keyboard enter
7910 * handler runs. This means the result of `getModifierKeys` may be momentarily incorrect
7911 * however it's corrected once #wl_keyboard_listener::enter runs.
7912 */
7913 const bool is_keyboard_active = seat->keyboard.wl.surface_window != nullptr;
7914 const xkb_mod_mask_t state = is_keyboard_active ?
7915 xkb_state_serialize_mods(seat->xkb.state,
7916 XKB_STATE_MODS_DEPRESSED) :
7917 0;
7918
7919 /* Use local #GWL_KeyboardDepressedState to check which key is pressed.
7920 * Use XKB as the source of truth, if there is any discrepancy. */
7921 for (int i = 0; i < MOD_INDEX_NUM; i++) {
7922 if (UNLIKELY(seat->xkb_keymap_mod_index[i] == XKB_MOD_INVALID)) {
7923 continue;
7924 }
7925
7926 const GWL_ModifierInfo &mod_info = g_modifier_info_table[i];
7927 /* NOTE(@ideasman42): it's important to write the XKB state back to #GWL_KeyboardDepressedState
7928 * otherwise changes to modifiers in the future wont generate events.
7929 * This can cause modifiers to be stuck when switching between windows in GNOME because
7930 * window activation is handled before the keyboard enter callback runs, see: #107314.
7931 * Now resolved upstream, keep this for GNOME 45 and older releases & misbehaving compositors
7932 * as the workaround doesn't have significant down-sides. */
7933 int16_t &depressed_l = seat->key_depressed.mods[GHOST_KEY_MODIFIER_TO_INDEX(mod_info.key_l)];
7934 int16_t &depressed_r = seat->key_depressed.mods[GHOST_KEY_MODIFIER_TO_INDEX(mod_info.key_r)];
7935 bool val_l = depressed_l > 0;
7936 bool val_r = depressed_r > 0;
7937
7938 if (is_keyboard_active) {
7939 const bool val = (state & (1 << seat->xkb_keymap_mod_index[i])) != 0;
7940 /* This shouldn't be needed, but guard against any possibility of modifiers being stuck.
7941 * Warn so if this happens it can be investigated. */
7942 if (val) {
7943 if (UNLIKELY(!(val_l || val_r))) {
7945 "modifier (%s) state is inconsistent (GHOST held keys do not match XKB)",
7946 mod_info.display_name);
7947
7948 /* Picking the left is arbitrary. */
7949 val_l = true;
7950 depressed_l = 1;
7951 }
7952 }
7953 else {
7954 if (UNLIKELY(val_l || val_r)) {
7956 "modifier (%s) state is inconsistent (GHOST released keys do not match XKB)",
7957 mod_info.display_name);
7958 val_l = false;
7959 val_r = false;
7960 depressed_l = 0;
7961 depressed_r = 0;
7962 }
7963 }
7964 }
7965
7966 keys.set(mod_info.mod_l, val_l);
7967 keys.set(mod_info.mod_r, val_r);
7968 }
7969
7970 return GHOST_kSuccess;
7971}
7972
7974{
7975#ifdef USE_EVENT_BACKGROUND_THREAD
7976 std::lock_guard lock_server_guard{*server_mutex};
7977#endif
7978
7979 GWL_Seat *seat = gwl_display_seat_active_get(display_);
7980 if (UNLIKELY(!seat)) {
7981 return GHOST_kFailure;
7982 }
7983 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
7984 if (!seat_state_pointer) {
7985 return GHOST_kFailure;
7986 }
7987
7988 buttons = seat_state_pointer->buttons;
7989 return GHOST_kSuccess;
7990}
7991
7997 const std::unordered_set<std::string> &data_offer_types)
7998{
7999 const char *ghost_supported_types[] = {ghost_wl_mime_text_utf8, ghost_wl_mime_text_plain};
8000 for (size_t i = 0; i < ARRAY_SIZE(ghost_supported_types); i++) {
8001 if (data_offer_types.count(ghost_supported_types[i])) {
8002 return ghost_supported_types[i];
8003 }
8004 }
8005 return nullptr;
8006}
8007
8009 const bool nil_terminate,
8010 const char *mime_receive_override,
8011 size_t *r_data_len)
8012{
8013 GWL_Seat *seat = gwl_display_seat_active_get(display);
8014 if (UNLIKELY(!seat)) {
8015 return nullptr;
8016 }
8017 GWL_PrimarySelection *primary = &seat->primary_selection;
8018 std::mutex &mutex = primary->data_offer_mutex;
8019
8020 mutex.lock();
8021 bool mutex_locked = true;
8022 char *data = nullptr;
8023
8024 GWL_PrimarySelection_DataOffer *data_offer = primary->data_offer;
8025 if (data_offer != nullptr) {
8026 const char *mime_receive = mime_receive_override ?
8027 mime_receive_override :
8029 GHOST_ASSERT((mime_receive_override == nullptr) ||
8030 data_offer->types.count(mime_receive_override) != 0,
8031 "Mime type override not found in data offer, caller must check");
8032
8033 if (mime_receive) {
8034 /* Receive the clipboard in a thread, performing round-trips while waiting.
8035 * This is needed so pasting contents from our own `primary->data_source` doesn't hang. */
8036 struct ThreadResult {
8037 char *data = nullptr;
8038 size_t data_len = 0;
8039 std::atomic<bool> done = false;
8040 } thread_result;
8041 auto read_clipboard_fn = [](GWL_PrimarySelection_DataOffer *data_offer,
8042 const bool nil_terminate,
8043 const char *mime_receive,
8044 std::mutex *mutex,
8045 ThreadResult *thread_result) {
8046 thread_result->data = read_buffer_from_primary_selection_offer(
8047 data_offer, mime_receive, mutex, nil_terminate, &thread_result->data_len);
8048 thread_result->done = true;
8049 };
8050 std::thread read_thread(
8051 read_clipboard_fn, data_offer, nil_terminate, mime_receive, &mutex, &thread_result);
8052 read_thread.detach();
8053
8054 while (!thread_result.done) {
8056 }
8057 data = thread_result.data;
8058 *r_data_len = thread_result.data_len;
8059
8060 /* Reading the data offer unlocks the mutex. */
8061 mutex_locked = false;
8062 }
8063 }
8064 if (mutex_locked) {
8065 mutex.unlock();
8066 }
8067 return data;
8068}
8069
8070static char *system_clipboard_get(GWL_Display *display,
8071 const bool nil_terminate,
8072 const char *mime_receive_override,
8073 size_t *r_data_len)
8074{
8075 GWL_Seat *seat = gwl_display_seat_active_get(display);
8076 if (UNLIKELY(!seat)) {
8077 return nullptr;
8078 }
8079 std::mutex &mutex = seat->data_offer_copy_paste_mutex;
8080
8081 mutex.lock();
8082 bool mutex_locked = true;
8083 char *data = nullptr;
8084
8085 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
8086 if (data_offer != nullptr) {
8087 const char *mime_receive = mime_receive_override ?
8088 mime_receive_override :
8090 GHOST_ASSERT((mime_receive_override == nullptr) ||
8091 data_offer->types.count(mime_receive_override) != 0,
8092 "Mime type override not found in data offer, caller must check");
8093
8094 if (mime_receive) {
8095 /* Receive the clipboard in a thread, performing round-trips while waiting.
8096 * This is needed so pasting contents from our own `seat->data_source` doesn't hang. */
8097 struct ThreadResult {
8098 char *data = nullptr;
8099 size_t data_len = 0;
8100 std::atomic<bool> done = false;
8101 } thread_result;
8102 auto read_clipboard_fn = [](GWL_DataOffer *data_offer,
8103 const bool nil_terminate,
8104 const char *mime_receive,
8105 std::mutex *mutex,
8106 ThreadResult *thread_result) {
8107 thread_result->data = read_buffer_from_data_offer(
8108 data_offer, mime_receive, mutex, nil_terminate, &thread_result->data_len);
8109 thread_result->done = true;
8110 };
8111 std::thread read_thread(
8112 read_clipboard_fn, data_offer, nil_terminate, mime_receive, &mutex, &thread_result);
8113 read_thread.detach();
8114
8115 while (!thread_result.done) {
8117 }
8118 data = thread_result.data;
8119 *r_data_len = thread_result.data_len;
8120
8121 /* Reading the data offer unlocks the mutex. */
8122 mutex_locked = false;
8123 }
8124 }
8125 if (mutex_locked) {
8126 mutex.unlock();
8127 }
8128 return data;
8129}
8130
8131char *GHOST_SystemWayland::getClipboard(const bool selection) const
8132{
8133#ifdef USE_EVENT_BACKGROUND_THREAD
8134 std::lock_guard lock_server_guard{*server_mutex};
8135#endif
8136
8137 const bool nil_terminate = true;
8138 char *data = nullptr;
8139 size_t data_len = 0;
8140 if (selection) {
8141 data = system_clipboard_get_primary_selection(display_, nil_terminate, nullptr, &data_len);
8142 }
8143 else {
8144 data = system_clipboard_get(display_, nil_terminate, nullptr, &data_len);
8145 }
8146 return data;
8147}
8148
8149static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer)
8150{
8151 if (!display->wp.primary_selection_device_manager) {
8152 return;
8153 }
8154 GWL_Seat *seat = gwl_display_seat_active_get(display);
8155 if (UNLIKELY(!seat)) {
8156 return;
8157 }
8158 GWL_PrimarySelection *primary = &seat->primary_selection;
8159
8160 std::lock_guard lock{primary->data_source_mutex};
8161
8163
8165 primary->data_source = data_source;
8166
8167 /* Copy buffer. */
8168 gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer);
8169
8170 data_source->wp.source = zwp_primary_selection_device_manager_v1_create_source(
8172
8173 zwp_primary_selection_source_v1_add_listener(
8174 data_source->wp.source, &primary_selection_source_listener, primary);
8175
8176 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) {
8177 zwp_primary_selection_source_v1_offer(data_source->wp.source, ghost_wl_mime_send[i]);
8178 }
8179
8180 if (seat->wp.primary_selection_device) {
8181 zwp_primary_selection_device_v1_set_selection(
8182 seat->wp.primary_selection_device, data_source->wp.source, seat->data_source_serial);
8183 }
8184}
8185
8186static void system_clipboard_put(GWL_Display *display, const char *buffer)
8187{
8188 if (!display->wl.data_device_manager) {
8189 return;
8190 }
8191 GWL_Seat *seat = gwl_display_seat_active_get(display);
8192 if (UNLIKELY(!seat)) {
8193 return;
8194 }
8195 std::lock_guard lock{seat->data_source_mutex};
8196
8197 GWL_DataSource *data_source = seat->data_source;
8198
8199 /* Copy buffer. */
8200 gwl_simple_buffer_set_from_string(&data_source->buffer_out, buffer);
8201
8202 data_source->wl.source = wl_data_device_manager_create_data_source(
8203 display->wl.data_device_manager);
8204
8205 wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
8206
8207 for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_send); i++) {
8208 wl_data_source_offer(data_source->wl.source, ghost_wl_mime_send[i]);
8209 }
8210
8211 if (seat->wl.data_device) {
8212 wl_data_device_set_selection(
8213 seat->wl.data_device, data_source->wl.source, seat->data_source_serial);
8214 }
8215}
8216
8217void GHOST_SystemWayland::putClipboard(const char *buffer, const bool selection) const
8218{
8219#ifdef USE_EVENT_BACKGROUND_THREAD
8220 std::lock_guard lock_server_guard{*server_mutex};
8221#endif
8222
8223 if (selection) {
8225 }
8226 else {
8227 system_clipboard_put(display_, buffer);
8228 }
8229}
8230
8231static constexpr const char *ghost_wl_mime_img_png = "image/png";
8232
8234{
8235#ifdef USE_EVENT_BACKGROUND_THREAD
8236 std::lock_guard lock_server_guard{*server_mutex};
8237#endif
8238
8239 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8240 if (UNLIKELY(!seat)) {
8241 return GHOST_kFailure;
8242 }
8243
8244 if (seat->data_offer_copy_paste_has_image.has_value()) {
8245 return *seat->data_offer_copy_paste_has_image;
8246 }
8247
8249
8250 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
8251 if (data_offer) {
8252 if (data_offer->types.count(ghost_wl_mime_img_png)) {
8254 }
8255 else if (data_offer->types.count(ghost_wl_mime_text_uri_list)) {
8256 const bool nil_terminate = true;
8257 size_t data_buf_len = 0;
8258 char *data = system_clipboard_get(
8259 display_, nil_terminate, ghost_wl_mime_text_uri_list, &data_buf_len);
8260
8261 if (data) {
8262 std::vector<std::string_view> uris = gwl_clipboard_uri_ranges(data, data_buf_len);
8263 if (!uris.empty()) {
8264 const std::string_view &uri = uris.front();
8265 char *filepath = GHOST_URL_decode_alloc(uri.data(), uri.size());
8266 if (IMB_test_image(filepath)) {
8268 }
8269 free(filepath);
8270 }
8271 free(data);
8272 }
8273 }
8274 }
8275
8277
8278 return result;
8279}
8280
8281uint *GHOST_SystemWayland::getClipboardImage(int *r_width, int *r_height) const
8282{
8283#ifdef USE_EVENT_BACKGROUND_THREAD
8284 std::lock_guard lock_server_guard{*server_mutex};
8285#endif
8286
8287 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8288 if (UNLIKELY(!seat)) {
8289 return nullptr;
8290 }
8291
8292 uint *rgba = nullptr;
8293
8294 GWL_DataOffer *data_offer = seat->data_offer_copy_paste;
8295 if (data_offer) {
8296 ImBuf *ibuf = nullptr;
8297
8298 /* Check if the source offers a supported mime type.
8299 * This check could be skipped, because the paste option is not supposed to be enabled
8300 * otherwise. */
8301 if (data_offer->types.count(ghost_wl_mime_img_png)) {
8302 size_t data_len = 0;
8303 char *data = system_clipboard_get(display_, false, ghost_wl_mime_img_png, &data_len);
8304
8305 if (data) {
8306 /* Generate the image buffer with the received data. */
8308 (const uint8_t *)data, data_len, IB_byte_data, "<clipboard>");
8309 free(data);
8310 }
8311 }
8312 else if (data_offer->types.count(ghost_wl_mime_text_uri_list)) {
8313 const bool nil_terminate = true;
8314 size_t data_len = 0;
8315 char *data = system_clipboard_get(
8316 display_, nil_terminate, ghost_wl_mime_text_uri_list, &data_len);
8317
8318 if (data) {
8319 std::vector<std::string_view> uris = gwl_clipboard_uri_ranges(data, data_len);
8320 if (!uris.empty()) {
8321 const std::string_view &uri = uris.front();
8322 char *filepath = GHOST_URL_decode_alloc(uri.data(), uri.size());
8324 free(filepath);
8325 }
8326 free(data);
8327 }
8328 }
8329
8330 if (ibuf) {
8331 *r_width = ibuf->x;
8332 *r_height = ibuf->y;
8333 const size_t byte_count = size_t(ibuf->x) * size_t(ibuf->y) * 4;
8334 rgba = (uint *)malloc(byte_count);
8335 std::memcpy(rgba, ibuf->byte_buffer.data, byte_count);
8336 IMB_freeImBuf(ibuf);
8337 }
8338 }
8339
8340 return rgba;
8341}
8342
8344{
8345#ifdef USE_EVENT_BACKGROUND_THREAD
8346 std::lock_guard lock_server_guard{*server_mutex};
8347#endif
8348
8349 /* Create a #wl_data_source object. */
8350 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8351 if (UNLIKELY(!seat)) {
8352 return GHOST_kFailure;
8353 }
8354 std::lock_guard lock(seat->data_source_mutex);
8355
8356 GWL_DataSource *data_source = seat->data_source;
8357
8358 /* Load buffer into an #ImBuf and convert to PNG. */
8359 ImBuf *ibuf = IMB_allocFromBuffer(reinterpret_cast<uint8_t *>(rgba), nullptr, width, height, 32);
8360 ibuf->ftype = IMB_FTYPE_PNG;
8361 ibuf->foptions.quality = 15;
8362 if (!IMB_save_image(ibuf, "<memory>", IB_byte_data | IB_mem)) {
8363 IMB_freeImBuf(ibuf);
8364 return GHOST_kFailure;
8365 }
8366
8367 /* Copy #ImBuf encoded_buffer to data source. */
8368 GWL_SimpleBuffer *imgbuffer = &data_source->buffer_out;
8369 gwl_simple_buffer_free_data(imgbuffer);
8370 imgbuffer->data_size = ibuf->encoded_buffer_size;
8371 char *data = static_cast<char *>(malloc(imgbuffer->data_size));
8372 std::memcpy(data, ibuf->encoded_buffer.data, ibuf->encoded_buffer_size);
8373 imgbuffer->data = data;
8374
8375 data_source->wl.source = wl_data_device_manager_create_data_source(
8376 display_->wl.data_device_manager);
8377 wl_data_source_add_listener(data_source->wl.source, &data_source_listener, seat);
8378
8379 /* Advertise the mime types supported. */
8380 wl_data_source_offer(data_source->wl.source, ghost_wl_mime_img_png);
8381
8382 if (seat->wl.data_device) {
8383 wl_data_device_set_selection(
8384 seat->wl.data_device, data_source->wl.source, seat->data_source_serial);
8385 }
8386
8387 IMB_freeImBuf(ibuf);
8388 return GHOST_kSuccess;
8389}
8390
8392{
8393#ifdef USE_EVENT_BACKGROUND_THREAD
8394 std::lock_guard lock_server_guard{*server_mutex};
8395#endif
8396
8397 return display_ ? uint8_t(display_->outputs.size()) : 0;
8398}
8399
8401{
8402 /* Match the timing method used by LIBINPUT, so the result is closer to WAYLAND's time-stamps. */
8403 timespec ts = {0, 0};
8404 clock_gettime(CLOCK_MONOTONIC, &ts);
8405 return (uint64_t(ts.tv_sec) * 1000) + uint64_t(ts.tv_nsec / 1000000);
8406}
8407
8409 const GWL_SeatStatePointer *seat_state_pointer,
8410 const GHOST_WindowWayland *win,
8411 int32_t &x,
8412 int32_t &y)
8413{
8414
8415 if (win->getCursorGrabModeIsWarp()) {
8416 /* As the cursor is restored at the warped location,
8417 * apply warping when requesting the cursor location. */
8418 GHOST_Rect wrap_bounds{};
8419 if (win->getCursorGrabBounds(wrap_bounds) == GHOST_kFailure) {
8420 win->getClientBounds(wrap_bounds);
8421 }
8422 wl_fixed_t xy_wrap[2] = {
8423 seat_state_pointer->xy[0],
8424 seat_state_pointer->xy[1],
8425 };
8426
8427 GHOST_Rect wrap_bounds_scale;
8428
8429 wrap_bounds_scale.l_ = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.l_));
8430 wrap_bounds_scale.t_ = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.t_));
8431 wrap_bounds_scale.r_ = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.r_));
8432 wrap_bounds_scale.b_ = win->wl_fixed_from_window(wl_fixed_from_int(wrap_bounds.b_));
8433 wrap_bounds_scale.wrapPoint(UNPACK2(xy_wrap), 0, win->getCursorGrabAxis());
8434
8435 x = wl_fixed_to_int(win->wl_fixed_to_window(xy_wrap[0]));
8436 y = wl_fixed_to_int(win->wl_fixed_to_window(xy_wrap[1]));
8437 }
8438 else {
8439 x = wl_fixed_to_int(win->wl_fixed_to_window(seat_state_pointer->xy[0]));
8440 y = wl_fixed_to_int(win->wl_fixed_to_window(seat_state_pointer->xy[1]));
8441 }
8442
8443 return GHOST_kSuccess;
8444}
8445
8448 const int32_t x,
8449 const int32_t y)
8450{
8451 /* NOTE(@ideasman42): Regarding Cursor Warp Support:
8452 *
8453 * Before Wayland Protocols 1.45, (`wp_pointer_warp_v1`) cursor warping wasn't supported,
8454 * although at time of writing (2025) no mainstream compositors support it.
8455 * As an alternative, a software cursor is used which can be warped while "grabbed"
8456 * (see `relative_pointer` & `pointer_constraints`).
8457 * Other backends use warping to implement grabbing,
8458 * however this is error-prone with issues such as:
8459 *
8460 * - Fast motion can exit the window.
8461 * - Absolute input devices don't work with warping, causing erratic cursor motion glitches.
8462 * - Motion events may be in the queue which need to be handled before the warp is handled.
8463 *
8464 * These issues can be mitigated but avoiding problems gets involved and isn't foolproof:
8465 * Input may be clamped, tablets can be assumed absolute & motion event timestamps
8466 * can be used to detect events that were in the queue before the artificial warp motion.
8467 *
8468 * Given the trouble cursor warping has caused on other platforms over the years
8469 * I believe we're better off avoiding cursor warping (`wp_pointer_warp_v1`)
8470 * and accept limited support for warping.
8471 *
8472 * See: #134818, #113511, #102346, #89399, #82870, #49498. */
8473
8474 /* When grab is enabled, we already simulate a cursor location,
8475 * so cursor warping can be supported in this case. */
8476 if (!seat->wp.relative_pointer) {
8477 return GHOST_kFailure;
8478 }
8479 const wl_fixed_t xy_next[2]{
8480 win->wl_fixed_from_window(wl_fixed_from_int(x)),
8481 win->wl_fixed_from_window(wl_fixed_from_int(y)),
8482 };
8483
8484 /* As the cursor was "warped" generate an event at the new location. */
8485 const uint64_t event_ms = seat->system->getMilliSeconds();
8486 relative_pointer_handle_relative_motion_impl(seat, win, xy_next, event_ms);
8487
8488 return GHOST_kSuccess;
8489}
8490
8492 int32_t &x,
8493 int32_t &y) const
8494{
8495#ifdef USE_EVENT_BACKGROUND_THREAD
8496 std::lock_guard lock_server_guard{*server_mutex};
8497#endif
8498
8499 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8500 if (UNLIKELY(!seat)) {
8501 return GHOST_kFailure;
8502 }
8503 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
8504 if (!seat_state_pointer || !seat_state_pointer->wl.surface_window) {
8505 return GHOST_kFailure;
8506 }
8507 const GHOST_WindowWayland *win = static_cast<const GHOST_WindowWayland *>(window);
8508 return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y);
8509}
8510
8512 const int32_t x,
8513 const int32_t y)
8514{
8515#ifdef USE_EVENT_BACKGROUND_THREAD
8516 std::lock_guard lock_server_guard{*server_mutex};
8517#endif
8518
8519 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8520 if (UNLIKELY(!seat)) {
8521 return GHOST_kFailure;
8522 }
8523 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(window);
8524 return setCursorPositionClientRelative_impl(seat, win, x, y);
8525}
8526
8528{
8529#ifdef USE_EVENT_BACKGROUND_THREAD
8530 std::lock_guard lock_server_guard{*server_mutex};
8531#endif
8532
8533 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8534 if (UNLIKELY(!seat)) {
8535 return GHOST_kFailure;
8536 }
8537 const GWL_SeatStatePointer *seat_state_pointer = gwl_seat_state_pointer_active(seat);
8538 if (!seat_state_pointer) {
8539 return GHOST_kFailure;
8540 }
8541
8542 if (wl_surface *wl_surface_focus = seat_state_pointer->wl.surface_window) {
8543 const GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
8544 return getCursorPositionClientRelative_impl(seat_state_pointer, win, x, y);
8545 }
8546 return GHOST_kFailure;
8547}
8548
8550{
8551 /* See comments in the body of #setCursorPositionClientRelative_impl
8552 * for an explanation of the state of this functionality. */
8553
8554#ifdef USE_EVENT_BACKGROUND_THREAD
8555 std::lock_guard lock_server_guard{*server_mutex};
8556#endif
8557
8558 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8559 if (UNLIKELY(!seat)) {
8560 return GHOST_kFailure;
8561 }
8562
8563 /* Intentionally different from `getCursorPosition` which supports both tablet & pointer.
8564 * In the case of setting the cursor location, tablets don't support this. */
8565 if (wl_surface *wl_surface_focus = seat->pointer.wl.surface_window) {
8566 GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
8567 return setCursorPositionClientRelative_impl(seat, win, x, y);
8568 }
8569 return GHOST_kFailure;
8570}
8571
8573{
8574#ifdef USE_EVENT_BACKGROUND_THREAD
8575 std::lock_guard lock_server_guard{*server_mutex};
8576#endif
8577 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8578 return seat->cursor.theme_size;
8579}
8580
8581void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
8582{
8583#ifdef USE_EVENT_BACKGROUND_THREAD
8584 std::lock_guard lock_server_guard{*server_mutex};
8585#endif
8586
8587 if (!display_->outputs.empty()) {
8588 /* We assume first output as main. */
8589 const GWL_Output *output = display_->outputs[0];
8590 int32_t size_native[2] = {UNPACK2(output->size_native)};
8591 if (output->transform & WL_OUTPUT_TRANSFORM_90) {
8592 std::swap(size_native[0], size_native[1]);
8593 }
8594 width = uint32_t(size_native[0]);
8595 height = uint32_t(size_native[1]);
8596 }
8597}
8598
8599void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
8600{
8601#ifdef USE_EVENT_BACKGROUND_THREAD
8602 std::lock_guard lock_server_guard{*server_mutex};
8603#endif
8604 if (!display_->outputs.empty()) {
8605 int32_t xy_min[2] = {INT32_MAX, INT32_MAX};
8606 int32_t xy_max[2] = {INT32_MIN, INT32_MIN};
8607
8608 for (const GWL_Output *output : display_->outputs) {
8609 int32_t xy[2] = {0, 0};
8610 int32_t size_native[2] = {UNPACK2(output->size_native)};
8611 if (output->has_position_logical) {
8612 xy[0] = output->position_logical[0];
8613 xy[1] = output->position_logical[1];
8614 }
8615 if (output->transform & WL_OUTPUT_TRANSFORM_90) {
8616 std::swap(size_native[0], size_native[1]);
8617 }
8618 xy_min[0] = std::min(xy_min[0], xy[0]);
8619 xy_min[1] = std::min(xy_min[1], xy[1]);
8620 xy_max[0] = std::max(xy_max[0], xy[0] + size_native[0]);
8621 xy_max[1] = std::max(xy_max[1], xy[1] + size_native[1]);
8622 }
8623
8624 width = xy_max[0] - xy_min[0];
8625 height = xy_max[1] - xy_min[1];
8626 }
8627}
8628
8630{
8631#ifdef USE_EVENT_BACKGROUND_THREAD
8632 std::lock_guard lock_server_guard{*server_mutex};
8633#endif
8634
8635 const GHOST_ContextParams context_params_offscreen =
8637
8638 switch (gpu_settings.context_type) {
8639
8640#ifdef WITH_VULKAN_BACKEND
8641 case GHOST_kDrawingContextTypeVulkan: {
8642 /* Create new off-screen surface only for vulkan. */
8643 wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor_get());
8644
8645 GHOST_Context *context = new GHOST_ContextVK(context_params_offscreen,
8646 GHOST_kVulkanPlatformWayland,
8647 0,
8648 nullptr,
8649 wl_surface,
8650 display_->wl.display,
8651 nullptr,
8652 1,
8653 2,
8654 gpu_settings.preferred_device);
8655
8656 if (context->initializeDrawingContext()) {
8657 context->setUserData(wl_surface);
8658 return context;
8659 }
8660 delete context;
8661
8662 if (wl_surface) {
8663 wl_surface_destroy(wl_surface);
8664 }
8665 return nullptr;
8666 }
8667#endif /* WITH_VULKAN_BACKEND */
8668
8669#ifdef WITH_OPENGL_BACKEND
8670 case GHOST_kDrawingContextTypeOpenGL: {
8671 /* Create new off-screen window. */
8672 wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor_get());
8673 wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr;
8674
8675 for (int minor = 6; minor >= 3; --minor) {
8676 /* Caller must lock `system->server_mutex`. */
8677 GHOST_Context *context = new GHOST_ContextEGL(
8678 this,
8679 context_params_offscreen,
8680 EGLNativeWindowType(egl_window),
8681 EGLNativeDisplayType(display_->wl.display),
8682 EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
8683 4,
8684 minor,
8686 (context_params_offscreen.is_debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
8688 EGL_OPENGL_API);
8689
8690 if (context->initializeDrawingContext()) {
8691 wl_surface_set_user_data(wl_surface, egl_window);
8692 context->setUserData(wl_surface);
8693 return context;
8694 }
8695 delete context;
8696 }
8697
8698 GHOST_PRINT("Cannot create off-screen EGL context" << std::endl);
8699 if (wl_surface) {
8700 wl_surface_destroy(wl_surface);
8701 }
8702 if (egl_window) {
8703 wl_egl_window_destroy(egl_window);
8704 }
8705 return nullptr;
8706 }
8707#endif /* WITH_OPENGL_BACKEND */
8708
8709 default: {
8710 /* Unsupported backend. */
8711 return nullptr;
8712 }
8713 }
8714}
8715
8717{
8718#ifdef USE_EVENT_BACKGROUND_THREAD
8719 std::lock_guard lock_server_guard{*server_mutex};
8720#endif
8721
8723#ifdef WITH_OPENGL_BACKEND
8724 if (dynamic_cast<GHOST_ContextEGL *>(context)) {
8725 type = GHOST_kDrawingContextTypeOpenGL;
8726 }
8727#endif /* WITH_OPENGL_BACKEND */
8728#ifdef WITH_VULKAN_BACKEND
8729 if (dynamic_cast<GHOST_ContextVK *>(context)) {
8730 type = GHOST_kDrawingContextTypeVulkan;
8731 }
8732#endif /* WITH_VULKAN_BACKEND */
8733
8734 wl_surface *wl_surface = static_cast<struct wl_surface *>(
8735 (static_cast<GHOST_Context *>(context))->getUserData());
8736
8737 /* Delete the context before the window so the context is able to release
8738 * native resources (such as the #EGLSurface) before WAYLAND frees them. */
8739 delete context;
8740
8741#ifdef WITH_OPENGL_BACKEND
8742 if (type == GHOST_kDrawingContextTypeOpenGL) {
8743 wl_egl_window *egl_window = static_cast<wl_egl_window *>(wl_surface_get_user_data(wl_surface));
8744 if (egl_window != nullptr) {
8745 wl_egl_window_destroy(egl_window);
8746 }
8747 }
8748#endif /* WITH_OPENGL_BACKEND */
8749
8750 wl_surface_destroy(wl_surface);
8751
8752 (void)type; /* Maybe unused. */
8753
8754 return GHOST_kSuccess;
8755}
8756
8758 const int32_t left,
8759 const int32_t top,
8760 const uint32_t width,
8761 const uint32_t height,
8763 const GHOST_GPUSettings gpu_settings,
8764 const bool exclusive,
8765 const bool is_dialog,
8766 const GHOST_IWindow *parent_window)
8767{
8768 const GHOST_ContextParams context_params = GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS(gpu_settings);
8769
8770 /* Globally store pointer to window manager. */
8771 GHOST_WindowWayland *window = new GHOST_WindowWayland(this,
8772 title,
8773 left,
8774 top,
8775 width,
8776 height,
8777 state,
8778 parent_window,
8779 gpu_settings.context_type,
8780 is_dialog,
8781 context_params,
8782 exclusive,
8783 gpu_settings.preferred_device);
8784
8785 if (window) {
8786 if (window->getValid()) {
8787 window_manager_->addWindow(window);
8788 window_manager_->setActiveWindow(window);
8789 const uint64_t event_ms = getMilliSeconds();
8790 pushEvent(new GHOST_Event(event_ms, GHOST_kEventWindowSize, window));
8791 }
8792 else {
8793 delete window;
8794 window = nullptr;
8795 }
8796 }
8797
8798 return window;
8799}
8800
8801static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_software_confine)
8802{
8803 if (mode == GHOST_kGrabWrap) {
8804 return true;
8805 }
8806#ifdef USE_GNOME_CONFINE_HACK
8807 if (mode == GHOST_kGrabNormal) {
8808 if (use_software_confine) {
8809 return true;
8810 }
8811 }
8812#else
8813 (void)use_software_confine;
8814#endif
8815 return false;
8816}
8817
8819{
8820 /* Caller must lock `server_mutex`. */
8821
8822 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8823 if (UNLIKELY(!seat)) {
8824 return GHOST_kFailure;
8825 }
8826
8827 if (!display_->wp.cursor_shape_manager) {
8828 return GHOST_kFailure;
8829 }
8830
8831 const std::optional<wp_cursor_shape_device_v1_shape> wl_shape =
8833 if (wl_shape == std::nullopt) {
8834 return GHOST_kFailure;
8835 }
8836
8837 if (seat->wl.pointer) {
8838 /* Set cursor for the pointer device. */
8839 if (seat->cursor.shape.device == nullptr) {
8840 seat->cursor.shape.device = wp_cursor_shape_manager_v1_get_pointer(
8841 display_->wp.cursor_shape_manager, seat->wl.pointer);
8842 }
8843 if (seat->cursor.shape.device) {
8844 if (seat->cursor.is_hardware) {
8845 wp_cursor_shape_device_v1_set_shape(
8846 seat->cursor.shape.device, seat->pointer.serial, *wl_shape);
8847 }
8848 }
8849 /* Set this to make sure we remember which shape we set when unhiding cursors. */
8850 seat->cursor.shape.enum_id = *wl_shape;
8851
8852 GWL_Cursor *cursor = &seat->cursor;
8853 cursor->visible = true;
8854 cursor->is_custom = false;
8855 }
8856
8857 /* Set cursor for all tablet tool devices. */
8858 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
8859 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
8860 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
8861 if (tablet_tool->shape.device == nullptr) {
8862 tablet_tool->shape.device = wp_cursor_shape_manager_v1_get_tablet_tool_v2(
8863 display_->wp.cursor_shape_manager, zwp_tablet_tool_v2);
8864 }
8865 if (tablet_tool->shape.device) {
8866 if (seat->cursor.is_hardware) {
8867 wp_cursor_shape_device_v1_set_shape(
8868 tablet_tool->shape.device, tablet_tool->serial, *wl_shape);
8869 }
8870 }
8871 /* Set this to make sure we remember which shape we set when unhiding cursors. */
8872 tablet_tool->shape.enum_id = *wl_shape;
8873 }
8874 return GHOST_kSuccess;
8875}
8876
8878{
8879 /* No need to lock `server_mutex`. */
8880 GWL_Seat *seat = gwl_display_seat_active_get(display_);
8881 if (UNLIKELY(!seat)) {
8882 return GHOST_kFailure;
8883 }
8884
8885 if (!display_->wp.cursor_shape_manager) {
8886 return GHOST_kFailure;
8887 }
8888
8889 const std::optional<wp_cursor_shape_device_v1_shape> wl_shape =
8891 if (wl_shape == std::nullopt) {
8892 return GHOST_kFailure;
8893 }
8894 return GHOST_kSuccess;
8895}
8896
8898 wl_shm *shm,
8899 void **buffer_data_p,
8900 size_t *buffer_data_size_p,
8901 const int cursor_size,
8902 const int cursor_size_max,
8903 const int use_dark_theme,
8904 const int scale,
8905 int r_bitmap_size[2],
8906 int r_hot_spot[2])
8907{
8908 if (*buffer_data_p) {
8909 munmap(*buffer_data_p, *buffer_data_size_p);
8910 *buffer_data_p = nullptr;
8911 *buffer_data_size_p = 0; /* Not needed, but the value is no longer meaningful. */
8912 }
8913
8914 int bitmap_size_src[2];
8915 int hot_spot[2];
8916 bool can_invert_color = false;
8917
8918 uint8_t *bitmap_src = cg.generate_fn(
8919 &cg,
8920 cursor_size,
8921 cursor_size_max,
8922 [](size_t size) -> uint8_t * { return new uint8_t[size]; },
8923 bitmap_size_src,
8924 hot_spot,
8925 &can_invert_color);
8926
8927 if (bitmap_src == nullptr) {
8928 return nullptr;
8929 }
8930
8931 const bool invert_color = can_invert_color && use_dark_theme;
8932
8933 /* There is no need to adjust the hot-spot when resizing. */
8934 int bitmap_size_dst[2] = {
8935 int(round_up_uint(bitmap_size_src[0], scale)),
8936 int(round_up_uint(bitmap_size_src[1], scale)),
8937 };
8938
8939 wl_buffer *buffer = ghost_wl_buffer_create_for_image(
8940 shm, bitmap_size_dst, WL_SHM_FORMAT_ARGB8888, buffer_data_p, buffer_data_size_p);
8941
8942 if (buffer != nullptr) {
8943 const bool is_trivial_copy = (bitmap_size_src[0] == bitmap_size_dst[0]) &&
8944 (bitmap_size_src[1] == bitmap_size_dst[1]);
8945 /* NOTE: the copy could be skipped in trivial cases.
8946 * Since it's such a small amount of data it hardly seems worth it. */
8947 if (is_trivial_copy) {
8948 /* RGBA color. */
8949 const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap_src);
8950 uint32_t *px_dst = static_cast<uint32_t *>(*buffer_data_p);
8951 if (invert_color) {
8952 for (int y = 0; y < bitmap_size_src[1]; y++) {
8953 for (int x = 0; x < bitmap_size_src[0]; x++) {
8954 *px_dst++ = rgba_straight_to_premul_inverted(*px_src++);
8955 }
8956 }
8957 }
8958 else {
8959 for (int y = 0; y < bitmap_size_src[1]; y++) {
8960 for (int x = 0; x < bitmap_size_src[0]; x++) {
8961 *px_dst++ = rgba_straight_to_premul(*px_src++);
8962 }
8963 }
8964 }
8965 }
8966 else {
8967 /* RGBA color, copy into an expanded buffer. */
8968 const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap_src);
8969 uint32_t *px_dst = static_cast<uint32_t *>(*buffer_data_p);
8970 if (invert_color) {
8971 for (int y = 0; y < bitmap_size_dst[1]; y++) {
8972 for (int x = 0; x < bitmap_size_dst[0]; x++) {
8973 *px_dst++ = (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) ?
8974 0x0 :
8976 }
8977 }
8978 }
8979 else {
8980 for (int y = 0; y < bitmap_size_dst[1]; y++) {
8981 for (int x = 0; x < bitmap_size_dst[0]; x++) {
8982 *px_dst++ = (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) ?
8983 0x0 :
8984 rgba_straight_to_premul(*px_src++);
8985 }
8986 }
8987 }
8988 }
8989
8990 r_bitmap_size[0] = bitmap_size_dst[0];
8991 r_bitmap_size[1] = bitmap_size_dst[1];
8992
8993 r_hot_spot[0] = hot_spot[0];
8994 r_hot_spot[1] = hot_spot[1];
8995 }
8996
8997 delete[] bitmap_src;
8998 return buffer;
8999}
9000
9002{
9003 /* Caller needs to lock `server_mutex`. */
9004 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9005 if (UNLIKELY(!seat)) {
9006 return GHOST_kFailure;
9007 }
9008 /* If we were using a wayland cursor shape, be sure to free it up before we try to use any
9009 * custom shapes. */
9010 if (seat->cursor.shape.device) {
9011 wp_cursor_shape_device_v1_destroy(seat->cursor.shape.device);
9012 seat->cursor.shape.device = nullptr;
9013 }
9014
9015 /* If we were using a wayland cursor shape, be sure to free it for the tablet tools
9016 * before we try to use any custom shapes. */
9017 for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
9018 GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
9019 zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
9020 if (tablet_tool->shape.device) {
9021 wp_cursor_shape_device_v1_destroy(tablet_tool->shape.device);
9022 tablet_tool->shape.device = nullptr;
9023 }
9024 }
9025
9026 /* Generate the buffer. */
9027 GWL_Cursor &cursor = seat->cursor;
9028
9029 int bitmap_size[2] = {0, 0};
9030 int hot_spot[2] = {0, 0};
9031
9032 const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
9033 const int cursor_size = seat->cursor.theme_size * scale;
9034 /* NOTE: Regarding the maximum cursor size.
9035 * - 256 seems to be a hardware limit.
9036 * - Twice the cursor size to allow "text" cursor to display wider than a typical cursor
9037 * without being *very* large - as that looks strange.
9038 */
9039 const int cursor_size_max = std::min(256, cursor_size * 2);
9040
9041 wl_buffer *buffer = ghost_wl_buffer_from_cursor_generator(cg,
9042 display_->wl.shm,
9043 &cursor.custom_data,
9044 &cursor.custom_data_size,
9045 cursor_size,
9046 cursor_size_max,
9047 cursor.use_dark_theme,
9048 scale,
9049 bitmap_size,
9050 hot_spot);
9051 if (UNLIKELY(buffer == nullptr)) {
9052 return GHOST_kFailure;
9053 }
9054
9055 wl_buffer_add_listener(buffer, &cursor_buffer_listener, &cursor);
9056
9057 cursor.visible = true;
9058 cursor.is_custom = true;
9059
9060 cursor.wl.buffer = buffer;
9061 cursor.wl.image.width = uint32_t(bitmap_size[0]);
9062 cursor.wl.image.height = uint32_t(bitmap_size[1]);
9063 cursor.wl.image.hotspot_x = uint32_t(hot_spot[0]);
9064 cursor.wl.image.hotspot_y = uint32_t(hot_spot[1]);
9065
9067
9068 return GHOST_kSuccess;
9069}
9070
9072{
9073 /* Caller must lock `server_mutex`. */
9074 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9075 if (UNLIKELY(!seat)) {
9076 return GHOST_kFailure;
9077 }
9078
9079 GWL_Cursor *cursor = &seat->cursor;
9080 if (cursor->custom_data == nullptr) {
9081 return GHOST_kFailure;
9082 }
9083 if (!cursor->is_custom) {
9084 return GHOST_kFailure;
9085 }
9086
9087 bitmap->data_size[0] = cursor->wl.image.width;
9088 bitmap->data_size[1] = cursor->wl.image.height;
9089
9090 bitmap->hot_spot[0] = cursor->wl.image.hotspot_x;
9091 bitmap->hot_spot[1] = cursor->wl.image.hotspot_y;
9092
9093 bitmap->data = static_cast<const uint8_t *>(cursor->custom_data);
9094
9095 return GHOST_kSuccess;
9096}
9097
9099{
9100 /* Caller must lock `server_mutex`. */
9101 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9102 if (UNLIKELY(!seat)) {
9103 return GHOST_kFailure;
9104 }
9105
9107 return GHOST_kSuccess;
9108}
9109
9111{
9112 /* It's possible there are no seats, ignore the value in this case. */
9113 GHOST_ASSERT(((gwl_display_seat_active_get(display_) == nullptr) ||
9115 "The trackpad direction was expected to be initialized");
9116
9117 return GHOST_TCapabilityFlag(
9119 /* NOTE: order the following flags as they they're declared in the source. */
9120 ~(
9121 /* WAYLAND doesn't support accessing the window position. */
9123 /* WAYLAND cannot precisely place windows among multiple monitors. */
9125 /* WAYLAND doesn't support setting the cursor position directly,
9126 * this is an intentional choice, forcing us to use a software cursor in this case.
9127 * For details see comments in: #setCursorPositionClientRelative_impl. */
9129 /* Some drivers don't support front-buffer reading, see: #98462 & #106264.
9130 *
9131 * NOTE(@ideasman42): the EGL flag `EGL_BUFFER_PRESERVED` is intended request support for
9132 * front-buffer reading however in my tests requesting the flag didn't work with AMD,
9133 * and it's not even requirement - so we can't rely on this feature being supported.
9134 *
9135 * Instead of assuming this is not supported, the graphics card driver could be inspected
9136 * (enable for NVIDIA for example), but the advantage in supporting this is minimal.
9137 * In practice it means an off-screen buffer is used to redraw the window for the
9138 * screen-shot and eye-dropper sampling logic, both operations where the overhead
9139 * is negligible. */
9141 /* This WAYLAND back-end has not yet implemented desktop color sample. */
9143 /* This flag will eventually be removed when support
9144 * for the old track-pad protocol is dropped. */
9146 0 :
9148 /* This WAYLAND back-end doesn't have support for window decoration styles.
9149 * In all likelihood, this back-end will eventually need to support client-side
9150 * decorations, see #113795. */
9152}
9153
9155{
9156 /* Caller must lock `server_mutex`. */
9157 const GWL_Seat *seat = gwl_display_seat_active_get(display_);
9158 if (UNLIKELY(!seat)) {
9159 return false;
9160 }
9161
9162#ifdef USE_GNOME_CONFINE_HACK
9163 const bool use_software_confine = seat->use_pointer_software_confine;
9164#else
9165 const bool use_software_confine = false;
9166#endif
9167
9168 return cursor_is_software(mode, use_software_confine);
9169}
9170
9171#ifdef USE_GNOME_CONFINE_HACK
9174{
9175# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
9176 if (use_gnome_confine_hack == false) {
9177 return false;
9178 }
9179# endif
9180 if (mode != GHOST_kGrabNormal) {
9181 return false;
9182 }
9184 if (!win) {
9185 return false;
9186 }
9187
9188# ifndef USE_GNOME_CONFINE_HACK_ALWAYS_ON
9189 if (win->scale_get() <= 1) {
9190 return false;
9191 }
9192# endif
9193 return true;
9194}
9195#endif
9196
9198 const bool use_software_confine)
9199{
9200 /* Initialize all members. */
9201 GWL_SeatStateGrab grab_state;
9202 /* Warping happens to require software cursor which also hides. */
9203 grab_state.use_lock = ELEM(mode, GHOST_kGrabWrap, GHOST_kGrabHide) || use_software_confine;
9204 grab_state.use_confine = (mode == GHOST_kGrabNormal) && (use_software_confine == false);
9205 return grab_state;
9206}
9207
9209{
9210 if (multitouch_gestures_ == use) {
9211 return;
9212 }
9214
9215#ifdef USE_EVENT_BACKGROUND_THREAD
9216 /* Ensure this listeners aren't removed while events are generated. */
9217 std::lock_guard lock_server_guard{*server_mutex};
9218#endif
9219 for (GWL_Seat *seat : display_->seats) {
9220 if (use == gwl_seat_capability_pointer_multitouch_check(seat, use)) {
9221 continue;
9222 }
9223 if (use) {
9225 }
9226 else {
9228 }
9229 }
9230}
9231
9233
9234/* -------------------------------------------------------------------- */
9237
9238static const char *ghost_wl_output_tag_id = "GHOST-output";
9239static const char *ghost_wl_surface_tag_id = "GHOST-window";
9240static const char *ghost_wl_surface_cursor_pointer_tag_id = "GHOST-cursor-pointer";
9241static const char *ghost_wl_surface_cursor_tablet_tag_id = "GHOST-cursor-tablet";
9242
9243bool ghost_wl_output_own(const wl_output *wl_output)
9244{
9245 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_output);
9246 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_output_tag_id;
9247}
9248
9250{
9251 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
9252 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_surface_tag_id;
9253}
9254
9259
9261{
9262 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
9263 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) ==
9265}
9266
9268{
9269 const wl_proxy *proxy = reinterpret_cast<const wl_proxy *>(wl_surface);
9270 return wl_proxy_get_tag(const_cast<wl_proxy *>(proxy)) == &ghost_wl_surface_cursor_tablet_tag_id;
9271}
9272
9273void ghost_wl_output_tag(wl_output *wl_output)
9274{
9275 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_output);
9277}
9278
9280{
9281 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
9283}
9284
9286{
9287 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
9289}
9290
9292{
9293 wl_proxy *proxy = reinterpret_cast<wl_proxy *>(wl_surface);
9295}
9296
9298
9299/* -------------------------------------------------------------------- */
9304
9306{
9307 return display_->wl.display;
9308}
9309
9311{
9312 return display_->wl.compositor;
9313}
9314
9315zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wp_primary_selection_manager_get()
9316{
9317 return display_->wp.primary_selection_device_manager;
9318}
9319
9321{
9322 return display_->xdg.activation_manager;
9323}
9324
9326{
9327 return display_->wp.fractional_scale_manager;
9328}
9330{
9331 return display_->wp.viewporter;
9332}
9333
9335{
9336 return display_->wp.pointer_gestures;
9337}
9338
9339/* This value is expected to match the base name of the `.desktop` file. see #101805.
9340 *
9341 * NOTE: the XDG desktop-entry-spec defines that this should follow the "reverse DNS" convention.
9342 * For example `org.blender.Blender` - however the `.desktop` file distributed with Blender is
9343 * simply called `blender.desktop`, so the it's important to follow that name.
9344 * Other distributions such as SNAP & FLATPAK may need to change this value #101779.
9345 * Currently there isn't a way to configure this, we may want to support that. */
9346static const char *ghost_wl_app_id = (
9347#ifdef WITH_GHOST_WAYLAND_APP_ID
9348 STRINGIFY(WITH_GHOST_WAYLAND_APP_ID)
9349#else
9350 "blender"
9351#endif
9352);
9353
9355{
9356 return ghost_wl_app_id;
9357}
9358
9359#ifdef WITH_GHOST_WAYLAND_LIBDECOR
9360
9361libdecor *GHOST_SystemWayland::libdecor_context_get()
9362{
9363 return display_->libdecor->context;
9364}
9365
9366#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
9367
9369{
9370 return display_->xdg_decor->shell;
9371}
9372
9374{
9375 return display_->xdg_decor->manager;
9376}
9377
9378/* End `xdg_decor`. */
9379
9380const std::vector<GWL_Output *> &GHOST_SystemWayland::outputs_get() const
9381{
9382 return display_->outputs;
9383}
9384
9386{
9387 return display_->wl.shm;
9388}
9389
9390#ifdef USE_EVENT_BACKGROUND_THREAD
9392{
9393 return display_->ghost_timer_manager;
9394}
9395#endif
9396
9398{
9399 return display_->use_window_frame;
9400}
9401
9403
9404/* -------------------------------------------------------------------- */
9409
9410#ifdef WITH_INPUT_IME
9411
9413 const int32_t x,
9414 const int32_t y,
9415 const int32_t w,
9416 const int32_t h,
9417 const bool completed) const
9418{
9419 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9420 if (UNLIKELY(!seat)) {
9421 return;
9422 }
9423 if (seat->wp.text_input == nullptr) {
9424 return;
9425 }
9426
9427 /* Prevent a feedback loop because the commits from this function cause
9428 * #zwp_text_input_v3_listener::preedit_string to run again which sends an event,
9429 * refreshing the position, running this function again. */
9430 gwl_seat_ime_result_reset(seat);
9431
9432 /* Don't re-enable if we're already enabled. */
9433 if (seat->ime.is_enabled && completed) {
9434 return;
9435 }
9436
9437 bool force_rect_update = false;
9438 if (seat->ime.is_enabled == false) {
9439 seat->ime.has_preedit = false;
9440 seat->ime.is_enabled = true;
9441
9442 zwp_text_input_v3_enable(seat->wp.text_input);
9443 zwp_text_input_v3_commit(seat->wp.text_input);
9444
9445 /* Now that it's enabled, set the input properties. */
9446 zwp_text_input_v3_set_content_type(seat->wp.text_input,
9447 ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
9448 ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
9449
9450 gwl_seat_ime_rect_reset(seat);
9451 force_rect_update = true;
9452 }
9453
9454 if ((force_rect_update == false) && /* Was just created, always update. */
9455 (seat->ime.rect.x == x) && /* X. */
9456 (seat->ime.rect.y == y) && /* Y. */
9457 (seat->ime.rect.w == w) && /* W. */
9458 (seat->ime.rect.h == h)) /* H. */
9459 {
9460 /* Only re-update the rectangle as needed. */
9461 }
9462 else {
9463 const int rect_x = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(x)));
9464 const int rect_y = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(y)));
9465 const int rect_w = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(w))) + 1;
9466 const int rect_h = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(h))) + 1;
9467
9468 zwp_text_input_v3_set_cursor_rectangle(seat->wp.text_input, rect_x, rect_y, rect_w, rect_h);
9469
9470 zwp_text_input_v3_commit(seat->wp.text_input);
9471
9472 seat->ime.rect.x = x;
9473 seat->ime.rect.y = y;
9474 seat->ime.rect.w = w;
9475 seat->ime.rect.h = h;
9476 }
9477}
9478
9479void GHOST_SystemWayland::ime_end(const GHOST_WindowWayland * /*window*/) const
9480{
9481 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9482 if (UNLIKELY(!seat)) {
9483 return;
9484 }
9485
9486 seat->ime.is_enabled = false;
9487
9488 gwl_seat_ime_rect_reset(seat);
9489
9490 if (seat->wp.text_input == nullptr) {
9491 return;
9492 }
9493
9494 zwp_text_input_v3_disable(seat->wp.text_input);
9495 zwp_text_input_v3_commit(seat->wp.text_input);
9496}
9497
9498#endif /* WITH_INPUT_IME */
9499
9501
9502/* -------------------------------------------------------------------- */
9505
9507{
9508 GHOST_ASSERT(wl_output, "output must not be nullptr");
9509 GHOST_ASSERT(ghost_wl_output_own(wl_output), "output is not owned by GHOST");
9510 GWL_Output *output = static_cast<GWL_Output *>(wl_output_get_user_data(wl_output));
9511 return output;
9512}
9513
9515{
9516 GHOST_ASSERT(wl_surface, "wl_surface must not be nullptr");
9517 GHOST_ASSERT(ghost_wl_surface_own(wl_surface), "wl_surface is not owned by GHOST");
9518 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(
9519 wl_surface_get_user_data(wl_surface));
9520 return win;
9521}
9522
9524
9525/* -------------------------------------------------------------------- */
9530
9531uint64_t GHOST_SystemWayland::ms_from_input_time(const uint32_t timestamp_as_uint)
9532{
9533 /* NOTE(@ideasman42): Return a time compatible with `getMilliSeconds()`,
9534 * this is needed as WAYLAND time-stamps don't have a well defined beginning
9535 * use `timestamp_as_uint` to calculate an offset which is applied to future events.
9536 * This is updated because time may have passed between generating the time-stamp and `now`.
9537 * The method here is used by SDL. */
9538 uint64_t timestamp = uint64_t(timestamp_as_uint);
9539
9540 GWL_DisplayTimeStamp &input_timestamp = display_->input_timestamp;
9541 if (UNLIKELY(timestamp_as_uint < input_timestamp.last)) {
9542 /* NOTE(@ideasman42): Sometimes event times are out of order,
9543 * while this should _never_ happen, it occasionally does:
9544 * - When resizing the window then clicking on the window with GNOME+LIBDECOR.
9545 * - With accepting IME text with GNOME-v45.2 the timestamp is in seconds, see:
9546 * https://gitlab.gnome.org/GNOME/mutter/-/issues/3214
9547 * Accept events must occur within ~25 days, out-of-order time-stamps above this time-frame
9548 * will be treated as a wrapped integer. */
9549 if (input_timestamp.last - timestamp_as_uint > std::numeric_limits<uint32_t>::max() / 2) {
9550 /* Finally check to avoid invalid rollover,
9551 * ensure the rolled over time is closer to "now" than it is currently. */
9552 const uint64_t offset_test = input_timestamp.offset +
9553 uint64_t(std::numeric_limits<uint32_t>::max()) + 1;
9554 const uint64_t now = getMilliSeconds();
9555 if (sub_abs_u64(now, timestamp + offset_test) <
9556 sub_abs_u64(now, timestamp + input_timestamp.offset))
9557 {
9558 /* 32-bit timer rollover, bump the offset. */
9559 input_timestamp.offset = offset_test;
9560 }
9561 }
9562 }
9563 input_timestamp.last = timestamp_as_uint;
9564
9565 if (input_timestamp.exact_match) {
9566 timestamp += input_timestamp.offset;
9567 }
9568 else {
9569 const uint64_t now = getMilliSeconds();
9570 const uint32_t now_as_uint32 = uint32_t(now);
9571 if (now_as_uint32 == timestamp_as_uint) {
9572 input_timestamp.exact_match = true;
9573 /* For systems with up times exceeding 47 days
9574 * it's possible we need to begin with an offset. */
9575 input_timestamp.offset = now - uint64_t(now_as_uint32);
9576 timestamp = now;
9577 }
9578 else {
9579 if (!input_timestamp.offset) {
9580 input_timestamp.offset = (now - timestamp);
9581 }
9582 timestamp += input_timestamp.offset;
9583
9584 if (timestamp > now) {
9585 input_timestamp.offset -= (timestamp - now);
9586 timestamp = now;
9587 }
9588 }
9589 }
9590
9591 return timestamp;
9592}
9593
9595{
9596#ifdef USE_EVENT_BACKGROUND_THREAD
9597 GHOST_ASSERT(!display_->background, "Foreground only");
9598 if (main_thread_id != std::this_thread::get_id()) {
9599 std::lock_guard lock{display_->events_pending_mutex};
9600 display_->events_pending.push_back(event);
9601 return GHOST_kSuccess;
9602 }
9603#endif
9604 return pushEvent(event);
9605}
9606
9608{
9609 gwl_display_seat_active_set(display_, seat);
9610}
9611
9613{
9614 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9615 if (UNLIKELY(!seat)) {
9616 return nullptr;
9617 }
9618
9619 serial = seat->data_source_serial;
9620 return seat->wl.seat;
9621}
9622
9624{
9625 bool changed = false;
9626#define SURFACE_CLEAR_PTR(surface_test) \
9627 if (surface_test == wl_surface) { \
9628 surface_test = nullptr; \
9629 changed = true; \
9630 } \
9631 ((void)0);
9632
9633 /* Only clear window surfaces (not cursors, off-screen surfaces etc). */
9634 for (GWL_Seat *seat : display_->seats) {
9635 SURFACE_CLEAR_PTR(seat->pointer.wl.surface_window);
9637 SURFACE_CLEAR_PTR(seat->keyboard.wl.surface_window);
9639#ifdef WITH_INPUT_IME
9640 SURFACE_CLEAR_PTR(seat->ime.surface_window);
9641#endif
9642 }
9643#undef SURFACE_CLEAR_PTR
9644
9645 return changed;
9646}
9647
9648bool GHOST_SystemWayland::output_unref(wl_output *wl_output)
9649{
9650 bool changed = false;
9651 if (!ghost_wl_output_own(wl_output)) {
9652 return changed;
9653 }
9654
9655 /* NOTE: keep in sync with `output_scale_update`. */
9657 const GHOST_WindowManager *window_manager = getWindowManager();
9658 if (window_manager) {
9659 for (GHOST_IWindow *iwin : window_manager->getWindows()) {
9660 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
9661 if (win->outputs_leave(output)) {
9663 changed = true;
9664 }
9665 }
9666 }
9667 for (GWL_Seat *seat : display_->seats) {
9668 if (seat->pointer.outputs.erase(output)) {
9669 changed = true;
9670 }
9671 if (seat->tablet.outputs.erase(output)) {
9672 changed = true;
9673 }
9674 }
9675 return changed;
9676}
9677
9679{
9680 /* NOTE: keep in sync with `output_unref`. */
9681 const GHOST_WindowManager *window_manager = getWindowManager();
9682 if (window_manager) {
9683 for (GHOST_IWindow *iwin : window_manager->getWindows()) {
9684 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
9685 const std::vector<GWL_Output *> &outputs = win->outputs_get();
9686 if (!(std::find(outputs.begin(), outputs.end(), output) == outputs.cend())) {
9688 }
9689 }
9690 }
9691 for (GWL_Seat *seat : display_->seats) {
9692 if (seat->pointer.outputs.count(output)) {
9693 update_cursor_scale(seat, seat->cursor, &seat->pointer);
9694 }
9695
9696 if (seat->tablet.outputs.count(output)) {
9697 update_cursor_scale(seat, seat->cursor, &seat->tablet);
9698 }
9699 }
9700}
9701
9703 const GHOST_TGrabCursorMode mode_current,
9704 int32_t init_grab_xy[2],
9705 const GHOST_Rect *wrap_bounds,
9706 const GHOST_TAxisFlag wrap_axis,
9708 const GWL_WindowScaleParams &scale_params)
9709{
9710 /* Caller must lock `server_mutex`. */
9711
9712 /* Ignore, if the required protocols are not supported. */
9713 if (UNLIKELY(!display_->wp.relative_pointer_manager || !display_->wp.pointer_constraints)) {
9714 return false;
9715 }
9716
9717 GWL_Seat *seat = gwl_display_seat_active_get(display_);
9718 if (UNLIKELY(!seat)) {
9719 return false;
9720 }
9721 /* No change, success. */
9722 if (mode == mode_current) {
9723 return true;
9724 }
9725
9726#ifdef USE_GNOME_CONFINE_HACK
9727 const bool was_software_confine = seat->use_pointer_software_confine;
9728 const bool use_software_confine = setCursorGrab_use_software_confine(mode, wl_surface);
9729#else
9730 const bool was_software_confine = false;
9731 const bool use_software_confine = false;
9732#endif
9733
9734 const GWL_SeatStateGrab grab_state_prev = seat_grab_state_from_mode(mode_current,
9735 was_software_confine);
9736 const GWL_SeatStateGrab grab_state_next = seat_grab_state_from_mode(mode, use_software_confine);
9737
9738 /* Check for wrap as #GHOST_kCapabilityCursorWarp isn't supported. */
9739 const bool use_visible = !(ELEM(mode, GHOST_kGrabHide, GHOST_kGrabWrap) || use_software_confine);
9740 const bool is_hardware_cursor = !cursor_is_software(mode, use_software_confine);
9741
9742 /* Only hide so the cursor is not made visible before it's location is restored.
9743 * This function is called again at the end of this function which only shows. */
9744 gwl_seat_cursor_visible_set(seat, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_HIDE);
9745
9746 /* Switching from one grab mode to another,
9747 * in this case disable the current locks as it makes logic confusing,
9748 * postpone changing the cursor to avoid flickering. */
9749 if (!grab_state_next.use_lock) {
9750 if (seat->wp.relative_pointer) {
9751 zwp_relative_pointer_v1_destroy(seat->wp.relative_pointer);
9752 seat->wp.relative_pointer = nullptr;
9753 }
9754 if (seat->wp.locked_pointer) {
9755 /* Potentially add a motion event so the application has updated X/Y coordinates. */
9756 wl_fixed_t xy_motion[2] = {0, 0};
9757 bool xy_motion_create_event = false;
9758
9759 /* Request location to restore to. */
9760 if (mode_current == GHOST_kGrabWrap) {
9761 /* Since this call is initiated by Blender, we can be sure the window wasn't closed
9762 * by logic outside this function - as the window was needed to make this call. */
9763 wl_fixed_t xy_next[2] = {UNPACK2(seat->pointer.xy)};
9764
9765 GHOST_Rect bounds_scale;
9766
9767 bounds_scale.l_ = gwl_window_scale_wl_fixed_from(scale_params,
9768 wl_fixed_from_int(wrap_bounds->l_));
9769 bounds_scale.t_ = gwl_window_scale_wl_fixed_from(scale_params,
9770 wl_fixed_from_int(wrap_bounds->t_));
9771 bounds_scale.r_ = gwl_window_scale_wl_fixed_from(scale_params,
9772 wl_fixed_from_int(wrap_bounds->r_));
9773 bounds_scale.b_ = gwl_window_scale_wl_fixed_from(scale_params,
9774 wl_fixed_from_int(wrap_bounds->b_));
9775
9776 bounds_scale.wrapPoint(UNPACK2(xy_next), 0, wrap_axis);
9777
9778 /* Push an event so the new location is registered. */
9779 if ((xy_next[0] != seat->pointer.xy[0]) || (xy_next[1] != seat->pointer.xy[1])) {
9780 xy_motion[0] = xy_next[0];
9781 xy_motion[1] = xy_next[1];
9782 xy_motion_create_event = true;
9783 }
9784 seat->pointer.xy[0] = xy_next[0];
9785 seat->pointer.xy[1] = xy_next[1];
9786
9787 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer, UNPACK2(xy_next));
9788 wl_surface_commit(wl_surface);
9789 }
9790 else if (mode_current == GHOST_kGrabHide) {
9791 const wl_fixed_t xy_next[2] = {
9792 gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[0])),
9793 gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[1])),
9794 };
9795
9796 if ((init_grab_xy[0] != seat->grab_lock_xy[0]) ||
9797 (init_grab_xy[1] != seat->grab_lock_xy[1]))
9798 {
9799 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer,
9800 UNPACK2(xy_next));
9801 wl_surface_commit(wl_surface);
9802
9803 /* NOTE(@ideasman42): The new cursor position is a hint,
9804 * it's possible the hint is ignored. It doesn't seem like there is a good way to
9805 * know if the hint will be used or not, at least not immediately. */
9806 xy_motion[0] = xy_next[0];
9807 xy_motion[1] = xy_next[1];
9808 xy_motion_create_event = true;
9809 }
9810 else if (grab_state_prev.use_lock) {
9811 /* NOTE(@ideasman42): From WAYLAND's perspective the cursor did not move.
9812 * The application will have received "hidden" events to warped locations.
9813 * So generate event without setting the cursor position hint. */
9814 xy_motion[0] = xy_next[0];
9815 xy_motion[1] = xy_next[1];
9816 xy_motion_create_event = true;
9817 }
9818 }
9819#ifdef USE_GNOME_CONFINE_HACK
9820 else if (mode_current == GHOST_kGrabNormal) {
9821 if (was_software_confine) {
9822 zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp.locked_pointer,
9823 UNPACK2(seat->pointer.xy));
9824 wl_surface_commit(wl_surface);
9825 }
9826 }
9827#endif
9828
9829 if (xy_motion_create_event) {
9830 /* Caller has no time-stamp. */
9831 const uint64_t event_ms = getMilliSeconds();
9833 event_ms,
9836 wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[0])),
9837 wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[1])),
9839 }
9840
9841 zwp_locked_pointer_v1_destroy(seat->wp.locked_pointer);
9842 seat->wp.locked_pointer = nullptr;
9843 }
9844 }
9845
9846 if (!grab_state_next.use_confine) {
9847 if (seat->wp.confined_pointer) {
9848 zwp_confined_pointer_v1_destroy(seat->wp.confined_pointer);
9849 seat->wp.confined_pointer = nullptr;
9850 }
9851 }
9852
9853 if (mode != GHOST_kGrabDisable) {
9854 if (grab_state_next.use_lock) {
9855 if (!grab_state_prev.use_lock) {
9856 /* As WAYLAND does not support setting the cursor coordinates programmatically,
9857 * #GHOST_kGrabWrap cannot be supported by positioning the cursor directly.
9858 * Instead the cursor is locked in place, using a software cursor that is warped.
9859 * Then WAYLAND's #zwp_locked_pointer_v1_set_cursor_position_hint is used to restore
9860 * the cursor to the warped location. */
9861 seat->wp.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
9862 display_->wp.relative_pointer_manager, seat->wl.pointer);
9863 zwp_relative_pointer_v1_add_listener(
9865 seat->wp.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
9866 display_->wp.pointer_constraints,
9867 wl_surface,
9868 seat->wl.pointer,
9869 nullptr,
9870 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9871 }
9872 if (mode == GHOST_kGrabHide) {
9873 /* Set the initial position to detect any changes when un-grabbing,
9874 * otherwise the unlocked cursor defaults to un-locking in-place. */
9875 init_grab_xy[0] = wl_fixed_to_int(
9876 gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[0]));
9877 init_grab_xy[1] = wl_fixed_to_int(
9878 gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[1]));
9879 seat->grab_lock_xy[0] = init_grab_xy[0];
9880 seat->grab_lock_xy[1] = init_grab_xy[1];
9881 }
9882 }
9883 else if (grab_state_next.use_confine) {
9884 if (!grab_state_prev.use_confine) {
9885 seat->wp.confined_pointer = zwp_pointer_constraints_v1_confine_pointer(
9886 display_->wp.pointer_constraints,
9887 wl_surface,
9888 seat->wl.pointer,
9889 nullptr,
9890 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
9891 }
9892 }
9893 }
9894
9895 /* Only show so the cursor is made visible as the last step. */
9896 gwl_seat_cursor_visible_set(seat, use_visible, is_hardware_cursor, CURSOR_VISIBLE_ONLY_SHOW);
9897
9898#ifdef USE_GNOME_CONFINE_HACK
9899 seat->use_pointer_software_confine = use_software_confine;
9900#endif
9901
9902 return true;
9903}
9904
9905#ifdef WITH_GHOST_WAYLAND_LIBDECOR
9906bool GHOST_SystemWayland::use_libdecor_runtime()
9907{
9908 return use_libdecor;
9909}
9910#endif
9911
9912#ifdef WITH_GHOST_WAYLAND_DYNLOAD
9913bool ghost_wl_dynload_libraries_init(const bool use_window_frame)
9914{
9915# ifdef WITH_GHOST_X11
9916 /* When running in WAYLAND, let the user know when a missing library is the only reason
9917 * WAYLAND could not be used. Otherwise it's not obvious why X11 is used as a fallback.
9918 * Otherwise when X11 is used, reporting WAYLAND library warnings is unwelcome noise. */
9919 bool verbose = getenv("WAYLAND_DISPLAY") != nullptr;
9920# else
9921 bool verbose = true;
9922# endif /* !WITH_GHOST_X11 */
9923
9924 if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */
9925 wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */
9926# ifdef WITH_OPENGL_BACKEND
9927 wayland_dynload_egl_init(verbose) /* `libwayland-egl`. */
9928# else
9929 true
9930# endif
9931 )
9932 {
9933# ifdef WITH_GHOST_WAYLAND_LIBDECOR
9934 if (use_window_frame) {
9935 has_libdecor = wayland_dynload_libdecor_init(verbose); /* `libdecor-0`. */
9936 }
9937# else
9938 (void)use_window_frame;
9939# endif
9940 return true;
9941 }
9942
9945# ifdef WITH_OPENGL_BACKEND
9947# endif
9948
9949 return false;
9950}
9951
9952void ghost_wl_dynload_libraries_exit()
9953{
9956# ifdef WITH_OPENGL_BACKEND
9958# endif
9959# ifdef WITH_GHOST_WAYLAND_LIBDECOR
9961# endif
9962}
9963
9964#endif /* WITH_GHOST_WAYLAND_DYNLOAD */
9965
KDTree *BLI_kdtree_nd_ new(unsigned int nodes_len_capacity)
Definition kdtree_impl.h:98
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_DEBUG(clg_ref,...)
Definition CLG_log.h:191
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
#define GHOST_OPENGL_EGL_CONTEXT_FLAGS
#define GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY
#define wl_display
#define wl_surface
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
char * GHOST_URL_decode_alloc(const char *buf_src, const int buf_src_len)
static void keyboard_handle_repeat_info(void *data, wl_keyboard *, const int32_t rate, const int32_t delay)
void(*)(GWL_Display *display, void *user_data, bool on_exit) GWL_RegistryEntry_RemoveFn
static constexpr int smooth_as_discrete_steps
static void ghost_wl_display_lock_without_input(wl_display *wl_display, std::mutex *server_mutex)
static void gwl_registry_wp_viewporter_remove(GWL_Display *display, void *, const bool)
static void tablet_tool_handle_button(void *data, zwp_tablet_tool_v2 *, const uint32_t serial, const uint32_t button, const uint32_t state)
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE
#define USE_NON_LATIN_KB_WORKAROUND
#define WL_FIXED_TO_INT_FOR_WINDOW_V2(win, xy)
static const char * ghost_wl_mime_send[]
static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_output *wl_output)
static void tablet_tool_handle_distance(void *, zwp_tablet_tool_v2 *, const uint32_t distance)
static void data_source_handle_target(void *, wl_data_source *, const char *)
static void primary_selection_device_handle_selection(void *data, zwp_primary_selection_device_v1 *, zwp_primary_selection_offer_v1 *id)
static void global_handle_add(void *data, wl_registry *wl_registry, const uint32_t name, const char *interface, const uint32_t version)
static void gwl_registry_wp_relative_pointer_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static int gwl_registry_handler_interface_slot_from_string(const char *interface)
static const wl_keyboard_listener keyboard_listener
static CLG_LogRef LOG_WL_TABLET_TOOL
static void gwl_registry_xdg_wm_base_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void touch_seat_handle_cancel(void *, wl_touch *)
static void keyboard_depressed_state_push_events_from_change(GWL_Seat *seat, GHOST_IWindow *win, const uint64_t event_ms, const GWL_KeyboardDepressedState &key_depressed_prev)
static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_tablet_tool_type)
#define BTN_RANGE_MAX
static void keyboard_handle_modifiers(void *data, wl_keyboard *, const uint32_t serial, const uint32_t mods_depressed, const uint32_t mods_latched, const uint32_t mods_locked, const uint32_t group)
#define pushEvent
static 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)
static int gwl_seat_cursor_buffer_scale_calc(const GWL_Seat *seat)
bool ghost_wl_surface_own(const wl_surface *wl_surface)
static char * system_clipboard_get(GWL_Display *display, const bool nil_terminate, const char *mime_receive_override, size_t *r_data_len)
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 touch_seat_handle_down(void *data, wl_touch *, const uint32_t serial, const uint32_t time, wl_surface *surface, const int32_t id, const wl_fixed_t x, const wl_fixed_t y)
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 pointer_handle_axis_discrete(void *data, wl_pointer *, const uint32_t axis, const int32_t discrete)
static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat)
static void tablet_tool_handle_type(void *data, zwp_tablet_tool_v2 *, const uint32_t tool_type)
static bool gwl_registry_entry_remove_by_interface_slot(GWL_Display *display, const int interface_slot, const bool on_exit)
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 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)
static void touch_seat_handle_motion(void *data, wl_touch *, const uint32_t time, const int32_t id, const wl_fixed_t x, const wl_fixed_t y)
#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 touch_seat_handle_up(void *data, wl_touch *, const uint32_t, const uint32_t time, const int32_t id)
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 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 uint32_t rgba_straight_to_premul(uint32_t rgba_uint)
static CLG_LogRef LOG_WL_RELATIVE_POINTER
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 wl_buffer * ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenerator &cg, wl_shm *shm, void **buffer_data_p, size_t *buffer_data_size_p, const int cursor_size, const int cursor_size_max, const int use_dark_theme, const int scale, int r_bitmap_size[2], int r_hot_spot[2])
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_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 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 void gwl_registry_wp_cursor_shape_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
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 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 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 primary_selection_source_send(void *data, zwp_primary_selection_source_v1 *, const char *, const int32_t fd)
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 void touch_seat_handle_frame(void *data, wl_touch *)
static const zwp_tablet_tool_v2_listener tablet_tool_listner
static void tablet_seat_handle_pad_added(void *, zwp_tablet_seat_v2 *, zwp_tablet_pad_v2 *id)
static void tablet_tool_handle_hardware_serial(void *, zwp_tablet_tool_v2 *, const uint32_t, const uint32_t)
#define WL_NAME_UNSET
static 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 gwl_seat_capability_touch_disable(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 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 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_motion(void *data, wl_pointer *, const uint32_t time, const wl_fixed_t surface_x, const wl_fixed_t surface_y)
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()
static void pointer_handle_axis_source(void *data, wl_pointer *, const uint32_t axis_source)
#define EVDEV_OFFSET
static uint32_t rgba_straight_to_premul_inverted(uint32_t rgba_uint)
static void gwl_registry_wp_viewporter_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static void pointer_handle_leave(void *data, wl_pointer *, const uint32_t, wl_surface *wl_surface)
static void gwl_registry_wl_data_device_manager_add(GWL_Display *display, const GWL_RegisteryAdd_Params &params)
static CLG_LogRef LOG_WL_DATA_OFFER
static void data_offer_handle_source_actions(void *data, wl_data_offer *, const uint32_t source_actions)
static void tablet_tool_handle_rotation(void *, zwp_tablet_tool_v2 *, const wl_fixed_t degrees)
static void gwl_seat_capability_pointer_enable(GWL_Seat *seat)
static CLG_LogRef LOG_WL_SEAT
static void data_offer_handle_action(void *data, wl_data_offer *, const uint32_t dnd_action)
static void gwl_seat_cursor_buffer_set_current(GWL_Seat *seat)
static void tablet_tool_handle_proximity_in(void *data, zwp_tablet_tool_v2 *, const uint32_t serial, zwp_tablet_v2 *, wl_surface *wl_surface)
static void gwl_display_event_thread_create(GWL_Display *display)
#define SURFACE_CLEAR_PTR(surface_test)
static void seat_handle_name(void *data, wl_seat *, const char *name)
static void gwl_tablet_tool_frame_event_add(GWL_TabletTool *tablet_tool, const GWL_TabletTool_EventTypes ty)
static void tablet_tool_handle_wheel(void *data, zwp_tablet_tool_v2 *, const wl_fixed_t, const int32_t clicks)
static void pointer_handle_axis_stop(void *data, wl_pointer *, const uint32_t time, const uint32_t axis)
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
@ GWL_IOR_NO_RETRY
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_shape(void *, wl_touch *, const int32_t, const wl_fixed_t, const wl_fixed_t)
static uint32_t round_up_uint(const uint32_t x, const uint32_t multiple)
@ MOD_INDEX_HYPER
@ MOD_INDEX_SHIFT
@ MOD_INDEX_ALT
@ MOD_INDEX_CTRL
static void gwl_seat_capability_touch_enable(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 void pointer_handle_axis_value120(void *data, wl_pointer *, const uint32_t axis, const int32_t value120)
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_cursor_shape_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)
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 void touch_seat_handle_orientation(void *, wl_touch *, const int32_t, const wl_fixed_t)
static CLG_LogRef LOG_WL_CURSOR_BUFFER
GWL_Pointer_EventTypes
static CLG_LogRef LOG_WL_DATA_DEVICE
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 bool update_cursor_scale(GWL_Seat *seat, GWL_Cursor &cursor, GWL_SeatStatePointer *seat_state_pointer)
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 std::optional< wp_cursor_shape_device_v1_shape > gwl_seat_cursor_find_wl_shape_from_ghost(const GHOST_TStandardCursor shape)
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 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 cursor_surface_handle_preferred_buffer_scale(void *, wl_surface *, const int32_t factor)
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:55
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_kStandardCursorSlip
@ 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)
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
@ GHOST_kCapabilityWindowPosition
@ GHOST_kCapabilityGPUReadFrontBuffer
@ GHOST_kCapabilityCursorWarp
@ GHOST_kCapabilityTrackpadPhysicalDirection
@ GHOST_kCapabilityMultiMonitorPlacement
@ GHOST_kCapabilityWindowDecorationStyles
@ GHOST_kCapabilityDesktopSample
#define GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS(gpu_settings)
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:57
@ GHOST_kFailure
Definition GHOST_Types.h:57
@ GHOST_kSuccess
Definition GHOST_Types.h:57
void(* GHOST_TBacktraceFn)(void *file_handle)
Definition GHOST_Types.h:53
GHOST_TGrabCursorMode
@ GHOST_kGrabWrap
@ GHOST_kGrabDisable
@ GHOST_kGrabHide
@ GHOST_kGrabNormal
#define GHOST_CONTEXT_PARAMS_FROM_GPU_SETTINGS_OFFSCREEN(gpu_settings)
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:189
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:121
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:23
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
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
static int verbose
Definition cineonlib.cc:30
uint64_t getTime() const override
virtual GHOST_TEventType getType() const =0
virtual GHOST_IWindow * getWindow() const =0
static GHOST_TBacktraceFn getBacktraceFn()
static bool getUseWindowFrame()
virtual GHOST_TUserDataPtr getUserData() const =0
virtual GHOST_TimerProcPtr getTimerProc() const =0
int32_t t_
int32_t r_
virtual void wrapPoint(int32_t &x, int32_t &y, int32_t ofs, GHOST_TAxisFlag axis)
int32_t b_
int32_t l_
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_IContext * createOffscreenContext(GHOST_GPUSettings gpu_settings) 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
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()
GHOST_IWindow * createWindow(const char *title, int32_t left, int32_t top, uint32_t width, uint32_t height, GHOST_TWindowState state, GHOST_GPUSettings gpu_settings, const bool exclusive, const bool is_dialog, const GHOST_IWindow *parent_window) override
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
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 GHOST_CursorGenerator &cg)
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 cursor_shape_check(GHOST_TStandardCursor cursor_shape)
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
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
GHOST_ITimerTask * installTimer(uint64_t delay, uint64_t interval, GHOST_TimerProcPtr timer_proc, GHOST_TUserDataPtr user_data=nullptr) override
GHOST_WindowManager * window_manager_
GHOST_TSuccess init() override
GHOST_TimerManager * getTimerManager() const
bool multitouch_gestures_
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
nullptr float
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 interface
#define printf(...)
#define output
#define select(A, B, C)
float distance(VecOp< float, D >, VecOp< float, D >) RET
constexpr T degrees(T) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
format
#define LOG(level)
Definition log.h:97
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[]
const char * name
void set(GHOST_TButton mask, bool down)
const uint8_t * data
Definition GHOST_Types.h:64
uint8_t *(* generate_fn)(const struct GHOST_CursorGenerator *cursor_generator, int cursor_size, int cursor_size_max, uint8_t *(*alloc_fn)(size_t size), int r_bitmap_size[2], int r_hot_spot[2], bool *r_can_invert_color)
Definition GHOST_Types.h:92
GHOST_TDrawingContextType context_type
GHOST_GPUDevice preferred_device
void set(GHOST_TModifierKey mask, bool down)
std::string composite
GHOST_TTabletMode Active
wp_cursor_shape_device_v1_shape enum_id
wp_cursor_shape_device_v1 * device
struct GWL_Cursor::@361377342315024131007131141106246124016160013120 wl
wl_cursor_image image
wl_surface * surface_cursor
GWL_CursorShape shape
std::unordered_set< std::string > types
wl_data_offer * id
struct GWL_DataOffer::@334103022322207061254104303345205274250014044005 wl
enum wl_data_device_manager_dnd_action source_actions
struct GWL_DataOffer::@217324163252040336113376260233024075243016057037 dnd
enum wl_data_device_manager_dnd_action action
GWL_SimpleBuffer buffer_out
struct GWL_DataSource::@266042222204251015347010154006334262054211005334 wl
wl_data_source * source
wp_viewporter * viewporter
zwp_primary_selection_device_manager_v1 * primary_selection_device_manager
std::vector< GWL_Seat * > seats
struct GWL_Display::@377160373310317124234021123011227252215025331223 wp
GHOST_TimerManager * ghost_timer_manager
wp_cursor_shape_manager_v1 * cursor_shape_manager
std::vector< const GHOST_IEvent * > events_pending
std::vector< GWL_Output * > outputs
zwp_pointer_gestures_v1 * pointer_gestures
GWL_DisplayTimeStamp input_timestamp
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
xdg_activation_v1 * activation_manager
wl_data_device_manager * data_device_manager
zwp_tablet_manager_v2 * tablet_manager
GHOST_SystemWayland * system
struct GWL_Display::@234333126216254210337351216147121177233062323122 xdg
std::mutex events_pending_mutex
wp_fractional_scale_manager_v1 * fractional_scale_manager
struct GWL_Display::@321377230270061333154270000162234377344051377131 wl
struct GWL_KeyRepeatPlayload::@077373356266136034236002110051217054213267072014 key_data
int16_t mods[GHOST_KEY_MODIFIER_NUM]
GHOST_TModifierKey mod_l
GHOST_TModifierKey mod_r
zwp_primary_selection_offer_v1 * id
struct GWL_PrimarySelection_DataOffer::@126163024221241304171225225122074375250327314300 wp
std::unordered_set< std::string > types
zwp_primary_selection_source_v1 * source
struct GWL_PrimarySelection_DataSource::@146206146215104204277350107221366004233173220362 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::@353342232162005204307367117272165333367002150347 wl
enum wl_pointer_axis_source axis_source
GWL_SeatStatePointerScroll_SmoothAsDiscrete smooth_as_discrete
struct GWL_SeatStatePointer_Events::@244132231361010161020033004333042172111261173215 frame_pending
GWL_Pointer_EventTypes frame_types[GWL_TabletTool_FrameTypes_NUM]
uint64_t frame_event_ms[GWL_TabletTool_FrameTypes_NUM]
struct GWL_SeatStatePointer::@336213342257301200376135072032273072076332056355 wl
std::unordered_set< const GWL_Output * > outputs
wl_keyboard * keyboard
uint64_t down_event_time_ms
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
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
struct GWL_Seat::@132041123005365121307037035360256064046111337367 touch_state
struct GWL_Seat::@360021217123271153135215030240240320226166147312 wl
GWL_PrimarySelection primary_selection
std::mutex data_offer_copy_paste_mutex
xkb_state * state_empty
wl_pointer * pointer
uint32_t cursor_source_serial
GWL_SeatStatePointer tablet
bool use_pointer_scroll_smooth_as_discrete
zwp_tablet_seat_v2 * tablet_seat
wl_surface * surface_window_focus_dnd
xkb_layout_index_t layout_active
zwp_confined_pointer_v1 * confined_pointer
xkb_compose_table * compose_table
wl_data_device * data_device
struct GWL_Seat::@375225021115324154323117303040370103243002125241 xkb
GHOST_ITimerTask * timer
xkb_mod_index_t xkb_keymap_mod_index_numlock
bool xkb_use_non_latin_workaround
xkb_state * state
uint64_t up_event_time_ms
wl_fixed_t grab_lock_xy[2]
struct GWL_Seat::@133203264172213324173147220125305012136217124230 wp
GWL_DataOffer * data_offer_dnd
struct GWL_Seat::@225162300071177161133132354334042056236157324101 key_repeat
xkb_mod_index_t xkb_keymap_mod_index_mod2
zwp_locked_pointer_v1 * locked_pointer
GWL_SeatStatePointerGesture_Pinch pointer_gesture_pinch
uint64_t motion_event_time_ms
bool use_pointer_software_confine
GWL_CursorShape shape
GHOST_TabletData data
wl_surface * surface_cursor
struct GWL_TabletTool::@370226105366251170227362332317205250007014370060 wl
struct GWL_TabletTool::@223072153020032023233072277032274100065016226102::@024216134204072030360377242327133156304221041137 wheel
GWL_TabletTool_EventTypes frame_types[GWL_TabletTool_FrameTypes_NUM]
struct GWL_TabletTool::@223072153020032023233072277032274100065016226102 frame_pending
zxdg_decoration_manager_v1 * manager
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
enum eImbFileType ftype
unsigned int encoded_buffer_size
ImBufByteBuffer encoded_buffer
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(...)
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:178