Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/platform/linuxbsd/wayland/wayland_thread.cpp
21052 views
1
/**************************************************************************/
2
/* wayland_thread.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "wayland_thread.h"
32
33
#include "core/config/engine.h"
34
35
#ifdef WAYLAND_ENABLED
36
37
#ifdef __FreeBSD__
38
#include <dev/evdev/input-event-codes.h>
39
#else
40
// Assume Linux.
41
#include <linux/input-event-codes.h>
42
#endif
43
44
// For the actual polling thread.
45
#include <poll.h>
46
47
// For shared memory buffer creation.
48
#include <fcntl.h>
49
#include <sys/mman.h>
50
#include <unistd.h>
51
52
// Fix the wl_array_for_each macro to work with C++. This is based on the
53
// original from `wayland-util.h` in the Wayland client library.
54
#undef wl_array_for_each
55
#define wl_array_for_each(pos, array) \
56
for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++)
57
58
#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED
59
#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED
60
#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__)
61
#else
62
#define DEBUG_LOG_WAYLAND_THREAD(...)
63
#endif
64
65
// Since we're never going to use this interface directly, it's not worth
66
// generating the whole deal.
67
#define FIFO_INTERFACE_NAME "wp_fifo_manager_v1"
68
69
// Read the content pointed by fd into a Vector<uint8_t>.
70
Vector<uint8_t> WaylandThread::_read_fd(int fd) {
71
// This is pretty much an arbitrary size.
72
uint32_t chunk_size = 2048;
73
74
LocalVector<uint8_t> data;
75
data.resize(chunk_size);
76
77
uint32_t bytes_read = 0;
78
79
while (true) {
80
ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size);
81
if (last_bytes_read < 0) {
82
ERR_PRINT(vformat("Read error %d.", errno));
83
84
data.clear();
85
break;
86
}
87
88
if (last_bytes_read == 0) {
89
// We're done, we've reached the EOF.
90
DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read));
91
92
close(fd);
93
94
data.resize(bytes_read);
95
break;
96
}
97
98
DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read));
99
100
bytes_read += last_bytes_read;
101
102
// Increase the buffer size by one chunk in preparation of the next read.
103
data.resize(bytes_read + chunk_size);
104
}
105
106
return Vector<uint8_t>(data);
107
}
108
109
// Based on the wayland book's shared memory boilerplate (PD/CC0).
110
// See: https://wayland-book.com/surfaces/shared-memory.html
111
int WaylandThread::_allocate_shm_file(size_t size) {
112
int retries = 100;
113
114
do {
115
// Generate a random name.
116
char name[] = "/wl_shm-godot-XXXXXX";
117
for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) {
118
name[i] = Math::random('A', 'Z');
119
}
120
121
// Try to open a shared memory object with that name.
122
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
123
if (fd >= 0) {
124
// Success, unlink its name as we just need the file descriptor.
125
shm_unlink(name);
126
127
// Resize the file to the requested length.
128
int ret;
129
do {
130
ret = ftruncate(fd, size);
131
} while (ret < 0 && errno == EINTR);
132
133
if (ret < 0) {
134
close(fd);
135
return -1;
136
}
137
138
return fd;
139
}
140
141
retries--;
142
} while (retries > 0 && errno == EEXIST);
143
144
return -1;
145
}
146
147
// Return the content of a wl_data_offer.
148
Vector<uint8_t> WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) {
149
if (!p_offer) {
150
return Vector<uint8_t>();
151
}
152
153
int fds[2];
154
if (pipe(fds) == 0) {
155
wl_data_offer_receive(p_offer, p_mime, fds[1]);
156
157
// Let the compositor know about the pipe.
158
// NOTE: It's important to just flush and not roundtrip here as we would risk
159
// running some cleanup event, like for example `wl_data_device::leave`. We're
160
// going to wait for the message anyways as the read will probably block if
161
// the compositor doesn't read from the other end of the pipe.
162
wl_display_flush(p_display);
163
164
// Close the write end of the pipe, which we don't need and would otherwise
165
// just stall our next `read`s.
166
close(fds[1]);
167
168
return _read_fd(fds[0]);
169
}
170
171
return Vector<uint8_t>();
172
}
173
174
// Read the content of a wp_primary_selection_offer.
175
Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_display *p_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *p_offer) {
176
if (!p_offer) {
177
return Vector<uint8_t>();
178
}
179
180
int fds[2];
181
if (pipe(fds) == 0) {
182
zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]);
183
184
// NOTE: It's important to just flush and not roundtrip here as we would risk
185
// running some cleanup event, like for example `wl_data_device::leave`. We're
186
// going to wait for the message anyways as the read will probably block if
187
// the compositor doesn't read from the other end of the pipe.
188
wl_display_flush(p_display);
189
190
// Close the write end of the pipe, which we don't need and would otherwise
191
// just stall our next `read`s.
192
close(fds[1]);
193
194
return _read_fd(fds[0]);
195
}
196
197
return Vector<uint8_t>();
198
}
199
200
Ref<InputEventKey> WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed) {
201
Ref<InputEventKey> event;
202
203
ERR_FAIL_NULL_V(p_ss, event);
204
205
Key shifted_key = KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(p_ss->xkb_state, p_keycode));
206
207
Key plain_key = Key::NONE;
208
// NOTE: xkbcommon's API really encourages to apply the modifier state but we
209
// only want a "plain" symbol so that we can convert it into a godot keycode.
210
const xkb_keysym_t *syms = nullptr;
211
int num_sys = xkb_keymap_key_get_syms_by_level(p_ss->xkb_keymap, p_keycode, p_ss->current_layout_index, 0, &syms);
212
if (num_sys > 0 && syms) {
213
plain_key = KeyMappingXKB::get_keycode(syms[0]);
214
}
215
216
Key physical_keycode = KeyMappingXKB::get_scancode(p_keycode);
217
KeyLocation key_location = KeyMappingXKB::get_location(p_keycode);
218
uint32_t unicode = xkb_state_key_get_utf32(p_ss->xkb_state, p_keycode);
219
220
Key keycode = Key::NONE;
221
222
if ((shifted_key & Key::SPECIAL) != Key::NONE || (plain_key & Key::SPECIAL) != Key::NONE) {
223
keycode = shifted_key;
224
}
225
226
if (keycode == Key::NONE) {
227
keycode = plain_key;
228
}
229
230
if (keycode == Key::NONE) {
231
keycode = physical_keycode;
232
}
233
234
if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
235
keycode -= 'a' - 'A';
236
}
237
238
if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {
239
return event;
240
}
241
242
event.instantiate();
243
244
event->set_window_id(p_ss->focused_id);
245
246
// Set all pressed modifiers.
247
event->set_shift_pressed(p_ss->shift_pressed);
248
event->set_ctrl_pressed(p_ss->ctrl_pressed);
249
event->set_alt_pressed(p_ss->alt_pressed);
250
event->set_meta_pressed(p_ss->meta_pressed);
251
252
event->set_pressed(p_pressed);
253
event->set_keycode(keycode);
254
event->set_physical_keycode(physical_keycode);
255
event->set_location(key_location);
256
257
if (unicode != 0) {
258
event->set_key_label(fix_key_label(unicode, keycode));
259
} else {
260
event->set_key_label(keycode);
261
}
262
263
if (p_pressed) {
264
event->set_unicode(fix_unicode(unicode));
265
}
266
267
// Taken from DisplayServerX11.
268
if (event->get_keycode() == Key::BACKTAB) {
269
// Make it consistent across platforms.
270
event->set_keycode(Key::TAB);
271
event->set_physical_keycode(Key::TAB);
272
event->set_shift_pressed(true);
273
}
274
275
return event;
276
}
277
278
// NOTE: Due to the nature of the way keys are encoded, there's an ambiguity
279
// regarding "special" keys. In other words: there's no reliable way of
280
// switching between a special key and a character key if not marking a
281
// different Godot keycode, even if we're actually using the same XKB raw
282
// keycode. This means that, during this switch, the old key will get "stuck",
283
// as it will never receive a release event. This method returns the necessary
284
// event to fix this if needed.
285
Ref<InputEventKey> WaylandThread::_seat_state_get_unstuck_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed, Key p_key) {
286
Ref<InputEventKey> event;
287
288
if (p_pressed) {
289
Key *old_key = p_ss->pressed_keycodes.getptr(p_keycode);
290
if (old_key != nullptr && *old_key != p_key) {
291
print_verbose(vformat("%s and %s have same keycode. Generating release event for %s", keycode_get_string(*old_key), keycode_get_string(p_key), keycode_get_string(*old_key)));
292
event = _seat_state_get_key_event(p_ss, p_keycode, false);
293
if (event.is_valid()) {
294
event->set_keycode(*old_key);
295
}
296
}
297
p_ss->pressed_keycodes[p_keycode] = p_key;
298
} else {
299
p_ss->pressed_keycodes.erase(p_keycode);
300
}
301
302
return event;
303
}
304
305
void WaylandThread::_seat_state_handle_xkb_keycode(SeatState *p_ss, xkb_keycode_t p_xkb_keycode, bool p_pressed, bool p_echo) {
306
ERR_FAIL_NULL(p_ss);
307
308
WaylandThread *wayland_thread = p_ss->wayland_thread;
309
ERR_FAIL_NULL(wayland_thread);
310
311
Key last_key = Key::NONE;
312
xkb_compose_status compose_status = xkb_compose_state_get_status(p_ss->xkb_compose_state);
313
314
if (p_pressed) {
315
xkb_keysym_t keysym = xkb_state_key_get_one_sym(p_ss->xkb_state, p_xkb_keycode);
316
xkb_compose_feed_result compose_result = xkb_compose_state_feed(p_ss->xkb_compose_state, keysym);
317
compose_status = xkb_compose_state_get_status(p_ss->xkb_compose_state);
318
319
if (compose_result == XKB_COMPOSE_FEED_ACCEPTED && compose_status == XKB_COMPOSE_COMPOSED) {
320
// We need to generate multiple key events to report the composed result, One
321
// per character.
322
char str_xkb[256] = {};
323
int str_xkb_size = xkb_compose_state_get_utf8(p_ss->xkb_compose_state, str_xkb, 255);
324
325
String decoded_str = String::utf8(str_xkb, str_xkb_size);
326
for (int i = 0; i < decoded_str.length(); ++i) {
327
Ref<InputEventKey> k = _seat_state_get_key_event(p_ss, p_xkb_keycode, p_pressed);
328
if (k.is_null()) {
329
continue;
330
}
331
332
k->set_unicode(decoded_str[i]);
333
k->set_echo(p_echo);
334
335
Ref<InputEventMessage> msg;
336
msg.instantiate();
337
msg->event = k;
338
wayland_thread->push_message(msg);
339
340
last_key = k->get_keycode();
341
}
342
}
343
}
344
345
if (last_key == Key::NONE && compose_status == XKB_COMPOSE_NOTHING) {
346
// If we continued with other compose status (e.g. XKB_COMPOSE_COMPOSING) we
347
// would get the composing keys _and_ the result.
348
Ref<InputEventKey> k = _seat_state_get_key_event(p_ss, p_xkb_keycode, p_pressed);
349
if (k.is_valid()) {
350
k->set_echo(p_echo);
351
352
Ref<InputEventMessage> msg;
353
msg.instantiate();
354
msg->event = k;
355
wayland_thread->push_message(msg);
356
357
last_key = k->get_keycode();
358
}
359
}
360
361
if (last_key != Key::NONE) {
362
Ref<InputEventKey> uk = _seat_state_get_unstuck_key_event(p_ss, p_xkb_keycode, p_pressed, last_key);
363
if (uk.is_valid()) {
364
Ref<InputEventMessage> u_msg;
365
u_msg.instantiate();
366
u_msg->event = uk;
367
wayland_thread->push_message(u_msg);
368
}
369
}
370
}
371
372
void WaylandThread::_set_current_seat(struct wl_seat *p_seat) {
373
if (p_seat == wl_seat_current) {
374
return;
375
}
376
377
SeatState *old_state = wl_seat_get_seat_state(wl_seat_current);
378
379
if (old_state) {
380
seat_state_unlock_pointer(old_state);
381
}
382
383
SeatState *new_state = wl_seat_get_seat_state(p_seat);
384
seat_state_unlock_pointer(new_state);
385
386
wl_seat_current = p_seat;
387
pointer_set_constraint(pointer_constraint);
388
}
389
390
// Returns whether it loaded the theme or not.
391
bool WaylandThread::_load_cursor_theme(int p_cursor_size) {
392
if (wl_cursor_theme) {
393
wl_cursor_theme_destroy(wl_cursor_theme);
394
wl_cursor_theme = nullptr;
395
}
396
397
if (cursor_theme_name.is_empty()) {
398
cursor_theme_name = "default";
399
}
400
401
print_verbose(vformat("Loading cursor theme \"%s\" size %d.", cursor_theme_name, p_cursor_size));
402
403
wl_cursor_theme = wl_cursor_theme_load(cursor_theme_name.utf8().get_data(), p_cursor_size, registry.wl_shm);
404
405
ERR_FAIL_NULL_V_MSG(wl_cursor_theme, false, "Can't load any cursor theme.");
406
407
static const char *cursor_names[] = {
408
"left_ptr",
409
"xterm",
410
"hand2",
411
"cross",
412
"watch",
413
"left_ptr_watch",
414
"fleur",
415
"dnd-move",
416
"crossed_circle",
417
"v_double_arrow",
418
"h_double_arrow",
419
"size_bdiag",
420
"size_fdiag",
421
"move",
422
"row_resize",
423
"col_resize",
424
"question_arrow"
425
};
426
427
static const char *cursor_names_fallback[] = {
428
nullptr,
429
nullptr,
430
"pointer",
431
"cross",
432
"wait",
433
"progress",
434
"grabbing",
435
"hand1",
436
"forbidden",
437
"ns-resize",
438
"ew-resize",
439
"fd_double_arrow",
440
"bd_double_arrow",
441
"fleur",
442
"sb_v_double_arrow",
443
"sb_h_double_arrow",
444
"help"
445
};
446
447
for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {
448
struct wl_cursor *cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names[i]);
449
450
if (!cursor && cursor_names_fallback[i]) {
451
cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names_fallback[i]);
452
}
453
454
if (cursor && cursor->image_count > 0) {
455
wl_cursors[i] = cursor;
456
} else {
457
wl_cursors[i] = nullptr;
458
print_verbose("Failed loading cursor: " + String(cursor_names[i]));
459
}
460
}
461
462
return true;
463
}
464
465
void WaylandThread::_update_scale(int p_scale) {
466
if (p_scale <= cursor_scale) {
467
return;
468
}
469
470
print_verbose(vformat("Bumping cursor scale to %d", p_scale));
471
472
// There's some display that's bigger than the cache, let's update it.
473
cursor_scale = p_scale;
474
475
if (wl_cursor_theme == nullptr) {
476
// Ugh. Either we're still initializing (this must've been called from the
477
// first roundtrips) or we had some error while doing so. We'll trust that it
478
// will be updated for us if needed.
479
return;
480
}
481
482
int cursor_size = unscaled_cursor_size * p_scale;
483
484
if (_load_cursor_theme(cursor_size)) {
485
for (struct wl_seat *wl_seat : registry.wl_seats) {
486
SeatState *ss = wl_seat_get_seat_state(wl_seat);
487
ERR_FAIL_NULL(ss);
488
489
seat_state_update_cursor(ss);
490
}
491
}
492
}
493
494
void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) {
495
RegistryState *registry = (RegistryState *)data;
496
ERR_FAIL_NULL(registry);
497
498
if (strcmp(interface, wl_shm_interface.name) == 0) {
499
registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);
500
registry->wl_shm_name = name;
501
return;
502
}
503
504
// NOTE: Deprecated.
505
if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) {
506
registry->xdg_exporter_v1 = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1);
507
registry->xdg_exporter_v1_name = name;
508
return;
509
}
510
511
if (strcmp(interface, zxdg_exporter_v2_interface.name) == 0) {
512
registry->xdg_exporter_v2 = (struct zxdg_exporter_v2 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v2_interface, 1);
513
registry->xdg_exporter_v2_name = name;
514
return;
515
}
516
517
if (strcmp(interface, wl_compositor_interface.name) == 0) {
518
registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, CLAMP((int)version, 1, 6));
519
registry->wl_compositor_name = name;
520
return;
521
}
522
523
if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
524
registry->wl_data_device_manager = (struct wl_data_device_manager *)wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, CLAMP((int)version, 1, 3));
525
registry->wl_data_device_manager_name = name;
526
527
// This global creates some seat data. Let's do that for the ones already available.
528
for (struct wl_seat *wl_seat : registry->wl_seats) {
529
SeatState *ss = wl_seat_get_seat_state(wl_seat);
530
ERR_FAIL_NULL(ss);
531
532
if (ss->wl_data_device == nullptr) {
533
ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);
534
wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);
535
}
536
}
537
return;
538
}
539
540
if (strcmp(interface, wl_output_interface.name) == 0) {
541
struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, CLAMP((int)version, 1, 4));
542
wl_proxy_tag_godot((struct wl_proxy *)wl_output);
543
544
registry->wl_outputs.push_back(wl_output);
545
546
ScreenState *ss = memnew(ScreenState);
547
ss->wl_output_name = name;
548
ss->wayland_thread = registry->wayland_thread;
549
550
wl_proxy_tag_godot((struct wl_proxy *)wl_output);
551
wl_output_add_listener(wl_output, &wl_output_listener, ss);
552
return;
553
}
554
555
if (strcmp(interface, wl_seat_interface.name) == 0) {
556
struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, CLAMP((int)version, 1, 9));
557
wl_proxy_tag_godot((struct wl_proxy *)wl_seat);
558
559
SeatState *ss = memnew(SeatState);
560
ss->wl_seat = wl_seat;
561
ss->wl_seat_name = name;
562
563
ss->registry = registry;
564
ss->wayland_thread = registry->wayland_thread;
565
566
// Some extra stuff depends on other globals. We'll initialize them if the
567
// globals are already there, otherwise we'll have to do that once and if they
568
// get announced.
569
//
570
// NOTE: Don't forget to also bind/destroy with the respective global.
571
if (!ss->wl_data_device && registry->wl_data_device_manager) {
572
// Clipboard & DnD.
573
ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);
574
wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);
575
}
576
577
if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {
578
// Primary selection.
579
ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);
580
zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
581
}
582
583
if (!ss->wp_tablet_seat && registry->wp_tablet_manager) {
584
// Tablet.
585
ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
586
zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
587
}
588
589
if (!ss->wp_text_input && registry->wp_text_input_manager) {
590
// IME.
591
ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);
592
zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);
593
}
594
595
registry->wl_seats.push_back(wl_seat);
596
597
wl_seat_add_listener(wl_seat, &wl_seat_listener, ss);
598
599
if (registry->wayland_thread->wl_seat_current == nullptr) {
600
registry->wayland_thread->_set_current_seat(wl_seat);
601
}
602
603
return;
604
}
605
606
if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
607
registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, CLAMP((int)version, 1, 6));
608
registry->xdg_wm_base_name = name;
609
610
xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr);
611
return;
612
}
613
614
if (strcmp(interface, wp_viewporter_interface.name) == 0) {
615
registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1);
616
registry->wp_viewporter_name = name;
617
}
618
619
if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
620
registry->wp_cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_cursor_shape_manager_v1_interface, 1);
621
registry->wp_cursor_shape_manager_name = name;
622
return;
623
}
624
625
if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {
626
registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1);
627
registry->wp_fractional_scale_manager_name = name;
628
629
// NOTE: We're not mapping the fractional scale object here because this is
630
// supposed to be a "startup global". If for some reason this isn't true (who
631
// knows), add a conditional branch for creating the add-on object.
632
}
633
634
if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
635
registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);
636
registry->xdg_decoration_manager_name = name;
637
return;
638
}
639
640
if (strcmp(interface, xdg_system_bell_v1_interface.name) == 0) {
641
registry->xdg_system_bell = (struct xdg_system_bell_v1 *)wl_registry_bind(wl_registry, name, &xdg_system_bell_v1_interface, 1);
642
registry->xdg_system_bell_name = name;
643
return;
644
}
645
646
if (strcmp(interface, xdg_toplevel_icon_manager_v1_interface.name) == 0) {
647
registry->xdg_toplevel_icon_manager = (struct xdg_toplevel_icon_manager_v1 *)wl_registry_bind(wl_registry, name, &xdg_toplevel_icon_manager_v1_interface, 1);
648
registry->xdg_toplevel_icon_manager_name = name;
649
return;
650
}
651
652
if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {
653
registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1);
654
registry->xdg_activation_name = name;
655
return;
656
}
657
658
if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) {
659
registry->wp_primary_selection_device_manager = (struct zwp_primary_selection_device_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1);
660
661
// This global creates some seat data. Let's do that for the ones already available.
662
for (struct wl_seat *wl_seat : registry->wl_seats) {
663
SeatState *ss = wl_seat_get_seat_state(wl_seat);
664
ERR_FAIL_NULL(ss);
665
666
if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {
667
ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);
668
zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
669
}
670
}
671
}
672
673
if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
674
registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1);
675
registry->wp_relative_pointer_manager_name = name;
676
return;
677
}
678
679
if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {
680
registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1);
681
registry->wp_pointer_constraints_name = name;
682
return;
683
}
684
685
if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {
686
registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1);
687
registry->wp_pointer_gestures_name = name;
688
return;
689
}
690
691
if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
692
registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);
693
registry->wp_idle_inhibit_manager_name = name;
694
return;
695
}
696
697
if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) {
698
registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1);
699
registry->wp_tablet_manager_name = name;
700
701
// This global creates some seat data. Let's do that for the ones already available.
702
for (struct wl_seat *wl_seat : registry->wl_seats) {
703
SeatState *ss = wl_seat_get_seat_state(wl_seat);
704
ERR_FAIL_NULL(ss);
705
706
ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
707
zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
708
}
709
710
return;
711
}
712
713
if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {
714
registry->wp_text_input_manager = (struct zwp_text_input_manager_v3 *)wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, 1);
715
registry->wp_text_input_manager_name = name;
716
717
// This global creates some seat data. Let's do that for the ones already available.
718
for (struct wl_seat *wl_seat : registry->wl_seats) {
719
SeatState *ss = wl_seat_get_seat_state(wl_seat);
720
ERR_FAIL_NULL(ss);
721
722
ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);
723
zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);
724
}
725
726
return;
727
}
728
729
if (strcmp(interface, FIFO_INTERFACE_NAME) == 0) {
730
registry->wp_fifo_manager_name = name;
731
}
732
733
if (strcmp(interface, godot_embedding_compositor_interface.name) == 0) {
734
registry->godot_embedding_compositor = (struct godot_embedding_compositor *)wl_registry_bind(wl_registry, name, &godot_embedding_compositor_interface, 1);
735
registry->godot_embedding_compositor_name = name;
736
737
godot_embedding_compositor_add_listener(registry->godot_embedding_compositor, &godot_embedding_compositor_listener, memnew(EmbeddingCompositorState));
738
}
739
}
740
741
void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {
742
RegistryState *registry = (RegistryState *)data;
743
ERR_FAIL_NULL(registry);
744
745
if (name == registry->wl_shm_name) {
746
if (registry->wl_shm) {
747
wl_shm_destroy(registry->wl_shm);
748
registry->wl_shm = nullptr;
749
}
750
751
registry->wl_shm_name = 0;
752
753
return;
754
}
755
756
// NOTE: Deprecated.
757
if (name == registry->xdg_exporter_v1_name) {
758
if (registry->xdg_exporter_v1) {
759
zxdg_exporter_v1_destroy(registry->xdg_exporter_v1);
760
registry->xdg_exporter_v1 = nullptr;
761
}
762
763
registry->xdg_exporter_v1_name = 0;
764
765
return;
766
}
767
768
if (name == registry->xdg_exporter_v2_name) {
769
if (registry->xdg_exporter_v2) {
770
zxdg_exporter_v2_destroy(registry->xdg_exporter_v2);
771
registry->xdg_exporter_v2 = nullptr;
772
}
773
774
registry->xdg_exporter_v2_name = 0;
775
776
return;
777
}
778
779
if (name == registry->wl_compositor_name) {
780
if (registry->wl_compositor) {
781
wl_compositor_destroy(registry->wl_compositor);
782
registry->wl_compositor = nullptr;
783
}
784
785
registry->wl_compositor_name = 0;
786
787
return;
788
}
789
790
if (name == registry->wl_data_device_manager_name) {
791
if (registry->wl_data_device_manager) {
792
wl_data_device_manager_destroy(registry->wl_data_device_manager);
793
registry->wl_data_device_manager = nullptr;
794
}
795
796
registry->wl_data_device_manager_name = 0;
797
798
// This global is used to create some seat data. Let's clean it.
799
for (struct wl_seat *wl_seat : registry->wl_seats) {
800
SeatState *ss = wl_seat_get_seat_state(wl_seat);
801
ERR_FAIL_NULL(ss);
802
803
if (ss->wl_data_device) {
804
wl_data_device_destroy(ss->wl_data_device);
805
ss->wl_data_device = nullptr;
806
}
807
808
ss->wl_data_device = nullptr;
809
}
810
811
return;
812
}
813
814
if (name == registry->xdg_wm_base_name) {
815
if (registry->xdg_wm_base) {
816
xdg_wm_base_destroy(registry->xdg_wm_base);
817
registry->xdg_wm_base = nullptr;
818
}
819
820
registry->xdg_wm_base_name = 0;
821
822
return;
823
}
824
825
if (name == registry->wp_viewporter_name) {
826
for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {
827
WindowState &ws = pair.value;
828
if (registry->wp_viewporter) {
829
wp_viewporter_destroy(registry->wp_viewporter);
830
registry->wp_viewporter = nullptr;
831
}
832
833
if (ws.wp_viewport) {
834
wp_viewport_destroy(ws.wp_viewport);
835
ws.wp_viewport = nullptr;
836
}
837
}
838
839
registry->wp_viewporter_name = 0;
840
841
return;
842
}
843
844
if (name == registry->wp_cursor_shape_manager_name) {
845
if (registry->wp_cursor_shape_manager) {
846
wp_cursor_shape_manager_v1_destroy(registry->wp_cursor_shape_manager);
847
registry->wp_cursor_shape_manager = nullptr;
848
}
849
850
registry->wp_cursor_shape_manager_name = 0;
851
852
for (struct wl_seat *wl_seat : registry->wl_seats) {
853
SeatState *ss = wl_seat_get_seat_state(wl_seat);
854
ERR_FAIL_NULL(ss);
855
856
if (ss->wp_cursor_shape_device) {
857
wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);
858
ss->wp_cursor_shape_device = nullptr;
859
}
860
}
861
}
862
863
if (name == registry->wp_fractional_scale_manager_name) {
864
for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {
865
WindowState &ws = pair.value;
866
867
if (registry->wp_fractional_scale_manager) {
868
wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);
869
registry->wp_fractional_scale_manager = nullptr;
870
}
871
872
if (ws.wp_fractional_scale) {
873
wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);
874
ws.wp_fractional_scale = nullptr;
875
}
876
}
877
878
registry->wp_fractional_scale_manager_name = 0;
879
}
880
881
if (name == registry->xdg_decoration_manager_name) {
882
if (registry->xdg_decoration_manager) {
883
zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager);
884
registry->xdg_decoration_manager = nullptr;
885
}
886
887
registry->xdg_decoration_manager_name = 0;
888
889
return;
890
}
891
892
if (name == registry->xdg_system_bell_name) {
893
if (registry->xdg_system_bell) {
894
xdg_system_bell_v1_destroy(registry->xdg_system_bell);
895
registry->xdg_system_bell = nullptr;
896
}
897
898
registry->xdg_system_bell_name = 0;
899
900
return;
901
}
902
903
if (name == registry->xdg_toplevel_icon_manager_name) {
904
if (registry->xdg_toplevel_icon_manager) {
905
xdg_toplevel_icon_manager_v1_destroy(registry->xdg_toplevel_icon_manager);
906
registry->xdg_toplevel_icon_manager = nullptr;
907
}
908
909
if (registry->wayland_thread->xdg_icon) {
910
xdg_toplevel_icon_v1_destroy(registry->wayland_thread->xdg_icon);
911
}
912
913
if (registry->wayland_thread->icon_buffer) {
914
wl_buffer_destroy(registry->wayland_thread->icon_buffer);
915
}
916
917
registry->xdg_toplevel_icon_manager_name = 0;
918
919
return;
920
}
921
922
if (name == registry->xdg_activation_name) {
923
if (registry->xdg_activation) {
924
xdg_activation_v1_destroy(registry->xdg_activation);
925
registry->xdg_activation = nullptr;
926
}
927
928
registry->xdg_activation_name = 0;
929
930
return;
931
}
932
933
if (name == registry->wp_primary_selection_device_manager_name) {
934
if (registry->wp_primary_selection_device_manager) {
935
zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager);
936
registry->wp_primary_selection_device_manager = nullptr;
937
}
938
939
registry->wp_primary_selection_device_manager_name = 0;
940
941
// This global is used to create some seat data. Let's clean it.
942
for (struct wl_seat *wl_seat : registry->wl_seats) {
943
SeatState *ss = wl_seat_get_seat_state(wl_seat);
944
ERR_FAIL_NULL(ss);
945
946
if (ss->wp_primary_selection_device) {
947
zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device);
948
ss->wp_primary_selection_device = nullptr;
949
}
950
951
if (ss->wp_primary_selection_source) {
952
zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);
953
ss->wp_primary_selection_source = nullptr;
954
}
955
956
if (ss->wp_primary_selection_offer) {
957
memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));
958
zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);
959
ss->wp_primary_selection_offer = nullptr;
960
}
961
}
962
963
return;
964
}
965
966
if (name == registry->wp_relative_pointer_manager_name) {
967
if (registry->wp_relative_pointer_manager) {
968
zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager);
969
registry->wp_relative_pointer_manager = nullptr;
970
}
971
972
registry->wp_relative_pointer_manager_name = 0;
973
974
// This global is used to create some seat data. Let's clean it.
975
for (struct wl_seat *wl_seat : registry->wl_seats) {
976
SeatState *ss = wl_seat_get_seat_state(wl_seat);
977
ERR_FAIL_NULL(ss);
978
979
if (ss->wp_relative_pointer) {
980
zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
981
ss->wp_relative_pointer = nullptr;
982
}
983
}
984
985
return;
986
}
987
988
if (name == registry->wp_pointer_constraints_name) {
989
if (registry->wp_pointer_constraints) {
990
zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints);
991
registry->wp_pointer_constraints = nullptr;
992
}
993
994
registry->wp_pointer_constraints_name = 0;
995
996
// This global is used to create some seat data. Let's clean it.
997
for (struct wl_seat *wl_seat : registry->wl_seats) {
998
SeatState *ss = wl_seat_get_seat_state(wl_seat);
999
ERR_FAIL_NULL(ss);
1000
1001
if (ss->wp_relative_pointer) {
1002
zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
1003
ss->wp_relative_pointer = nullptr;
1004
}
1005
1006
if (ss->wp_locked_pointer) {
1007
zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
1008
ss->wp_locked_pointer = nullptr;
1009
}
1010
1011
if (ss->wp_confined_pointer) {
1012
zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
1013
ss->wp_confined_pointer = nullptr;
1014
}
1015
}
1016
1017
return;
1018
}
1019
1020
if (name == registry->wp_pointer_gestures_name) {
1021
if (registry->wp_pointer_gestures) {
1022
zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures);
1023
}
1024
1025
registry->wp_pointer_gestures = nullptr;
1026
registry->wp_pointer_gestures_name = 0;
1027
1028
// This global is used to create some seat data. Let's clean it.
1029
for (struct wl_seat *wl_seat : registry->wl_seats) {
1030
SeatState *ss = wl_seat_get_seat_state(wl_seat);
1031
ERR_FAIL_NULL(ss);
1032
1033
if (ss->wp_pointer_gesture_pinch) {
1034
zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch);
1035
ss->wp_pointer_gesture_pinch = nullptr;
1036
}
1037
}
1038
1039
return;
1040
}
1041
1042
if (name == registry->wp_idle_inhibit_manager_name) {
1043
if (registry->wp_idle_inhibit_manager) {
1044
zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager);
1045
registry->wp_idle_inhibit_manager = nullptr;
1046
}
1047
1048
registry->wp_idle_inhibit_manager_name = 0;
1049
1050
return;
1051
}
1052
1053
if (name == registry->wp_tablet_manager_name) {
1054
if (registry->wp_tablet_manager) {
1055
zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager);
1056
registry->wp_tablet_manager = nullptr;
1057
}
1058
1059
registry->wp_tablet_manager_name = 0;
1060
1061
// This global is used to create some seat data. Let's clean it.
1062
for (struct wl_seat *wl_seat : registry->wl_seats) {
1063
SeatState *ss = wl_seat_get_seat_state(wl_seat);
1064
ERR_FAIL_NULL(ss);
1065
1066
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
1067
TabletToolState *state = wp_tablet_tool_get_state(tool);
1068
if (state) {
1069
memdelete(state);
1070
}
1071
1072
zwp_tablet_tool_v2_destroy(tool);
1073
}
1074
1075
ss->tablet_tools.clear();
1076
}
1077
1078
return;
1079
}
1080
1081
if (name == registry->wp_text_input_manager_name) {
1082
if (registry->wp_text_input_manager) {
1083
zwp_text_input_manager_v3_destroy(registry->wp_text_input_manager);
1084
registry->wp_text_input_manager = nullptr;
1085
}
1086
1087
registry->wp_text_input_manager_name = 0;
1088
1089
for (struct wl_seat *wl_seat : registry->wl_seats) {
1090
SeatState *ss = wl_seat_get_seat_state(wl_seat);
1091
ERR_FAIL_NULL(ss);
1092
1093
zwp_text_input_v3_destroy(ss->wp_text_input);
1094
ss->wp_text_input = nullptr;
1095
}
1096
1097
return;
1098
}
1099
1100
{
1101
// Iterate through all of the seats to find if any got removed.
1102
List<struct wl_seat *>::Element *E = registry->wl_seats.front();
1103
while (E) {
1104
struct wl_seat *wl_seat = E->get();
1105
List<struct wl_seat *>::Element *N = E->next();
1106
1107
SeatState *ss = wl_seat_get_seat_state(wl_seat);
1108
ERR_FAIL_NULL(ss);
1109
1110
if (ss->wl_seat_name == name) {
1111
if (wl_seat) {
1112
wl_seat_destroy(wl_seat);
1113
}
1114
1115
if (ss->wl_data_device) {
1116
wl_data_device_destroy(ss->wl_data_device);
1117
}
1118
1119
if (ss->wp_tablet_seat) {
1120
zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
1121
1122
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
1123
TabletToolState *state = wp_tablet_tool_get_state(tool);
1124
if (state) {
1125
memdelete(state);
1126
}
1127
1128
zwp_tablet_tool_v2_destroy(tool);
1129
}
1130
}
1131
1132
memdelete(ss);
1133
1134
registry->wl_seats.erase(E);
1135
return;
1136
}
1137
1138
E = N;
1139
}
1140
}
1141
1142
{
1143
// Iterate through all of the outputs to find if any got removed.
1144
// FIXME: This is a very bruteforce approach.
1145
List<struct wl_output *>::Element *it = registry->wl_outputs.front();
1146
while (it) {
1147
// Iterate through all of the screens to find if any got removed.
1148
struct wl_output *wl_output = it->get();
1149
ERR_FAIL_NULL(wl_output);
1150
1151
ScreenState *ss = wl_output_get_screen_state(wl_output);
1152
1153
if (ss->wl_output_name == name) {
1154
registry->wl_outputs.erase(it);
1155
1156
memdelete(ss);
1157
wl_output_destroy(wl_output);
1158
1159
return;
1160
}
1161
1162
it = it->next();
1163
}
1164
}
1165
1166
if (name == registry->wp_fifo_manager_name) {
1167
registry->wp_fifo_manager_name = 0;
1168
}
1169
1170
if (name == registry->godot_embedding_compositor_name) {
1171
registry->godot_embedding_compositor_name = 0;
1172
1173
EmbeddingCompositorState *es = godot_embedding_compositor_get_state(registry->godot_embedding_compositor);
1174
ERR_FAIL_NULL(es);
1175
1176
es->mapped_clients.clear();
1177
1178
for (struct godot_embedded_client *client : es->clients) {
1179
godot_embedded_client_destroy(client);
1180
}
1181
es->clients.clear();
1182
1183
memdelete(es);
1184
1185
godot_embedding_compositor_destroy(registry->godot_embedding_compositor);
1186
registry->godot_embedding_compositor = nullptr;
1187
}
1188
}
1189
1190
void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
1191
if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {
1192
// This won't have the right data bound to it. Not worth it and would probably
1193
// just break everything.
1194
return;
1195
}
1196
1197
WindowState *ws = (WindowState *)data;
1198
ERR_FAIL_NULL(ws);
1199
1200
DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.", (size_t)wl_output));
1201
1202
ws->wl_outputs.insert(wl_output);
1203
1204
// Workaround for buffer scaling as there's no guaranteed way of knowing the
1205
// preferred scale.
1206
// TODO: Skip this branch for newer `wl_surface`s once we add support for
1207
// `wl_surface::preferred_buffer_scale`
1208
if (ws->preferred_fractional_scale == 0) {
1209
window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);
1210
}
1211
}
1212
1213
void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) {
1214
wl_callback_destroy(wl_callback);
1215
1216
WindowState *ws = (WindowState *)data;
1217
ERR_FAIL_NULL(ws);
1218
ERR_FAIL_NULL(ws->wayland_thread);
1219
ERR_FAIL_NULL(ws->wl_surface);
1220
1221
ws->last_frame_time = OS::get_singleton()->get_ticks_usec();
1222
ws->wayland_thread->set_frame();
1223
1224
ws->frame_callback = wl_surface_frame(ws->wl_surface);
1225
wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);
1226
1227
if (ws->wl_surface && ws->buffer_scale_changed) {
1228
// NOTE: We're only now setting the buffer scale as the idea is to get this
1229
// data committed together with the new frame, all by the rendering driver.
1230
// This is important because we might otherwise set an invalid combination of
1231
// buffer size and scale (e.g. odd size and 2x scale). We're pretty much
1232
// guaranteed to get a proper buffer in the next render loop as the rescaling
1233
// method also informs the engine of a "window rect change", triggering
1234
// rendering if needed.
1235
wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));
1236
}
1237
}
1238
1239
void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
1240
if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {
1241
// This won't have the right data bound to it. Not worth it and would probably
1242
// just break everything.
1243
return;
1244
}
1245
1246
WindowState *ws = (WindowState *)data;
1247
ERR_FAIL_NULL(ws);
1248
1249
ws->wl_outputs.erase(wl_output);
1250
1251
DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output));
1252
}
1253
1254
// TODO: Add support to this event.
1255
void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) {
1256
}
1257
1258
// TODO: Add support to this event.
1259
void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) {
1260
}
1261
1262
void WaylandThread::_wl_output_on_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) {
1263
ScreenState *ss = (ScreenState *)data;
1264
ERR_FAIL_NULL(ss);
1265
1266
ss->pending_data.position.x = x;
1267
1268
ss->pending_data.position.x = x;
1269
ss->pending_data.position.y = y;
1270
1271
ss->pending_data.physical_size.width = physical_width;
1272
ss->pending_data.physical_size.height = physical_height;
1273
1274
ss->pending_data.make.clear();
1275
ss->pending_data.make.append_utf8(make);
1276
ss->pending_data.model.clear();
1277
ss->pending_data.model.append_utf8(model);
1278
1279
// `wl_output::done` is a version 2 addition. We'll directly update the data
1280
// for compatibility.
1281
if (wl_output_get_version(wl_output) == 1) {
1282
ss->data = ss->pending_data;
1283
}
1284
}
1285
1286
void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
1287
ScreenState *ss = (ScreenState *)data;
1288
ERR_FAIL_NULL(ss);
1289
1290
ss->pending_data.size.width = width;
1291
ss->pending_data.size.height = height;
1292
1293
ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1;
1294
1295
// `wl_output::done` is a version 2 addition. We'll directly update the data
1296
// for compatibility.
1297
if (wl_output_get_version(wl_output) == 1) {
1298
ss->data = ss->pending_data;
1299
}
1300
}
1301
1302
// NOTE: The following `wl_output` events are only for version 2 onwards, so we
1303
// can assume that they're "atomic" (i.e. rely on the `wl_output::done` event).
1304
1305
void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) {
1306
ScreenState *ss = (ScreenState *)data;
1307
ERR_FAIL_NULL(ss);
1308
1309
ss->data = ss->pending_data;
1310
1311
ss->wayland_thread->_update_scale(ss->data.scale);
1312
1313
DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output));
1314
}
1315
1316
void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) {
1317
ScreenState *ss = (ScreenState *)data;
1318
ERR_FAIL_NULL(ss);
1319
1320
ss->pending_data.scale = factor;
1321
1322
DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor));
1323
}
1324
1325
void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) {
1326
}
1327
1328
void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) {
1329
}
1330
1331
void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {
1332
xdg_wm_base_pong(xdg_wm_base, serial);
1333
}
1334
1335
void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
1336
xdg_surface_ack_configure(xdg_surface, serial);
1337
1338
WindowState *ws = (WindowState *)data;
1339
ERR_FAIL_NULL(ws);
1340
1341
DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure rect %s", ws->rect));
1342
}
1343
1344
void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) {
1345
WindowState *ws = (WindowState *)data;
1346
ERR_FAIL_NULL(ws);
1347
1348
// Expect the window to be in a plain state. It will get properly set if the
1349
// compositor reports otherwise below.
1350
ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
1351
ws->maximized = false;
1352
ws->fullscreen = false;
1353
ws->resizing = false;
1354
ws->tiled_left = false;
1355
ws->tiled_right = false;
1356
ws->tiled_top = false;
1357
ws->tiled_bottom = false;
1358
ws->suspended = false;
1359
1360
uint32_t *state = nullptr;
1361
wl_array_for_each(state, states) {
1362
switch (*state) {
1363
case XDG_TOPLEVEL_STATE_MAXIMIZED: {
1364
ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
1365
ws->maximized = true;
1366
} break;
1367
1368
case XDG_TOPLEVEL_STATE_FULLSCREEN: {
1369
ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
1370
ws->fullscreen = true;
1371
} break;
1372
1373
case XDG_TOPLEVEL_STATE_RESIZING: {
1374
ws->resizing = true;
1375
} break;
1376
1377
case XDG_TOPLEVEL_STATE_TILED_LEFT: {
1378
ws->tiled_left = true;
1379
} break;
1380
1381
case XDG_TOPLEVEL_STATE_TILED_RIGHT: {
1382
ws->tiled_right = true;
1383
} break;
1384
1385
case XDG_TOPLEVEL_STATE_TILED_TOP: {
1386
ws->tiled_top = true;
1387
} break;
1388
1389
case XDG_TOPLEVEL_STATE_TILED_BOTTOM: {
1390
ws->tiled_bottom = true;
1391
} break;
1392
1393
case XDG_TOPLEVEL_STATE_SUSPENDED: {
1394
ws->suspended = true;
1395
} break;
1396
1397
default: {
1398
// We don't care about the other states (for now).
1399
} break;
1400
}
1401
}
1402
1403
if (width != 0 && height != 0) {
1404
window_state_update_size(ws, width, height);
1405
}
1406
1407
DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height));
1408
}
1409
1410
void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) {
1411
WindowState *ws = (WindowState *)data;
1412
ERR_FAIL_NULL(ws);
1413
1414
Ref<WindowEventMessage> msg;
1415
msg.instantiate();
1416
msg->id = ws->id;
1417
msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
1418
ws->wayland_thread->push_message(msg);
1419
}
1420
1421
void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) {
1422
}
1423
1424
void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) {
1425
WindowState *ws = (WindowState *)data;
1426
ERR_FAIL_NULL(ws);
1427
1428
ws->can_maximize = false;
1429
ws->can_fullscreen = false;
1430
ws->can_minimize = false;
1431
1432
uint32_t *capability = nullptr;
1433
wl_array_for_each(capability, capabilities) {
1434
switch (*capability) {
1435
case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: {
1436
ws->can_maximize = true;
1437
} break;
1438
case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: {
1439
ws->can_fullscreen = true;
1440
} break;
1441
1442
case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: {
1443
ws->can_minimize = true;
1444
} break;
1445
1446
default: {
1447
} break;
1448
}
1449
}
1450
}
1451
1452
void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) {
1453
WindowState *ws = (WindowState *)data;
1454
ERR_FAIL_NULL(ws);
1455
1456
if (width != 0 && height != 0) {
1457
window_state_update_size(ws, width, height);
1458
}
1459
1460
WindowState *parent = ws->wayland_thread->window_get_state(ws->parent_id);
1461
ERR_FAIL_NULL(parent);
1462
1463
Point2i pos = Point2i(x, y);
1464
#ifdef LIBDECOR_ENABLED
1465
if (parent->libdecor_frame) {
1466
int translated_x = x;
1467
int translated_y = y;
1468
libdecor_frame_translate_coordinate(parent->libdecor_frame, x, y, &translated_x, &translated_y);
1469
1470
pos.x = translated_x;
1471
pos.y = translated_y;
1472
}
1473
#endif
1474
1475
// Looks like the position returned here is relative to the parent. We have to
1476
// accumulate it or there's gonna be a lot of confusion godot-side.
1477
pos += parent->rect.position;
1478
1479
if (ws->rect.position != pos) {
1480
DEBUG_LOG_WAYLAND_THREAD(vformat("Repositioning popup %d from %s to %s", ws->id, ws->rect.position, pos));
1481
1482
double parent_scale = window_state_get_scale_factor(parent);
1483
1484
ws->rect.position = pos;
1485
1486
Ref<WindowRectMessage> rect_msg;
1487
rect_msg.instantiate();
1488
rect_msg->id = ws->id;
1489
rect_msg->rect.position = scale_vector2i(ws->rect.position, parent_scale);
1490
rect_msg->rect.size = scale_vector2i(ws->rect.size, parent_scale);
1491
1492
ws->wayland_thread->push_message(rect_msg);
1493
}
1494
1495
DEBUG_LOG_WAYLAND_THREAD(vformat("xdg popup on configure x%d y%d w%d h%d", x, y, width, height));
1496
}
1497
1498
void WaylandThread::_xdg_popup_on_popup_done(void *data, struct xdg_popup *xdg_popup) {
1499
WindowState *ws = (WindowState *)data;
1500
ERR_FAIL_NULL(ws);
1501
1502
Ref<WindowEventMessage> ev_msg;
1503
ev_msg.instantiate();
1504
ev_msg->id = ws->id;
1505
ev_msg->event = DisplayServer::WINDOW_EVENT_FORCE_CLOSE;
1506
1507
ws->wayland_thread->push_message(ev_msg);
1508
}
1509
1510
void WaylandThread::_xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token) {
1511
DEBUG_LOG_WAYLAND_THREAD(vformat("stub xdg popup repositioned %x", token));
1512
}
1513
1514
// NOTE: Deprecated.
1515
void WaylandThread::_xdg_exported_v1_on_handle(void *data, zxdg_exported_v1 *exported, const char *handle) {
1516
WindowState *ws = (WindowState *)data;
1517
ERR_FAIL_NULL(ws);
1518
1519
ws->exported_handle = vformat("wayland:%s", String::utf8(handle));
1520
}
1521
1522
void WaylandThread::_xdg_exported_v2_on_handle(void *data, zxdg_exported_v2 *exported, const char *handle) {
1523
WindowState *ws = (WindowState *)data;
1524
ERR_FAIL_NULL(ws);
1525
1526
ws->exported_handle = vformat("wayland:%s", String::utf8(handle));
1527
}
1528
1529
void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) {
1530
if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {
1531
#ifdef LIBDECOR_ENABLED
1532
WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!");
1533
#else
1534
WARN_PRINT_ONCE("Native client side decorations are not yet supported!");
1535
#endif // LIBDECOR_ENABLED
1536
}
1537
}
1538
1539
#ifdef LIBDECOR_ENABLED
1540
void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) {
1541
ERR_PRINT(vformat("libdecor error %d: %s", error, message));
1542
}
1543
1544
// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure
1545
// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything,
1546
// forcing us to do stuff like this.
1547
void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) {
1548
WindowState *ws = (WindowState *)user_data;
1549
ERR_FAIL_NULL(ws);
1550
1551
int width = 0;
1552
int height = 0;
1553
1554
ws->pending_libdecor_configuration = configuration;
1555
1556
if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
1557
// The configuration doesn't have a size. We'll use the one already set in the window.
1558
width = ws->rect.size.width;
1559
height = ws->rect.size.height;
1560
}
1561
1562
ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size.");
1563
1564
libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE;
1565
1566
// Expect the window to be in a plain state. It will get properly set if the
1567
// compositor reports otherwise below.
1568
ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
1569
ws->maximized = false;
1570
ws->fullscreen = false;
1571
ws->resizing = false;
1572
ws->tiled_left = false;
1573
ws->tiled_right = false;
1574
ws->tiled_top = false;
1575
ws->tiled_bottom = false;
1576
ws->suspended = false;
1577
1578
if (libdecor_configuration_get_window_state(configuration, &window_state)) {
1579
if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
1580
ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
1581
ws->maximized = true;
1582
}
1583
1584
if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
1585
ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
1586
ws->fullscreen = true;
1587
}
1588
1589
// libdecor doesn't have the resizing state for whatever reason.
1590
1591
if (window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT) {
1592
ws->tiled_left = true;
1593
}
1594
1595
if (window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT) {
1596
ws->tiled_right = true;
1597
}
1598
1599
if (window_state & LIBDECOR_WINDOW_STATE_TILED_TOP) {
1600
ws->tiled_top = true;
1601
}
1602
1603
if (window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM) {
1604
ws->tiled_bottom = true;
1605
}
1606
1607
if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) {
1608
ws->suspended = true;
1609
}
1610
}
1611
1612
window_state_update_size(ws, width, height);
1613
1614
DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect));
1615
}
1616
1617
void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) {
1618
WindowState *ws = (WindowState *)user_data;
1619
ERR_FAIL_NULL(ws);
1620
1621
Ref<WindowEventMessage> winevent_msg;
1622
winevent_msg.instantiate();
1623
winevent_msg->id = ws->id;
1624
winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
1625
1626
ws->wayland_thread->push_message(winevent_msg);
1627
1628
DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close");
1629
}
1630
1631
void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) {
1632
// We're skipping this as we don't really care about libdecor's commit for
1633
// atomicity reasons. See `_frame_wl_callback_on_done` for more info.
1634
1635
DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit");
1636
}
1637
1638
void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) {
1639
}
1640
#endif // LIBDECOR_ENABLED
1641
1642
void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {
1643
SeatState *ss = (SeatState *)data;
1644
1645
ERR_FAIL_NULL(ss);
1646
1647
// TODO: Handle touch.
1648
1649
// Pointer handling.
1650
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
1651
if (!ss->wl_pointer) {
1652
ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);
1653
wl_surface_commit(ss->cursor_surface);
1654
1655
ss->wl_pointer = wl_seat_get_pointer(wl_seat);
1656
wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);
1657
1658
if (ss->registry->wp_cursor_shape_manager) {
1659
ss->wp_cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(ss->registry->wp_cursor_shape_manager, ss->wl_pointer);
1660
}
1661
1662
if (ss->registry->wp_relative_pointer_manager) {
1663
ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);
1664
zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);
1665
}
1666
1667
if (ss->registry->wp_pointer_gestures) {
1668
ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);
1669
zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);
1670
}
1671
1672
// TODO: Constrain new pointers if the global mouse mode is constrained.
1673
}
1674
} else {
1675
if (ss->cursor_frame_callback) {
1676
// Just in case. I got bitten by weird race-like conditions already.
1677
wl_callback_set_user_data(ss->cursor_frame_callback, nullptr);
1678
1679
wl_callback_destroy(ss->cursor_frame_callback);
1680
ss->cursor_frame_callback = nullptr;
1681
}
1682
1683
if (ss->cursor_surface) {
1684
wl_surface_destroy(ss->cursor_surface);
1685
ss->cursor_surface = nullptr;
1686
}
1687
1688
if (ss->wl_pointer) {
1689
wl_pointer_destroy(ss->wl_pointer);
1690
ss->wl_pointer = nullptr;
1691
}
1692
1693
if (ss->wp_cursor_shape_device) {
1694
wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);
1695
ss->wp_cursor_shape_device = nullptr;
1696
}
1697
1698
if (ss->wp_relative_pointer) {
1699
zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
1700
ss->wp_relative_pointer = nullptr;
1701
}
1702
1703
if (ss->wp_confined_pointer) {
1704
zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
1705
ss->wp_confined_pointer = nullptr;
1706
}
1707
1708
if (ss->wp_locked_pointer) {
1709
zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
1710
ss->wp_locked_pointer = nullptr;
1711
}
1712
}
1713
1714
// Keyboard handling.
1715
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
1716
if (!ss->wl_keyboard) {
1717
ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
1718
ERR_FAIL_NULL(ss->xkb_context);
1719
1720
ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);
1721
wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);
1722
}
1723
} else {
1724
if (ss->xkb_context) {
1725
xkb_context_unref(ss->xkb_context);
1726
ss->xkb_context = nullptr;
1727
}
1728
1729
if (ss->xkb_compose_table) {
1730
xkb_compose_table_unref(ss->xkb_compose_table);
1731
ss->xkb_compose_table = nullptr;
1732
}
1733
1734
if (ss->xkb_compose_state) {
1735
xkb_compose_state_unref(ss->xkb_compose_state);
1736
ss->xkb_compose_state = nullptr;
1737
}
1738
1739
if (ss->xkb_keymap) {
1740
xkb_keymap_unref(ss->xkb_keymap);
1741
ss->xkb_keymap = nullptr;
1742
}
1743
1744
if (ss->xkb_state) {
1745
xkb_state_unref(ss->xkb_state);
1746
ss->xkb_state = nullptr;
1747
}
1748
1749
if (ss->wl_keyboard) {
1750
wl_keyboard_destroy(ss->wl_keyboard);
1751
ss->wl_keyboard = nullptr;
1752
}
1753
}
1754
}
1755
1756
void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) {
1757
}
1758
1759
void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) {
1760
wl_callback_destroy(wl_callback);
1761
1762
SeatState *ss = (SeatState *)data;
1763
ERR_FAIL_NULL(ss);
1764
1765
ss->cursor_frame_callback = nullptr;
1766
1767
ss->cursor_time_ms = time_ms;
1768
1769
seat_state_update_cursor(ss);
1770
}
1771
1772
void WaylandThread::_wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
1773
WindowState *ws = wl_surface_get_window_state(surface);
1774
if (!ws) {
1775
return;
1776
}
1777
1778
SeatState *ss = (SeatState *)data;
1779
ERR_FAIL_NULL(ss);
1780
1781
ERR_FAIL_NULL(ss->cursor_surface);
1782
1783
PointerData &pd = ss->pointer_data_buffer;
1784
1785
ss->pointer_enter_serial = serial;
1786
pd.pointed_id = ws->id;
1787
pd.last_pointed_id = ws->id;
1788
pd.position.x = wl_fixed_to_double(surface_x);
1789
pd.position.y = wl_fixed_to_double(surface_y);
1790
1791
seat_state_update_cursor(ss);
1792
1793
DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer entered window %d.", ws->id));
1794
1795
if (wl_pointer_get_version(wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
1796
_wl_pointer_on_frame(data, wl_pointer);
1797
}
1798
}
1799
1800
void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
1801
// NOTE: `surface` will probably be null when the surface is destroyed.
1802
// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/366
1803
// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/465
1804
1805
SeatState *ss = (SeatState *)data;
1806
ERR_FAIL_NULL(ss);
1807
1808
PointerData &pd = ss->pointer_data_buffer;
1809
1810
if (pd.pointed_id == DisplayServer::INVALID_WINDOW_ID) {
1811
// We're probably on a decoration or some other third-party thing.
1812
return;
1813
}
1814
1815
DisplayServer::WindowID id = pd.pointed_id;
1816
1817
pd.pointed_id = DisplayServer::INVALID_WINDOW_ID;
1818
pd.pressed_button_mask.clear();
1819
1820
DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer left window %d.", id));
1821
1822
if (wl_pointer_get_version(wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
1823
_wl_pointer_on_frame(data, wl_pointer);
1824
}
1825
}
1826
1827
void WaylandThread::_wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
1828
SeatState *ss = (SeatState *)data;
1829
ERR_FAIL_NULL(ss);
1830
1831
PointerData &pd = ss->pointer_data_buffer;
1832
1833
pd.position.x = wl_fixed_to_double(surface_x);
1834
pd.position.y = wl_fixed_to_double(surface_y);
1835
1836
pd.motion_time = time;
1837
1838
if (wl_pointer_get_version(wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
1839
_wl_pointer_on_frame(data, wl_pointer);
1840
}
1841
}
1842
1843
void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
1844
SeatState *ss = (SeatState *)data;
1845
ERR_FAIL_NULL(ss);
1846
1847
PointerData &pd = ss->pointer_data_buffer;
1848
1849
MouseButton button_pressed = MouseButton::NONE;
1850
1851
switch (button) {
1852
case BTN_LEFT:
1853
button_pressed = MouseButton::LEFT;
1854
break;
1855
1856
case BTN_MIDDLE:
1857
button_pressed = MouseButton::MIDDLE;
1858
break;
1859
1860
case BTN_RIGHT:
1861
button_pressed = MouseButton::RIGHT;
1862
break;
1863
1864
case BTN_EXTRA:
1865
button_pressed = MouseButton::MB_XBUTTON1;
1866
break;
1867
1868
case BTN_SIDE:
1869
button_pressed = MouseButton::MB_XBUTTON2;
1870
break;
1871
1872
default: {
1873
}
1874
}
1875
1876
MouseButtonMask mask = mouse_button_to_mask(button_pressed);
1877
1878
if (state & WL_POINTER_BUTTON_STATE_PRESSED) {
1879
pd.pressed_button_mask.set_flag(mask);
1880
pd.last_button_pressed = button_pressed;
1881
pd.double_click_begun = true;
1882
} else {
1883
pd.pressed_button_mask.clear_flag(mask);
1884
}
1885
1886
pd.button_time = time;
1887
pd.button_serial = serial;
1888
1889
if (wl_pointer_get_version(wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
1890
_wl_pointer_on_frame(data, wl_pointer);
1891
}
1892
}
1893
1894
void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
1895
SeatState *ss = (SeatState *)data;
1896
ERR_FAIL_NULL(ss);
1897
1898
PointerData &pd = ss->pointer_data_buffer;
1899
1900
switch (axis) {
1901
case WL_POINTER_AXIS_VERTICAL_SCROLL: {
1902
pd.scroll_vector.y = wl_fixed_to_double(value);
1903
} break;
1904
1905
case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {
1906
pd.scroll_vector.x = wl_fixed_to_double(value);
1907
} break;
1908
}
1909
1910
pd.button_time = time;
1911
1912
if (wl_pointer_get_version(wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
1913
_wl_pointer_on_frame(data, wl_pointer);
1914
}
1915
}
1916
1917
void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) {
1918
SeatState *ss = (SeatState *)data;
1919
ERR_FAIL_NULL(ss);
1920
1921
WaylandThread *wayland_thread = ss->wayland_thread;
1922
ERR_FAIL_NULL(wayland_thread);
1923
1924
PointerData &old_pd = ss->pointer_data;
1925
PointerData &pd = ss->pointer_data_buffer;
1926
1927
if (pd.pointed_id != old_pd.pointed_id) {
1928
if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {
1929
Ref<WindowEventMessage> msg;
1930
msg.instantiate();
1931
msg->id = old_pd.pointed_id;
1932
msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
1933
1934
wayland_thread->push_message(msg);
1935
}
1936
1937
if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {
1938
Ref<WindowEventMessage> msg;
1939
msg.instantiate();
1940
msg->id = pd.pointed_id;
1941
msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
1942
1943
wayland_thread->push_message(msg);
1944
}
1945
}
1946
1947
WindowState *ws = nullptr;
1948
1949
// NOTE: At least on sway, with wl_pointer version 5 or greater,
1950
// wl_pointer::leave might be emitted with other events (like
1951
// wl_pointer::button) within the same wl_pointer::frame. Because of this, we
1952
// need to account for when the currently pointed window might be invalid
1953
// (third-party or even none) and fall back to the old one.
1954
if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {
1955
ws = ss->wayland_thread->window_get_state(pd.pointed_id);
1956
ERR_FAIL_NULL(ws);
1957
} else if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {
1958
ws = ss->wayland_thread->window_get_state(old_pd.pointed_id);
1959
ERR_FAIL_NULL(ws);
1960
}
1961
1962
if (ws == nullptr) {
1963
// We're probably on a decoration or some other third-party thing. Let's
1964
// "commit" the data and call it a day.
1965
old_pd = pd;
1966
return;
1967
}
1968
1969
double scale = window_state_get_scale_factor(ws);
1970
1971
wayland_thread->_set_current_seat(ss->wl_seat);
1972
1973
if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) {
1974
Ref<InputEventMouseMotion> mm;
1975
mm.instantiate();
1976
1977
// Set all pressed modifiers.
1978
mm->set_shift_pressed(ss->shift_pressed);
1979
mm->set_ctrl_pressed(ss->ctrl_pressed);
1980
mm->set_alt_pressed(ss->alt_pressed);
1981
mm->set_meta_pressed(ss->meta_pressed);
1982
1983
mm->set_window_id(ws->id);
1984
1985
mm->set_button_mask(pd.pressed_button_mask);
1986
1987
mm->set_position(pd.position * scale);
1988
mm->set_global_position(pd.position * scale);
1989
1990
Vector2 pos_delta = (pd.position - old_pd.position) * scale;
1991
1992
if (old_pd.relative_motion_time != pd.relative_motion_time) {
1993
uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time;
1994
1995
mm->set_relative(pd.relative_motion * scale);
1996
mm->set_velocity((Vector2)pos_delta / time_delta);
1997
} else {
1998
// The spec includes the possibility of having motion events without an
1999
// associated relative motion event. If that's the case, fallback to a
2000
// simple delta of the position. The captured mouse won't report the
2001
// relative speed anymore though.
2002
uint32_t time_delta = pd.motion_time - old_pd.motion_time;
2003
2004
mm->set_relative(pos_delta);
2005
mm->set_velocity((Vector2)pos_delta / time_delta);
2006
}
2007
mm->set_relative_screen_position(mm->get_relative());
2008
mm->set_screen_velocity(mm->get_velocity());
2009
2010
Ref<InputEventMessage> msg;
2011
msg.instantiate();
2012
2013
msg->event = mm;
2014
2015
wayland_thread->push_message(msg);
2016
}
2017
2018
if (pd.discrete_scroll_vector_120 - old_pd.discrete_scroll_vector_120 != Vector2i()) {
2019
// This is a discrete scroll (eg. from a scroll wheel), so we'll just emit
2020
// scroll wheel buttons.
2021
if (pd.scroll_vector.y != 0) {
2022
MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP;
2023
pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));
2024
}
2025
2026
if (pd.scroll_vector.x != 0) {
2027
MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT;
2028
pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));
2029
}
2030
} else {
2031
if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) {
2032
// This is a continuous scroll, so we'll emit a pan gesture.
2033
Ref<InputEventPanGesture> pg;
2034
pg.instantiate();
2035
2036
// Set all pressed modifiers.
2037
pg->set_shift_pressed(ss->shift_pressed);
2038
pg->set_ctrl_pressed(ss->ctrl_pressed);
2039
pg->set_alt_pressed(ss->alt_pressed);
2040
pg->set_meta_pressed(ss->meta_pressed);
2041
2042
pg->set_position(pd.position * scale);
2043
2044
pg->set_window_id(ws->id);
2045
2046
pg->set_delta(pd.scroll_vector);
2047
2048
Ref<InputEventMessage> msg;
2049
msg.instantiate();
2050
2051
msg->event = pg;
2052
2053
wayland_thread->push_message(msg);
2054
}
2055
}
2056
2057
if (old_pd.pressed_button_mask != pd.pressed_button_mask) {
2058
BitField<MouseButtonMask> pressed_mask_delta = old_pd.pressed_button_mask.get_different(pd.pressed_button_mask);
2059
2060
const MouseButton buttons_to_test[] = {
2061
MouseButton::LEFT,
2062
MouseButton::MIDDLE,
2063
MouseButton::RIGHT,
2064
MouseButton::WHEEL_UP,
2065
MouseButton::WHEEL_DOWN,
2066
MouseButton::WHEEL_LEFT,
2067
MouseButton::WHEEL_RIGHT,
2068
MouseButton::MB_XBUTTON1,
2069
MouseButton::MB_XBUTTON2,
2070
};
2071
2072
for (MouseButton test_button : buttons_to_test) {
2073
MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);
2074
if (pressed_mask_delta.has_flag(test_button_mask)) {
2075
Ref<InputEventMouseButton> mb;
2076
mb.instantiate();
2077
2078
// Set all pressed modifiers.
2079
mb->set_shift_pressed(ss->shift_pressed);
2080
mb->set_ctrl_pressed(ss->ctrl_pressed);
2081
mb->set_alt_pressed(ss->alt_pressed);
2082
mb->set_meta_pressed(ss->meta_pressed);
2083
2084
mb->set_window_id(ws->id);
2085
mb->set_position(pd.position * scale);
2086
mb->set_global_position(pd.position * scale);
2087
2088
if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) {
2089
// If this is a discrete scroll, specify how many "clicks" it did for this
2090
// pointer frame.
2091
mb->set_factor(Math::abs(pd.discrete_scroll_vector_120.y / (float)120));
2092
}
2093
2094
if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) {
2095
// If this is a discrete scroll, specify how many "clicks" it did for this
2096
// pointer frame.
2097
mb->set_factor(std::abs(pd.discrete_scroll_vector_120.x / (float)120));
2098
}
2099
2100
mb->set_button_mask(pd.pressed_button_mask);
2101
2102
mb->set_button_index(test_button);
2103
mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask));
2104
2105
// We have to set the last position pressed here as we can't take for
2106
// granted what the individual events might have seen due to them not having
2107
// a guaranteed order.
2108
if (mb->is_pressed()) {
2109
pd.last_pressed_position = pd.position;
2110
}
2111
2112
if (old_pd.double_click_begun && mb->is_pressed() && pd.last_button_pressed == old_pd.last_button_pressed && (pd.button_time - old_pd.button_time) < 400 && Vector2(old_pd.last_pressed_position * scale).distance_to(Vector2(pd.last_pressed_position * scale)) < 5) {
2113
pd.double_click_begun = false;
2114
mb->set_double_click(true);
2115
}
2116
2117
Ref<InputEventMessage> msg;
2118
msg.instantiate();
2119
2120
msg->event = mb;
2121
2122
wayland_thread->push_message(msg);
2123
2124
// Send an event resetting immediately the wheel key.
2125
// Wayland specification defines axis_stop events as optional and says to
2126
// treat all axis events as unterminated. As such, we have to manually do
2127
// it ourselves.
2128
if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) {
2129
// FIXME: This is ugly, I can't find a clean way to clone an InputEvent.
2130
// This works for now, despite being horrible.
2131
Ref<InputEventMouseButton> wh_up;
2132
wh_up.instantiate();
2133
2134
wh_up->set_window_id(ws->id);
2135
wh_up->set_position(pd.position * scale);
2136
wh_up->set_global_position(pd.position * scale);
2137
2138
// We have to unset the button to avoid it getting stuck.
2139
pd.pressed_button_mask.clear_flag(test_button_mask);
2140
wh_up->set_button_mask(pd.pressed_button_mask);
2141
2142
wh_up->set_button_index(test_button);
2143
wh_up->set_pressed(false);
2144
2145
Ref<InputEventMessage> msg_up;
2146
msg_up.instantiate();
2147
msg_up->event = wh_up;
2148
wayland_thread->push_message(msg_up);
2149
}
2150
}
2151
}
2152
}
2153
2154
// Reset the scroll vectors as we already handled them.
2155
pd.scroll_vector = Vector2();
2156
pd.discrete_scroll_vector_120 = Vector2i();
2157
2158
// Update the data all getters read. Wayland's specification requires us to do
2159
// this, since all pointer actions are sent in individual events.
2160
old_pd = pd;
2161
}
2162
2163
void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) {
2164
SeatState *ss = (SeatState *)data;
2165
ERR_FAIL_NULL(ss);
2166
2167
ss->pointer_data_buffer.scroll_type = axis_source;
2168
}
2169
2170
void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {
2171
}
2172
2173
// NOTE: This event is deprecated since version 8 and superseded by
2174
// `wl_pointer::axis_value120`. This thus converts the data to its
2175
// fraction-of-120 format.
2176
void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
2177
SeatState *ss = (SeatState *)data;
2178
ERR_FAIL_NULL(ss);
2179
2180
PointerData &pd = ss->pointer_data_buffer;
2181
2182
// NOTE: We can allow ourselves to not accumulate this data (and thus just
2183
// assign it) as the spec guarantees only one event per axis type.
2184
2185
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
2186
pd.discrete_scroll_vector_120.y = discrete * 120;
2187
}
2188
2189
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
2190
pd.discrete_scroll_vector_120.x = discrete * 120;
2191
}
2192
}
2193
2194
// Supersedes `wl_pointer::axis_discrete` Since version 8.
2195
void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) {
2196
SeatState *ss = (SeatState *)data;
2197
ERR_FAIL_NULL(ss);
2198
2199
PointerData &pd = ss->pointer_data_buffer;
2200
2201
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
2202
pd.discrete_scroll_vector_120.y += value120;
2203
}
2204
2205
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
2206
pd.discrete_scroll_vector_120.x += value120;
2207
}
2208
}
2209
2210
// TODO: Add support to this event.
2211
void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) {
2212
}
2213
2214
void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) {
2215
ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor.");
2216
2217
SeatState *ss = (SeatState *)data;
2218
ERR_FAIL_NULL(ss);
2219
2220
if (ss->keymap_buffer) {
2221
// We have already a mapped buffer, so we unmap it. There's no need to reset
2222
// its pointer or size, as we're gonna set them below.
2223
munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);
2224
ss->keymap_buffer = nullptr;
2225
}
2226
2227
ss->keymap_buffer = (const char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
2228
ss->keymap_buffer_size = size;
2229
2230
xkb_keymap_unref(ss->xkb_keymap);
2231
ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer,
2232
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
2233
2234
xkb_state_unref(ss->xkb_state);
2235
ss->xkb_state = xkb_state_new(ss->xkb_keymap);
2236
2237
xkb_compose_table_unref(ss->xkb_compose_table);
2238
const char *locale = getenv("LC_ALL");
2239
if (!locale || !*locale) {
2240
locale = getenv("LC_CTYPE");
2241
}
2242
if (!locale || !*locale) {
2243
locale = getenv("LANG");
2244
}
2245
if (!locale || !*locale) {
2246
locale = "C";
2247
}
2248
ss->xkb_compose_table = xkb_compose_table_new_from_locale(ss->xkb_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
2249
2250
xkb_compose_state_unref(ss->xkb_compose_state);
2251
ss->xkb_compose_state = xkb_compose_state_new(ss->xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
2252
2253
xkb_state_update_mask(ss->xkb_state, ss->mods_depressed, ss->mods_latched, ss->mods_locked, 0, 0, ss->current_layout_index);
2254
}
2255
2256
void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
2257
WindowState *ws = wl_surface_get_window_state(surface);
2258
if (!ws) {
2259
return;
2260
}
2261
2262
SeatState *ss = (SeatState *)data;
2263
ERR_FAIL_NULL(ss);
2264
2265
WaylandThread *wayland_thread = ss->wayland_thread;
2266
ERR_FAIL_NULL(wayland_thread);
2267
2268
ss->focused_id = ws->id;
2269
2270
wayland_thread->_set_current_seat(ss->wl_seat);
2271
2272
Ref<WindowEventMessage> msg;
2273
msg.instantiate();
2274
msg->id = ws->id;
2275
msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN;
2276
wayland_thread->push_message(msg);
2277
2278
DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard focused window %d.", ws->id));
2279
}
2280
2281
void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {
2282
// NOTE: `surface` will probably be null when the surface is destroyed.
2283
// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/366
2284
// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/465
2285
2286
if (surface && !wl_proxy_is_godot((struct wl_proxy *)surface)) {
2287
return;
2288
}
2289
2290
SeatState *ss = (SeatState *)data;
2291
ERR_FAIL_NULL(ss);
2292
2293
WaylandThread *wayland_thread = ss->wayland_thread;
2294
ERR_FAIL_NULL(wayland_thread);
2295
2296
ss->repeating_keycode = XKB_KEYCODE_INVALID;
2297
2298
if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {
2299
// We're probably on a decoration or some other third-party thing.
2300
return;
2301
}
2302
2303
WindowState *ws = wayland_thread->window_get_state(ss->focused_id);
2304
ERR_FAIL_NULL(ws);
2305
2306
ss->focused_id = DisplayServer::INVALID_WINDOW_ID;
2307
2308
Ref<WindowEventMessage> msg;
2309
msg.instantiate();
2310
msg->id = ws->id;
2311
msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;
2312
wayland_thread->push_message(msg);
2313
2314
ss->shift_pressed = false;
2315
ss->ctrl_pressed = false;
2316
ss->alt_pressed = false;
2317
ss->meta_pressed = false;
2318
2319
if (ss->xkb_state != nullptr) {
2320
xkb_state_update_mask(ss->xkb_state, 0, 0, 0, 0, 0, 0);
2321
}
2322
2323
DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard unfocused window %d.", ws->id));
2324
}
2325
2326
void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
2327
SeatState *ss = (SeatState *)data;
2328
ERR_FAIL_NULL(ss);
2329
2330
if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {
2331
return;
2332
}
2333
2334
// We have to add 8 to the scancode to get an XKB-compatible keycode.
2335
xkb_keycode_t xkb_keycode = key + 8;
2336
2337
bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED;
2338
2339
if (pressed) {
2340
if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) {
2341
ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec();
2342
ss->repeating_keycode = xkb_keycode;
2343
}
2344
2345
ss->last_key_pressed_serial = serial;
2346
} else if (ss->repeating_keycode == xkb_keycode) {
2347
ss->repeating_keycode = XKB_KEYCODE_INVALID;
2348
}
2349
2350
_seat_state_handle_xkb_keycode(ss, xkb_keycode, pressed);
2351
}
2352
2353
void WaylandThread::_wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
2354
SeatState *ss = (SeatState *)data;
2355
ERR_FAIL_NULL(ss);
2356
2357
ss->mods_depressed = mods_depressed;
2358
ss->mods_latched = mods_latched;
2359
ss->mods_locked = mods_locked;
2360
ss->current_layout_index = group;
2361
2362
if (ss->xkb_state == nullptr) {
2363
return;
2364
}
2365
2366
xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
2367
2368
ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE);
2369
ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE);
2370
ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE);
2371
ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE);
2372
}
2373
2374
void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {
2375
SeatState *ss = (SeatState *)data;
2376
ERR_FAIL_NULL(ss);
2377
2378
ss->repeat_key_delay_msec = rate ? 1000 / rate : 0;
2379
ss->repeat_start_delay_msec = delay;
2380
}
2381
2382
// NOTE: Don't forget to `memfree` the offer's state.
2383
void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
2384
wl_proxy_tag_godot((struct wl_proxy *)id);
2385
wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState));
2386
}
2387
2388
void WaylandThread::_wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
2389
WindowState *ws = wl_surface_get_window_state(surface);
2390
if (!ws) {
2391
return;
2392
}
2393
2394
SeatState *ss = (SeatState *)data;
2395
ERR_FAIL_NULL(ss);
2396
2397
ss->dnd_id = ws->id;
2398
2399
ss->dnd_enter_serial = serial;
2400
ss->wl_data_offer_dnd = id;
2401
2402
// Godot only supports DnD file copying for now.
2403
wl_data_offer_accept(id, serial, "text/uri-list");
2404
wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
2405
}
2406
2407
void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) {
2408
SeatState *ss = (SeatState *)data;
2409
ERR_FAIL_NULL(ss);
2410
2411
if (ss->wl_data_offer_dnd) {
2412
memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
2413
wl_data_offer_destroy(ss->wl_data_offer_dnd);
2414
ss->wl_data_offer_dnd = nullptr;
2415
ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;
2416
}
2417
}
2418
2419
void WaylandThread::_wl_data_device_on_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
2420
}
2421
2422
void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) {
2423
SeatState *ss = (SeatState *)data;
2424
ERR_FAIL_NULL(ss);
2425
2426
WaylandThread *wayland_thread = ss->wayland_thread;
2427
ERR_FAIL_NULL(wayland_thread);
2428
2429
OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd);
2430
ERR_FAIL_NULL(os);
2431
2432
if (os) {
2433
Ref<DropFilesEventMessage> msg;
2434
msg.instantiate();
2435
msg->id = ss->dnd_id;
2436
2437
Vector<uint8_t> list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd);
2438
2439
msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false);
2440
for (int i = 0; i < msg->files.size(); i++) {
2441
msg->files.write[i] = msg->files[i].replace("file://", "").uri_file_decode();
2442
}
2443
2444
wayland_thread->push_message(msg);
2445
2446
wl_data_offer_finish(ss->wl_data_offer_dnd);
2447
}
2448
2449
memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
2450
wl_data_offer_destroy(ss->wl_data_offer_dnd);
2451
ss->wl_data_offer_dnd = nullptr;
2452
ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;
2453
}
2454
2455
void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
2456
SeatState *ss = (SeatState *)data;
2457
ERR_FAIL_NULL(ss);
2458
2459
if (ss->wl_data_offer_selection) {
2460
memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection));
2461
wl_data_offer_destroy(ss->wl_data_offer_selection);
2462
}
2463
2464
ss->wl_data_offer_selection = id;
2465
}
2466
2467
void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) {
2468
OfferState *os = (OfferState *)data;
2469
ERR_FAIL_NULL(os);
2470
2471
if (os) {
2472
os->mime_types.insert(String::utf8(mime_type));
2473
}
2474
}
2475
2476
void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {
2477
}
2478
2479
void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) {
2480
}
2481
2482
void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) {
2483
}
2484
2485
void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) {
2486
SeatState *ss = (SeatState *)data;
2487
ERR_FAIL_NULL(ss);
2488
2489
Vector<uint8_t> *data_to_send = nullptr;
2490
2491
if (wl_data_source == ss->wl_data_source_selection) {
2492
data_to_send = &ss->selection_data;
2493
DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection.");
2494
}
2495
2496
if (data_to_send) {
2497
ssize_t written_bytes = 0;
2498
2499
bool valid_mime = false;
2500
2501
if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {
2502
valid_mime = true;
2503
} else if (strcmp(mime_type, "text/plain") == 0) {
2504
valid_mime = true;
2505
}
2506
2507
if (valid_mime) {
2508
written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());
2509
}
2510
2511
if (written_bytes > 0) {
2512
DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));
2513
} else if (written_bytes == 0) {
2514
DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");
2515
} else {
2516
ERR_PRINT(vformat("Clipboard: write error %d.", errno));
2517
}
2518
}
2519
2520
close(fd);
2521
}
2522
2523
void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) {
2524
SeatState *ss = (SeatState *)data;
2525
ERR_FAIL_NULL(ss);
2526
2527
wl_data_source_destroy(wl_data_source);
2528
2529
if (wl_data_source == ss->wl_data_source_selection) {
2530
ss->wl_data_source_selection = nullptr;
2531
ss->selection_data.clear();
2532
2533
DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program.");
2534
return;
2535
}
2536
}
2537
2538
void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) {
2539
}
2540
2541
void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) {
2542
}
2543
2544
void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) {
2545
}
2546
2547
void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) {
2548
WindowState *ws = (WindowState *)data;
2549
ERR_FAIL_NULL(ws);
2550
2551
ws->preferred_fractional_scale = (double)scale / 120;
2552
2553
window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);
2554
}
2555
2556
void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct zwp_relative_pointer_v1 *wp_relative_pointer, uint32_t uptime_hi, uint32_t uptime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) {
2557
SeatState *ss = (SeatState *)data;
2558
ERR_FAIL_NULL(ss);
2559
2560
PointerData &pd = ss->pointer_data_buffer;
2561
2562
pd.relative_motion.x = wl_fixed_to_double(dx);
2563
pd.relative_motion.y = wl_fixed_to_double(dy);
2564
2565
pd.relative_motion_time = uptime_lo;
2566
}
2567
2568
void WaylandThread::_wp_pointer_gesture_pinch_on_begin(void *data, struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, struct wl_surface *surface, uint32_t fingers) {
2569
SeatState *ss = (SeatState *)data;
2570
ERR_FAIL_NULL(ss);
2571
2572
if (fingers == 2) {
2573
ss->old_pinch_scale = wl_fixed_from_int(1);
2574
ss->active_gesture = Gesture::MAGNIFY;
2575
}
2576
}
2577
2578
void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation) {
2579
SeatState *ss = (SeatState *)data;
2580
ERR_FAIL_NULL(ss);
2581
2582
// NOTE: From what I can tell, this and all other pointer gestures are separate
2583
// from the "frame" mechanism of regular pointers. Thus, let's just assume we
2584
// can read from the "committed" state.
2585
const PointerData &pd = ss->pointer_data;
2586
2587
WaylandThread *wayland_thread = ss->wayland_thread;
2588
ERR_FAIL_NULL(wayland_thread);
2589
2590
WindowState *ws = wayland_thread->window_get_state(pd.pointed_id);
2591
ERR_FAIL_NULL(ws);
2592
2593
double win_scale = window_state_get_scale_factor(ws);
2594
2595
if (ss->active_gesture == Gesture::MAGNIFY) {
2596
Ref<InputEventMagnifyGesture> mg;
2597
mg.instantiate();
2598
2599
mg->set_window_id(pd.pointed_id);
2600
2601
if (ws) {
2602
mg->set_window_id(ws->id);
2603
}
2604
2605
// Set all pressed modifiers.
2606
mg->set_shift_pressed(ss->shift_pressed);
2607
mg->set_ctrl_pressed(ss->ctrl_pressed);
2608
mg->set_alt_pressed(ss->alt_pressed);
2609
mg->set_meta_pressed(ss->meta_pressed);
2610
2611
mg->set_position(pd.position * win_scale);
2612
2613
wl_fixed_t scale_delta = scale - ss->old_pinch_scale;
2614
mg->set_factor(1 + wl_fixed_to_double(scale_delta));
2615
2616
Ref<InputEventMessage> magnify_msg;
2617
magnify_msg.instantiate();
2618
magnify_msg->event = mg;
2619
2620
// Since Wayland allows only one gesture at a time and godot instead expects
2621
// both of them, we'll have to create two separate input events: one for
2622
// magnification and one for panning.
2623
2624
Ref<InputEventPanGesture> pg;
2625
pg.instantiate();
2626
2627
// Set all pressed modifiers.
2628
pg->set_shift_pressed(ss->shift_pressed);
2629
pg->set_ctrl_pressed(ss->ctrl_pressed);
2630
pg->set_alt_pressed(ss->alt_pressed);
2631
pg->set_meta_pressed(ss->meta_pressed);
2632
2633
pg->set_position(pd.position * win_scale);
2634
pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy)));
2635
2636
Ref<InputEventMessage> pan_msg;
2637
pan_msg.instantiate();
2638
pan_msg->event = pg;
2639
2640
wayland_thread->push_message(magnify_msg);
2641
wayland_thread->push_message(pan_msg);
2642
2643
ss->old_pinch_scale = scale;
2644
}
2645
}
2646
2647
void WaylandThread::_wp_pointer_gesture_pinch_on_end(void *data, struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, int32_t cancelled) {
2648
SeatState *ss = (SeatState *)data;
2649
ERR_FAIL_NULL(ss);
2650
2651
ss->active_gesture = Gesture::NONE;
2652
}
2653
2654
// NOTE: Don't forget to `memfree` the offer's state.
2655
void WaylandThread::_wp_primary_selection_device_on_data_offer(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *offer) {
2656
wl_proxy_tag_godot((struct wl_proxy *)offer);
2657
zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState));
2658
}
2659
2660
void WaylandThread::_wp_primary_selection_device_on_selection(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
2661
SeatState *ss = (SeatState *)data;
2662
ERR_FAIL_NULL(ss);
2663
2664
if (ss->wp_primary_selection_offer) {
2665
memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));
2666
zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);
2667
}
2668
2669
ss->wp_primary_selection_offer = id;
2670
}
2671
2672
void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer_v1, const char *mime_type) {
2673
OfferState *os = (OfferState *)data;
2674
ERR_FAIL_NULL(os);
2675
2676
if (os) {
2677
os->mime_types.insert(String::utf8(mime_type));
2678
}
2679
}
2680
2681
void WaylandThread::_wp_primary_selection_source_on_send(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1, const char *mime_type, int32_t fd) {
2682
SeatState *ss = (SeatState *)data;
2683
ERR_FAIL_NULL(ss);
2684
2685
Vector<uint8_t> *data_to_send = nullptr;
2686
2687
if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {
2688
data_to_send = &ss->primary_data;
2689
DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection.");
2690
}
2691
2692
if (data_to_send) {
2693
ssize_t written_bytes = 0;
2694
2695
if (strcmp(mime_type, "text/plain") == 0) {
2696
written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());
2697
}
2698
2699
if (written_bytes > 0) {
2700
DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));
2701
} else if (written_bytes == 0) {
2702
DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");
2703
} else {
2704
ERR_PRINT(vformat("Clipboard: write error %d.", errno));
2705
}
2706
}
2707
2708
close(fd);
2709
}
2710
2711
void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) {
2712
SeatState *ss = (SeatState *)data;
2713
ERR_FAIL_NULL(ss);
2714
2715
if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {
2716
zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);
2717
ss->wp_primary_selection_source = nullptr;
2718
2719
ss->primary_data.clear();
2720
2721
DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program.");
2722
return;
2723
}
2724
}
2725
2726
void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_v2 *id) {
2727
}
2728
2729
void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) {
2730
SeatState *ss = (SeatState *)data;
2731
ERR_FAIL_NULL(ss);
2732
2733
TabletToolState *state = memnew(TabletToolState);
2734
state->wl_seat = ss->wl_seat;
2735
2736
wl_proxy_tag_godot((struct wl_proxy *)id);
2737
zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state);
2738
ss->tablet_tools.push_back(id);
2739
}
2740
2741
void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) {
2742
}
2743
2744
void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t tool_type) {
2745
TabletToolState *state = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2746
2747
if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {
2748
state->is_eraser = true;
2749
}
2750
}
2751
2752
void WaylandThread::_wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) {
2753
}
2754
2755
void WaylandThread::_wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo) {
2756
}
2757
2758
void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t capability) {
2759
}
2760
2761
void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
2762
}
2763
2764
void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
2765
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2766
if (!ts) {
2767
return;
2768
}
2769
2770
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
2771
if (!ss) {
2772
return;
2773
}
2774
2775
List<struct zwp_tablet_tool_v2 *>::Element *E = ss->tablet_tools.find(wp_tablet_tool_v2);
2776
2777
if (E && E->get()) {
2778
struct zwp_tablet_tool_v2 *tool = E->get();
2779
TabletToolState *state = wp_tablet_tool_get_state(tool);
2780
if (state) {
2781
memdelete(state);
2782
}
2783
2784
zwp_tablet_tool_v2_destroy(tool);
2785
ss->tablet_tools.erase(E);
2786
}
2787
}
2788
2789
void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {
2790
// NOTE: Works pretty much like wl_pointer::enter.
2791
2792
WindowState *ws = wl_surface_get_window_state(surface);
2793
if (!ws) {
2794
return;
2795
}
2796
2797
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2798
ERR_FAIL_NULL(ts);
2799
2800
ts->data_pending.proximity_serial = serial;
2801
ts->data_pending.proximal_id = ws->id;
2802
ts->data_pending.last_proximal_id = ws->id;
2803
2804
DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool entered window %d.", ts->data_pending.proximal_id));
2805
}
2806
2807
void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
2808
// NOTE: Works pretty much like wl_pointer::leave.
2809
2810
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2811
ERR_FAIL_NULL(ts);
2812
2813
if (ts->data_pending.proximal_id == DisplayServer::INVALID_WINDOW_ID) {
2814
// We're probably on a decoration or some other third-party thing.
2815
return;
2816
}
2817
2818
DisplayServer::WindowID id = ts->data_pending.proximal_id;
2819
2820
ts->data_pending.proximal_id = DisplayServer::INVALID_WINDOW_ID;
2821
ts->data_pending.pressed_button_mask.clear();
2822
2823
DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool left window %d.", id));
2824
}
2825
2826
void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) {
2827
// NOTE: Works pretty much like wl_pointer::button but only for a pressed left
2828
// button.
2829
2830
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2831
ERR_FAIL_NULL(ts);
2832
2833
TabletToolData &td = ts->data_pending;
2834
2835
td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT));
2836
td.last_button_pressed = MouseButton::LEFT;
2837
td.double_click_begun = true;
2838
2839
// The protocol doesn't cover this, but we can use this funky hack to make
2840
// double clicking work.
2841
td.button_time = OS::get_singleton()->get_ticks_msec();
2842
}
2843
2844
void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
2845
// NOTE: Works pretty much like wl_pointer::button but only for a released left
2846
// button.
2847
2848
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2849
ERR_FAIL_NULL(ts);
2850
2851
TabletToolData &td = ts->data_pending;
2852
2853
td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT));
2854
2855
// The protocol doesn't cover this, but we can use this funky hack to make
2856
// double clicking work.
2857
td.button_time = OS::get_singleton()->get_ticks_msec();
2858
}
2859
2860
void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {
2861
// NOTE: Works pretty much like wl_pointer::motion.
2862
2863
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2864
ERR_FAIL_NULL(ts);
2865
2866
TabletToolData &td = ts->data_pending;
2867
2868
td.position.x = wl_fixed_to_double(x);
2869
td.position.y = wl_fixed_to_double(y);
2870
}
2871
2872
void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) {
2873
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2874
ERR_FAIL_NULL(ts);
2875
2876
ts->data_pending.pressure = pressure;
2877
}
2878
2879
void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t distance) {
2880
// Unsupported
2881
}
2882
2883
void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {
2884
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2885
ERR_FAIL_NULL(ts);
2886
2887
TabletToolData &td = ts->data_pending;
2888
2889
td.tilt.x = wl_fixed_to_double(tilt_x);
2890
td.tilt.y = wl_fixed_to_double(tilt_y);
2891
}
2892
2893
void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees) {
2894
// Unsupported.
2895
}
2896
2897
void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, int32_t position) {
2898
// Unsupported.
2899
}
2900
2901
void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) {
2902
// TODO
2903
}
2904
2905
void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {
2906
// NOTE: Works pretty much like wl_pointer::button.
2907
2908
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2909
ERR_FAIL_NULL(ts);
2910
2911
TabletToolData &td = ts->data_pending;
2912
2913
MouseButton mouse_button = MouseButton::NONE;
2914
2915
if (button == BTN_STYLUS) {
2916
mouse_button = MouseButton::LEFT;
2917
}
2918
2919
if (button == BTN_STYLUS2) {
2920
mouse_button = MouseButton::RIGHT;
2921
}
2922
2923
if (mouse_button != MouseButton::NONE) {
2924
MouseButtonMask mask = mouse_button_to_mask(mouse_button);
2925
2926
if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) {
2927
td.pressed_button_mask.set_flag(mask);
2928
td.last_button_pressed = mouse_button;
2929
td.double_click_begun = true;
2930
} else {
2931
td.pressed_button_mask.clear_flag(mask);
2932
}
2933
2934
// The protocol doesn't cover this, but we can use this funky hack to make
2935
// double clicking work.
2936
td.button_time = OS::get_singleton()->get_ticks_msec();
2937
}
2938
}
2939
2940
void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) {
2941
// NOTE: Works pretty much like wl_pointer::frame.
2942
2943
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
2944
ERR_FAIL_NULL(ts);
2945
2946
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
2947
ERR_FAIL_NULL(ss);
2948
2949
WaylandThread *wayland_thread = ss->wayland_thread;
2950
ERR_FAIL_NULL(wayland_thread);
2951
2952
TabletToolData &old_td = ts->data;
2953
TabletToolData &td = ts->data_pending;
2954
2955
if (td.proximal_id != old_td.proximal_id) {
2956
if (old_td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {
2957
Ref<WindowEventMessage> msg;
2958
msg.instantiate();
2959
msg->id = old_td.proximal_id;
2960
msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
2961
2962
wayland_thread->push_message(msg);
2963
}
2964
2965
if (td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {
2966
Ref<WindowEventMessage> msg;
2967
msg.instantiate();
2968
msg->id = td.proximal_id;
2969
msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
2970
2971
wayland_thread->push_message(msg);
2972
}
2973
}
2974
2975
if (td.proximal_id == DisplayServer::INVALID_WINDOW_ID) {
2976
// We're probably on a decoration or some other third-party thing. Let's
2977
// "commit" the data and call it a day.
2978
old_td = td;
2979
return;
2980
}
2981
2982
WindowState *ws = wayland_thread->window_get_state(td.proximal_id);
2983
ERR_FAIL_NULL(ws);
2984
2985
double scale = window_state_get_scale_factor(ws);
2986
if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {
2987
td.motion_time = time;
2988
2989
Ref<InputEventMouseMotion> mm;
2990
mm.instantiate();
2991
2992
mm->set_window_id(td.proximal_id);
2993
2994
// Set all pressed modifiers.
2995
mm->set_shift_pressed(ss->shift_pressed);
2996
mm->set_ctrl_pressed(ss->ctrl_pressed);
2997
mm->set_alt_pressed(ss->alt_pressed);
2998
mm->set_meta_pressed(ss->meta_pressed);
2999
3000
mm->set_button_mask(td.pressed_button_mask);
3001
3002
mm->set_global_position(td.position * scale);
3003
mm->set_position(td.position * scale);
3004
3005
// NOTE: The Godot API expects normalized values and we store them raw,
3006
// straight from the compositor, so we have to normalize them here.
3007
3008
// According to the tablet proto spec, tilt is expressed in degrees relative
3009
// to the Z axis of the tablet, so it shouldn't go over 90 degrees either way,
3010
// I think. We'll clamp it just in case.
3011
td.tilt = td.tilt.clampf(-90, 90);
3012
3013
mm->set_tilt(td.tilt / 90);
3014
3015
// The tablet proto spec explicitly says that pressure is defined as a value
3016
// between 0 to 65535.
3017
mm->set_pressure(td.pressure / (float)65535);
3018
3019
mm->set_pen_inverted(ts->is_eraser);
3020
3021
Vector2 pos_delta = (td.position - old_td.position) * scale;
3022
3023
mm->set_relative(pos_delta);
3024
mm->set_relative_screen_position(pos_delta);
3025
3026
uint32_t time_delta = td.motion_time - old_td.motion_time;
3027
mm->set_velocity((Vector2)pos_delta / time_delta);
3028
3029
Ref<InputEventMessage> inputev_msg;
3030
inputev_msg.instantiate();
3031
3032
inputev_msg->event = mm;
3033
3034
wayland_thread->push_message(inputev_msg);
3035
}
3036
3037
if (old_td.pressed_button_mask != td.pressed_button_mask) {
3038
td.button_time = time;
3039
3040
BitField<MouseButtonMask> pressed_mask_delta = old_td.pressed_button_mask.get_different(td.pressed_button_mask);
3041
3042
for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) {
3043
MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);
3044
3045
if (pressed_mask_delta.has_flag(test_button_mask)) {
3046
Ref<InputEventMouseButton> mb;
3047
mb.instantiate();
3048
3049
// Set all pressed modifiers.
3050
mb->set_shift_pressed(ss->shift_pressed);
3051
mb->set_ctrl_pressed(ss->ctrl_pressed);
3052
mb->set_alt_pressed(ss->alt_pressed);
3053
mb->set_meta_pressed(ss->meta_pressed);
3054
3055
mb->set_window_id(td.proximal_id);
3056
mb->set_position(td.position * scale);
3057
mb->set_global_position(td.position * scale);
3058
3059
mb->set_button_mask(td.pressed_button_mask);
3060
mb->set_button_index(test_button);
3061
mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask));
3062
3063
// We have to set the last position pressed here as we can't take for
3064
// granted what the individual events might have seen due to them not having
3065
// a garaunteed order.
3066
if (mb->is_pressed()) {
3067
td.last_pressed_position = td.position;
3068
}
3069
3070
if (old_td.double_click_begun && mb->is_pressed() && td.last_button_pressed == old_td.last_button_pressed && (td.button_time - old_td.button_time) < 400 && Vector2(td.last_pressed_position * scale).distance_to(Vector2(old_td.last_pressed_position * scale)) < 5) {
3071
td.double_click_begun = false;
3072
mb->set_double_click(true);
3073
}
3074
3075
Ref<InputEventMessage> msg;
3076
msg.instantiate();
3077
3078
msg->event = mb;
3079
3080
wayland_thread->push_message(msg);
3081
}
3082
}
3083
}
3084
3085
old_td = td;
3086
}
3087
3088
void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {
3089
SeatState *ss = (SeatState *)data;
3090
if (!ss) {
3091
return;
3092
}
3093
3094
WindowState *ws = wl_surface_get_window_state(surface);
3095
if (!ws) {
3096
return;
3097
}
3098
3099
ss->ime_window_id = ws->id;
3100
ss->ime_enabled = true;
3101
}
3102
3103
// NOTE: From now on, we must ignore all further events until an enter event.
3104
void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {
3105
SeatState *ss = (SeatState *)data;
3106
if (!ss) {
3107
return;
3108
}
3109
3110
if (ss->ime_window_id == DisplayServer::INVALID_WINDOW_ID) {
3111
return;
3112
}
3113
3114
Ref<IMEUpdateEventMessage> msg;
3115
msg.instantiate();
3116
msg->id = ss->ime_window_id;
3117
msg->text = String();
3118
msg->selection = Vector2i();
3119
ss->wayland_thread->push_message(msg);
3120
3121
ss->ime_window_id = DisplayServer::INVALID_WINDOW_ID;
3122
ss->ime_enabled = false;
3123
ss->ime_active = false;
3124
ss->ime_text = String();
3125
ss->ime_text_commit = String();
3126
ss->ime_cursor = Vector2i();
3127
}
3128
3129
void WaylandThread::_wp_text_input_on_preedit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text, int32_t cursor_begin, int32_t cursor_end) {
3130
SeatState *ss = (SeatState *)data;
3131
if (!ss) {
3132
return;
3133
}
3134
3135
if (ss->ime_window_id == DisplayServer::INVALID_WINDOW_ID) {
3136
return;
3137
}
3138
3139
ss->ime_text = String::utf8(text);
3140
3141
// Convert cursor positions from UTF-8 to UTF-32 offset.
3142
int32_t cursor_begin_utf32 = 0;
3143
int32_t cursor_end_utf32 = 0;
3144
for (int i = 0; i < ss->ime_text.length(); i++) {
3145
uint32_t c = ss->ime_text[i];
3146
if (c <= 0x7f) { // 7 bits.
3147
cursor_begin -= 1;
3148
cursor_end -= 1;
3149
} else if (c <= 0x7ff) { // 11 bits
3150
cursor_begin -= 2;
3151
cursor_end -= 2;
3152
} else if (c <= 0xffff) { // 16 bits
3153
cursor_begin -= 3;
3154
cursor_end -= 3;
3155
} else if (c <= 0x001fffff) { // 21 bits
3156
cursor_begin -= 4;
3157
cursor_end -= 4;
3158
} else if (c <= 0x03ffffff) { // 26 bits
3159
cursor_begin -= 5;
3160
cursor_end -= 5;
3161
} else if (c <= 0x7fffffff) { // 31 bits
3162
cursor_begin -= 6;
3163
cursor_end -= 6;
3164
} else {
3165
cursor_begin -= 1;
3166
cursor_end -= 1;
3167
}
3168
if (cursor_begin == 0) {
3169
cursor_begin_utf32 = i + 1;
3170
}
3171
if (cursor_end == 0) {
3172
cursor_end_utf32 = i + 1;
3173
}
3174
if (cursor_begin <= 0 && cursor_end <= 0) {
3175
break;
3176
}
3177
}
3178
ss->ime_cursor = Vector2i(cursor_begin_utf32, cursor_end_utf32 - cursor_begin_utf32);
3179
}
3180
3181
void WaylandThread::_wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text) {
3182
SeatState *ss = (SeatState *)data;
3183
if (!ss) {
3184
return;
3185
}
3186
3187
if (ss->ime_window_id == DisplayServer::INVALID_WINDOW_ID) {
3188
return;
3189
}
3190
3191
ss->ime_text_commit = String::utf8(text);
3192
}
3193
3194
void WaylandThread::_wp_text_input_on_delete_surrounding_text(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t before_length, uint32_t after_length) {
3195
// Not implemented.
3196
}
3197
3198
void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial) {
3199
SeatState *ss = (SeatState *)data;
3200
if (!ss) {
3201
return;
3202
}
3203
3204
if (ss->ime_window_id == DisplayServer::INVALID_WINDOW_ID) {
3205
return;
3206
}
3207
3208
if (!ss->ime_text_commit.is_empty()) {
3209
Ref<IMECommitEventMessage> msg;
3210
msg.instantiate();
3211
msg->id = ss->ime_window_id;
3212
msg->text = ss->ime_text_commit;
3213
ss->wayland_thread->push_message(msg);
3214
} else {
3215
Ref<IMEUpdateEventMessage> msg;
3216
msg.instantiate();
3217
msg->id = ss->ime_window_id;
3218
msg->text = ss->ime_text;
3219
msg->selection = ss->ime_cursor;
3220
ss->wayland_thread->push_message(msg);
3221
}
3222
3223
ss->ime_text = String();
3224
ss->ime_text_commit = String();
3225
ss->ime_cursor = Vector2i();
3226
}
3227
3228
void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) {
3229
WindowState *ws = (WindowState *)data;
3230
ERR_FAIL_NULL(ws);
3231
ERR_FAIL_NULL(ws->wayland_thread);
3232
ERR_FAIL_NULL(ws->wl_surface);
3233
3234
xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface);
3235
xdg_activation_token_v1_destroy(xdg_activation_token);
3236
3237
DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation."));
3238
}
3239
3240
void WaylandThread::_godot_embedding_compositor_on_client(void *data, struct godot_embedding_compositor *godot_embedding_compositor, struct godot_embedded_client *godot_embedded_client, int32_t pid) {
3241
EmbeddingCompositorState *state = (EmbeddingCompositorState *)data;
3242
ERR_FAIL_NULL(state);
3243
3244
EmbeddedClientState *client_state = memnew(EmbeddedClientState);
3245
client_state->embedding_compositor = godot_embedding_compositor;
3246
client_state->pid = pid;
3247
godot_embedded_client_add_listener(godot_embedded_client, &godot_embedded_client_listener, client_state);
3248
3249
DEBUG_LOG_WAYLAND_THREAD(vformat("New client %d.", pid));
3250
state->clients.push_back(godot_embedded_client);
3251
}
3252
3253
void WaylandThread::_godot_embedded_client_on_disconnected(void *data, struct godot_embedded_client *godot_embedded_client) {
3254
EmbeddedClientState *state = (EmbeddedClientState *)data;
3255
ERR_FAIL_NULL(state);
3256
3257
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
3258
ERR_FAIL_NULL(ecomp_state);
3259
3260
ecomp_state->clients.erase_unordered(godot_embedded_client);
3261
ecomp_state->mapped_clients.erase(state->pid);
3262
3263
memfree(state);
3264
godot_embedded_client_destroy(godot_embedded_client);
3265
3266
DEBUG_LOG_WAYLAND_THREAD(vformat("Client %d disconnected.", state->pid));
3267
}
3268
3269
void WaylandThread::_godot_embedded_client_on_window_embedded(void *data, struct godot_embedded_client *godot_embedded_client) {
3270
EmbeddedClientState *state = (EmbeddedClientState *)data;
3271
ERR_FAIL_NULL(state);
3272
3273
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
3274
ERR_FAIL_NULL(ecomp_state);
3275
3276
state->window_mapped = true;
3277
3278
ERR_FAIL_COND_MSG(ecomp_state->mapped_clients.has(state->pid), "More than one Wayland client per PID tried to create a window.");
3279
3280
ecomp_state->mapped_clients[state->pid] = godot_embedded_client;
3281
}
3282
3283
void WaylandThread::_godot_embedded_client_on_window_focus_in(void *data, struct godot_embedded_client *godot_embedded_client) {
3284
EmbeddedClientState *state = (EmbeddedClientState *)data;
3285
ERR_FAIL_NULL(state);
3286
3287
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
3288
ERR_FAIL_NULL(ecomp_state);
3289
3290
ecomp_state->focused_pid = state->pid;
3291
DEBUG_LOG_WAYLAND_THREAD(vformat("Embedded client pid %d focus in", state->pid));
3292
}
3293
3294
void WaylandThread::_godot_embedded_client_on_window_focus_out(void *data, struct godot_embedded_client *godot_embedded_client) {
3295
EmbeddedClientState *state = (EmbeddedClientState *)data;
3296
ERR_FAIL_NULL(state);
3297
3298
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
3299
ERR_FAIL_NULL(ecomp_state);
3300
3301
ecomp_state->focused_pid = -1;
3302
DEBUG_LOG_WAYLAND_THREAD(vformat("Embedded client pid %d focus out", state->pid));
3303
}
3304
3305
// NOTE: This must be started after a valid wl_display is loaded.
3306
void WaylandThread::_poll_events_thread(void *p_data) {
3307
Thread::set_name("Wayland Events");
3308
3309
ThreadData *data = (ThreadData *)p_data;
3310
ERR_FAIL_NULL(data);
3311
ERR_FAIL_NULL(data->wl_display);
3312
3313
struct pollfd poll_fd = {};
3314
poll_fd.fd = wl_display_get_fd(data->wl_display);
3315
poll_fd.events = POLLIN;
3316
3317
while (true) {
3318
// Empty the event queue while it's full.
3319
while (wl_display_prepare_read(data->wl_display) != 0) {
3320
// We aren't using wl_display_dispatch(), instead "manually" handling events
3321
// through wl_display_dispatch_pending so that we can use a global mutex and
3322
// be sure that this and the main thread won't race over stuff, as long as
3323
// the main thread locks it too.
3324
//
3325
// Note that the main thread can still call wl_display_roundtrip as that
3326
// method directly handles all events, effectively bypassing this polling
3327
// loop and thus the mutex locking, avoiding a deadlock.
3328
//
3329
// WARNING: Never call `wl_display_roundtrip` inside event handlers or while
3330
// this mutex isn't held! `wl_display_roundtrip` manually handles new events
3331
// and if not properly gated it _will_ cause potentially stall-inducing race
3332
// conditions. Ask me how I know.
3333
MutexLock mutex_lock(data->mutex);
3334
3335
if (wl_display_dispatch_pending(data->wl_display) == -1) {
3336
// Oh no. We'll check and handle any display error below.
3337
break;
3338
}
3339
}
3340
3341
int werror = wl_display_get_error(data->wl_display);
3342
3343
if (werror) {
3344
if (werror == EPROTO) {
3345
struct wl_interface *wl_interface = nullptr;
3346
uint32_t id = 0;
3347
3348
int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id);
3349
CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
3350
} else {
3351
CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
3352
}
3353
}
3354
3355
wl_display_flush(data->wl_display);
3356
3357
// Wait for the event file descriptor to have new data.
3358
poll(&poll_fd, 1, -1);
3359
3360
if (data->thread_done.is_set()) {
3361
wl_display_cancel_read(data->wl_display);
3362
break;
3363
}
3364
3365
if (poll_fd.revents | POLLIN) {
3366
// Load the queues with fresh new data.
3367
wl_display_read_events(data->wl_display);
3368
} else {
3369
// Oh well... Stop signaling that we want to read.
3370
wl_display_cancel_read(data->wl_display);
3371
}
3372
3373
// The docs advise to redispatch unconditionally and it looks like that if we
3374
// don't do this we can't catch protocol errors, which is bad.
3375
MutexLock mutex_lock(data->mutex);
3376
wl_display_dispatch_pending(data->wl_display);
3377
}
3378
}
3379
3380
struct wl_display *WaylandThread::get_wl_display() const {
3381
return wl_display;
3382
}
3383
3384
// NOTE: Stuff like libdecor can (and will) register foreign proxies which
3385
// aren't formatted as we like. This method is needed to detect whether a proxy
3386
// has our tag. Also, be careful! The proxy has to be manually tagged or it
3387
// won't be recognized.
3388
bool WaylandThread::wl_proxy_is_godot(struct wl_proxy *p_proxy) {
3389
ERR_FAIL_NULL_V(p_proxy, false);
3390
3391
return wl_proxy_get_tag(p_proxy) == &proxy_tag;
3392
}
3393
3394
void WaylandThread::wl_proxy_tag_godot(struct wl_proxy *p_proxy) {
3395
ERR_FAIL_NULL(p_proxy);
3396
3397
wl_proxy_set_tag(p_proxy, &proxy_tag);
3398
}
3399
3400
// Returns the wl_surface's `WindowState`, otherwise `nullptr`.
3401
// NOTE: This will fail if the surface isn't tagged as ours.
3402
WaylandThread::WindowState *WaylandThread::wl_surface_get_window_state(struct wl_surface *p_surface) {
3403
if (p_surface && wl_proxy_is_godot((wl_proxy *)p_surface)) {
3404
return (WindowState *)wl_surface_get_user_data(p_surface);
3405
}
3406
3407
return nullptr;
3408
}
3409
3410
// Returns the wl_outputs's `ScreenState`, otherwise `nullptr`.
3411
// NOTE: This will fail if the output isn't tagged as ours.
3412
WaylandThread::ScreenState *WaylandThread::wl_output_get_screen_state(struct wl_output *p_output) {
3413
if (p_output && wl_proxy_is_godot((wl_proxy *)p_output)) {
3414
return (ScreenState *)wl_output_get_user_data(p_output);
3415
}
3416
3417
return nullptr;
3418
}
3419
3420
// Returns the wl_seat's `SeatState`, otherwise `nullptr`.
3421
// NOTE: This will fail if the output isn't tagged as ours.
3422
WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat *p_seat) {
3423
if (p_seat && wl_proxy_is_godot((wl_proxy *)p_seat)) {
3424
return (SeatState *)wl_seat_get_user_data(p_seat);
3425
}
3426
3427
return nullptr;
3428
}
3429
3430
// Returns the wp_tablet_tool's `TabletToolState`, otherwise `nullptr`.
3431
// NOTE: This will fail if the output isn't tagged as ours.
3432
WaylandThread::TabletToolState *WaylandThread::wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool) {
3433
if (p_tool && wl_proxy_is_godot((wl_proxy *)p_tool)) {
3434
return (TabletToolState *)zwp_tablet_tool_v2_get_user_data(p_tool);
3435
}
3436
3437
return nullptr;
3438
}
3439
// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
3440
// NOTE: This will fail if the output isn't tagged as ours.
3441
WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) {
3442
if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {
3443
return (OfferState *)wl_data_offer_get_user_data(p_offer);
3444
}
3445
3446
return nullptr;
3447
}
3448
3449
// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
3450
// NOTE: This will fail if the output isn't tagged as ours.
3451
WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer) {
3452
if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {
3453
return (OfferState *)zwp_primary_selection_offer_v1_get_user_data(p_offer);
3454
}
3455
3456
return nullptr;
3457
}
3458
3459
WaylandThread::EmbeddingCompositorState *WaylandThread::godot_embedding_compositor_get_state(struct godot_embedding_compositor *p_compositor) {
3460
// NOTE: No need for tag check as it's a "fake" interface - nothing else exposes it.
3461
if (p_compositor) {
3462
return (EmbeddingCompositorState *)godot_embedding_compositor_get_user_data(p_compositor);
3463
}
3464
3465
return nullptr;
3466
}
3467
3468
// This is implemented as a method because this is the simplest way of
3469
// accounting for dynamic output scale changes.
3470
int WaylandThread::window_state_get_preferred_buffer_scale(WindowState *p_ws) {
3471
ERR_FAIL_NULL_V(p_ws, 1);
3472
3473
if (p_ws->preferred_fractional_scale > 0) {
3474
// We're scaling fractionally. Per spec, the buffer scale is always 1.
3475
return 1;
3476
}
3477
3478
if (p_ws->wl_outputs.is_empty()) {
3479
DEBUG_LOG_WAYLAND_THREAD("Window has no output associated, returning buffer scale of 1.");
3480
return 1;
3481
}
3482
3483
// TODO: Cache value?
3484
int max_size = 1;
3485
3486
// ================================ IMPORTANT =================================
3487
// NOTE: Due to a Godot limitation, we can't really rescale the whole UI yet.
3488
// Because of this reason, all platforms have resorted to forcing the highest
3489
// scale possible of a system on any window, despite of what screen it's onto.
3490
// On this backend everything's already in place for dynamic window scale
3491
// handling, but in the meantime we'll just select the biggest _global_ output.
3492
// To restore dynamic scale selection, simply iterate over `p_ws->wl_outputs`
3493
// instead.
3494
for (struct wl_output *wl_output : p_ws->registry->wl_outputs) {
3495
ScreenState *ss = wl_output_get_screen_state(wl_output);
3496
3497
if (ss && ss->pending_data.scale > max_size) {
3498
// NOTE: For some mystical reason, wl_output.done is emitted _after_ windows
3499
// get resized but the scale event gets sent _before_ that. I'm still leaning
3500
// towards the idea that rescaling when a window gets a resolution change is a
3501
// pretty good approach, but this means that we'll have to use the screen data
3502
// before it's "committed".
3503
// FIXME: Use the committed data. Somehow.
3504
max_size = ss->pending_data.scale;
3505
}
3506
}
3507
3508
return max_size;
3509
}
3510
3511
double WaylandThread::window_state_get_scale_factor(const WindowState *p_ws) {
3512
ERR_FAIL_NULL_V(p_ws, 1);
3513
3514
if (p_ws->fractional_scale > 0) {
3515
// The fractional scale amount takes priority.
3516
return p_ws->fractional_scale;
3517
}
3518
3519
return p_ws->buffer_scale;
3520
}
3521
3522
void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) {
3523
ERR_FAIL_NULL(p_ws);
3524
3525
int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws);
3526
bool using_fractional = p_ws->preferred_fractional_scale > 0;
3527
3528
// If neither is true we no-op.
3529
bool scale_changed = false;
3530
bool size_changed = false;
3531
3532
if (p_ws->rect.size.width != p_width || p_ws->rect.size.height != p_height) {
3533
p_ws->rect.size.width = p_width;
3534
p_ws->rect.size.height = p_height;
3535
3536
size_changed = true;
3537
}
3538
3539
if (using_fractional && p_ws->fractional_scale != p_ws->preferred_fractional_scale) {
3540
p_ws->fractional_scale = p_ws->preferred_fractional_scale;
3541
scale_changed = true;
3542
}
3543
3544
if (p_ws->buffer_scale != preferred_buffer_scale) {
3545
// The buffer scale is always important, even if we use frac scaling.
3546
p_ws->buffer_scale = preferred_buffer_scale;
3547
p_ws->buffer_scale_changed = true;
3548
3549
if (!using_fractional) {
3550
// We don't bother updating everything else if it's turned on though.
3551
scale_changed = true;
3552
}
3553
}
3554
3555
if (p_ws->wl_surface) {
3556
if (p_ws->wp_viewport) {
3557
wp_viewport_set_destination(p_ws->wp_viewport, p_width, p_height);
3558
}
3559
3560
if (p_ws->xdg_surface) {
3561
xdg_surface_set_window_geometry(p_ws->xdg_surface, 0, 0, p_width, p_height);
3562
}
3563
}
3564
3565
#ifdef LIBDECOR_ENABLED
3566
if (p_ws->libdecor_frame) {
3567
struct libdecor_state *state = libdecor_state_new(p_width, p_height);
3568
libdecor_frame_commit(p_ws->libdecor_frame, state, p_ws->pending_libdecor_configuration);
3569
libdecor_state_free(state);
3570
p_ws->pending_libdecor_configuration = nullptr;
3571
}
3572
#endif
3573
3574
if (size_changed || scale_changed) {
3575
double win_scale = window_state_get_scale_factor(p_ws);
3576
Size2i scaled_size = scale_vector2i(p_ws->rect.size, win_scale);
3577
3578
if (using_fractional) {
3579
DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (fractional scale x%f).", p_ws->rect.size, scaled_size, p_ws->fractional_scale));
3580
} else {
3581
DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (buffer scale x%d).", p_ws->rect.size, scaled_size, p_ws->buffer_scale));
3582
}
3583
3584
// FIXME: Actually resize the hint instead of centering it.
3585
p_ws->wayland_thread->pointer_set_hint(scaled_size / 2);
3586
3587
Ref<WindowRectMessage> rect_msg;
3588
rect_msg.instantiate();
3589
rect_msg->id = p_ws->id;
3590
rect_msg->rect.position = scale_vector2i(p_ws->rect.position, win_scale);
3591
rect_msg->rect.size = scaled_size;
3592
p_ws->wayland_thread->push_message(rect_msg);
3593
}
3594
3595
if (scale_changed) {
3596
Ref<WindowEventMessage> dpi_msg;
3597
dpi_msg.instantiate();
3598
dpi_msg->id = p_ws->id;
3599
dpi_msg->event = DisplayServer::WINDOW_EVENT_DPI_CHANGE;
3600
p_ws->wayland_thread->push_message(dpi_msg);
3601
}
3602
}
3603
3604
// Scales a vector according to wp_fractional_scale's rules, where coordinates
3605
// must be scaled with away from zero half-rounding.
3606
Vector2i WaylandThread::scale_vector2i(const Vector2i &p_vector, double p_amount) {
3607
// This snippet is tiny, I know, but this is done a lot.
3608
int x = std::round(p_vector.x * p_amount);
3609
int y = std::round(p_vector.y * p_amount);
3610
3611
return Vector2i(x, y);
3612
}
3613
3614
void WaylandThread::seat_state_unlock_pointer(SeatState *p_ss) {
3615
ERR_FAIL_NULL(p_ss);
3616
3617
if (p_ss->wl_pointer == nullptr) {
3618
return;
3619
}
3620
3621
if (p_ss->wp_locked_pointer) {
3622
zwp_locked_pointer_v1_destroy(p_ss->wp_locked_pointer);
3623
p_ss->wp_locked_pointer = nullptr;
3624
}
3625
3626
if (p_ss->wp_confined_pointer) {
3627
zwp_confined_pointer_v1_destroy(p_ss->wp_confined_pointer);
3628
p_ss->wp_confined_pointer = nullptr;
3629
}
3630
}
3631
3632
void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
3633
ERR_FAIL_NULL(p_ss);
3634
3635
if (p_ss->wl_pointer == nullptr) {
3636
WARN_PRINT("Can't lock - no pointer?");
3637
return;
3638
}
3639
3640
if (registry.wp_pointer_constraints == nullptr) {
3641
WARN_PRINT("Can't lock - no constraints global.");
3642
return;
3643
}
3644
3645
if (p_ss->wp_locked_pointer == nullptr) {
3646
struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
3647
if (locked_surface == nullptr) {
3648
locked_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
3649
}
3650
ERR_FAIL_NULL(locked_surface);
3651
3652
p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
3653
}
3654
}
3655
3656
void WaylandThread::seat_state_set_hint(SeatState *p_ss, int p_x, int p_y) {
3657
if (p_ss->wp_locked_pointer == nullptr) {
3658
return;
3659
}
3660
3661
zwp_locked_pointer_v1_set_cursor_position_hint(p_ss->wp_locked_pointer, wl_fixed_from_int(p_x), wl_fixed_from_int(p_y));
3662
}
3663
3664
void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
3665
ERR_FAIL_NULL(p_ss);
3666
3667
if (p_ss->wl_pointer == nullptr) {
3668
return;
3669
}
3670
3671
if (registry.wp_pointer_constraints == nullptr) {
3672
return;
3673
}
3674
3675
if (p_ss->wp_confined_pointer == nullptr) {
3676
struct wl_surface *confined_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
3677
ERR_FAIL_NULL(confined_surface);
3678
3679
p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
3680
}
3681
}
3682
3683
void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {
3684
ERR_FAIL_NULL(p_ss);
3685
3686
WaylandThread *thread = p_ss->wayland_thread;
3687
ERR_FAIL_NULL(p_ss->wayland_thread);
3688
3689
if (!p_ss->wl_pointer || !p_ss->cursor_surface) {
3690
return;
3691
}
3692
3693
// NOTE: Those values are valid by default and will hide the cursor when
3694
// unchanged.
3695
struct wl_buffer *cursor_buffer = nullptr;
3696
uint32_t hotspot_x = 0;
3697
uint32_t hotspot_y = 0;
3698
int scale = 1;
3699
3700
if (thread->cursor_visible) {
3701
DisplayServer::CursorShape shape = thread->cursor_shape;
3702
3703
struct CustomCursor *custom_cursor = thread->custom_cursors.getptr(shape);
3704
3705
if (custom_cursor) {
3706
cursor_buffer = custom_cursor->wl_buffer;
3707
hotspot_x = custom_cursor->hotspot.x;
3708
hotspot_y = custom_cursor->hotspot.y;
3709
3710
// We can't really reasonably scale custom cursors, so we'll let the
3711
// compositor do it for us (badly).
3712
scale = 1;
3713
} else if (thread->registry.wp_cursor_shape_manager) {
3714
wp_cursor_shape_device_v1_shape wp_shape = thread->standard_cursors[shape];
3715
wp_cursor_shape_device_v1_set_shape(p_ss->wp_cursor_shape_device, p_ss->pointer_enter_serial, wp_shape);
3716
3717
// We should avoid calling the `wl_pointer_set_cursor` at the end of this method.
3718
return;
3719
} else {
3720
struct wl_cursor *wl_cursor = thread->wl_cursors[shape];
3721
3722
if (!wl_cursor) {
3723
return;
3724
}
3725
3726
int frame_idx = 0;
3727
3728
if (wl_cursor->image_count > 1) {
3729
// The cursor is animated.
3730
frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);
3731
3732
if (!p_ss->cursor_frame_callback) {
3733
// Since it's animated, we'll re-update it the next frame.
3734
p_ss->cursor_frame_callback = wl_surface_frame(p_ss->cursor_surface);
3735
wl_callback_add_listener(p_ss->cursor_frame_callback, &cursor_frame_callback_listener, p_ss);
3736
}
3737
}
3738
3739
struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx];
3740
3741
scale = thread->cursor_scale;
3742
3743
cursor_buffer = wl_cursor_image_get_buffer(wl_cursor_image);
3744
3745
// As the surface's buffer is scaled (thus the surface is smaller) and the
3746
// hotspot must be expressed in surface-local coordinates, we need to scale
3747
// it down accordingly.
3748
hotspot_x = wl_cursor_image->hotspot_x / scale;
3749
hotspot_y = wl_cursor_image->hotspot_y / scale;
3750
}
3751
}
3752
3753
wl_pointer_set_cursor(p_ss->wl_pointer, p_ss->pointer_enter_serial, p_ss->cursor_surface, hotspot_x, hotspot_y);
3754
wl_surface_set_buffer_scale(p_ss->cursor_surface, scale);
3755
wl_surface_attach(p_ss->cursor_surface, cursor_buffer, 0, 0);
3756
wl_surface_damage_buffer(p_ss->cursor_surface, 0, 0, INT_MAX, INT_MAX);
3757
3758
wl_surface_commit(p_ss->cursor_surface);
3759
}
3760
3761
void WaylandThread::seat_state_echo_keys(SeatState *p_ss) {
3762
ERR_FAIL_NULL(p_ss);
3763
3764
if (p_ss->wl_keyboard == nullptr) {
3765
return;
3766
}
3767
3768
// TODO: Comment and document out properly this block of code.
3769
// In short, this implements key repeating.
3770
if (p_ss->repeat_key_delay_msec && p_ss->repeating_keycode != XKB_KEYCODE_INVALID) {
3771
uint64_t current_ticks = OS::get_singleton()->get_ticks_msec();
3772
uint64_t delayed_start_ticks = p_ss->last_repeat_start_msec + p_ss->repeat_start_delay_msec;
3773
3774
if (p_ss->last_repeat_msec < delayed_start_ticks) {
3775
p_ss->last_repeat_msec = delayed_start_ticks;
3776
}
3777
3778
if (current_ticks >= delayed_start_ticks) {
3779
uint64_t ticks_delta = current_ticks - p_ss->last_repeat_msec;
3780
3781
int keys_amount = (ticks_delta / p_ss->repeat_key_delay_msec);
3782
3783
for (int i = 0; i < keys_amount; i++) {
3784
_seat_state_handle_xkb_keycode(p_ss, p_ss->repeating_keycode, true, true);
3785
}
3786
3787
p_ss->last_repeat_msec += ticks_delta - (ticks_delta % p_ss->repeat_key_delay_msec);
3788
}
3789
}
3790
}
3791
3792
void WaylandThread::push_message(Ref<Message> message) {
3793
messages.push_back(message);
3794
}
3795
3796
bool WaylandThread::has_message() {
3797
return messages.front() != nullptr;
3798
}
3799
3800
Ref<WaylandThread::Message> WaylandThread::pop_message() {
3801
if (messages.front() != nullptr) {
3802
Ref<Message> msg = messages.front()->get();
3803
messages.pop_front();
3804
return msg;
3805
}
3806
3807
// This method should only be called if `has_messages` returns true but if
3808
// that isn't the case we'll just return an invalid `Ref`. After all, due to
3809
// its `InputEvent`-like interface, we still have to dynamically cast and check
3810
// the `Ref`'s validity anyways.
3811
return Ref<Message>();
3812
}
3813
3814
void WaylandThread::window_create(DisplayServer::WindowID p_window_id, const Size2i &p_size, DisplayServer::WindowID p_parent_id) {
3815
ERR_FAIL_COND(windows.has(p_window_id));
3816
WindowState &ws = windows[p_window_id];
3817
3818
ws.id = p_window_id;
3819
3820
ws.registry = &registry;
3821
ws.wayland_thread = this;
3822
3823
ws.rect.size = p_size;
3824
3825
ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);
3826
wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);
3827
wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);
3828
3829
if (registry.wp_viewporter) {
3830
ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);
3831
3832
if (registry.wp_fractional_scale_manager) {
3833
ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);
3834
wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);
3835
}
3836
}
3837
3838
bool decorated = false;
3839
3840
#ifdef LIBDECOR_ENABLED
3841
if (!decorated && libdecor_context) {
3842
ws.libdecor_frame = libdecor_decorate(libdecor_context, ws.wl_surface, (struct libdecor_frame_interface *)&libdecor_frame_interface, &ws);
3843
libdecor_frame_map(ws.libdecor_frame);
3844
3845
if (registry.xdg_toplevel_icon_manager) {
3846
xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(ws.libdecor_frame);
3847
if (toplevel != nullptr) {
3848
xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, toplevel, xdg_icon);
3849
}
3850
}
3851
3852
decorated = true;
3853
}
3854
#endif
3855
3856
if (!decorated) {
3857
// libdecor has failed loading or is disabled, we shall handle xdg_toplevel
3858
// creation and decoration ourselves (and by decorating for now I just mean
3859
// asking for SSDs and hoping for the best).
3860
ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);
3861
xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);
3862
3863
ws.xdg_toplevel = xdg_surface_get_toplevel(ws.xdg_surface);
3864
xdg_toplevel_add_listener(ws.xdg_toplevel, &xdg_toplevel_listener, &ws);
3865
3866
if (registry.xdg_decoration_manager) {
3867
ws.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(registry.xdg_decoration_manager, ws.xdg_toplevel);
3868
zxdg_toplevel_decoration_v1_add_listener(ws.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &ws);
3869
3870
decorated = true;
3871
}
3872
3873
if (registry.xdg_toplevel_icon_manager) {
3874
xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, ws.xdg_toplevel, xdg_icon);
3875
}
3876
}
3877
3878
if (p_parent_id != DisplayServer::INVALID_WINDOW_ID) {
3879
// NOTE: It's important to set the parent ASAP to avoid misunderstandings with
3880
// the compositor. For example, niri immediately resizes the window to full
3881
// size as soon as it's configured if it's not parented to another toplevel.
3882
window_set_parent(p_window_id, p_parent_id);
3883
}
3884
3885
ws.frame_callback = wl_surface_frame(ws.wl_surface);
3886
wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);
3887
3888
if (registry.xdg_exporter_v2) {
3889
ws.xdg_exported_v2 = zxdg_exporter_v2_export_toplevel(registry.xdg_exporter_v2, ws.wl_surface);
3890
zxdg_exported_v2_add_listener(ws.xdg_exported_v2, &xdg_exported_v2_listener, &ws);
3891
} else if (registry.xdg_exporter_v1) {
3892
ws.xdg_exported_v1 = zxdg_exporter_v1_export(registry.xdg_exporter_v1, ws.wl_surface);
3893
zxdg_exported_v1_add_listener(ws.xdg_exported_v1, &xdg_exported_v1_listener, &ws);
3894
}
3895
3896
wl_surface_commit(ws.wl_surface);
3897
3898
// Wait for the surface to be configured before continuing.
3899
wl_display_roundtrip(wl_display);
3900
3901
window_state_update_size(&ws, ws.rect.size.width, ws.rect.size.height);
3902
}
3903
3904
void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect) {
3905
ERR_FAIL_COND(windows.has(p_window_id));
3906
ERR_FAIL_COND(!windows.has(p_parent_id));
3907
3908
WindowState &ws = windows[p_window_id];
3909
WindowState &parent = windows[p_parent_id];
3910
3911
double parent_scale = window_state_get_scale_factor(&parent);
3912
3913
p_rect.position = scale_vector2i(p_rect.position, 1.0 / parent_scale);
3914
p_rect.size = scale_vector2i(p_rect.size, 1.0 / parent_scale);
3915
3916
// We manually scaled based on the parent. If we don't set the relevant fields,
3917
// the resizing routines will get confused and scale once more.
3918
ws.preferred_fractional_scale = parent.preferred_fractional_scale;
3919
ws.fractional_scale = parent.fractional_scale;
3920
ws.buffer_scale = parent.buffer_scale;
3921
3922
ws.id = p_window_id;
3923
ws.parent_id = p_parent_id;
3924
ws.registry = &registry;
3925
ws.wayland_thread = this;
3926
3927
ws.rect = p_rect;
3928
3929
ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);
3930
wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);
3931
wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);
3932
3933
if (registry.wp_viewporter) {
3934
ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);
3935
3936
if (registry.wp_fractional_scale_manager) {
3937
ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);
3938
wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);
3939
}
3940
}
3941
3942
ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);
3943
xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);
3944
3945
Rect2i positioner_rect;
3946
positioner_rect.size = parent.rect.size;
3947
struct xdg_surface *parent_xdg_surface = parent.xdg_surface;
3948
3949
Point2i offset = ws.rect.position - parent.rect.position;
3950
3951
#ifdef LIBDECOR_ENABLED
3952
if (!parent_xdg_surface && parent.libdecor_frame) {
3953
parent_xdg_surface = libdecor_frame_get_xdg_surface(parent.libdecor_frame);
3954
3955
int corner_x = 0;
3956
int corner_y = 0;
3957
libdecor_frame_translate_coordinate(parent.libdecor_frame, 0, 0, &corner_x, &corner_y);
3958
3959
positioner_rect.position.x = corner_x;
3960
positioner_rect.position.y = corner_y;
3961
3962
positioner_rect.size.width -= corner_x;
3963
positioner_rect.size.height -= corner_y;
3964
}
3965
#endif
3966
3967
ERR_FAIL_NULL(parent_xdg_surface);
3968
3969
struct xdg_positioner *xdg_positioner = xdg_wm_base_create_positioner(registry.xdg_wm_base);
3970
xdg_positioner_set_size(xdg_positioner, ws.rect.size.width, ws.rect.size.height);
3971
xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
3972
xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
3973
xdg_positioner_set_constraint_adjustment(xdg_positioner, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y);
3974
xdg_positioner_set_anchor_rect(xdg_positioner, positioner_rect.position.x, positioner_rect.position.y, positioner_rect.size.width, positioner_rect.size.height);
3975
xdg_positioner_set_offset(xdg_positioner, offset.x, offset.y);
3976
3977
ws.xdg_popup = xdg_surface_get_popup(ws.xdg_surface, parent_xdg_surface, xdg_positioner);
3978
xdg_popup_add_listener(ws.xdg_popup, &xdg_popup_listener, &ws);
3979
3980
xdg_positioner_destroy(xdg_positioner);
3981
3982
ws.frame_callback = wl_surface_frame(ws.wl_surface);
3983
wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);
3984
3985
wl_surface_commit(ws.wl_surface);
3986
3987
// Wait for the surface to be configured before continuing.
3988
wl_display_roundtrip(wl_display);
3989
}
3990
3991
void WaylandThread::window_destroy(DisplayServer::WindowID p_window_id) {
3992
ERR_FAIL_COND(!windows.has(p_window_id));
3993
WindowState &ws = windows[p_window_id];
3994
3995
if (ws.xdg_popup) {
3996
xdg_popup_destroy(ws.xdg_popup);
3997
}
3998
3999
if (ws.xdg_toplevel_decoration) {
4000
zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration);
4001
}
4002
4003
if (ws.xdg_toplevel) {
4004
xdg_toplevel_destroy(ws.xdg_toplevel);
4005
}
4006
4007
#ifdef LIBDECOR_ENABLED
4008
if (ws.libdecor_frame) {
4009
libdecor_frame_unref(ws.libdecor_frame);
4010
}
4011
#endif // LIBDECOR_ENABLED
4012
4013
if (ws.wp_fractional_scale) {
4014
wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);
4015
}
4016
4017
if (ws.wp_viewport) {
4018
wp_viewport_destroy(ws.wp_viewport);
4019
}
4020
4021
if (ws.frame_callback) {
4022
wl_callback_destroy(ws.frame_callback);
4023
}
4024
4025
if (ws.xdg_surface) {
4026
xdg_surface_destroy(ws.xdg_surface);
4027
}
4028
4029
if (ws.wl_surface) {
4030
wl_surface_destroy(ws.wl_surface);
4031
}
4032
4033
// Before continuing, let's handle any leftover event that might still refer to
4034
// this window.
4035
wl_display_roundtrip(wl_display);
4036
4037
// We can already clean up here, we're done.
4038
windows.erase(p_window_id);
4039
}
4040
4041
struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServer::WindowID p_window_id) const {
4042
const WindowState *ws = windows.getptr(p_window_id);
4043
if (ws) {
4044
return ws->wl_surface;
4045
}
4046
4047
return nullptr;
4048
}
4049
4050
WaylandThread::WindowState *WaylandThread::window_get_state(DisplayServer::WindowID p_window_id) {
4051
return windows.getptr(p_window_id);
4052
}
4053
4054
const WaylandThread::WindowState *WaylandThread::window_get_state(DisplayServer::WindowID p_window_id) const {
4055
return windows.getptr(p_window_id);
4056
}
4057
4058
Size2i WaylandThread::window_set_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
4059
ERR_FAIL_COND_V(!windows.has(p_window_id), p_size);
4060
WindowState &ws = windows[p_window_id];
4061
4062
double window_scale = window_state_get_scale_factor(&ws);
4063
4064
if (ws.maximized) {
4065
// Can't do anything.
4066
return scale_vector2i(ws.rect.size, window_scale);
4067
}
4068
4069
Size2i new_size = scale_vector2i(p_size, 1 / window_scale);
4070
4071
if (ws.tiled_left && ws.tiled_right) {
4072
// Tiled left and right, we shouldn't change from our current width or else
4073
// it'll look wonky.
4074
new_size.width = ws.rect.size.width;
4075
}
4076
4077
if (ws.tiled_top && ws.tiled_bottom) {
4078
// Tiled top and bottom. Same as above, but for the height.
4079
new_size.height = ws.rect.size.height;
4080
}
4081
4082
if (ws.resizing && ws.rect.size.width > 0 && ws.rect.size.height > 0) {
4083
// The spec says that we shall not resize further than the config size. We can
4084
// resize less than that though.
4085
new_size = new_size.min(ws.rect.size);
4086
}
4087
4088
// NOTE: Older versions of libdecor (~2022) do not have a way to get the max
4089
// content size. Let's also check for its pointer so that we can preserve
4090
// compatibility with older distros.
4091
if (ws.libdecor_frame && libdecor_frame_get_max_content_size) {
4092
int max_width = new_size.width;
4093
int max_height = new_size.height;
4094
4095
// NOTE: Max content size is dynamic on libdecor, as plugins can override it
4096
// to accommodate their decorations.
4097
libdecor_frame_get_max_content_size(ws.libdecor_frame, &max_width, &max_height);
4098
4099
if (max_width > 0 && max_height > 0) {
4100
new_size.width = MIN(new_size.width, max_width);
4101
new_size.height = MIN(new_size.height, max_height);
4102
}
4103
}
4104
4105
window_state_update_size(&ws, new_size.width, new_size.height);
4106
4107
return scale_vector2i(new_size, window_scale);
4108
}
4109
4110
void WaylandThread::beep() const {
4111
if (registry.xdg_system_bell) {
4112
xdg_system_bell_v1_ring(registry.xdg_system_bell, nullptr);
4113
}
4114
}
4115
4116
void WaylandThread::window_start_drag(DisplayServer::WindowID p_window_id) {
4117
ERR_FAIL_COND(!windows.has(p_window_id));
4118
WindowState &ws = windows[p_window_id];
4119
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4120
4121
if (ss && ws.xdg_toplevel) {
4122
xdg_toplevel_move(ws.xdg_toplevel, ss->wl_seat, ss->pointer_data.button_serial);
4123
}
4124
4125
#ifdef LIBDECOR_ENABLED
4126
if (ws.libdecor_frame) {
4127
libdecor_frame_move(ws.libdecor_frame, ss->wl_seat, ss->pointer_data.button_serial);
4128
}
4129
#endif
4130
}
4131
4132
void WaylandThread::window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window) {
4133
ERR_FAIL_COND(!windows.has(p_window));
4134
WindowState &ws = windows[p_window];
4135
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4136
4137
if (ss && ws.xdg_toplevel) {
4138
xdg_toplevel_resize_edge edge = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
4139
switch (p_edge) {
4140
case DisplayServer::WINDOW_EDGE_TOP_LEFT: {
4141
edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
4142
} break;
4143
case DisplayServer::WINDOW_EDGE_TOP: {
4144
edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
4145
} break;
4146
case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {
4147
edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
4148
} break;
4149
case DisplayServer::WINDOW_EDGE_LEFT: {
4150
edge = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
4151
} break;
4152
case DisplayServer::WINDOW_EDGE_RIGHT: {
4153
edge = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
4154
} break;
4155
case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {
4156
edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
4157
} break;
4158
case DisplayServer::WINDOW_EDGE_BOTTOM: {
4159
edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
4160
} break;
4161
case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {
4162
edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
4163
} break;
4164
default:
4165
break;
4166
}
4167
xdg_toplevel_resize(ws.xdg_toplevel, ss->wl_seat, ss->pointer_data.button_serial, edge);
4168
}
4169
4170
#ifdef LIBDECOR_ENABLED
4171
if (ws.libdecor_frame) {
4172
libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE;
4173
switch (p_edge) {
4174
case DisplayServer::WINDOW_EDGE_TOP_LEFT: {
4175
edge = LIBDECOR_RESIZE_EDGE_TOP_LEFT;
4176
} break;
4177
case DisplayServer::WINDOW_EDGE_TOP: {
4178
edge = LIBDECOR_RESIZE_EDGE_TOP;
4179
} break;
4180
case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {
4181
edge = LIBDECOR_RESIZE_EDGE_TOP_RIGHT;
4182
} break;
4183
case DisplayServer::WINDOW_EDGE_LEFT: {
4184
edge = LIBDECOR_RESIZE_EDGE_LEFT;
4185
} break;
4186
case DisplayServer::WINDOW_EDGE_RIGHT: {
4187
edge = LIBDECOR_RESIZE_EDGE_RIGHT;
4188
} break;
4189
case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {
4190
edge = LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT;
4191
} break;
4192
case DisplayServer::WINDOW_EDGE_BOTTOM: {
4193
edge = LIBDECOR_RESIZE_EDGE_BOTTOM;
4194
} break;
4195
case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {
4196
edge = LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT;
4197
} break;
4198
default:
4199
break;
4200
}
4201
libdecor_frame_resize(ws.libdecor_frame, ss->wl_seat, ss->pointer_data.button_serial, edge);
4202
}
4203
#endif
4204
}
4205
4206
void WaylandThread::window_set_parent(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id) {
4207
ERR_FAIL_COND(!windows.has(p_window_id));
4208
ERR_FAIL_COND(!windows.has(p_parent_id));
4209
4210
WindowState &child = windows[p_window_id];
4211
child.parent_id = p_parent_id;
4212
4213
WindowState &parent = windows[p_parent_id];
4214
4215
// NOTE: We can't really unparent as, at the time of writing, libdecor
4216
// segfaults when trying to set a null parent. Hopefully unparenting is not
4217
// that common. Bummer.
4218
4219
#ifdef LIBDECOR_ENABLED
4220
if (child.libdecor_frame && parent.libdecor_frame) {
4221
libdecor_frame_set_parent(child.libdecor_frame, parent.libdecor_frame);
4222
return;
4223
}
4224
#endif
4225
4226
if (child.xdg_toplevel && parent.xdg_toplevel) {
4227
xdg_toplevel_set_parent(child.xdg_toplevel, parent.xdg_toplevel);
4228
}
4229
}
4230
4231
void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
4232
ERR_FAIL_COND(!windows.has(p_window_id));
4233
WindowState &ws = windows[p_window_id];
4234
4235
Vector2i logical_max_size = scale_vector2i(p_size, 1 / window_state_get_scale_factor(&ws));
4236
4237
if (ws.wl_surface && ws.xdg_toplevel) {
4238
xdg_toplevel_set_max_size(ws.xdg_toplevel, logical_max_size.width, logical_max_size.height);
4239
}
4240
4241
#ifdef LIBDECOR_ENABLED
4242
if (ws.libdecor_frame) {
4243
libdecor_frame_set_max_content_size(ws.libdecor_frame, logical_max_size.width, logical_max_size.height);
4244
}
4245
4246
// FIXME: I'm not sure whether we have to commit the surface for this to apply.
4247
#endif
4248
}
4249
4250
void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
4251
ERR_FAIL_COND(!windows.has(p_window_id));
4252
WindowState &ws = windows[p_window_id];
4253
4254
Size2i logical_min_size = scale_vector2i(p_size, 1 / window_state_get_scale_factor(&ws));
4255
4256
if (ws.wl_surface && ws.xdg_toplevel) {
4257
xdg_toplevel_set_min_size(ws.xdg_toplevel, logical_min_size.width, logical_min_size.height);
4258
}
4259
4260
#ifdef LIBDECOR_ENABLED
4261
if (ws.libdecor_frame) {
4262
libdecor_frame_set_min_content_size(ws.libdecor_frame, logical_min_size.width, logical_min_size.height);
4263
}
4264
4265
// FIXME: I'm not sure whether we have to commit the surface for this to apply.
4266
#endif
4267
}
4268
4269
bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const {
4270
ERR_FAIL_COND_V(!windows.has(p_window_id), false);
4271
const WindowState &ws = windows[p_window_id];
4272
4273
switch (p_window_mode) {
4274
case DisplayServer::WINDOW_MODE_WINDOWED: {
4275
// Looks like it's guaranteed.
4276
return true;
4277
};
4278
4279
case DisplayServer::WINDOW_MODE_MINIMIZED: {
4280
#ifdef LIBDECOR_ENABLED
4281
if (ws.libdecor_frame) {
4282
return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_MINIMIZE);
4283
}
4284
#endif // LIBDECOR_ENABLED
4285
4286
return ws.can_minimize;
4287
};
4288
4289
case DisplayServer::WINDOW_MODE_MAXIMIZED: {
4290
if (ws.libdecor_frame) {
4291
// NOTE: libdecor doesn't seem to have a maximize capability query?
4292
// The fact that there's a fullscreen one makes me suspicious. Anyways,
4293
// let's act as if we always can.
4294
return true;
4295
}
4296
return ws.can_maximize;
4297
};
4298
4299
case DisplayServer::WINDOW_MODE_FULLSCREEN:
4300
case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
4301
#ifdef LIBDECOR_ENABLED
4302
if (ws.libdecor_frame) {
4303
return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_FULLSCREEN);
4304
}
4305
#endif // LIBDECOR_ENABLED
4306
4307
return ws.can_fullscreen;
4308
};
4309
}
4310
4311
return false;
4312
}
4313
4314
void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) {
4315
ERR_FAIL_COND(!windows.has(p_window_id));
4316
WindowState &ws = windows[p_window_id];
4317
4318
if (ws.mode == p_window_mode) {
4319
return;
4320
}
4321
4322
// Don't waste time with hidden windows and whatnot. Behave like it worked.
4323
#ifdef LIBDECOR_ENABLED
4324
if ((!ws.wl_surface || !ws.xdg_toplevel) && !ws.libdecor_frame) {
4325
#else
4326
if (!ws.wl_surface || !ws.xdg_toplevel) {
4327
#endif // LIBDECOR_ENABLED
4328
ws.mode = p_window_mode;
4329
return;
4330
}
4331
4332
// Return back to a windowed state so that we can apply what the user asked.
4333
switch (ws.mode) {
4334
case DisplayServer::WINDOW_MODE_WINDOWED: {
4335
// Do nothing.
4336
} break;
4337
4338
case DisplayServer::WINDOW_MODE_MINIMIZED: {
4339
// We can't do much according to the xdg_shell protocol. I have no idea
4340
// whether this implies that we should return or who knows what. For now
4341
// we'll do nothing.
4342
// TODO: Test this properly.
4343
} break;
4344
4345
case DisplayServer::WINDOW_MODE_MAXIMIZED: {
4346
// Try to unmaximize. This isn't garaunteed to work actually, so we'll have
4347
// to check whether something changed.
4348
if (ws.xdg_toplevel) {
4349
xdg_toplevel_unset_maximized(ws.xdg_toplevel);
4350
}
4351
4352
#ifdef LIBDECOR_ENABLED
4353
if (ws.libdecor_frame) {
4354
libdecor_frame_unset_maximized(ws.libdecor_frame);
4355
}
4356
#endif // LIBDECOR_ENABLED
4357
} break;
4358
4359
case DisplayServer::WINDOW_MODE_FULLSCREEN:
4360
case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
4361
// Same thing as above, unset fullscreen and check later if it worked.
4362
if (ws.xdg_toplevel) {
4363
xdg_toplevel_unset_fullscreen(ws.xdg_toplevel);
4364
}
4365
4366
#ifdef LIBDECOR_ENABLED
4367
if (ws.libdecor_frame) {
4368
libdecor_frame_unset_fullscreen(ws.libdecor_frame);
4369
}
4370
#endif // LIBDECOR_ENABLED
4371
} break;
4372
}
4373
4374
// Wait for a configure event and hope that something changed.
4375
wl_display_roundtrip(wl_display);
4376
4377
if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) {
4378
// The compositor refused our "normalization" request. It'd be useless or
4379
// unpredictable to attempt setting a new state. We're done.
4380
return;
4381
}
4382
4383
// Ask the compositor to set the state indicated by the new mode.
4384
switch (p_window_mode) {
4385
case DisplayServer::WINDOW_MODE_WINDOWED: {
4386
// Do nothing. We're already windowed.
4387
} break;
4388
4389
case DisplayServer::WINDOW_MODE_MINIMIZED: {
4390
if (!window_can_set_mode(p_window_id, p_window_mode)) {
4391
// Minimization is special (read below). Better not mess with it if the
4392
// compositor explicitly announces that it doesn't support it.
4393
break;
4394
}
4395
4396
if (ws.xdg_toplevel) {
4397
xdg_toplevel_set_minimized(ws.xdg_toplevel);
4398
}
4399
4400
#ifdef LIBDECOR_ENABLED
4401
if (ws.libdecor_frame) {
4402
libdecor_frame_set_minimized(ws.libdecor_frame);
4403
}
4404
#endif // LIBDECOR_ENABLED
4405
// We have no way to actually detect this state, so we'll have to report it
4406
// manually to the engine (hoping that it worked). In the worst case it'll
4407
// get reset by the next configure event.
4408
ws.mode = DisplayServer::WINDOW_MODE_MINIMIZED;
4409
} break;
4410
4411
case DisplayServer::WINDOW_MODE_MAXIMIZED: {
4412
if (ws.xdg_toplevel) {
4413
xdg_toplevel_set_maximized(ws.xdg_toplevel);
4414
}
4415
4416
#ifdef LIBDECOR_ENABLED
4417
if (ws.libdecor_frame) {
4418
libdecor_frame_set_maximized(ws.libdecor_frame);
4419
}
4420
#endif // LIBDECOR_ENABLED
4421
} break;
4422
4423
case DisplayServer::WINDOW_MODE_FULLSCREEN:
4424
case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
4425
if (ws.xdg_toplevel) {
4426
xdg_toplevel_set_fullscreen(ws.xdg_toplevel, nullptr);
4427
}
4428
4429
#ifdef LIBDECOR_ENABLED
4430
if (ws.libdecor_frame) {
4431
libdecor_frame_set_fullscreen(ws.libdecor_frame, nullptr);
4432
}
4433
#endif // LIBDECOR_ENABLED
4434
} break;
4435
4436
default: {
4437
} break;
4438
}
4439
}
4440
4441
void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless) {
4442
ERR_FAIL_COND(!windows.has(p_window_id));
4443
WindowState &ws = windows[p_window_id];
4444
4445
if (ws.xdg_toplevel_decoration) {
4446
if (p_borderless) {
4447
// We implement borderless windows by simply asking the compositor to let
4448
// us handle decorations (we don't).
4449
zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
4450
} else {
4451
zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
4452
}
4453
}
4454
4455
#ifdef LIBDECOR_ENABLED
4456
if (ws.libdecor_frame) {
4457
bool visible_current = libdecor_frame_is_visible(ws.libdecor_frame);
4458
bool visible_target = !p_borderless;
4459
4460
// NOTE: We have to do this otherwise we trip on a libdecor bug where it's
4461
// possible to destroy the frame more than once, by setting the visibility
4462
// to false multiple times and thus crashing.
4463
if (visible_current != visible_target) {
4464
print_verbose(vformat("Setting libdecor frame visibility to %s", visible_target));
4465
libdecor_frame_set_visibility(ws.libdecor_frame, visible_target);
4466
}
4467
}
4468
#endif // LIBDECOR_ENABLED
4469
}
4470
4471
void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const String &p_title) {
4472
ERR_FAIL_COND(!windows.has(p_window_id));
4473
WindowState &ws = windows[p_window_id];
4474
4475
#ifdef LIBDECOR_ENABLED
4476
if (ws.libdecor_frame) {
4477
libdecor_frame_set_title(ws.libdecor_frame, p_title.utf8().get_data());
4478
}
4479
#endif // LIBDECOR_ENABLE
4480
4481
if (ws.xdg_toplevel) {
4482
xdg_toplevel_set_title(ws.xdg_toplevel, p_title.utf8().get_data());
4483
}
4484
}
4485
4486
void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id) {
4487
ERR_FAIL_COND(!windows.has(p_window_id));
4488
WindowState &ws = windows[p_window_id];
4489
4490
#ifdef LIBDECOR_ENABLED
4491
if (ws.libdecor_frame) {
4492
libdecor_frame_set_app_id(ws.libdecor_frame, p_app_id.utf8().get_data());
4493
return;
4494
}
4495
#endif // LIBDECOR_ENABLED
4496
4497
if (ws.xdg_toplevel) {
4498
xdg_toplevel_set_app_id(ws.xdg_toplevel, p_app_id.utf8().get_data());
4499
return;
4500
}
4501
}
4502
4503
void WaylandThread::set_icon(const Ref<Image> &p_icon) {
4504
ERR_FAIL_COND(p_icon.is_null());
4505
4506
Size2i icon_size = p_icon->get_size();
4507
ERR_FAIL_COND(icon_size.width != icon_size.height);
4508
4509
if (!registry.xdg_toplevel_icon_manager) {
4510
return;
4511
}
4512
4513
if (xdg_icon) {
4514
xdg_toplevel_icon_v1_destroy(xdg_icon);
4515
}
4516
4517
if (icon_buffer) {
4518
wl_buffer_destroy(icon_buffer);
4519
}
4520
4521
// NOTE: The stride is the width of the icon in bytes.
4522
uint32_t icon_stride = icon_size.width * 4;
4523
uint32_t data_size = icon_stride * icon_size.height;
4524
4525
// We need a shared memory object file descriptor in order to create a
4526
// wl_buffer through wl_shm.
4527
int fd = WaylandThread::_allocate_shm_file(data_size);
4528
ERR_FAIL_COND(fd == -1);
4529
4530
uint32_t *buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
4531
4532
// Create the Wayland buffer.
4533
struct wl_shm_pool *shm_pool = wl_shm_create_pool(registry.wl_shm, fd, data_size);
4534
icon_buffer = wl_shm_pool_create_buffer(shm_pool, 0, icon_size.width, icon_size.height, icon_stride, WL_SHM_FORMAT_ARGB8888);
4535
wl_shm_pool_destroy(shm_pool);
4536
4537
// Fill the cursor buffer with the image data.
4538
for (uint32_t index = 0; index < (uint32_t)(icon_size.width * icon_size.height); index++) {
4539
int row_index = index / icon_size.width;
4540
int column_index = (index % icon_size.width);
4541
4542
buffer_data[index] = p_icon->get_pixel(column_index, row_index).to_argb32();
4543
4544
// Wayland buffers, unless specified, require associated alpha, so we'll just
4545
// associate the alpha in-place.
4546
uint8_t *pixel_data = (uint8_t *)&buffer_data[index];
4547
pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;
4548
pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;
4549
pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;
4550
}
4551
4552
xdg_icon = xdg_toplevel_icon_manager_v1_create_icon(registry.xdg_toplevel_icon_manager);
4553
xdg_toplevel_icon_v1_add_buffer(xdg_icon, icon_buffer, icon_size.width);
4554
4555
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
4556
// Setting a name allows the godot icon to be overridden by a system theme.
4557
// We only want the project manager and editor to get themed,
4558
// Games will get icons with the protocol and themed icons with .desktop entries.
4559
// NOTE: should be synced with the icon name in misc/dist/linuxbsd/Godot.desktop
4560
xdg_toplevel_icon_v1_set_name(xdg_icon, "godot");
4561
}
4562
4563
for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {
4564
WindowState &ws = pair.value;
4565
#ifdef LIBDECOR_ENABLED
4566
if (ws.libdecor_frame) {
4567
xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(ws.libdecor_frame);
4568
ERR_FAIL_NULL(toplevel);
4569
xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, toplevel, xdg_icon);
4570
}
4571
#endif
4572
if (ws.xdg_toplevel) {
4573
xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, ws.xdg_toplevel, xdg_icon);
4574
}
4575
}
4576
}
4577
4578
DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const {
4579
ERR_FAIL_COND_V(!windows.has(p_window_id), DisplayServer::WINDOW_MODE_WINDOWED);
4580
const WindowState &ws = windows[p_window_id];
4581
4582
return ws.mode;
4583
}
4584
4585
void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id) {
4586
ERR_FAIL_COND(!windows.has(p_window_id));
4587
WindowState &ws = windows[p_window_id];
4588
4589
if (registry.xdg_activation) {
4590
// Window attention requests are done through the XDG activation protocol.
4591
xdg_activation_token_v1 *xdg_activation_token = xdg_activation_v1_get_activation_token(registry.xdg_activation);
4592
xdg_activation_token_v1_add_listener(xdg_activation_token, &xdg_activation_token_listener, &ws);
4593
xdg_activation_token_v1_commit(xdg_activation_token);
4594
}
4595
}
4596
4597
void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable) {
4598
ERR_FAIL_COND(!windows.has(p_window_id));
4599
WindowState &ws = windows[p_window_id];
4600
4601
if (p_enable) {
4602
if (ws.registry->wp_idle_inhibit_manager && !ws.wp_idle_inhibitor) {
4603
ERR_FAIL_NULL(ws.wl_surface);
4604
ws.wp_idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(ws.registry->wp_idle_inhibit_manager, ws.wl_surface);
4605
}
4606
} else {
4607
if (ws.wp_idle_inhibitor) {
4608
zwp_idle_inhibitor_v1_destroy(ws.wp_idle_inhibitor);
4609
ws.wp_idle_inhibitor = nullptr;
4610
}
4611
}
4612
}
4613
4614
bool WaylandThread::window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const {
4615
ERR_FAIL_COND_V(!windows.has(p_window_id), false);
4616
const WindowState &ws = windows[p_window_id];
4617
4618
return ws.wp_idle_inhibitor != nullptr;
4619
}
4620
4621
WaylandThread::ScreenData WaylandThread::screen_get_data(int p_screen) const {
4622
ERR_FAIL_INDEX_V(p_screen, registry.wl_outputs.size(), ScreenData());
4623
4624
return wl_output_get_screen_state(registry.wl_outputs.get(p_screen))->data;
4625
}
4626
4627
int WaylandThread::get_screen_count() const {
4628
return registry.wl_outputs.size();
4629
}
4630
4631
DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const {
4632
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4633
4634
if (ss) {
4635
// Let's determine the most recently used tablet tool.
4636
TabletToolState *max_ts = nullptr;
4637
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
4638
TabletToolState *ts = wp_tablet_tool_get_state(tool);
4639
ERR_CONTINUE(ts == nullptr);
4640
4641
TabletToolData &td = ts->data;
4642
4643
if (!max_ts) {
4644
max_ts = ts;
4645
continue;
4646
}
4647
4648
if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {
4649
max_ts = ts;
4650
}
4651
}
4652
4653
const PointerData &pd = ss->pointer_data;
4654
4655
if (max_ts) {
4656
TabletToolData &td = max_ts->data;
4657
if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {
4658
return td.proximal_id;
4659
}
4660
}
4661
4662
return ss->pointer_data.pointed_id;
4663
}
4664
4665
return DisplayServer::INVALID_WINDOW_ID;
4666
}
4667
DisplayServer::WindowID WaylandThread::pointer_get_last_pointed_window_id() const {
4668
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4669
4670
if (ss) {
4671
// Let's determine the most recently used tablet tool.
4672
TabletToolState *max_ts = nullptr;
4673
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
4674
TabletToolState *ts = wp_tablet_tool_get_state(tool);
4675
ERR_CONTINUE(ts == nullptr);
4676
4677
TabletToolData &td = ts->data;
4678
4679
if (!max_ts) {
4680
max_ts = ts;
4681
continue;
4682
}
4683
4684
if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {
4685
max_ts = ts;
4686
}
4687
}
4688
4689
const PointerData &pd = ss->pointer_data;
4690
4691
if (max_ts) {
4692
TabletToolData &td = max_ts->data;
4693
if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {
4694
return td.last_proximal_id;
4695
}
4696
}
4697
4698
return ss->pointer_data.last_pointed_id;
4699
}
4700
4701
return DisplayServer::INVALID_WINDOW_ID;
4702
}
4703
4704
void WaylandThread::pointer_set_constraint(PointerConstraint p_constraint) {
4705
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4706
4707
if (ss) {
4708
seat_state_unlock_pointer(ss);
4709
4710
if (p_constraint == PointerConstraint::LOCKED) {
4711
seat_state_lock_pointer(ss);
4712
} else if (p_constraint == PointerConstraint::CONFINED) {
4713
seat_state_confine_pointer(ss);
4714
}
4715
}
4716
4717
pointer_constraint = p_constraint;
4718
}
4719
4720
void WaylandThread::pointer_set_hint(const Point2i &p_hint) {
4721
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4722
if (!ss) {
4723
return;
4724
}
4725
4726
WindowState *ws = window_get_state(ss->pointer_data.pointed_id);
4727
4728
int hint_x = 0;
4729
int hint_y = 0;
4730
4731
if (ws) {
4732
// NOTE: It looks like it's not really recommended to convert from
4733
// "godot-space" to "wayland-space" and in general I received mixed feelings
4734
// discussing about this. I'm not really sure about the maths behind this but,
4735
// oh well, we're setting a cursor hint. ¯\_(ツ)_/¯
4736
// See: https://oftc.irclog.whitequark.org/wayland/2023-08-23#1692756914-1692816818
4737
hint_x = std::round(p_hint.x / window_state_get_scale_factor(ws));
4738
hint_y = std::round(p_hint.y / window_state_get_scale_factor(ws));
4739
}
4740
4741
if (ss) {
4742
seat_state_set_hint(ss, hint_x, hint_y);
4743
}
4744
}
4745
4746
WaylandThread::PointerConstraint WaylandThread::pointer_get_constraint() const {
4747
return pointer_constraint;
4748
}
4749
4750
BitField<MouseButtonMask> WaylandThread::pointer_get_button_mask() const {
4751
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
4752
4753
if (ss) {
4754
return ss->pointer_data.pressed_button_mask;
4755
}
4756
4757
return BitField<MouseButtonMask>();
4758
}
4759
4760
Error WaylandThread::init() {
4761
#ifdef SOWRAP_ENABLED
4762
#ifdef DEBUG_ENABLED
4763
int dylibloader_verbose = 1;
4764
#else
4765
int dylibloader_verbose = 0;
4766
#endif // DEBUG_ENABLED
4767
4768
if (initialize_wayland_client(dylibloader_verbose) != 0) {
4769
WARN_PRINT("Can't load the Wayland client library.");
4770
return ERR_CANT_CREATE;
4771
}
4772
4773
if (initialize_wayland_cursor(dylibloader_verbose) != 0) {
4774
WARN_PRINT("Can't load the Wayland cursor library.");
4775
return ERR_CANT_CREATE;
4776
}
4777
4778
if (initialize_xkbcommon(dylibloader_verbose) != 0) {
4779
WARN_PRINT("Can't load the XKBcommon library.");
4780
return ERR_CANT_CREATE;
4781
}
4782
#endif // SOWRAP_ENABLED
4783
4784
KeyMappingXKB::initialize();
4785
4786
String embedder_socket_path;
4787
4788
#ifdef TOOLS_ENABLED
4789
bool embedder_enabled = true;
4790
4791
if (OS::get_singleton()->get_environment("GODOT_WAYLAND_DISABLE_EMBEDDER") == "1") {
4792
print_verbose("Disabling Wayland embedder as per GODOT_WAYLAND_DISABLE_EMBEDDER.");
4793
embedder_enabled = false;
4794
}
4795
4796
if (embedder_enabled && Engine::get_singleton()->is_editor_hint() && !Engine::get_singleton()->is_project_manager_hint()) {
4797
print_verbose("Initializing Wayland embedder.");
4798
Error embedder_status = embedder.init();
4799
ERR_FAIL_COND_V_MSG(embedder_status != OK, ERR_CANT_CREATE, "Can't initialize Wayland embedder.");
4800
4801
embedder_socket_path = embedder.get_socket_path();
4802
ERR_FAIL_COND_V_MSG(embedder_socket_path.is_empty(), ERR_CANT_CREATE, "Wayland embedder returned invalid path.");
4803
4804
OS::get_singleton()->set_environment("GODOT_WAYLAND_DISPLAY", embedder_socket_path);
4805
}
4806
#endif // TOOLS_ENABLED
4807
4808
if (Engine::get_singleton()->is_embedded_in_editor()) {
4809
embedder_socket_path = OS::get_singleton()->get_environment("GODOT_WAYLAND_DISPLAY");
4810
#if 0
4811
// Debug
4812
OS::get_singleton()->set_environment("WAYLAND_DEBUG", "1");
4813
int fd = open("/tmp/gdembedded.log", O_CREAT | O_RDWR, 0666);
4814
dup2(fd, 1);
4815
dup2(fd, 2);
4816
#endif
4817
}
4818
4819
if (embedder_socket_path.is_empty()) {
4820
print_verbose("Connecting to the default Wayland display.");
4821
wl_display = wl_display_connect(nullptr);
4822
} else {
4823
print_verbose("Connecting to the Wayland embedder display.");
4824
wl_display = wl_display_connect(embedder_socket_path.utf8().get_data());
4825
}
4826
4827
ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display.");
4828
4829
thread_data.wl_display = wl_display;
4830
4831
wl_registry = wl_display_get_registry(wl_display);
4832
4833
ERR_FAIL_NULL_V_MSG(wl_registry, ERR_UNAVAILABLE, "Can't obtain the Wayland registry global.");
4834
4835
registry.wayland_thread = this;
4836
4837
wl_registry_add_listener(wl_registry, &wl_registry_listener, &registry);
4838
4839
// Wait for registry to get notified from the compositor.
4840
wl_display_roundtrip(wl_display);
4841
4842
ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global.");
4843
ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global.");
4844
ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global.");
4845
4846
// Embedded games can't access the decoration and icon protocol.
4847
if (!Engine::get_singleton()->is_embedded_in_editor()) {
4848
if (!registry.xdg_decoration_manager) {
4849
#ifdef LIBDECOR_ENABLED
4850
WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available.");
4851
#else
4852
WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up.");
4853
#endif // LIBDECOR_ENABLED
4854
}
4855
4856
if (!registry.xdg_toplevel_icon_manager_name) {
4857
WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon.");
4858
}
4859
}
4860
4861
if (!registry.xdg_activation) {
4862
WARN_PRINT("Can't obtain the XDG activation global. Attention requesting won't work!");
4863
}
4864
4865
#ifndef DBUS_ENABLED
4866
if (!registry.wp_idle_inhibit_manager) {
4867
WARN_PRINT("Can't obtain the idle inhibition manager. The screen might turn off even after calling screen_set_keep_on()!");
4868
}
4869
#endif // DBUS_ENABLED
4870
4871
if (!registry.wp_fifo_manager_name) {
4872
WARN_PRINT("FIFO protocol not found! Frame pacing will be degraded.");
4873
}
4874
4875
// Wait for seat capabilities.
4876
wl_display_roundtrip(wl_display);
4877
4878
#ifdef LIBDECOR_ENABLED
4879
bool libdecor_found = true;
4880
4881
bool skip_libdecor = OS::get_singleton()->get_environment("GODOT_WAYLAND_DISABLE_LIBDECOR") == "1";
4882
4883
#ifdef SOWRAP_ENABLED
4884
if (!skip_libdecor && initialize_libdecor(dylibloader_verbose) != 0) {
4885
libdecor_found = false;
4886
}
4887
#endif // SOWRAP_ENABLED
4888
4889
if (skip_libdecor) {
4890
print_verbose("Skipping libdecor check because GODOT_WAYLAND_DISABLE_LIBDECOR is set to 1.");
4891
} else {
4892
if (libdecor_found) {
4893
libdecor_context = libdecor_new(wl_display, (struct libdecor_interface *)&libdecor_interface);
4894
} else {
4895
print_verbose("libdecor not found. Client-side decorations disabled.");
4896
}
4897
}
4898
#endif // LIBDECOR_ENABLED
4899
4900
cursor_theme_name = OS::get_singleton()->get_environment("XCURSOR_THEME");
4901
4902
unscaled_cursor_size = OS::get_singleton()->get_environment("XCURSOR_SIZE").to_int();
4903
if (unscaled_cursor_size <= 0) {
4904
print_verbose("Detected invalid cursor size preference, defaulting to 24.");
4905
unscaled_cursor_size = 24;
4906
}
4907
4908
// NOTE: The scale is useful here as it might've been updated by _update_scale.
4909
bool cursor_theme_loaded = _load_cursor_theme(unscaled_cursor_size * cursor_scale);
4910
4911
if (!cursor_theme_loaded) {
4912
return ERR_CANT_CREATE;
4913
}
4914
4915
// Update the cursor.
4916
cursor_set_shape(DisplayServer::CURSOR_ARROW);
4917
4918
events_thread.start(_poll_events_thread, &thread_data);
4919
4920
initialized = true;
4921
return OK;
4922
}
4923
4924
void WaylandThread::cursor_set_visible(bool p_visible) {
4925
cursor_visible = p_visible;
4926
4927
for (struct wl_seat *wl_seat : registry.wl_seats) {
4928
SeatState *ss = wl_seat_get_seat_state(wl_seat);
4929
ERR_FAIL_NULL(ss);
4930
4931
seat_state_update_cursor(ss);
4932
}
4933
}
4934
4935
void WaylandThread::cursor_set_shape(DisplayServer::CursorShape p_cursor_shape) {
4936
cursor_shape = p_cursor_shape;
4937
4938
for (struct wl_seat *wl_seat : registry.wl_seats) {
4939
SeatState *ss = wl_seat_get_seat_state(wl_seat);
4940
ERR_FAIL_NULL(ss);
4941
4942
seat_state_update_cursor(ss);
4943
}
4944
}
4945
4946
void WaylandThread::cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot) {
4947
ERR_FAIL_COND(p_image.is_null());
4948
4949
Size2i image_size = p_image->get_size();
4950
4951
// NOTE: The stride is the width of the image in bytes.
4952
unsigned int image_stride = image_size.width * 4;
4953
unsigned int data_size = image_stride * image_size.height;
4954
4955
// We need a shared memory object file descriptor in order to create a
4956
// wl_buffer through wl_shm.
4957
int fd = WaylandThread::_allocate_shm_file(data_size);
4958
ERR_FAIL_COND(fd == -1);
4959
4960
CustomCursor &cursor = custom_cursors[p_cursor_shape];
4961
cursor.hotspot = p_hotspot;
4962
4963
if (cursor.wl_buffer) {
4964
// Clean up the old Wayland buffer.
4965
wl_buffer_destroy(cursor.wl_buffer);
4966
}
4967
4968
if (cursor.buffer_data) {
4969
// Clean up the old buffer data.
4970
munmap(cursor.buffer_data, cursor.buffer_data_size);
4971
}
4972
4973
cursor.buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
4974
cursor.buffer_data_size = data_size;
4975
4976
// Create the Wayland buffer.
4977
struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(registry.wl_shm, fd, data_size);
4978
// TODO: Make sure that WL_SHM_FORMAT_ARGB8888 format is supported. It
4979
// technically isn't garaunteed to be supported, but I think that'd be a
4980
// pretty unlikely thing to stumble upon.
4981
cursor.wl_buffer = wl_shm_pool_create_buffer(wl_shm_pool, 0, image_size.width, image_size.height, image_stride, WL_SHM_FORMAT_ARGB8888);
4982
wl_shm_pool_destroy(wl_shm_pool);
4983
4984
// Fill the cursor buffer with the image data.
4985
for (unsigned int index = 0; index < (unsigned int)(image_size.width * image_size.height); index++) {
4986
int row_index = std::floor(index / image_size.width);
4987
int column_index = (index % int(image_size.width));
4988
4989
cursor.buffer_data[index] = p_image->get_pixel(column_index, row_index).to_argb32();
4990
4991
// Wayland buffers, unless specified, require associated alpha, so we'll just
4992
// associate the alpha in-place.
4993
uint8_t *pixel_data = (uint8_t *)&cursor.buffer_data[index];
4994
pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;
4995
pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;
4996
pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;
4997
}
4998
}
4999
5000
void WaylandThread::cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape) {
5001
if (custom_cursors.has(p_cursor_shape)) {
5002
CustomCursor cursor = custom_cursors[p_cursor_shape];
5003
custom_cursors.erase(p_cursor_shape);
5004
5005
if (cursor.wl_buffer) {
5006
wl_buffer_destroy(cursor.wl_buffer);
5007
}
5008
5009
if (cursor.buffer_data) {
5010
munmap(cursor.buffer_data, cursor.buffer_data_size);
5011
}
5012
}
5013
}
5014
5015
void WaylandThread::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {
5016
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5017
5018
if (ss && ss->wp_text_input && ss->ime_enabled) {
5019
if (p_active) {
5020
ss->ime_active = true;
5021
zwp_text_input_v3_enable(ss->wp_text_input);
5022
zwp_text_input_v3_set_cursor_rectangle(ss->wp_text_input, ss->ime_rect.position.x, ss->ime_rect.position.y, ss->ime_rect.size.x, ss->ime_rect.size.y);
5023
} else {
5024
ss->ime_active = false;
5025
ss->ime_text = String();
5026
ss->ime_text_commit = String();
5027
ss->ime_cursor = Vector2i();
5028
zwp_text_input_v3_disable(ss->wp_text_input);
5029
}
5030
zwp_text_input_v3_commit(ss->wp_text_input);
5031
}
5032
}
5033
5034
void WaylandThread::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {
5035
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5036
5037
if (ss && ss->wp_text_input && ss->ime_enabled) {
5038
ss->ime_rect = Rect2i(p_pos, Size2i(1, 10));
5039
zwp_text_input_v3_set_cursor_rectangle(ss->wp_text_input, ss->ime_rect.position.x, ss->ime_rect.position.y, ss->ime_rect.size.x, ss->ime_rect.size.y);
5040
zwp_text_input_v3_commit(ss->wp_text_input);
5041
}
5042
}
5043
5044
int WaylandThread::keyboard_get_layout_count() const {
5045
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5046
5047
if (ss && ss->xkb_keymap) {
5048
return xkb_keymap_num_layouts(ss->xkb_keymap);
5049
}
5050
5051
return 0;
5052
}
5053
5054
int WaylandThread::keyboard_get_current_layout_index() const {
5055
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5056
5057
if (ss) {
5058
return ss->current_layout_index;
5059
}
5060
5061
return 0;
5062
}
5063
5064
void WaylandThread::keyboard_set_current_layout_index(int p_index) {
5065
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5066
5067
if (ss) {
5068
ss->current_layout_index = p_index;
5069
}
5070
}
5071
5072
String WaylandThread::keyboard_get_layout_name(int p_index) const {
5073
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5074
5075
if (ss && ss->xkb_keymap) {
5076
return String::utf8(xkb_keymap_layout_get_name(ss->xkb_keymap, p_index));
5077
}
5078
5079
return "";
5080
}
5081
5082
Key WaylandThread::keyboard_get_key_from_physical(Key p_key) const {
5083
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5084
5085
if (ss && ss->xkb_state) {
5086
Key modifiers = p_key & KeyModifierMask::MODIFIER_MASK;
5087
Key keycode_no_mod = p_key & KeyModifierMask::CODE_MASK;
5088
5089
xkb_keycode_t xkb_keycode = KeyMappingXKB::get_xkb_keycode(keycode_no_mod);
5090
Key key = KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(ss->xkb_state, xkb_keycode));
5091
return (Key)(key | modifiers);
5092
}
5093
5094
return p_key;
5095
}
5096
5097
Key WaylandThread::keyboard_get_label_from_physical(Key p_key) const {
5098
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5099
5100
if (ss && ss->xkb_state) {
5101
Key modifiers = p_key & KeyModifierMask::MODIFIER_MASK;
5102
Key keycode_no_mod = p_key & KeyModifierMask::CODE_MASK;
5103
5104
xkb_keycode_t xkb_keycode = KeyMappingXKB::get_xkb_keycode(keycode_no_mod);
5105
xkb_keycode_t xkb_keysym = xkb_state_key_get_one_sym(ss->xkb_state, xkb_keycode);
5106
char32_t chr = xkb_keysym_to_utf32(xkb_keysym_to_upper(xkb_keysym));
5107
if (chr != 0) {
5108
String keysym = String::chr(chr);
5109
Key key = fix_key_label(keysym[0], KeyMappingXKB::get_keycode(xkb_keysym));
5110
return (Key)(key | modifiers);
5111
}
5112
}
5113
5114
return p_key;
5115
}
5116
5117
void WaylandThread::keyboard_echo_keys() {
5118
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5119
5120
if (ss) {
5121
seat_state_echo_keys(ss);
5122
}
5123
}
5124
5125
void WaylandThread::selection_set_text(const String &p_text) {
5126
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5127
5128
if (registry.wl_data_device_manager == nullptr) {
5129
DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, wl_data_device_manager global not available.");
5130
return;
5131
}
5132
5133
if (ss == nullptr) {
5134
DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, current seat not set.");
5135
return;
5136
}
5137
5138
if (ss->wl_data_device == nullptr) {
5139
DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, seat doesn't have wl_data_device.");
5140
return;
5141
}
5142
5143
ss->selection_data = p_text.to_utf8_buffer();
5144
5145
if (ss->wl_data_source_selection == nullptr) {
5146
ss->wl_data_source_selection = wl_data_device_manager_create_data_source(registry.wl_data_device_manager);
5147
wl_data_source_add_listener(ss->wl_data_source_selection, &wl_data_source_listener, ss);
5148
wl_data_source_offer(ss->wl_data_source_selection, "text/plain;charset=utf-8");
5149
wl_data_source_offer(ss->wl_data_source_selection, "text/plain");
5150
5151
// TODO: Implement a good way of getting the latest serial from the user.
5152
wl_data_device_set_selection(ss->wl_data_device, ss->wl_data_source_selection, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));
5153
}
5154
5155
// Wait for the message to get to the server before continuing, otherwise the
5156
// clipboard update might come with a delay.
5157
wl_display_roundtrip(wl_display);
5158
}
5159
5160
bool WaylandThread::selection_has_mime(const String &p_mime) const {
5161
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5162
5163
if (ss == nullptr) {
5164
DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
5165
return false;
5166
}
5167
5168
OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);
5169
if (!os) {
5170
return false;
5171
}
5172
5173
return os->mime_types.has(p_mime);
5174
}
5175
5176
Vector<uint8_t> WaylandThread::selection_get_mime(const String &p_mime) const {
5177
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5178
if (ss == nullptr) {
5179
DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
5180
return Vector<uint8_t>();
5181
}
5182
5183
if (ss->wl_data_source_selection) {
5184
// We have a source so the stuff we're pasting is ours. We'll have to pass the
5185
// data directly or we'd stall waiting for Godot (ourselves) to send us the
5186
// data :P
5187
5188
OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);
5189
ERR_FAIL_NULL_V(os, Vector<uint8_t>());
5190
5191
if (os->mime_types.has(p_mime)) {
5192
// All righty, we're offering this type. Let's just return the data as is.
5193
return ss->selection_data;
5194
}
5195
5196
// ... we don't offer that type. Oh well.
5197
return Vector<uint8_t>();
5198
}
5199
5200
return _wl_data_offer_read(wl_display, p_mime.utf8().get_data(), ss->wl_data_offer_selection);
5201
}
5202
5203
bool WaylandThread::primary_has_mime(const String &p_mime) const {
5204
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5205
5206
if (ss == nullptr) {
5207
DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
5208
return false;
5209
}
5210
5211
OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);
5212
if (!os) {
5213
return false;
5214
}
5215
5216
return os->mime_types.has(p_mime);
5217
}
5218
5219
Vector<uint8_t> WaylandThread::primary_get_mime(const String &p_mime) const {
5220
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5221
if (ss == nullptr) {
5222
DEBUG_LOG_WAYLAND_THREAD("Couldn't get primary, current seat not set.");
5223
return Vector<uint8_t>();
5224
}
5225
5226
if (ss->wp_primary_selection_source) {
5227
// We have a source so the stuff we're pasting is ours. We'll have to pass the
5228
// data directly or we'd stall waiting for Godot (ourselves) to send us the
5229
// data :P
5230
5231
OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);
5232
ERR_FAIL_NULL_V(os, Vector<uint8_t>());
5233
5234
if (os->mime_types.has(p_mime)) {
5235
// All righty, we're offering this type. Let's just return the data as is.
5236
return ss->selection_data;
5237
}
5238
5239
// ... we don't offer that type. Oh well.
5240
return Vector<uint8_t>();
5241
}
5242
5243
return _wp_primary_selection_offer_read(wl_display, p_mime.utf8().get_data(), ss->wp_primary_selection_offer);
5244
}
5245
5246
void WaylandThread::primary_set_text(const String &p_text) {
5247
SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
5248
5249
if (registry.wp_primary_selection_device_manager == nullptr) {
5250
DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available.");
5251
return;
5252
}
5253
5254
if (ss == nullptr) {
5255
DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, current seat not set.");
5256
return;
5257
}
5258
5259
if (ss->wp_primary_selection_device == nullptr) {
5260
DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary selection, seat doesn't have wp_primary_selection_device.");
5261
return;
5262
}
5263
5264
ss->primary_data = p_text.to_utf8_buffer();
5265
5266
if (ss->wp_primary_selection_source == nullptr) {
5267
ss->wp_primary_selection_source = zwp_primary_selection_device_manager_v1_create_source(registry.wp_primary_selection_device_manager);
5268
zwp_primary_selection_source_v1_add_listener(ss->wp_primary_selection_source, &wp_primary_selection_source_listener, ss);
5269
zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain;charset=utf-8");
5270
zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain");
5271
5272
// TODO: Implement a good way of getting the latest serial from the user.
5273
zwp_primary_selection_device_v1_set_selection(ss->wp_primary_selection_device, ss->wp_primary_selection_source, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));
5274
}
5275
5276
// Wait for the message to get to the server before continuing, otherwise the
5277
// clipboard update might come with a delay.
5278
wl_display_roundtrip(wl_display);
5279
}
5280
5281
void WaylandThread::commit_surfaces() {
5282
for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {
5283
wl_surface_commit(pair.value.wl_surface);
5284
}
5285
}
5286
5287
void WaylandThread::set_frame() {
5288
frame = true;
5289
}
5290
5291
bool WaylandThread::get_reset_frame() {
5292
bool old_frame = frame;
5293
frame = false;
5294
5295
return old_frame;
5296
}
5297
5298
// Dispatches events until a frame event is received, a window is reported as
5299
// suspended or the timeout expires.
5300
bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
5301
// This is a bit of a chicken and egg thing... Looks like the main event loop
5302
// has to call its rightfully forever-blocking poll right in between
5303
// `wl_display_prepare_read` and `wl_display_read`. This means, that it will
5304
// basically be guaranteed to stay stuck in a "prepare read" state, where it
5305
// will block any other attempt at reading the display fd, such as ours. The
5306
// solution? Let's make sure the mutex is locked (it should) and unblock the
5307
// main thread with a roundtrip!
5308
MutexLock mutex_lock(mutex);
5309
wl_display_roundtrip(wl_display);
5310
5311
if (is_suspended()) {
5312
// All windows are suspended! The compositor is telling us _explicitly_ that
5313
// we don't need to draw, without letting us guess through the frame event's
5314
// timing and stuff like that. Our job here is done.
5315
return false;
5316
}
5317
5318
if (frame) {
5319
// We already have a frame! Probably it got there while the caller locked :D
5320
frame = false;
5321
return true;
5322
}
5323
5324
struct pollfd poll_fd;
5325
poll_fd.fd = wl_display_get_fd(wl_display);
5326
poll_fd.events = POLLIN | POLLHUP;
5327
5328
int begin_ms = OS::get_singleton()->get_ticks_msec();
5329
int remaining_ms = p_timeout;
5330
5331
while (remaining_ms > 0) {
5332
// Empty the event queue while it's full.
5333
while (wl_display_prepare_read(wl_display) != 0) {
5334
if (wl_display_dispatch_pending(wl_display) == -1) {
5335
// Oh no. We'll check and handle any display error below.
5336
break;
5337
}
5338
5339
if (is_suspended()) {
5340
return false;
5341
}
5342
5343
if (frame) {
5344
// We had a frame event in the queue :D
5345
frame = false;
5346
return true;
5347
}
5348
}
5349
5350
int werror = wl_display_get_error(wl_display);
5351
5352
if (werror) {
5353
if (werror == EPROTO) {
5354
struct wl_interface *wl_interface = nullptr;
5355
uint32_t id = 0;
5356
5357
int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id);
5358
CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
5359
} else {
5360
CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
5361
}
5362
}
5363
5364
wl_display_flush(wl_display);
5365
5366
// Wait for the event file descriptor to have new data.
5367
poll(&poll_fd, 1, remaining_ms);
5368
5369
if (poll_fd.revents | POLLIN) {
5370
// Load the queues with fresh new data.
5371
wl_display_read_events(wl_display);
5372
} else {
5373
// Oh well... Stop signaling that we want to read.
5374
wl_display_cancel_read(wl_display);
5375
5376
// We've got no new events :(
5377
// We won't even bother with checking the frame flag.
5378
return false;
5379
}
5380
5381
// Let's try dispatching now...
5382
wl_display_dispatch_pending(wl_display);
5383
5384
if (is_suspended()) {
5385
return false;
5386
}
5387
5388
if (frame) {
5389
frame = false;
5390
return true;
5391
}
5392
5393
remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms;
5394
}
5395
5396
DEBUG_LOG_WAYLAND_THREAD("Frame timeout.");
5397
return false;
5398
}
5399
5400
uint64_t WaylandThread::window_get_last_frame_time(DisplayServer::WindowID p_window_id) const {
5401
ERR_FAIL_COND_V(!windows.has(p_window_id), false);
5402
return windows[p_window_id].last_frame_time;
5403
}
5404
5405
bool WaylandThread::window_is_suspended(DisplayServer::WindowID p_window_id) const {
5406
ERR_FAIL_COND_V(!windows.has(p_window_id), false);
5407
return windows[p_window_id].suspended;
5408
}
5409
5410
bool WaylandThread::is_fifo_available() const {
5411
return registry.wp_fifo_manager_name != 0;
5412
}
5413
5414
bool WaylandThread::is_suspended() const {
5415
for (const KeyValue<DisplayServer::WindowID, WindowState> &E : windows) {
5416
if (!E.value.suspended) {
5417
return false;
5418
}
5419
}
5420
5421
return true;
5422
}
5423
5424
struct godot_embedding_compositor *WaylandThread::get_embedding_compositor() {
5425
return registry.godot_embedding_compositor;
5426
}
5427
5428
OS::ProcessID WaylandThread::embedded_compositor_get_focused_pid() {
5429
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(registry.godot_embedding_compositor);
5430
ERR_FAIL_NULL_V(ecomp_state, -1);
5431
5432
return ecomp_state->focused_pid;
5433
}
5434
5435
void WaylandThread::destroy() {
5436
if (!initialized) {
5437
return;
5438
}
5439
5440
if (wl_display && events_thread.is_started()) {
5441
thread_data.thread_done.set();
5442
5443
// By sending a roundtrip message we're unblocking the polling thread so that
5444
// it can realize that it's done and also handle every event that's left.
5445
wl_display_roundtrip(wl_display);
5446
5447
events_thread.wait_to_finish();
5448
}
5449
5450
for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {
5451
WindowState &ws = pair.value;
5452
if (ws.wp_fractional_scale) {
5453
wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);
5454
}
5455
5456
if (ws.wp_viewport) {
5457
wp_viewport_destroy(ws.wp_viewport);
5458
}
5459
5460
if (ws.frame_callback) {
5461
wl_callback_destroy(ws.frame_callback);
5462
}
5463
5464
#ifdef LIBDECOR_ENABLED
5465
if (ws.libdecor_frame) {
5466
libdecor_frame_close(ws.libdecor_frame);
5467
}
5468
#endif // LIBDECOR_ENABLED
5469
5470
if (ws.xdg_toplevel_decoration) {
5471
zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration);
5472
}
5473
5474
if (ws.xdg_toplevel) {
5475
xdg_toplevel_destroy(ws.xdg_toplevel);
5476
}
5477
5478
if (ws.xdg_surface) {
5479
xdg_surface_destroy(ws.xdg_surface);
5480
}
5481
5482
if (ws.wl_surface) {
5483
wl_surface_destroy(ws.wl_surface);
5484
}
5485
}
5486
5487
for (struct wl_seat *wl_seat : registry.wl_seats) {
5488
SeatState *ss = wl_seat_get_seat_state(wl_seat);
5489
ERR_FAIL_NULL(ss);
5490
5491
wl_seat_destroy(wl_seat);
5492
5493
xkb_context_unref(ss->xkb_context);
5494
xkb_state_unref(ss->xkb_state);
5495
xkb_keymap_unref(ss->xkb_keymap);
5496
xkb_compose_table_unref(ss->xkb_compose_table);
5497
xkb_compose_state_unref(ss->xkb_compose_state);
5498
5499
if (ss->wl_keyboard) {
5500
wl_keyboard_destroy(ss->wl_keyboard);
5501
}
5502
5503
if (ss->keymap_buffer) {
5504
munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);
5505
}
5506
5507
if (ss->wl_pointer) {
5508
wl_pointer_destroy(ss->wl_pointer);
5509
}
5510
5511
if (ss->cursor_frame_callback) {
5512
// We don't need to set a null userdata for safety as the thread is done.
5513
wl_callback_destroy(ss->cursor_frame_callback);
5514
}
5515
5516
if (ss->cursor_surface) {
5517
wl_surface_destroy(ss->cursor_surface);
5518
}
5519
5520
if (ss->wl_data_device) {
5521
wl_data_device_destroy(ss->wl_data_device);
5522
}
5523
5524
if (ss->wp_cursor_shape_device) {
5525
wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);
5526
}
5527
5528
if (ss->wp_relative_pointer) {
5529
zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
5530
}
5531
5532
if (ss->wp_locked_pointer) {
5533
zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
5534
}
5535
5536
if (ss->wp_confined_pointer) {
5537
zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
5538
}
5539
5540
if (ss->wp_tablet_seat) {
5541
zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
5542
}
5543
5544
for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
5545
TabletToolState *state = wp_tablet_tool_get_state(tool);
5546
if (state) {
5547
memdelete(state);
5548
}
5549
5550
zwp_tablet_tool_v2_destroy(tool);
5551
}
5552
5553
if (ss->wp_text_input) {
5554
zwp_text_input_v3_destroy(ss->wp_text_input);
5555
}
5556
5557
memdelete(ss);
5558
}
5559
5560
if (registry.wp_tablet_manager) {
5561
zwp_tablet_manager_v2_destroy(registry.wp_tablet_manager);
5562
}
5563
5564
if (registry.wp_text_input_manager) {
5565
zwp_text_input_manager_v3_destroy(registry.wp_text_input_manager);
5566
}
5567
5568
for (struct wl_output *wl_output : registry.wl_outputs) {
5569
ERR_FAIL_NULL(wl_output);
5570
5571
memdelete(wl_output_get_screen_state(wl_output));
5572
wl_output_destroy(wl_output);
5573
}
5574
5575
if (registry.godot_embedding_compositor) {
5576
EmbeddingCompositorState *es = godot_embedding_compositor_get_state(registry.godot_embedding_compositor);
5577
ERR_FAIL_NULL(es);
5578
5579
es->mapped_clients.clear();
5580
5581
for (struct godot_embedded_client *client : es->clients) {
5582
godot_embedded_client_destroy(client);
5583
}
5584
es->clients.clear();
5585
5586
memdelete(es);
5587
5588
godot_embedding_compositor_destroy(registry.godot_embedding_compositor);
5589
}
5590
5591
if (wl_cursor_theme) {
5592
wl_cursor_theme_destroy(wl_cursor_theme);
5593
}
5594
5595
if (registry.wp_idle_inhibit_manager) {
5596
zwp_idle_inhibit_manager_v1_destroy(registry.wp_idle_inhibit_manager);
5597
}
5598
5599
if (registry.wp_pointer_constraints) {
5600
zwp_pointer_constraints_v1_destroy(registry.wp_pointer_constraints);
5601
}
5602
5603
if (registry.wp_pointer_gestures) {
5604
zwp_pointer_gestures_v1_destroy(registry.wp_pointer_gestures);
5605
}
5606
5607
if (registry.wp_relative_pointer_manager) {
5608
zwp_relative_pointer_manager_v1_destroy(registry.wp_relative_pointer_manager);
5609
}
5610
5611
if (registry.xdg_activation) {
5612
xdg_activation_v1_destroy(registry.xdg_activation);
5613
}
5614
5615
if (registry.xdg_system_bell) {
5616
xdg_system_bell_v1_destroy(registry.xdg_system_bell);
5617
}
5618
5619
if (registry.xdg_toplevel_icon_manager) {
5620
xdg_toplevel_icon_manager_v1_destroy(registry.xdg_toplevel_icon_manager);
5621
5622
if (xdg_icon) {
5623
xdg_toplevel_icon_v1_destroy(xdg_icon);
5624
}
5625
5626
if (icon_buffer) {
5627
wl_buffer_destroy(icon_buffer);
5628
}
5629
}
5630
5631
if (registry.xdg_decoration_manager) {
5632
zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager);
5633
}
5634
5635
if (registry.wp_cursor_shape_manager) {
5636
wp_cursor_shape_manager_v1_destroy(registry.wp_cursor_shape_manager);
5637
}
5638
5639
if (registry.wp_fractional_scale_manager) {
5640
wp_fractional_scale_manager_v1_destroy(registry.wp_fractional_scale_manager);
5641
}
5642
5643
if (registry.wp_viewporter) {
5644
wp_viewporter_destroy(registry.wp_viewporter);
5645
}
5646
5647
if (registry.xdg_wm_base) {
5648
xdg_wm_base_destroy(registry.xdg_wm_base);
5649
}
5650
5651
// NOTE: Deprecated.
5652
if (registry.xdg_exporter_v1) {
5653
zxdg_exporter_v1_destroy(registry.xdg_exporter_v1);
5654
}
5655
5656
if (registry.xdg_exporter_v2) {
5657
zxdg_exporter_v2_destroy(registry.xdg_exporter_v2);
5658
}
5659
if (registry.wl_shm) {
5660
wl_shm_destroy(registry.wl_shm);
5661
}
5662
5663
if (registry.wl_compositor) {
5664
wl_compositor_destroy(registry.wl_compositor);
5665
}
5666
5667
if (wl_registry) {
5668
wl_registry_destroy(wl_registry);
5669
}
5670
5671
wl_display_roundtrip(wl_display);
5672
5673
if (wl_display) {
5674
wl_display_disconnect(wl_display);
5675
}
5676
}
5677
5678
#endif // WAYLAND_ENABLED
5679
5680