Blender V4.5
GHOST_WindowWayland.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
11#include "GHOST_WaylandUtils.hh"
13#include "GHOST_utildefines.hh"
14
15#include "GHOST_Event.hh"
16
17#include "GHOST_ContextNone.hh"
18#ifdef WITH_OPENGL_BACKEND
19# include "GHOST_ContextEGL.hh"
20#endif
21#ifdef WITH_VULKAN_BACKEND
22# include "GHOST_ContextVK.hh"
23#endif
24
25#include <wayland-client-protocol.h>
26
27#ifdef WITH_OPENGL_BACKEND
28# ifdef WITH_GHOST_WAYLAND_DYNLOAD
29# include <wayland_dynload_egl.h>
30# endif
31# include <wayland-egl.h>
32#endif
33
34#include <algorithm> /* For `std::find`. */
35
36#ifdef WITH_GHOST_WAYLAND_LIBDECOR
37# ifdef WITH_GHOST_WAYLAND_DYNLOAD
39# endif
40# include <libdecor.h>
41#endif
42
43/* Generated by `wayland-scanner`. */
44#include <fractional-scale-v1-client-protocol.h>
45#include <viewporter-client-protocol.h>
46#include <xdg-activation-v1-client-protocol.h>
47#include <xdg-decoration-unstable-v1-client-protocol.h>
48#include <xdg-shell-client-protocol.h>
49
50#include <atomic>
51#include <optional>
52
53#include <cstring> /* For `memcpy`. */
54#include <malloc.h> /* For `malloc_usable_size`. */
55
56/* Logging, use `ghost.wl.*` prefix. */
57#include "CLG_log.h"
58
59/* Disable, as this can "lock" the GUI even with the *pending* version of dispatch is used. */
60#if 0
75# define USE_CURSOR_IMMEDIATE_DISPATCH
76#endif
77
90#ifdef USE_EVENT_BACKGROUND_THREAD
91# ifdef WITH_GHOST_WAYLAND_LIBDECOR
92# ifdef HAVE_MALLOC_USABLE_SIZE
93# define USE_LIBDECOR_CONFIG_COPY_WORKAROUND
94# endif
95# endif
96#endif
97
103#ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
104# define USE_LIBDECOR_CONFIG_COPY_QUEUE
105#endif
106
107#ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
108static libdecor_configuration *ghost_wl_libdecor_configuration_copy(
109 const libdecor_configuration *configuration);
110static void ghost_wl_libdecor_configuration_free(libdecor_configuration *configuration);
111#endif
112
113static const xdg_activation_token_v1_listener *xdg_activation_listener_get();
114
115static constexpr size_t base_dpi = 96;
116
117#ifdef WITH_GHOST_WAYLAND_LIBDECOR
118/* Access `use_libdecor` in #GHOST_SystemWayland. */
119# define use_libdecor GHOST_SystemWayland::use_libdecor_runtime()
120#endif
121
122#ifdef WITH_GHOST_WAYLAND_LIBDECOR
123struct GWL_LibDecor_Window {
124 libdecor_frame *frame = nullptr;
125
130 struct {
132 bool ack_configure = false;
134 int size[2] = {0, 0};
135 libdecor_configuration *configuration = nullptr;
136
137# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
138 bool configuration_needs_free = false;
139# endif
140
141# ifdef USE_LIBDECOR_CONFIG_COPY_QUEUE
146 std::vector<libdecor_configuration *> configuration_queue;
147# endif
148
149 } pending;
150
152 bool initial_configure_seen = false;
153
154 std::optional<GHOST_TWindowState> initial_configure_state = std::nullopt;
155};
156
157static void gwl_libdecor_window_destroy(GWL_LibDecor_Window *decor)
158{
159# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
160 if (decor->pending.configuration_needs_free) {
161 ghost_wl_libdecor_configuration_free(decor->pending.configuration);
162 }
163
164# endif /* USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
165
166# ifdef USE_LIBDECOR_CONFIG_COPY_QUEUE
167 for (libdecor_configuration *configuration : decor->pending.configuration_queue) {
168 ghost_wl_libdecor_configuration_free(configuration);
169 }
170 decor->pending.configuration_queue.clear();
171# endif /* USE_LIBDECOR_CONFIG_COPY_QUEUE */
172
173 libdecor_frame_unref(decor->frame);
174 delete decor;
175}
176#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
177
179 xdg_surface *surface = nullptr;
180 zxdg_toplevel_decoration_v1 *toplevel_decor = nullptr;
181 xdg_toplevel *toplevel = nullptr;
182 enum zxdg_toplevel_decoration_v1_mode mode = (enum zxdg_toplevel_decoration_v1_mode)0;
183
188 struct {
190 bool ack_configure = false;
194
198 int initial_bounds[2] = {0, 0};
199};
200
202{
203 if (decor->toplevel_decor) {
204 zxdg_toplevel_decoration_v1_destroy(decor->toplevel_decor);
205 }
206 xdg_toplevel_destroy(decor->toplevel);
207 xdg_surface_destroy(decor->surface);
208 delete decor;
209}
210
211/* -------------------------------------------------------------------- */
214
215static void gwl_round_int_by(int *value_p, const int round_value)
216{
217 GHOST_ASSERT(round_value > 0, "Invalid rounding value!");
218 *value_p = (*value_p / round_value) * round_value;
219}
220
221static void gwl_round_int2_by(int value_p[2], const int round_value)
222{
223 GHOST_ASSERT(round_value > 0, "Invalid rounding value!");
224 value_p[0] = (value_p[0] / round_value) * round_value;
225 value_p[1] = (value_p[1] / round_value) * round_value;
226}
227
231static bool gwl_round_int_test(int value, const int round_value)
232{
233 return value == ((value / round_value) * round_value);
234}
235
237
238/* -------------------------------------------------------------------- */
241
243 bool is_fractional = false;
249 wl_fixed_t scale = 0;
250};
251
253 wl_fixed_t value)
254{
255 if (scale_params.is_fractional) {
256 return (value * scale_params.scale) / FRACTIONAL_DENOMINATOR;
257 }
258 return value * scale_params.scale;
259}
261 wl_fixed_t value)
262{
263 if (scale_params.is_fractional) {
264 return (value * FRACTIONAL_DENOMINATOR) / scale_params.scale;
265 }
266 return value / scale_params.scale;
267}
268
269int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value)
270{
271 return wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, wl_fixed_from_int(value)));
272}
273int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value)
274{
275 return wl_fixed_to_int(gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(value)));
276}
277
279
280/* -------------------------------------------------------------------- */
283
285 uint8_t *bitmap = nullptr;
286 uint8_t *mask = nullptr;
287 int32_t hot_spot[2] = {0, 0};
288 int32_t size[2] = {0, 0};
289 bool can_invert_color = false;
290};
291
293{
294 if (ccs.bitmap) {
295 free(ccs.bitmap);
296 }
297 if (ccs.mask) {
298 free(ccs.mask);
299 }
300}
301
307
309 const uint8_t *bitmap,
310 const uint8_t *mask,
311 const int32_t size[2],
312 const int32_t hot_spot[2],
313 bool can_invert_color)
314{
316 /* The width is divided by 8, rounding up. */
317 const size_t bitmap_size = sizeof(uint8_t) * ((size[0] + 7) / 8) * size[1];
318
319 if (bitmap) {
320 ccs.bitmap = static_cast<uint8_t *>(malloc(bitmap_size));
321 memcpy(ccs.bitmap, bitmap, bitmap_size);
322 }
323 if (mask) {
324 ccs.mask = static_cast<uint8_t *>(malloc(bitmap_size));
325 memcpy(ccs.mask, mask, bitmap_size);
326 }
327
328 ccs.size[0] = size[0];
329 ccs.size[1] = size[1];
330
331 ccs.hot_spot[0] = hot_spot[0];
332 ccs.hot_spot[1] = hot_spot[1];
333
334 ccs.can_invert_color = can_invert_color;
335}
336
338 GHOST_SystemWayland *system)
339{
340 return system->cursor_shape_custom_set(ccs.bitmap,
341 ccs.mask,
342 ccs.size[0],
343 ccs.size[1],
344 ccs.hot_spot[0],
345 ccs.hot_spot[1],
346 ccs.can_invert_color);
347}
348
351 GHOST_SystemWayland *system)
352{
353#ifdef USE_EVENT_BACKGROUND_THREAD
354 GHOST_ASSERT(system->main_thread_id == std::this_thread::get_id(), "Only from main thread!");
355#endif
356
357 if (shape == GHOST_kStandardCursorCustom) {
358 const GHOST_TSuccess ok = gwl_window_cursor_custom_load(ccs, system);
359 if (ok == GHOST_kSuccess) {
360 return ok;
361 }
363 system->cursor_shape_set(shape);
364 return GHOST_kFailure;
365 }
366
367 return system->cursor_shape_set(shape);
368}
369
371
372/* -------------------------------------------------------------------- */
375
376#ifdef USE_EVENT_BACKGROUND_THREAD
377
413# define PENDING_NUM (PENDING_WINDOW_CURSOR_SHAPE_REFRESH + 1)
414
415#endif /* USE_EVENT_BACKGROUND_THREAD */
416
424 int32_t size[2] = {0, 0};
425 bool is_maximised = false;
426 bool is_fullscreen = false;
427 bool is_active = false;
437
439 bool is_scale_init = false;
440};
441
443
445 struct {
446 wl_surface *surface = nullptr;
447 } wl;
448
450 struct {
451 wp_viewport *viewport = nullptr;
452
457 wp_fractional_scale_v1 *fractional_scale_handle = nullptr;
458 } wp;
459
461 struct {
463 xdg_activation_token_v1 *activation_token = nullptr;
465
466 struct {
467#ifdef WITH_OPENGL_BACKEND
468 wl_egl_window *egl_window = nullptr;
469#endif
470#ifdef WITH_VULKAN_BACKEND
471 GHOST_ContextVK_WindowInfo *vulkan_window_info = nullptr;
472#endif
474
478
485 std::vector<GWL_Output *> outputs;
486
487#ifdef WITH_GHOST_WAYLAND_LIBDECOR
488 GWL_LibDecor_Window *libdecor = nullptr;
489#endif
491
498
499#ifdef USE_EVENT_BACKGROUND_THREAD
505#endif
506
508
509 std::string title;
510
511 bool is_dialog = false;
512
514 bool is_init = false;
515
517 bool is_valid_setup = false;
518
521
522#ifdef USE_EVENT_BACKGROUND_THREAD
527 std::atomic<bool> pending_actions[PENDING_NUM] = {false};
528#endif /* USE_EVENT_BACKGROUND_THREAD */
529};
530
532{
533#ifdef WITH_OPENGL_BACKEND
534 if (win->ghost_context_type == GHOST_kDrawingContextTypeOpenGL) {
535 /* Null on window initialization. */
536 if (win->backend.egl_window) {
537 wl_egl_window_resize(win->backend.egl_window, UNPACK2(size), 0, 0);
538 }
539 }
540#endif
541#ifdef WITH_VULKAN_BACKEND
542 if (win->ghost_context_type == GHOST_kDrawingContextTypeVulkan) {
543 win->backend.vulkan_window_info->size[0] = size[0];
544 win->backend.vulkan_window_info->size[1] = size[1];
545 }
546#endif
547}
548
549static void gwl_window_title_set(GWL_Window *win, const char *title)
550{
551#ifdef WITH_GHOST_WAYLAND_LIBDECOR
552 if (use_libdecor) {
553 GWL_LibDecor_Window &decor = *win->libdecor;
554 libdecor_frame_set_title(decor.frame, title);
555 }
556 else
557#endif
558 {
559 GWL_XDG_Decor_Window &decor = *win->xdg_decor;
560 xdg_toplevel_set_title(decor.toplevel, title);
561 }
562
563 win->title = title;
564}
565
567{
568 if (win->frame.is_fullscreen) {
570 }
571 if (win->frame.is_maximised) {
573 }
575}
576
577#ifdef WITH_GHOST_WAYLAND_LIBDECOR
581static bool gwl_window_state_set_for_libdecor(libdecor_frame *frame,
583 const GHOST_TWindowState state_current)
584{
585 switch (state) {
587 /* Unset states. */
588 switch (state_current) {
591 break;
592 }
595 break;
596 }
597 default: {
598 break;
599 }
600 }
601 break;
604 break;
605 }
608 break;
609 }
611 libdecor_frame_set_fullscreen(frame, nullptr);
612 break;
613 }
614 }
615 return true;
616}
617
618#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
619
623static bool gwl_window_state_set_for_xdg(xdg_toplevel *toplevel,
625 const GHOST_TWindowState state_current)
626{
627 switch (state) {
629 /* Unset states. */
630 switch (state_current) {
632 xdg_toplevel_unset_maximized(toplevel);
633 break;
634 }
636 xdg_toplevel_unset_fullscreen(toplevel);
637 break;
638 }
639 default: {
640 break;
641 }
642 }
643 break;
645 xdg_toplevel_set_maximized(toplevel);
646 break;
647 }
649 xdg_toplevel_set_minimized(toplevel);
650 break;
651 }
653 xdg_toplevel_set_fullscreen(toplevel, nullptr);
654 break;
655 }
656 }
657 return true;
658}
659
661{
662 const GHOST_TWindowState state_current = gwl_window_state_get(win);
663 bool result;
664#ifdef WITH_GHOST_WAYLAND_LIBDECOR
665 if (use_libdecor) {
666 result = gwl_window_state_set_for_libdecor(win->libdecor->frame, state, state_current);
667 }
668 else
669#endif
670 {
672 }
673 return result;
674}
675
677
678/* -------------------------------------------------------------------- */
684
689static int gwl_window_fractional_to_viewport(const GWL_WindowFrame &frame, int value)
690{
691 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
692 return (value * frame.fractional_scale) / FRACTIONAL_DENOMINATOR;
693}
694
699static int gwl_window_fractional_from_viewport(const GWL_WindowFrame &frame, int value)
700{
701 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
702 return (value * FRACTIONAL_DENOMINATOR) / frame.fractional_scale;
703}
704
705/* NOTE: rounded versions are needed for window-frame dimensions conversions.
706 * (rounding is part of the WAYLAND spec). All other conversions such as cursor coordinates
707 * can used simple integer division as rounding is not defined in this case. */
708
710{
711 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
712 return lroundf(double(value * frame.fractional_scale) / double(FRACTIONAL_DENOMINATOR));
713}
714
716{
717 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
718 return lroundf(double(value * FRACTIONAL_DENOMINATOR) / double(frame.fractional_scale));
719}
720
722 bool *r_surface_needs_commit,
723 bool *r_surface_needs_buffer_scale)
724{
725 if (win->wp.viewport != nullptr) {
726 return false;
727 }
728 wp_viewporter *viewporter = win->ghost_system->wp_viewporter_get();
729 if (viewporter == nullptr) {
730 return false;
731 }
732 win->wp.viewport = wp_viewporter_get_viewport(viewporter, win->wl.surface);
733 if (win->wp.viewport == nullptr) {
734 return false;
735 }
736
737 /* Set the buffer scale to 1 since a viewport will be used. */
738 if (win->frame.buffer_scale != 1) {
739 win->frame.buffer_scale = 1;
740
741 if (r_surface_needs_buffer_scale) {
742 *r_surface_needs_buffer_scale = true;
743 }
744 else {
745 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
746 }
747
748 if (r_surface_needs_commit) {
749 *r_surface_needs_commit = true;
750 }
751 else {
752 wl_surface_commit(win->wl.surface);
753 }
754 }
755
756 return true;
757}
758
760 bool *r_surface_needs_commit,
761 bool *r_surface_needs_buffer_scale)
762{
763 if (win->wp.viewport == nullptr) {
764 return false;
765 }
766
767 wp_viewport_destroy(win->wp.viewport);
768 win->wp.viewport = nullptr;
769
770 GHOST_ASSERT(win->frame.buffer_scale == 1, "Unexpected scale!");
771 if (win->frame_pending.buffer_scale != win->frame.buffer_scale) {
773
774 if (r_surface_needs_buffer_scale) {
775 *r_surface_needs_buffer_scale = true;
776 }
777 else {
778 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
779 }
780
781 if (r_surface_needs_commit) {
782 *r_surface_needs_commit = true;
783 }
784 else {
785 wl_surface_commit(win->wl.surface);
786 }
787 }
788 return true;
789}
790
792{
793 if (win->wp.viewport == nullptr) {
794 return false;
795 }
796
797 /* Setting `wp_viewport_set_source` isn't necessary as an unset value is ensured on creation
798 * and documented to use the entire buffer, further this can crash with NVIDIA, see: #117531. */
799
800 wp_viewport_set_destination(
801 win->wp.viewport,
804 return true;
805}
806
808
809/* -------------------------------------------------------------------- */
812
820{
821 GHOST_SystemWayland *system = win->ghost_system;
822 xdg_activation_v1 *activation_manager = system->xdg_activation_manager_get();
823 if (UNLIKELY(activation_manager == nullptr)) {
824 return;
825 }
826
827 if (win->xdg.activation_token) {
828 /* We're about to overwrite this with a new request. */
829 xdg_activation_token_v1_destroy(win->xdg.activation_token);
830 }
831 win->xdg.activation_token = xdg_activation_v1_get_activation_token(activation_manager);
832
833 xdg_activation_token_v1_add_listener(
835
836 xdg_activation_token_v1_set_app_id(win->xdg.activation_token,
838
839 /* The serial of the input device requesting activation. */
840 {
841 uint32_t serial = 0;
842 wl_seat *seat = system->wl_seat_active_get_with_input_serial(serial);
843 if (seat) {
844 xdg_activation_token_v1_set_serial(win->xdg.activation_token, serial, seat);
845 }
846 }
847
848 /* The surface of the window requesting activation. */
849 {
850 GHOST_WindowWayland *ghost_window_active = static_cast<GHOST_WindowWayland *>(
851 system->getWindowManager()->getActiveWindow());
852 if (ghost_window_active) {
853 wl_surface *surface = ghost_window_active->wl_surface_get();
854 if (surface) {
855 xdg_activation_token_v1_set_surface(win->xdg.activation_token, surface);
856 }
857 }
858 }
859
860 xdg_activation_token_v1_commit(win->xdg.activation_token);
861}
862
864
865/* -------------------------------------------------------------------- */
868
870 GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
871{
874 gwl_window_viewport_set(win, r_surface_needs_commit, r_surface_needs_buffer_scale);
876 }
877 else {
878 if (win->wp.viewport) {
879 gwl_window_viewport_unset(win, r_surface_needs_commit, r_surface_needs_buffer_scale);
880 }
881 else {
883 if (r_surface_needs_buffer_scale) {
884 *r_surface_needs_buffer_scale = true;
885 }
886 else {
887 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
888 }
889 if (r_surface_needs_commit) {
890 *r_surface_needs_commit = true;
891 }
892 else {
893 wl_surface_commit(win->wl.surface);
894 }
895 }
896 }
897}
898
900 bool *r_surface_needs_commit,
901 bool *r_surface_needs_buffer_scale)
902{
905 {
906 return;
907 }
909 win, r_surface_needs_commit, r_surface_needs_buffer_scale);
910}
911
913 bool *r_surface_needs_commit,
914 bool *r_surface_needs_resize_for_backend,
915 bool *r_surface_needs_buffer_scale)
916{
917 if (win->frame_pending.size[0] == 0 || win->frame_pending.size[1] == 0) {
918 return;
919 }
920
921 win->frame.size[0] = win->frame_pending.size[0];
922 win->frame.size[1] = win->frame_pending.size[1];
923
926 {
928 win, r_surface_needs_commit, r_surface_needs_buffer_scale);
929 }
930 else {
932 }
933
934 if (r_surface_needs_resize_for_backend) {
935 *r_surface_needs_resize_for_backend = true;
936 }
937 else {
939 }
940
942
943 win->frame_pending.size[0] = 0;
944 win->frame_pending.size[1] = 0;
945}
946
948
949#ifdef USE_EVENT_BACKGROUND_THREAD
950
952{
953 win->pending_actions[int(type)].store(true);
955}
956
958{
959 /* Ensure pending actions always use the state when the function starts
960 * because one actions may trigger other pending actions an in that case
961 * exact behavior depends on the order functions are called here.
962 * Without this, configuring the frame will trigger the surface
963 * commit immediately instead of the next time pending actions are handled. */
964 bool actions[PENDING_NUM];
965 for (size_t i = 0; i < ARRAY_SIZE(actions); i++) {
966 actions[i] = win->pending_actions[i].exchange(false);
967 }
968
969 if (actions[PENDING_WINDOW_FRAME_CONFIGURE]) {
971 }
974 /* Force postponing scale update to ensure all scale information has been taken into account
975 * before the actual update is performed. Failing to do so tends to cause flickering. */
976 actions[PENDING_OUTPUT_SCALE_UPDATE] = false;
977 }
978 if (actions[PENDING_OUTPUT_SCALE_UPDATE]) {
980 }
981 if (actions[PENDING_WINDOW_SURFACE_COMMIT]) {
982 wl_surface_commit(win->wl.surface);
983 }
987 }
988}
989
990#endif /* USE_EVENT_BACKGROUND_THREAD */
991
997{
998#ifdef USE_EVENT_BACKGROUND_THREAD
999 GHOST_ASSERT(win->ghost_system->main_thread_id == std::this_thread::get_id(),
1000 "Only from main thread!");
1001
1002#endif
1003
1004 const bool dpi_changed = win->frame_pending.fractional_scale != win->frame.fractional_scale;
1005 bool surface_needs_commit = false;
1006 bool surface_needs_resize_for_backend = false;
1007 bool surface_needs_buffer_scale = false;
1008
1009 if (win->frame_pending.size[0] != 0 && win->frame_pending.size[1] != 0) {
1010 if ((win->frame.size[0] != win->frame_pending.size[0]) ||
1011 (win->frame.size[1] != win->frame_pending.size[1]))
1012 {
1014 &surface_needs_commit,
1015 &surface_needs_resize_for_backend,
1016 &surface_needs_buffer_scale);
1017 }
1018 }
1019
1022 win, &surface_needs_commit, &surface_needs_buffer_scale);
1023 }
1024 else {
1025 if (win->frame_pending.buffer_scale != win->frame.buffer_scale) {
1027 surface_needs_buffer_scale = true;
1028 }
1029 }
1030
1031 if (surface_needs_resize_for_backend) {
1033 }
1034
1035 if (surface_needs_buffer_scale) {
1036 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
1037 }
1038
1039#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1040 if (use_libdecor) {
1041 GWL_LibDecor_Window &decor = *win->libdecor;
1042 if (decor.pending.ack_configure) {
1043 surface_needs_commit = true;
1044
1045 decor.pending.ack_configure = false;
1046
1047 libdecor_state *state = libdecor_state_new(UNPACK2(decor.pending.size));
1048
1049# ifdef USE_LIBDECOR_CONFIG_COPY_QUEUE
1050 GHOST_ASSERT(decor.pending.size[0] != 0 && decor.pending.size[1] != 0, "Invalid size");
1051 for (libdecor_configuration *configuration : decor.pending.configuration_queue) {
1052 libdecor_frame_commit(decor.frame, state, configuration);
1053 ghost_wl_libdecor_configuration_free(configuration);
1054 }
1055 decor.pending.configuration_queue.clear();
1056# endif
1057
1058 libdecor_frame_commit(decor.frame, state, decor.pending.configuration);
1059
1061
1062 decor.pending.size[0] = 0;
1063 decor.pending.size[1] = 0;
1064
1065 if (decor.initial_configure_seen == false) {
1066 decor.initial_configure_seen = true;
1067
1068 if (decor.initial_configure_state) {
1069 xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame);
1071 toplevel, decor.initial_configure_state.value(), gwl_window_state_get(win));
1072 decor.initial_configure_state = std::nullopt;
1073 }
1074 }
1075
1076# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1077 if (decor.pending.configuration_needs_free) {
1078 ghost_wl_libdecor_configuration_free(decor.pending.configuration);
1079 decor.pending.configuration_needs_free = false;
1080 }
1081# endif
1082
1083 decor.pending.configuration = nullptr;
1084 }
1085 }
1086 else
1087#endif
1088 if (win->xdg_decor)
1089 {
1090 GWL_XDG_Decor_Window &decor = *win->xdg_decor;
1091 if (decor.pending.ack_configure) {
1092 xdg_surface_ack_configure(decor.surface, decor.pending.ack_configure_serial);
1093 /* The XDG spec states a commit event is required after ACK configure. */
1094 surface_needs_commit = true;
1095
1096 decor.pending.ack_configure = false;
1097 decor.pending.ack_configure_serial = 0;
1098
1099 decor.initial_configure_seen = true;
1100 }
1101 }
1102
1103 if (surface_needs_commit) {
1104#ifdef USE_EVENT_BACKGROUND_THREAD
1105 /* Postponing the commit avoids flickering when moving between monitors of different scale. */
1107#else
1108 wl_surface_commit(win->wl.surface);
1109#endif
1110 }
1111
1112 if (dpi_changed) {
1113 GHOST_SystemWayland *system = win->ghost_system;
1114 system->pushEvent(new GHOST_Event(
1116 }
1117
1118 if (win->frame.is_active != win->frame_pending.is_active) {
1119 if (win->frame_pending.is_active) {
1120 win->ghost_window->activate();
1121 }
1122 else {
1123 win->ghost_window->deactivate();
1124 }
1125 }
1126 else if (false) {
1127 /* Disabled, this can happen during debugging
1128 * when the window changed while the process has been paused. */
1130 win->frame.is_active ==
1132 "GHOST internal active state does not match WAYLAND!");
1133 }
1134
1135 win->frame_pending.size[0] = win->frame.size[0];
1136 win->frame_pending.size[1] = win->frame.size[1];
1137
1138 win->frame = win->frame_pending;
1139
1140 /* Signal not to apply the scale unless it's configured. */
1141 win->frame_pending.size[0] = 0;
1142 win->frame_pending.size[1] = 0;
1143}
1144
1145[[maybe_unused]] static void gwl_window_frame_update_from_pending(GWL_Window *win)
1146{
1147#ifdef USE_EVENT_BACKGROUND_THREAD
1148 std::lock_guard lock_frame_guard{win->frame_pending_mutex};
1149#endif
1151}
1152
1154
1155/* -------------------------------------------------------------------- */
1158
1162static int output_scale_cmp(const GWL_Output *output_a, const GWL_Output *output_b)
1163{
1164 if (output_a->scale < output_b->scale) {
1165 return -1;
1166 }
1167 if (output_a->scale > output_b->scale) {
1168 return 1;
1169 }
1170 if (output_a->has_scale_fractional || output_b->has_scale_fractional) {
1171 const int scale_fractional_a = output_a->has_scale_fractional ?
1172 output_a->scale_fractional :
1173 (output_a->scale * FRACTIONAL_DENOMINATOR);
1174 const int scale_fractional_b = output_b->has_scale_fractional ?
1175 output_b->scale_fractional :
1176 (output_b->scale * FRACTIONAL_DENOMINATOR);
1177 if (scale_fractional_a < scale_fractional_b) {
1178 return -1;
1179 }
1180 if (scale_fractional_a > scale_fractional_b) {
1181 return 1;
1182 }
1183 }
1184 return 0;
1185}
1186
1187static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs,
1188 const int32_t scale_default,
1189 int *r_scale_fractional)
1190{
1191 const GWL_Output *output_max = nullptr;
1192 for (const GWL_Output *reg_output : outputs) {
1193 if (!output_max || (output_scale_cmp(output_max, reg_output) == -1)) {
1194 output_max = reg_output;
1195 }
1196 }
1197
1198 if (output_max) {
1199 if (r_scale_fractional) {
1200 *r_scale_fractional = output_max->has_scale_fractional ?
1201 output_max->scale_fractional :
1202 (output_max->scale * FRACTIONAL_DENOMINATOR);
1203 }
1204 return output_max->scale;
1205 }
1206 if (r_scale_fractional) {
1207 *r_scale_fractional = scale_default * FRACTIONAL_DENOMINATOR;
1208 }
1209 return scale_default;
1210}
1211
1212static int outputs_uniform_scale_or_default(const std::vector<GWL_Output *> &outputs,
1213 const int32_t scale_default,
1214 int *r_scale_fractional)
1215{
1216 const GWL_Output *output_uniform = nullptr;
1217 for (const GWL_Output *reg_output : outputs) {
1218 if (!output_uniform) {
1219 output_uniform = reg_output;
1220 }
1221 else if (output_scale_cmp(output_uniform, reg_output) != 0) {
1222 /* Non-uniform. */
1223 output_uniform = nullptr;
1224 break;
1225 }
1226 }
1227
1228 if (output_uniform) {
1229 if (r_scale_fractional) {
1230 *r_scale_fractional = output_uniform->has_scale_fractional ?
1231 output_uniform->scale_fractional :
1232 (output_uniform->scale * FRACTIONAL_DENOMINATOR);
1233 }
1234 return output_uniform->scale;
1235 }
1236 if (r_scale_fractional) {
1237 *r_scale_fractional = scale_default * FRACTIONAL_DENOMINATOR;
1238 }
1239 return scale_default;
1240}
1241
1243
1244/* -------------------------------------------------------------------- */
1249
1250#ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1251
1252static libdecor_configuration *ghost_wl_libdecor_configuration_copy(
1253 const libdecor_configuration *configuration)
1254{
1255 size_t configuration_size = malloc_usable_size((void *)configuration);
1256 libdecor_configuration *configuration_copy = (libdecor_configuration *)malloc(
1257 configuration_size);
1258 memcpy((void *)configuration_copy, (const void *)configuration, configuration_size);
1259 return configuration_copy;
1260}
1261
1262static void ghost_wl_libdecor_configuration_free(libdecor_configuration *configuration)
1263{
1264 free((void *)configuration);
1265}
1266
1267#endif /* USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
1268
1270
1271/* -------------------------------------------------------------------- */
1274
1275static CLG_LogRef LOG_WL_XDG_TOPLEVEL = {"ghost.wl.handle.xdg_toplevel"};
1276#define LOG (&LOG_WL_XDG_TOPLEVEL)
1277
1279 xdg_toplevel * /*xdg_toplevel*/,
1280 const int32_t width,
1281 const int32_t height,
1282 wl_array *states)
1283{
1284 /* TODO: log `states`, not urgent. */
1285 CLOG_INFO(LOG, 2, "configure (size=[%d, %d])", width, height);
1286
1287 GWL_Window *win = static_cast<GWL_Window *>(data);
1288
1289#ifdef USE_EVENT_BACKGROUND_THREAD
1290 std::lock_guard lock_frame_guard{win->frame_pending_mutex};
1291#endif
1292
1293 const int32_t size[2] = {width, height};
1294 for (int i = 0; i < 2; i++) {
1295 if (size[i] == 0) {
1296 /* Values may be zero, in this case the client should choose. */
1297 continue;
1298 }
1301 (size[i] * win->frame.buffer_scale);
1302 }
1303
1304 win->frame_pending.is_maximised = false;
1305 win->frame_pending.is_fullscreen = false;
1306 win->frame_pending.is_active = false;
1307
1308 enum xdg_toplevel_state *state;
1309 WL_ARRAY_FOR_EACH (state, states) {
1310 switch (*state) {
1311 case XDG_TOPLEVEL_STATE_MAXIMIZED:
1312 win->frame_pending.is_maximised = true;
1313 break;
1314 case XDG_TOPLEVEL_STATE_FULLSCREEN:
1315 win->frame_pending.is_fullscreen = true;
1316 break;
1317 case XDG_TOPLEVEL_STATE_ACTIVATED:
1318 win->frame_pending.is_active = true;
1319 break;
1320 default:
1321 break;
1322 }
1323 }
1324}
1325
1326static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/)
1327{
1328 CLOG_INFO(LOG, 2, "close");
1329
1330 GWL_Window *win = static_cast<GWL_Window *>(data);
1331
1332 win->ghost_window->close();
1333}
1334
1336 xdg_toplevel * /*xdg_toplevel*/,
1337 int32_t width,
1338 int32_t height)
1339{
1340 /* Only available in interface version 4. */
1341 CLOG_INFO(LOG, 2, "configure_bounds (size=[%d, %d])", width, height);
1342
1343 /* No need to lock as this only runs on window creation. */
1344 GWL_Window *win = static_cast<GWL_Window *>(data);
1345 GWL_XDG_Decor_Window &decor = *win->xdg_decor;
1346 if (decor.initial_configure_seen == false) {
1347 decor.initial_bounds[0] = width;
1348 decor.initial_bounds[1] = height;
1349 }
1350}
1351static void xdg_toplevel_handle_wm_capabilities(void * /*data*/,
1352 xdg_toplevel * /*xdg_toplevel*/,
1353 wl_array * /*capabilities*/)
1354{
1355 /* Only available in interface version 5. */
1356 CLOG_INFO(LOG, 2, "wm_capabilities");
1357
1358 /* NOTE: this would be useful if blender had CSD. */
1359}
1360
1362 /*configure*/ xdg_toplevel_handle_configure,
1363 /*close*/ xdg_toplevel_handle_close,
1364 /*configure_bounds*/ xdg_toplevel_handle_configure_bounds,
1365 /*wm_capabilities*/ xdg_toplevel_handle_wm_capabilities,
1366};
1367
1368#undef LOG
1369
1371
1372/* -------------------------------------------------------------------- */
1377
1379 xdg_activation_token_v1 *xdg_activation_token_v1,
1380 const char *token)
1381{
1382 GWL_Window *win = static_cast<GWL_Window *>(data);
1383 if (xdg_activation_token_v1 != win->xdg.activation_token) {
1384 return;
1385 }
1386
1387 GHOST_SystemWayland *system = win->ghost_system;
1388 xdg_activation_v1 *activation_manager = system->xdg_activation_manager_get();
1389 xdg_activation_v1_activate(activation_manager, token, win->wl.surface);
1390 xdg_activation_token_v1_destroy(win->xdg.activation_token);
1391 win->xdg.activation_token = nullptr;
1392}
1393
1394static const xdg_activation_token_v1_listener xdg_activation_listener = {
1396};
1397
1398static const xdg_activation_token_v1_listener *xdg_activation_listener_get()
1399{
1401}
1402
1404
1405/* -------------------------------------------------------------------- */
1410
1411static CLG_LogRef LOG_WL_FRACTIONAL_SCALE = {"ghost.wl.handle.fractional_scale"};
1412#define LOG (&LOG_WL_FRACTIONAL_SCALE)
1413
1415 void *data, wp_fractional_scale_v1 * /*wp_fractional_scale_v1*/, uint preferred_scale)
1416{
1417#ifdef USE_EVENT_BACKGROUND_THREAD
1418 std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
1419#endif
1420 CLOG_INFO(LOG,
1421 2,
1422 "preferred_scale (preferred_scale=%.6f)",
1423 double(preferred_scale) / FRACTIONAL_DENOMINATOR);
1424
1425 GWL_Window *win = static_cast<GWL_Window *>(data);
1426
1427 if (win->frame_pending.fractional_scale_preferred != int(preferred_scale)) {
1428 win->frame_pending.fractional_scale_preferred = preferred_scale;
1430 }
1431}
1432
1433static const wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
1435};
1436
1437#undef LOG
1438
1440
1441/* -------------------------------------------------------------------- */
1444
1445#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1446
1447static CLG_LogRef LOG_WL_LIBDECOR_FRAME = {"ghost.wl.handle.libdecor_frame"};
1448# define LOG (&LOG_WL_LIBDECOR_FRAME)
1449
1450static void libdecor_frame_handle_configure(libdecor_frame *frame,
1451 libdecor_configuration *configuration,
1452 void *data)
1453{
1454 CLOG_INFO(LOG, 2, "configure");
1455
1456# ifdef USE_EVENT_BACKGROUND_THREAD
1457 std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
1458 const bool is_main_thread = [data] {
1459 const GWL_Window *win = static_cast<GWL_Window *>(data);
1460 const GHOST_SystemWayland *system = win->ghost_system;
1461 return system->main_thread_id == std::this_thread::get_id();
1462 }();
1463# endif
1464
1465 GWL_WindowFrame &frame_pending = static_cast<GWL_Window *>(data)->frame_pending;
1466
1467 /* Set the size. */
1468 int size_next[2] = {0, 0};
1469
1470 {
1471 GWL_Window *win = static_cast<GWL_Window *>(data);
1472 const int fractional_scale = win->frame.fractional_scale;
1473 /* It's important `fractional_scale` has a fractional component or rounding up will fail
1474 * to produce the correct whole-number scale. */
1475 GHOST_ASSERT((fractional_scale == 0) ||
1476 (gwl_round_int_test(fractional_scale, FRACTIONAL_DENOMINATOR) == false),
1477 "Fractional scale has no fractional component!");
1478 /* The size from LIBDECOR wont use the GHOST windows buffer size.
1479 * so it's important to calculate the buffer size that would have been used
1480 * if fractional scaling wasn't supported. */
1481 const int scale = fractional_scale ? (fractional_scale / FRACTIONAL_DENOMINATOR) + 1 :
1482 win->frame.buffer_scale;
1483 const int scale_as_fractional = scale * FRACTIONAL_DENOMINATOR;
1485 configuration, frame, &size_next[0], &size_next[1]))
1486 {
1487 if (fractional_scale) {
1488 frame_pending.size[0] = gwl_window_fractional_to_viewport_round(win->frame, size_next[0]);
1489 frame_pending.size[1] = gwl_window_fractional_to_viewport_round(win->frame, size_next[1]);
1490 }
1491 else if (fractional_scale && (fractional_scale != (scale * FRACTIONAL_DENOMINATOR))) {
1492 /* The windows `preferred_scale` is not yet available,
1493 * set the size as if fractional scale is available. */
1494 frame_pending.size[0] = ((size_next[0] * scale) * fractional_scale) / scale_as_fractional;
1495 frame_pending.size[1] = ((size_next[1] * scale) * fractional_scale) / scale_as_fractional;
1496 }
1497 else {
1498 frame_pending.size[0] = size_next[0] * scale;
1499 frame_pending.size[1] = size_next[1] * scale;
1500 }
1501
1502 /* Account for buffer rounding requirement, once fractional scaling is enabled
1503 * the buffer scale will be 1, rounding is a requirement until then. */
1504 gwl_round_int2_by(frame_pending.size, win->frame.buffer_scale);
1505 }
1506 else {
1507 /* These values are cleared after use & will practically always be zero.
1508 * Read them because it's possible multiple configure calls run before they can be handled.
1509 */
1510 const GWL_LibDecor_Window &decor = *win->libdecor;
1511 size_next[0] = decor.pending.size[0];
1512 size_next[1] = decor.pending.size[1];
1513 }
1514 }
1515
1516 /* Set the state. */
1517 {
1518 enum libdecor_window_state window_state;
1519 if (libdecor_configuration_get_window_state(configuration, &window_state)) {
1520 frame_pending.is_maximised = window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED;
1521 frame_pending.is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN;
1522 frame_pending.is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE;
1523 }
1524 }
1525
1526 {
1527 GWL_Window *win = static_cast<GWL_Window *>(data);
1528 GWL_LibDecor_Window &decor = *win->libdecor;
1529
1530# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1531 /* Unlikely but possible a previous configuration is unhandled. */
1532 if (decor.pending.configuration_needs_free) {
1533 ghost_wl_libdecor_configuration_free(decor.pending.configuration);
1534 decor.pending.configuration_needs_free = false;
1535 }
1536# endif /* USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
1537
1538 decor.pending.size[0] = size_next[0];
1539 decor.pending.size[1] = size_next[1];
1540 decor.pending.configuration = configuration;
1541 decor.pending.ack_configure = true;
1542
1543# ifdef USE_EVENT_BACKGROUND_THREAD
1544 if (!is_main_thread) {
1545# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1546 decor.pending.configuration = ghost_wl_libdecor_configuration_copy(configuration);
1547 decor.pending.configuration_needs_free = true;
1548# else
1549 /* Without a way to copy the configuration,
1550 * the configuration will be ignored as it can't be postponed. */
1551 decor.pending.configuration = nullptr;
1552# endif /* !USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
1553 }
1554# endif
1555
1556# ifdef USE_LIBDECOR_CONFIG_COPY_QUEUE
1557 if (!(size_next[0] && size_next[1])) {
1558 /* Always copy. */
1559 if (decor.pending.configuration_needs_free == false) {
1560 decor.pending.configuration = ghost_wl_libdecor_configuration_copy(
1561 decor.pending.configuration);
1562 decor.pending.configuration_needs_free = true;
1563 }
1564 /* Transfer ownership to the queue. */
1565 decor.pending.configuration_queue.push_back(decor.pending.configuration);
1566 decor.pending.configuration = nullptr;
1567 decor.pending.configuration_needs_free = false;
1568 /* Wait until we have a valid size. */
1569 decor.pending.ack_configure = false;
1570 }
1571# endif /* USE_LIBDECOR_CONFIG_COPY_QUEUE */
1572 }
1573
1574 /* Apply & commit the changes. */
1575 {
1576 GWL_Window *win = static_cast<GWL_Window *>(data);
1577# ifdef USE_EVENT_BACKGROUND_THREAD
1578 if (!is_main_thread) {
1580 }
1581 else
1582# endif
1583 {
1585 }
1586 }
1587}
1588
1589static void libdecor_frame_handle_close(libdecor_frame * /*frame*/, void *data)
1590{
1591 CLOG_INFO(LOG, 2, "close");
1592
1593 GWL_Window *win = static_cast<GWL_Window *>(data);
1594
1595 win->ghost_window->close();
1596}
1597
1598static void libdecor_frame_handle_commit(libdecor_frame * /*frame*/, void *data)
1599{
1600 CLOG_INFO(LOG, 2, "commit");
1601
1602# if 0
1603 GWL_Window *win = static_cast<GWL_Window *>(data);
1605# else
1606 (void)data;
1607# endif
1608}
1609
1610/* NOTE: cannot be `const` because of the LIBDECOR API. */
1611static libdecor_frame_interface libdecor_frame_iface = {
1612 /*configure*/ libdecor_frame_handle_configure,
1613 /*close*/ libdecor_frame_handle_close,
1614 /*commit*/ libdecor_frame_handle_commit,
1615};
1616
1617# undef LOG
1618
1619#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
1620
1622
1623/* -------------------------------------------------------------------- */
1626
1627static CLG_LogRef LOG_WL_XDG_TOPLEVEL_DECORATION = {"ghost.wl.handle.xdg_toplevel_decoration"};
1628#define LOG (&LOG_WL_XDG_TOPLEVEL_DECORATION)
1629
1631 void *data, zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/, const uint32_t mode)
1632{
1633 CLOG_INFO(LOG, 2, "configure (mode=%u)", mode);
1634
1635 GWL_Window *win = static_cast<GWL_Window *>(data);
1636
1637 win->xdg_decor->mode = (zxdg_toplevel_decoration_v1_mode)mode;
1638}
1639
1640static const zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_v1_listener = {
1642};
1643
1644#undef LOG
1645
1647
1648/* -------------------------------------------------------------------- */
1651
1652static CLG_LogRef LOG_WL_XDG_SURFACE = {"ghost.wl.handle.xdg_surface"};
1653#define LOG (&LOG_WL_XDG_SURFACE)
1654
1656 xdg_surface *xdg_surface,
1657 const uint32_t serial)
1658{
1659 GWL_Window *win = static_cast<GWL_Window *>(data);
1660
1661 if (win->xdg_decor->surface != xdg_surface) {
1662 CLOG_INFO(LOG, 2, "configure (skipped)");
1663 return;
1664 }
1665 CLOG_INFO(LOG, 2, "configure");
1666
1667#ifdef USE_EVENT_BACKGROUND_THREAD
1668 std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
1669#endif
1670 win->xdg_decor->pending.ack_configure = true;
1671 win->xdg_decor->pending.ack_configure_serial = serial;
1672
1673#ifdef USE_EVENT_BACKGROUND_THREAD
1674 const GHOST_SystemWayland *system = win->ghost_system;
1675 const bool is_main_thread = system->main_thread_id == std::this_thread::get_id();
1676 if (!is_main_thread) {
1677 /* NOTE(@ideasman42): this only gets one redraw,
1678 * I could not find a case where this causes problems. */
1680 }
1681 else
1682#endif
1683 {
1685 }
1686}
1687
1691
1692#undef LOG
1693
1695
1696/* -------------------------------------------------------------------- */
1699
1700static CLG_LogRef LOG_WL_SURFACE = {"ghost.wl.handle.surface"};
1701#define LOG (&LOG_WL_SURFACE)
1702
1703static void surface_handle_enter(void *data, wl_surface * /*wl_surface*/, wl_output *wl_output)
1704{
1705 if (!ghost_wl_output_own(wl_output)) {
1706 CLOG_INFO(LOG, 2, "enter (skipped)");
1707 return;
1708 }
1709 CLOG_INFO(LOG, 2, "enter");
1710
1711 GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
1712 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(data);
1713 if (win->outputs_enter(reg_output)) {
1715 }
1716}
1717
1718static void surface_handle_leave(void *data, wl_surface * /*wl_surface*/, wl_output *wl_output)
1719{
1720 if (!ghost_wl_output_own(wl_output)) {
1721 CLOG_INFO(LOG, 2, "leave (skipped)");
1722 return;
1723 }
1724 CLOG_INFO(LOG, 2, "leave");
1725
1726 GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
1727 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(data);
1728 if (win->outputs_leave(reg_output)) {
1730 }
1731}
1732
1733#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
1734 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
1735static void surface_handle_preferred_buffer_scale(void * /*data*/,
1736 wl_surface * /*wl_surface*/,
1737 int32_t factor)
1738{
1739 /* Only available in interface version 6. */
1740 CLOG_INFO(LOG, 2, "handle_preferred_buffer_scale (factor=%d)", factor);
1741}
1742
1743static void surface_handle_preferred_buffer_transform(void * /*data*/,
1744 wl_surface * /*wl_surface*/,
1745 uint32_t transform)
1746{
1747 /* Only available in interface version 6. */
1748 CLOG_INFO(LOG, 2, "handle_preferred_buffer_transform (transform=%u)", transform);
1749}
1750#endif /* WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION && \
1751 * WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION */
1752
1754 /*enter*/ surface_handle_enter,
1755 /*leave*/ surface_handle_leave,
1756#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
1757 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
1758 /*preferred_buffer_scale*/ surface_handle_preferred_buffer_scale,
1759 /*preferred_buffer_transform*/ surface_handle_preferred_buffer_transform,
1760#endif
1761};
1762
1763#undef LOG
1764
1766
1767/* -------------------------------------------------------------------- */
1772
1774 const char *title,
1775 const int32_t /*left*/,
1776 const int32_t /*top*/,
1777 const uint32_t width,
1778 const uint32_t height,
1780 const GHOST_IWindow *parentWindow,
1781 const GHOST_TDrawingContextType type,
1782 const bool is_dialog,
1783 const bool stereoVisual,
1784 const bool exclusive,
1785 const bool is_debug,
1786 const GHOST_GPUDevice &preferred_device)
1787 : GHOST_Window(width, height, state, stereoVisual, exclusive),
1788 system_(system),
1789 window_(new GWL_Window),
1790 is_debug_context_(is_debug),
1791 preferred_device_(preferred_device)
1792{
1793#ifdef USE_EVENT_BACKGROUND_THREAD
1794 std::lock_guard lock_server_guard{*system->server_mutex};
1795#endif
1796
1797 window_->ghost_window = this;
1798 window_->ghost_system = system;
1799 window_->ghost_context_type = type;
1800
1801 wl_display *display = system->wl_display_get();
1802
1803 /* NOTE(@ideasman42): The scale set here to avoid flickering on startup.
1804 * When all monitors use the same scale (which is quite common) there aren't any problems.
1805 *
1806 * When monitors have different scales there may still be a visible window resize on startup.
1807 * Ideally it would be possible to know the scale this window will use however that's only
1808 * known once #surface_enter callback runs (which isn't guaranteed to run at all).
1809 *
1810 * Using the maximum scale is best as it results in the window first being smaller,
1811 * avoiding a large window flashing before it's made smaller.
1812 *
1813 * For fractional scaling the buffer will eventually be 1. Setting it to 1 now
1814 * (to avoid window size rounding and buffer size switching) has some down-sides.
1815 * It means the window will be drawn larger for a moment then smaller once fractional scaling
1816 * is detected and enabled. Unfortunately, it doesn't seem possible to receive the
1817 * #wp_fractional_scale_v1_listener::preferred_scale information before the window is created
1818 * So leave the buffer scaled up because there is no *guarantee* the fractional scaling support
1819 * will run which could result in an incorrect buffer scale. */
1820 int scale_fractional_from_output;
1821 const int buffer_scale_from_output = outputs_uniform_scale_or_default(
1822 system_->outputs_get(), 0, &scale_fractional_from_output);
1823
1824 window_->frame.size[0] = int32_t(width);
1825 window_->frame.size[1] = int32_t(height);
1826
1827 window_->is_dialog = is_dialog;
1828
1829 /* Window surfaces. */
1830 window_->wl.surface = wl_compositor_create_surface(system_->wl_compositor_get());
1831 ghost_wl_surface_tag(window_->wl.surface);
1832
1833 wl_surface_add_listener(window_->wl.surface, &wl_surface_listener, window_);
1834
1835 wp_fractional_scale_manager_v1 *fractional_scale_manager =
1837 if (fractional_scale_manager) {
1838 window_->wp.fractional_scale_handle = wp_fractional_scale_manager_v1_get_fractional_scale(
1839 fractional_scale_manager, window_->wl.surface);
1840 wp_fractional_scale_v1_add_listener(
1841 window_->wp.fractional_scale_handle, &wp_fractional_scale_listener, window_);
1842 }
1843
1844 /* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels.
1845 * This has the advantage that the size limit is the same when moving the window between monitors
1846 * with different scales set. If it was important to limit in pixels it could be re-calculated
1847 * when the `window_->frame.buffer_scale` changed. */
1848 const int32_t size_min[2] = {320, 240};
1849
1850 const char *xdg_app_id = GHOST_SystemWayland::xdg_app_id_get();
1851
1852#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1853 if (use_libdecor) {
1854 window_->libdecor = new GWL_LibDecor_Window;
1855 GWL_LibDecor_Window &decor = *window_->libdecor;
1856
1857 /* create window decorations */
1858 decor.frame = libdecor_decorate(
1859 system_->libdecor_context_get(), window_->wl.surface, &libdecor_frame_iface, window_);
1860
1861 libdecor_frame_set_min_content_size(decor.frame, UNPACK2(size_min));
1862 libdecor_frame_set_app_id(decor.frame, xdg_app_id);
1863
1864 if (parentWindow) {
1865 GWL_LibDecor_Window &decor_parent =
1866 *dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->window_->libdecor;
1867 libdecor_frame_set_parent(decor.frame, decor_parent.frame);
1868 }
1869 }
1870 else
1871#endif
1872 {
1873 window_->xdg_decor = new GWL_XDG_Decor_Window;
1874 GWL_XDG_Decor_Window &decor = *window_->xdg_decor;
1875 decor.surface = xdg_wm_base_get_xdg_surface(system_->xdg_decor_shell_get(),
1876 window_->wl.surface);
1877 decor.toplevel = xdg_surface_get_toplevel(decor.surface);
1878
1879 xdg_toplevel_set_min_size(decor.toplevel, UNPACK2(size_min));
1880 xdg_toplevel_set_app_id(decor.toplevel, xdg_app_id);
1881
1882 xdg_surface_add_listener(decor.surface, &xdg_surface_listener, window_);
1883 xdg_toplevel_add_listener(decor.toplevel, &xdg_toplevel_listener, window_);
1884
1885 if (parentWindow && is_dialog) {
1886 GWL_XDG_Decor_Window &decor_parent =
1887 *dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->window_->xdg_decor;
1888 xdg_toplevel_set_parent(decor.toplevel, decor_parent.toplevel);
1889 }
1890 }
1891
1892 gwl_window_title_set(window_, title);
1893
1894#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1895 if (use_libdecor) {
1896 /* Postpone mapping the window until after the app-id & title have been set.
1897 * While this doesn't seem to be a requirement, LIBDECOR example code does this. */
1898 libdecor_frame_map(window_->libdecor->frame);
1899 }
1900#endif
1901
1902 wl_surface_set_user_data(window_->wl.surface, this);
1903
1904 /* NOTE: the method used for XDG & LIBDECOR initialization (using `initial_configure_seen`)
1905 * follows the method used in SDL 3.16. */
1906
1907 /* Causes a glitch with `libdecor` for some reason. */
1908#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1909 if (use_libdecor) {
1910 /* Pass. */
1911 }
1912 else
1913#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
1914 {
1915 GWL_XDG_Decor_Window &decor = *window_->xdg_decor;
1916
1917 if (system_->xdg_decor_manager_get()) {
1918 decor.toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration(
1919 system_->xdg_decor_manager_get(), decor.toplevel);
1920 zxdg_toplevel_decoration_v1_add_listener(
1922 zxdg_toplevel_decoration_v1_set_mode(decor.toplevel_decor,
1923 ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
1924 }
1925
1926 /* Commit needed to so configure callback runs. */
1927 wl_surface_commit(window_->wl.surface);
1928
1929 /* Failure exits with an error, simply prevent an eternal loop. */
1931 wl_display_flush(display);
1932 wl_display_dispatch(display);
1933 }
1934 }
1935
1936 /* If the scale is known early, setup the window scale.
1937 * Otherwise accept an unsightly flicker once the outputs scale can be found. */
1938 int early_buffer_scale = 0;
1939 int early_fractional_scale = 0;
1940
1941 if (const int test_fractional_scale =
1942 fractional_scale_manager ? (window_->frame_pending.fractional_scale_preferred ?
1943 window_->frame_pending.fractional_scale_preferred :
1944 scale_fractional_from_output) :
1945 0)
1946 {
1947 if (!gwl_round_int_test(test_fractional_scale, FRACTIONAL_DENOMINATOR)) {
1948 early_fractional_scale = test_fractional_scale;
1949 }
1950 else {
1951 /* Rounded, use simple integer buffer scaling. */
1952 early_buffer_scale = test_fractional_scale / FRACTIONAL_DENOMINATOR;
1953 early_fractional_scale = 0;
1954 }
1955 }
1956 else if (buffer_scale_from_output) {
1957 early_buffer_scale = buffer_scale_from_output;
1958 }
1959
1960 if (early_fractional_scale != 0) {
1961 /* Fractional scale is known. */
1962
1963 window_->frame.fractional_scale_preferred = early_fractional_scale;
1964 window_->frame.fractional_scale = early_fractional_scale;
1965 window_->frame.buffer_scale = 1;
1966
1967 window_->frame_pending.fractional_scale_preferred = early_fractional_scale;
1968 window_->frame_pending.fractional_scale = early_fractional_scale;
1969 window_->frame_pending.buffer_scale = 1;
1970
1971 /* The scale is considered initialized now. */
1972 window_->frame_pending.is_scale_init = true;
1973
1974 /* Always commit and set the scale. */
1975 bool surface_needs_commit_dummy = false, surface_needs_buffer_scale_dummy = false;
1977 window_, &surface_needs_commit_dummy, &surface_needs_buffer_scale_dummy);
1978 }
1979 else if (early_buffer_scale != 0) {
1980 /* Non-fractional scale is known. */
1981
1982 /* No fractional scale, simple initialization. */
1983 window_->frame.buffer_scale = early_buffer_scale;
1984 window_->frame_pending.buffer_scale = early_buffer_scale;
1985
1986 /* The scale is considered initialized now. */
1987 window_->frame_pending.is_scale_init = true;
1988
1989 /* The window surface must be rounded to the scale,
1990 * failing to do so causes the WAYLAND-server to close the window immediately. */
1991 gwl_round_int2_by(window_->frame.size, window_->frame.buffer_scale);
1992 }
1993 else {
1994 /* Scale isn't known (the windows size may flicker when #outputs_changed_update_scale runs). */
1995 window_->frame.buffer_scale = 1;
1996 window_->frame_pending.buffer_scale = 1;
1997 GHOST_ASSERT(window_->frame_pending.is_scale_init == false,
1998 "An initialized scale is not expected");
1999 }
2000
2001 if (window_->frame_pending.is_scale_init) {
2002 /* If the output scale changes here it means the scale settings were not properly set. */
2004 "Fractional scale was not properly initialized");
2005 }
2006
2007 wl_surface_set_buffer_scale(window_->wl.surface, window_->frame.buffer_scale);
2008
2009 /* Apply Bounds.
2010 * Important to run after the buffer scale is known & before the buffer is created. */
2011#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2012 if (use_libdecor) {
2013 /* Pass (unsupported). */
2014 }
2015 else
2016#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
2017 {
2018 const GWL_XDG_Decor_Window &decor = *window_->xdg_decor;
2019 if (decor.initial_bounds[0] && decor.initial_bounds[1]) {
2020 window_->frame.size[0] = std::min(window_->frame.size[0],
2021 decor.initial_bounds[0] * window_->frame.buffer_scale);
2022 window_->frame.size[1] = std::min(window_->frame.size[1],
2023 decor.initial_bounds[1] * window_->frame.buffer_scale);
2024 }
2025 }
2026
2027/* Postpone binding the buffer until after it's decor has been configured:
2028 * - Ensure the window is sized properly (with XDG window decorations), see: #113059.
2029 * - Avoids flickering on startup.
2030 */
2031#ifdef WITH_OPENGL_BACKEND
2032 if (type == GHOST_kDrawingContextTypeOpenGL) {
2033 window_->backend.egl_window = wl_egl_window_create(
2034 window_->wl.surface, int(window_->frame.size[0]), int(window_->frame.size[1]));
2035 }
2036#endif
2037#ifdef WITH_VULKAN_BACKEND
2038 if (type == GHOST_kDrawingContextTypeVulkan) {
2039 window_->backend.vulkan_window_info = new GHOST_ContextVK_WindowInfo;
2040 window_->backend.vulkan_window_info->size[0] = window_->frame.size[0];
2041 window_->backend.vulkan_window_info->size[1] = window_->frame.size[1];
2042 }
2043#endif
2044
2045#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2046# ifdef WITH_VULKAN_BACKEND
2047 const bool libdecor_wait_for_window_init = (type == GHOST_kDrawingContextTypeVulkan);
2048# else
2049 const bool libdecor_wait_for_window_init = false;
2050# endif
2051#endif
2052
2053 /* Drawing context. */
2055 /* This can happen when repeatedly creating windows, see #123096.
2056 * In this case #GHOST_WindowWayland::getValid will return false. */
2057 GHOST_PRINT("Failed to create drawing context" << std::endl);
2058 }
2059 else {
2060 window_->is_valid_setup = true;
2061 }
2062
2063 if (window_->is_valid_setup == false) {
2064 /* Don't attempt to setup the window if there is no context.
2065 * This window is considered invalid and will be removed. */
2066 }
2067 else
2068#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2069 if (use_libdecor && libdecor_wait_for_window_init)
2070 {
2071 /* Ensuring the XDG window has been created is *not* supported by VULKAN.
2072 *
2073 * Although this was once supported using a temporary SHM buffer,
2074 * a DMA buffer is now required by some drivers which turns out to be
2075 * impractical to create here, specially since it's only for a temporary buffer.
2076 *
2077 * Workaround the problem by postponing changes to the window state.
2078 * This causes minor but noticeable glitch when starting maximized,
2079 * where a rectangle is first shown before maximizing.
2080 * With EGL this also happens however maximizing is almost immediate.
2081 *
2082 * This can't be avoided at the moment since LIBDECOR requires the window
2083 * to be created before it's configured (sigh!).
2084 * This can be removed if CSD are implemented, see: #113795. */
2085 GWL_LibDecor_Window &decor = *window_->libdecor;
2086 decor.initial_configure_state = state;
2087 }
2088 else if (use_libdecor) {
2089 /* Commit needed so the top-level callbacks run (and `toplevel` can be accessed). */
2090 wl_surface_commit(window_->wl.surface);
2091 GWL_LibDecor_Window &decor = *window_->libdecor;
2092
2093 /* Additional round-trip is needed to ensure `xdg_toplevel` is set. */
2094 wl_display_roundtrip(display);
2095
2096 /* NOTE: LIBDECOR requires the window to be created & configured before the state can be set.
2097 * Workaround this by using the underlying `xdg_toplevel` */
2098
2099 /* Failure exits with an error, simply prevent an eternal loop. */
2100 while (!decor.initial_configure_seen && !ghost_wl_display_report_error_if_set(display)) {
2101 wl_display_flush(display);
2102 wl_display_dispatch(display);
2103 }
2104
2105 xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame);
2107
2108 /* NOTE(@ideasman42): Round trips are necessary with LIBDECOR on GNOME
2109 * because resizing later on and redrawing does *not* update as it should, see #119871.
2110 *
2111 * Without the round-trip here:
2112 * - The window will be created and this function will return using the requested buffer size,
2113 * instead of the window size which ends up being used (causing a visible flicker).
2114 * This has the down side that Blender's internal window state has the outdated size
2115 * which then gets immediately resized, causing a noticeable glitch.
2116 * - The window decorations will be displayed at the wrong size before refreshing
2117 * at the new size.
2118 * - On GNOME-Shell 46 shows the previous buffer-size under some conditions.
2119 *
2120 * In principle this could be used with XDG too however it causes problems with KDE
2121 * and some WLROOTS based compositors.
2122 */
2123 wl_display_roundtrip(display);
2124 }
2125 else
2126#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
2127 {
2128 gwl_window_state_set(window_, state);
2129 }
2130
2131 /* Commit after setting the buffer.
2132 * While postponing until after the buffer drawing is context is set
2133 * isn't essential, it reduces flickering. */
2134 wl_surface_commit(window_->wl.surface);
2135
2136 window_->is_init = true;
2137
2138 /* Set swap interval to 0 to prevent blocking. */
2139 setSwapInterval(0);
2140}
2141
2143{
2144#ifdef USE_EVENT_BACKGROUND_THREAD
2145 std::lock_guard lock_server_guard{*system_->server_mutex};
2146#endif
2147
2149
2150#ifdef WITH_OPENGL_BACKEND
2151 if (window_->ghost_context_type == GHOST_kDrawingContextTypeOpenGL) {
2152 wl_egl_window_destroy(window_->backend.egl_window);
2153 }
2154#endif
2155#ifdef WITH_VULKAN_BACKEND
2156 if (window_->ghost_context_type == GHOST_kDrawingContextTypeVulkan) {
2157 delete window_->backend.vulkan_window_info;
2158 }
2159#endif
2160
2161 if (window_->xdg.activation_token) {
2162 xdg_activation_token_v1_destroy(window_->xdg.activation_token);
2163 window_->xdg.activation_token = nullptr;
2164 }
2165
2166 if (window_->wp.fractional_scale_handle) {
2167 wp_fractional_scale_v1_destroy(window_->wp.fractional_scale_handle);
2168 window_->wp.fractional_scale_handle = nullptr;
2169 }
2170
2171 if (window_->wp.viewport) {
2172 wp_viewport_destroy(window_->wp.viewport);
2173 window_->wp.viewport = nullptr;
2174 }
2175
2176#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2177 if (use_libdecor) {
2178 gwl_libdecor_window_destroy(window_->libdecor);
2179 }
2180 else
2181#endif
2182 {
2183 gwl_xdg_decor_window_destroy(window_->xdg_decor);
2184 }
2185
2186 /* Clear any pointers to this window. This is needed because there are no guarantees
2187 * that flushing the display will the "leave" handlers before handling events. */
2188 system_->window_surface_unref(window_->wl.surface);
2189
2190 wl_surface_destroy(window_->wl.surface);
2191
2192 /* NOTE(@ideasman42): Flushing will often run the appropriate handlers event
2193 * (#wl_surface_listener.leave in particular) to avoid attempted access to the freed surfaces.
2194 * This is not fool-proof though, hence the call to #window_surface_unref, see: #99078. */
2195 wl_display_flush(system_->wl_display_get());
2196
2197 gwl_window_cursor_custom_free(window_->cursor_custom_shape);
2198
2199 delete window_;
2200}
2201
2202#ifdef USE_EVENT_BACKGROUND_THREAD
2204{
2205 GHOST_ASSERT(system_->main_thread_id == std::this_thread::get_id(), "Only from main thread!");
2207}
2208#endif /* USE_EVENT_BACKGROUND_THREAD */
2209
2211{
2212 return system_->cursor_shape_check(cursorShape);
2213}
2214
2216{
2217#ifdef USE_EVENT_BACKGROUND_THREAD
2218 std::lock_guard lock_server_guard{*system_->server_mutex};
2219#endif
2220
2221 GHOST_Rect bounds_buf;
2222 const GHOST_Rect *bounds = nullptr;
2224 if (getCursorGrabBounds(bounds_buf) == GHOST_kFailure) {
2225 getClientBounds(bounds_buf);
2226 }
2227 bounds = &bounds_buf;
2228 }
2229
2230 if (system_->window_cursor_grab_set(mode,
2233 bounds,
2235 window_->wl.surface,
2236 this->scale_params_get()))
2237 {
2238 return GHOST_kSuccess;
2239 }
2240 return GHOST_kFailure;
2241}
2242
2244{
2245#ifdef USE_EVENT_BACKGROUND_THREAD
2246 std::lock_guard lock_server_guard{*system_->server_mutex};
2247#endif
2248
2249 const bool is_active = this == static_cast<const GHOST_WindowWayland *>(
2250 system_->getWindowManager()->getActiveWindow());
2251 gwl_window_cursor_custom_clear(window_->cursor_custom_shape);
2252 m_cursorShape = shape;
2253
2254 GHOST_TSuccess ok;
2255 if (is_active) {
2256 ok = system_->cursor_shape_set(m_cursorShape);
2257 GHOST_TSuccess ok_test = ok;
2258 if (ok == GHOST_kFailure) {
2259 /* Failed, try again with the default cursor. */
2261 ok_test = system_->cursor_shape_set(m_cursorShape);
2262 }
2263
2264 wl_display *display = system_->wl_display_get();
2265#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
2266 if (ok == GHOST_kSuccess || ok_test == GHOST_kSuccess) {
2267 wl_display_flush(display);
2269 }
2270 else
2271#endif /* USE_CURSOR_IMMEDIATE_DISPATCH */
2272 if (ok_test == GHOST_kFailure) {
2273 /* For the cursor to display when the event queue isn't being handled. */
2274 wl_display_flush(display);
2275 }
2276 }
2277 else {
2278 /* Set later when activating the window. */
2279 ok = system_->cursor_shape_check(shape);
2280 if (ok == GHOST_kFailure) {
2282 }
2283 }
2284 return ok;
2285}
2286
2288{
2289#ifdef USE_EVENT_BACKGROUND_THREAD
2290 std::lock_guard lock_server_guard{*system_->server_mutex};
2291#endif
2292 return system_->cursor_grab_use_software_display_get(m_cursorGrab);
2293}
2294
2296 uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor)
2297{
2298#ifdef USE_EVENT_BACKGROUND_THREAD
2299 std::lock_guard lock_server_guard{*system_->server_mutex};
2300#endif
2301
2302 const bool is_active = this == static_cast<const GHOST_WindowWayland *>(
2303 system_->getWindowManager()->getActiveWindow());
2304 const int32_t size[2] = {sizex, sizey};
2305 const int32_t hot_spot[2] = {hotX, hotY};
2306
2308 window_->cursor_custom_shape, bitmap, mask, size, hot_spot, canInvertColor);
2310
2311 GHOST_TSuccess ok;
2312 if (is_active) {
2313 ok = gwl_window_cursor_custom_load(window_->cursor_custom_shape, system_);
2314 GHOST_TSuccess ok_test = ok;
2315 if (ok == GHOST_kFailure) {
2316 /* Failed, try again with the default cursor. */
2318 ok_test = system_->cursor_shape_set(m_cursorShape);
2319 }
2320 if (ok_test == GHOST_kSuccess) {
2321 wl_display *display = system_->wl_display_get();
2322 /* For the cursor to display when the event queue isn't being handled. */
2323 wl_display_flush(display);
2324#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
2326#endif
2327 }
2328 }
2329 else {
2330 /* Set later when activating the window. */
2331 ok = GHOST_kSuccess;
2332 }
2333 return ok;
2334}
2335
2337{
2338#ifdef USE_EVENT_BACKGROUND_THREAD
2339 std::lock_guard lock_server_guard{*system_->server_mutex};
2340#endif
2341 return system_->cursor_bitmap_get(bitmap);
2342}
2343
2345{
2346 return GHOST_Window::getValid() && window_->is_valid_setup;
2347}
2348
2349void GHOST_WindowWayland::setTitle(const char *title)
2350{
2351#ifdef USE_EVENT_BACKGROUND_THREAD
2352 std::lock_guard lock_server_guard{*system_->server_mutex};
2353#endif
2354 gwl_window_title_set(window_, title);
2355}
2356
2358{
2359 /* No need to lock `server_mutex` (WAYLAND never changes this). */
2360 return window_->title.empty() ? "untitled" : window_->title;
2361}
2362
2367
2369{
2370 /* No need to lock `server_mutex` (WAYLAND never changes this in a thread). */
2371 bounds.set(0, 0, UNPACK2(window_->frame.size));
2372}
2373
2375{
2376 return setClientSize(width, uint32_t(window_->frame.size[1]));
2377}
2378
2380{
2381 return setClientSize(uint32_t(window_->frame.size[0]), height);
2382}
2383
2384GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const uint32_t height)
2385{
2386#ifdef USE_EVENT_BACKGROUND_THREAD
2387 std::lock_guard lock_server_guard{*system_->server_mutex};
2388 std::lock_guard lock_frame_guard{window_->frame_pending_mutex};
2389#endif
2390
2391 GWL_WindowFrame &frame_pending = window_->frame_pending;
2392
2393 frame_pending.size[0] = width;
2394 frame_pending.size[1] = height;
2395 gwl_round_int2_by(frame_pending.size, frame_pending.buffer_scale);
2396
2397 gwl_window_frame_pending_size_set(window_, nullptr, nullptr, nullptr);
2398
2399 return GHOST_kSuccess;
2400}
2401
2403 int32_t inY,
2404 int32_t &outX,
2405 int32_t &outY) const
2406{
2407 outX = inX;
2408 outY = inY;
2409}
2410
2412 int32_t inY,
2413 int32_t &outX,
2414 int32_t &outY) const
2415{
2416 outX = inX;
2417 outY = inY;
2418}
2419
2421{
2422 /* No need to lock `server_mutex`
2423 * (`outputs_changed_update_scale` never changes values in a non-main thread). */
2424
2425 if (window_->frame.fractional_scale) {
2426 return gwl_window_fractional_to_viewport(window_->frame, base_dpi);
2427 }
2428
2429 return window_->frame.buffer_scale * base_dpi;
2430}
2431
2433{
2434#ifdef USE_EVENT_BACKGROUND_THREAD
2435 std::lock_guard lock_server_guard{*system_->server_mutex};
2436#endif
2437 const GHOST_TSuccess ok = system_->cursor_visibility_set(visible);
2438 if (ok == GHOST_kSuccess) {
2439 wl_display *display = system_->wl_display_get();
2440 /* For the cursor to display when the event queue isn't being handled. */
2441 wl_display_flush(display);
2442#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
2444#endif
2445 }
2446 return ok;
2447}
2448
2450{
2451#ifdef USE_EVENT_BACKGROUND_THREAD
2452 std::lock_guard lock_server_guard{*system_->server_mutex};
2453#endif
2455}
2456
2458{
2459#ifdef USE_EVENT_BACKGROUND_THREAD
2460 std::lock_guard lock_server_guard{*system_->server_mutex};
2461#endif
2462 return gwl_window_state_get(window_);
2463}
2464
2469
2471{
2472 /* NOTE(@ideasman42): only activation is supported (on X11 & Cocoa for example)
2473 * both activation and raising is performed. Since WAYLAND only supports activation,
2474 * do that as the compositor will likely raise the window as well.
2475 * Although it's not ideal that raising does something but lowering a window is ignored. */
2476 if (order == GHOST_kWindowOrderTop) {
2477 gwl_window_activate(window_);
2478 }
2479
2480 return GHOST_kSuccess;
2481}
2482
2484{
2485 return window_->is_dialog;
2486}
2487
2488GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType type)
2489{
2490 switch (type) {
2493 return context;
2494 }
2495
2496#ifdef WITH_VULKAN_BACKEND
2497 case GHOST_kDrawingContextTypeVulkan: {
2498 GHOST_ContextVK *context = new GHOST_ContextVK(m_wantStereoVisual,
2499 GHOST_kVulkanPlatformWayland,
2500 0,
2501 nullptr,
2502 window_->wl.surface,
2503 system_->wl_display_get(),
2504 window_->backend.vulkan_window_info,
2505 1,
2506 2,
2507 is_debug_context_,
2508 preferred_device_);
2509 if (context->initializeDrawingContext()) {
2510 return context;
2511 }
2512 delete context;
2513 return nullptr;
2514 }
2515#endif
2516
2517#ifdef WITH_OPENGL_BACKEND
2518 case GHOST_kDrawingContextTypeOpenGL: {
2519 for (int minor = 6; minor >= 3; --minor) {
2520 GHOST_Context *context = new GHOST_ContextEGL(
2521 system_,
2523 EGLNativeWindowType(window_->backend.egl_window),
2524 EGLNativeDisplayType(system_->wl_display_get()),
2525 EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
2526 4,
2527 minor,
2529 (is_debug_context_ ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
2531 EGL_OPENGL_API);
2532
2533 if (context->initializeDrawingContext()) {
2534 return context;
2535 }
2536 delete context;
2537 }
2538 return nullptr;
2539 }
2540#endif
2541
2542 default:
2543 /* Unsupported backend. */
2544 return nullptr;
2545 }
2546}
2547
2548#ifdef WITH_INPUT_IME
2549
2550void GHOST_WindowWayland::beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed)
2551{
2552 system_->ime_begin(this, x, y, w, h, completed);
2553}
2554
2555void GHOST_WindowWayland::endIME()
2556{
2557 system_->ime_end(this);
2558}
2559
2560#endif
2561
2563
2564/* -------------------------------------------------------------------- */
2569
2571{
2572 return window_->frame.buffer_scale;
2573}
2574
2576{
2577 /* NOTE(@ideasman42): This could be kept initialized,
2578 * since it's such a small struct it's not so important. */
2579 GWL_WindowScaleParams *scale_params = &window_->scale_params;
2580 scale_params->is_fractional = (window_->frame.fractional_scale != 0);
2581 scale_params->scale = scale_params->is_fractional ? window_->frame.fractional_scale :
2582 window_->frame.buffer_scale;
2583 return *scale_params;
2584}
2585
2586wl_fixed_t GHOST_WindowWayland::wl_fixed_from_window(wl_fixed_t value) const
2587{
2588 if (window_->frame.fractional_scale) {
2589 return gwl_window_fractional_from_viewport(window_->frame, value);
2590 }
2591 return value / window_->frame.buffer_scale;
2592}
2593
2594wl_fixed_t GHOST_WindowWayland::wl_fixed_to_window(wl_fixed_t value) const
2595{
2596 if (window_->frame.fractional_scale) {
2597 return gwl_window_fractional_to_viewport(window_->frame, value);
2598 }
2599 return value * window_->frame.buffer_scale;
2600}
2601
2603{
2604 return window_->wl.surface;
2605}
2606
2607const std::vector<GWL_Output *> &GHOST_WindowWayland::outputs_get()
2608{
2609 return window_->outputs;
2610}
2611
2613
2614/* -------------------------------------------------------------------- */
2619
2621{
2622 return system_->pushEvent_maybe_pending(
2623 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowClose, this));
2624}
2625
2627{
2628#ifdef USE_EVENT_BACKGROUND_THREAD
2629 const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
2630 if (is_main_thread)
2631#endif
2632 {
2633 /* This can run while the window being initialized.
2634 * In this case, skip setting the window active but add the event, see: #120465. */
2635 if (window_->is_init) {
2636 if (system_->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
2637 return GHOST_kFailure;
2638 }
2639 }
2640 }
2641 const GHOST_TSuccess success = system_->pushEvent_maybe_pending(
2642 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowActivate, this));
2643#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2644 if (success == GHOST_kSuccess) {
2645 if (use_libdecor) {
2646 /* Ensure there is a swap-buffers, needed for the updated window borders to refresh. */
2648 }
2649 }
2650#endif
2651 return success;
2652}
2653
2655{
2656#ifdef USE_EVENT_BACKGROUND_THREAD
2657 /* Actual activation is handled when processing pending events. */
2658 const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
2659 if (is_main_thread)
2660#endif
2661 {
2662 /* See code comments for #GHOST_WindowWayland::activate. */
2663 if (window_->is_init) {
2664 system_->getWindowManager()->setWindowInactive(this);
2665 }
2666 }
2667 const GHOST_TSuccess success = system_->pushEvent_maybe_pending(
2668 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowDeactivate, this));
2669#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2670 if (success == GHOST_kSuccess) {
2671 if (use_libdecor) {
2672 /* Ensure there is a swap-buffers, needed for the updated window borders to refresh. */
2674 }
2675 }
2676#endif
2677 return success;
2678}
2679
2681{
2682 return system_->pushEvent_maybe_pending(
2683 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowSize, this));
2684}
2685
2687{
2688 /* NOTE: we want to `swapBuffers`, however this may run from a thread and
2689 * when this windows OpenGL context is not active, so send and update event instead. */
2690 return system_->pushEvent_maybe_pending(
2691 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowUpdateDecor, this));
2692}
2693
2695
2696/* -------------------------------------------------------------------- */
2701
2703{
2704#ifdef USE_EVENT_BACKGROUND_THREAD
2705 if (system_->main_thread_id != std::this_thread::get_id()) {
2707 return GHOST_kSuccess;
2708 }
2709#endif
2710 return gwl_window_cursor_shape_refresh(m_cursorShape, window_->cursor_custom_shape, system_);
2711}
2712
2714{
2715#ifdef USE_EVENT_BACKGROUND_THREAD
2716
2717 /* NOTE: if deferring causes problems, it could be isolated to the first scale initialization
2718 * See: #GWL_WindowFrame::is_scale_init. */
2720#else
2722#endif
2723}
2724
2726{
2727#ifdef USE_EVENT_BACKGROUND_THREAD
2728 if (system_->main_thread_id != std::this_thread::get_id()) {
2730 return false;
2731 }
2732#endif
2733 int fractional_scale_next = -1;
2734 int fractional_scale_from_output = 0;
2735
2736 int scale_next = outputs_max_scale_or_default(outputs_get(), 0, &fractional_scale_from_output);
2737
2738 if (UNLIKELY(scale_next == 0)) {
2739 return false;
2740 }
2741
2742#ifdef USE_EVENT_BACKGROUND_THREAD
2743 std::lock_guard lock_frame_guard{window_->frame_pending_mutex};
2744#endif
2745
2746 if (window_->wp.fractional_scale_handle) {
2747 /* Let the #wp_fractional_scale_v1_listener::preferred_scale callback handle
2748 * changes to the windows scale. */
2749 if (window_->frame_pending.fractional_scale_preferred != 0) {
2750 fractional_scale_next = window_->frame_pending.fractional_scale_preferred;
2751 scale_next = fractional_scale_next / FRACTIONAL_DENOMINATOR;
2752 }
2753 }
2754
2755 if (fractional_scale_next == -1) {
2756 fractional_scale_next = fractional_scale_from_output;
2757 scale_next = fractional_scale_next / FRACTIONAL_DENOMINATOR;
2758 }
2759
2760 bool changed = false;
2761
2762 const bool is_fractional_prev = window_->frame.fractional_scale != 0;
2763 const bool is_fractional_next = (fractional_scale_next % FRACTIONAL_DENOMINATOR) != 0;
2764
2765 /* When non-fractional, never use fractional scaling! */
2766 window_->frame_pending.fractional_scale = is_fractional_next ? fractional_scale_next : 0;
2767 window_->frame_pending.buffer_scale = is_fractional_next ?
2768 1 :
2769 fractional_scale_next / FRACTIONAL_DENOMINATOR;
2770
2771 const int fractional_scale_prev = window_->frame.fractional_scale ?
2772 window_->frame.fractional_scale :
2773 window_->frame.buffer_scale * FRACTIONAL_DENOMINATOR;
2774 const int scale_prev = fractional_scale_prev / FRACTIONAL_DENOMINATOR;
2775
2776 /* Resizing implies updating. */
2777 bool do_frame_resize = false;
2778 bool do_frame_update = false;
2779
2780 if (window_->frame_pending.is_scale_init == false) {
2781 window_->frame_pending.is_scale_init = true;
2782
2783 /* NOTE(@ideasman42): Needed because new windows are created at their previous pixel-dimensions
2784 * as the window doesn't save it's DPI. Restore the window size under the assumption it's
2785 * opening on the same monitor so a window keeps it's previous size on a users system.
2786 *
2787 * To support anything more sophisticated, windows would need to be created with a scale
2788 * argument (representing the scale used when the window was stored, for example). */
2789
2790#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2791 if (use_libdecor) {
2792 /* LIBDECOR needs its own logic. Failing to do this causes the window border
2793 * not to follow the GHOST window on startup - with multiple monitors,
2794 * each with different fractional scale, see: #109194.
2795 *
2796 * Note that the window will show larger, then resize to be smaller soon
2797 * after opening. This would be nice to avoid but would require DPI
2798 * to be stored in the window (as noted above). */
2799 int size_next[2] = {0, 0};
2800 int size_orig[2] = {0, 0};
2801
2802 /* Leave `window_->frame_pending` as-is, only change the window frame. */
2803 for (size_t i = 0; i < ARRAY_SIZE(window_->frame_pending.size); i++) {
2804 const int value = size_next[i] ? window_->frame_pending.size[i] : window_->frame.size[i];
2805 size_orig[i] = value;
2806 if (is_fractional_prev || is_fractional_next) {
2807 size_next[i] = lroundf((value * double(FRACTIONAL_DENOMINATOR)) /
2808 double(fractional_scale_next));
2809 }
2810 else {
2811 size_next[i] = value / scale_prev;
2812 }
2813 if (window_->frame_pending.buffer_scale > 1) {
2814 gwl_round_int_by(&size_next[i], window_->frame_pending.buffer_scale);
2815 }
2816 }
2817
2818 if (size_orig[0] != size_next[0] || size_orig[1] != size_next[1]) {
2819 GWL_LibDecor_Window &decor = *window_->libdecor;
2820 libdecor_state *state = libdecor_state_new(UNPACK2(size_next));
2821 libdecor_frame_commit(decor.frame, state, nullptr);
2823 }
2824 }
2825#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
2826 /* Leave `window_->frame_pending` as-is, so changes are detected and updates are applied. */
2827 do_frame_resize = false;
2828 do_frame_update = true;
2829
2830 /* If the buffer scale changes, the window size (and underlying buffer-size)
2831 * must always be a multiple of the buffer size. Resizing ensures this is the case.
2832 * See replies to #135764 for details.
2833 *
2834 * NOTE: We could skip resize if the current window size is a multiple of the buffer scale,
2835 * avoids this as it will result in unpredictable behavior based on single pixel differences
2836 * in window size. */
2837 if (window_->frame_pending.buffer_scale != window_->frame.buffer_scale) {
2838 do_frame_resize = true;
2839 }
2840 }
2841 else {
2842 /* Test if the scale changed. */
2843 if ((fractional_scale_prev != fractional_scale_next) ||
2844 (window_->frame_pending.buffer_scale != window_->frame.buffer_scale))
2845 {
2846 do_frame_resize = true;
2847 }
2848 }
2849
2850 if (do_frame_resize) {
2851 /* Resize the window failing to do so results in severe flickering with a
2852 * multi-monitor setup when multiple monitors have different scales.
2853 *
2854 * NOTE: some flickering is still possible even when resizing this
2855 * happens when dragging the right hand side of the title-bar in KDE
2856 * as expanding changed the size on the RHS, this may be up to the compositor to fix. */
2857 for (size_t i = 0; i < ARRAY_SIZE(window_->frame_pending.size); i++) {
2858 const int value = window_->frame_pending.size[i] ? window_->frame_pending.size[i] :
2859 window_->frame.size[i];
2860 if (is_fractional_prev || is_fractional_next) {
2861 window_->frame_pending.size[i] = lroundf((value * double(fractional_scale_next)) /
2862 double(fractional_scale_prev));
2863 }
2864 else {
2865 window_->frame_pending.size[i] = (value * scale_next) / scale_prev;
2866 }
2867 if (window_->frame_pending.buffer_scale > 1) {
2868 gwl_round_int_by(&window_->frame_pending.size[i], window_->frame_pending.buffer_scale);
2869 }
2870 }
2871 do_frame_update = true;
2872 }
2873
2874 if (do_frame_update) {
2876 changed = true;
2877 }
2878
2879 return changed;
2880}
2881
2883{
2884 std::vector<GWL_Output *> &outputs = window_->outputs;
2885 auto it = std::find(outputs.begin(), outputs.end(), output);
2886 if (it != outputs.end()) {
2887 return false;
2888 }
2889 outputs.push_back(output);
2890 return true;
2891}
2892
2894{
2895 std::vector<GWL_Output *> &outputs = window_->outputs;
2896 auto it = std::find(outputs.begin(), outputs.end(), output);
2897 if (it == outputs.end()) {
2898 return false;
2899 }
2900 outputs.erase(it);
2901 return true;
2902}
2903
2904#ifdef USE_EVENT_BACKGROUND_THREAD
2905
2907{
2908 /* Caller must lock `server_mutex`, while individual actions could lock,
2909 * it's simpler to lock once when handling all window actions. */
2910 GWL_Window *win = window_;
2911 GHOST_ASSERT(win->ghost_system->main_thread_id == std::this_thread::get_id(),
2912 "Run from main thread!");
2913
2915}
2916
2917#endif /* USE_EVENT_BACKGROUND_THREAD */
2918
KDTree *BLI_kdtree_nd_ new(unsigned int nodes_len_capacity)
Definition kdtree_impl.h:97
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
#define UNPACK2(a)
#define ARRAY_SIZE(arr)
#define UNLIKELY(x)
#define CLOG_INFO(clg_ref, level,...)
Definition CLG_log.h:179
#define GHOST_OPENGL_EGL_CONTEXT_FLAGS
#define GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY
#define wl_display
#define wl_surface
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
GWL_Output * ghost_wl_output_user_data(wl_output *wl_output)
void ghost_wl_surface_tag(wl_surface *wl_surface)
bool ghost_wl_display_report_error_if_set(wl_display *display)
bool ghost_wl_output_own(const wl_output *wl_output)
#define FRACTIONAL_DENOMINATOR
GHOST_TWindowState
@ GHOST_kWindowStateMinimized
@ GHOST_kWindowStateMaximized
@ GHOST_kWindowStateNormal
@ GHOST_kWindowStateFullScreen
GHOST_TStandardCursor
@ GHOST_kStandardCursorCustom
@ GHOST_kStandardCursorDefault
@ GHOST_kEventWindowClose
@ GHOST_kEventWindowSize
@ GHOST_kEventWindowActivate
@ GHOST_kEventWindowUpdateDecor
@ GHOST_kEventWindowDeactivate
@ GHOST_kEventWindowDPIHintChanged
GHOST_TDrawingContextType
@ GHOST_kDrawingContextTypeNone
GHOST_TWindowOrder
@ GHOST_kWindowOrderTop
GHOST_TSuccess
Definition GHOST_Types.h:80
@ GHOST_kFailure
Definition GHOST_Types.h:80
@ GHOST_kSuccess
Definition GHOST_Types.h:80
GHOST_TGrabCursorMode
@ GHOST_kGrabWrap
#define WL_ARRAY_FOR_EACH(pos, array)
static void wp_fractional_scale_handle_preferred_scale(void *data, wp_fractional_scale_v1 *, uint preferred_scale)
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params, wl_fixed_t value)
eGWL_PendingWindowActions
@ PENDING_WINDOW_FRAME_CONFIGURE
@ PENDING_OUTPUT_SCALE_UPDATE_DEFERRED
@ PENDING_WINDOW_CURSOR_SHAPE_REFRESH
@ PENDING_WINDOW_SURFACE_COMMIT
@ PENDING_OUTPUT_SCALE_UPDATE
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params, wl_fixed_t value)
static void gwl_round_int2_by(int value_p[2], const int round_value)
static int gwl_window_fractional_from_viewport(const GWL_WindowFrame &frame, int value)
static void surface_handle_leave(void *data, wl_surface *, wl_output *wl_output)
static void gwl_window_frame_pending_fractional_scale_set_notest(GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
static CLG_LogRef LOG_WL_XDG_TOPLEVEL_DECORATION
static void gwl_window_frame_update_from_pending_no_lock(GWL_Window *win)
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value)
static bool gwl_window_viewport_unset(GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
static void gwl_window_cursor_custom_free(GWL_WindowCursorCustomShape &ccs)
static GHOST_TWindowState gwl_window_state_get(const GWL_Window *win)
#define PENDING_NUM
static void xdg_toplevel_handle_configure(void *data, xdg_toplevel *, const int32_t width, const int32_t height, wl_array *states)
static CLG_LogRef LOG_WL_XDG_SURFACE
static void gwl_window_frame_pending_fractional_scale_set(GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
static void xdg_surface_handle_configure(void *data, xdg_surface *xdg_surface, const uint32_t serial)
static void gwl_window_frame_update_from_pending(GWL_Window *win)
static const wp_fractional_scale_v1_listener wp_fractional_scale_listener
static CLG_LogRef LOG_WL_SURFACE
static const xdg_toplevel_listener xdg_toplevel_listener
static const zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_v1_listener
static GHOST_TSuccess gwl_window_cursor_custom_load(const GWL_WindowCursorCustomShape &ccs, GHOST_SystemWayland *system)
static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state)
static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shape, const GWL_WindowCursorCustomShape &ccs, GHOST_SystemWayland *system)
static void gwl_window_frame_pending_size_set(GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_resize_for_backend, bool *r_surface_needs_buffer_scale)
static bool gwl_window_state_set_for_xdg(xdg_toplevel *toplevel, const GHOST_TWindowState state, const GHOST_TWindowState state_current)
static int outputs_uniform_scale_or_default(const std::vector< GWL_Output * > &outputs, const int32_t scale_default, int *r_scale_fractional)
static void gwl_xdg_decor_window_destroy(GWL_XDG_Decor_Window *decor)
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value)
static CLG_LogRef LOG_WL_FRACTIONAL_SCALE
static void xdg_toplevel_handle_configure_bounds(void *data, xdg_toplevel *, int32_t width, int32_t height)
static int gwl_window_fractional_to_viewport(const GWL_WindowFrame &frame, int value)
static void gwl_window_activate(GWL_Window *win)
static void gwl_window_resize_for_backend(GWL_Window *win, const int32_t size[2])
static int outputs_max_scale_or_default(const std::vector< GWL_Output * > &outputs, const int32_t scale_default, int *r_scale_fractional)
static constexpr size_t base_dpi
static void gwl_window_cursor_custom_clear(GWL_WindowCursorCustomShape &ccs)
static void gwl_window_pending_actions_handle(GWL_Window *win)
static const xdg_surface_listener xdg_surface_listener
static int gwl_window_fractional_to_viewport_round(const GWL_WindowFrame &frame, int value)
static int output_scale_cmp(const GWL_Output *output_a, const GWL_Output *output_b)
static void gwl_round_int_by(int *value_p, const int round_value)
static CLG_LogRef LOG_WL_XDG_TOPLEVEL
static void gwl_window_cursor_custom_store(GWL_WindowCursorCustomShape &ccs, const uint8_t *bitmap, const uint8_t *mask, const int32_t size[2], const int32_t hot_spot[2], bool can_invert_color)
static bool gwl_window_viewport_size_update(GWL_Window *win)
static void xdg_activation_handle_done(void *data, xdg_activation_token_v1 *xdg_activation_token_v1, const char *token)
static const xdg_activation_token_v1_listener xdg_activation_listener
static void xdg_toplevel_handle_wm_capabilities(void *, xdg_toplevel *, wl_array *)
static void gwl_window_pending_actions_tag(GWL_Window *win, enum eGWL_PendingWindowActions type)
static void surface_handle_enter(void *data, wl_surface *, wl_output *wl_output)
static bool gwl_round_int_test(int value, const int round_value)
static bool gwl_window_viewport_set(GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
static void gwl_window_title_set(GWL_Window *win, const char *title)
static int gwl_window_fractional_from_viewport_round(const GWL_WindowFrame &frame, int value)
static void xdg_toplevel_decoration_handle_configure(void *data, zxdg_toplevel_decoration_v1 *, const uint32_t mode)
static const xdg_activation_token_v1_listener * xdg_activation_listener_get()
static const wl_surface_listener wl_surface_listener
static void xdg_toplevel_handle_close(void *data, xdg_toplevel *)
BMesh const char void * data
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
static const char * xdg_app_id_get()
uint64_t getMilliSeconds() const override
std::thread::id main_thread_id
struct wl_display * wl_display_get()
struct wp_fractional_scale_manager_v1 * wp_fractional_scale_manager_get()
GHOST_TSuccess cursor_shape_set(GHOST_TStandardCursor shape)
struct wp_viewporter * wp_viewporter_get()
struct wl_seat * wl_seat_active_get_with_input_serial(uint32_t &serial)
GHOST_TSuccess cursor_shape_custom_set(const uint8_t *bitmap, const uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor)
std::atomic< bool > has_pending_actions_for_window
struct xdg_activation_v1 * xdg_activation_manager_get()
GHOST_WindowManager * getWindowManager() const
GHOST_TSuccess pushEvent(const GHOST_IEvent *event)
GHOST_IWindow * getActiveWindow() const
GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursorShape) override
const struct GWL_WindowScaleParams & scale_params_get() const
GHOST_TSuccess setClientSize(uint32_t width, uint32_t height) override
bool outputs_leave(GWL_Output *output)
GHOST_TSuccess setClientWidth(uint32_t width) override
wl_fixed_t wl_fixed_to_window(wl_fixed_t value) const
GHOST_TSuccess setOrder(GHOST_TWindowOrder order) override
GHOST_TSuccess swapBuffers() override
GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override
GHOST_TSuccess setState(GHOST_TWindowState state) override
GHOST_TSuccess setWindowCursorVisibility(bool visible) override
GHOST_TWindowState getState() const override
bool getCursorGrabUseSoftwareDisplay() override
bool isDialog() const override
struct wl_surface * wl_surface_get() const
GHOST_TSuccess notify_decor_redraw()
bool getValid() const override
wl_fixed_t wl_fixed_from_window(wl_fixed_t value) const
std::string getTitle() const override
void getClientBounds(GHOST_Rect &bounds) const override
uint16_t getDPIHint() override
void clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const override
GHOST_TSuccess setClientHeight(uint32_t height) override
GHOST_TSuccess cursor_shape_refresh()
void screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const override
bool outputs_enter(GWL_Output *output)
GHOST_TSuccess invalidate() override
GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override
void setTitle(const char *title) override
GHOST_WindowWayland(GHOST_SystemWayland *system, const char *title, int32_t left, int32_t top, uint32_t width, uint32_t height, GHOST_TWindowState state, const GHOST_IWindow *parentWindow, GHOST_TDrawingContextType type, const bool is_dialog, const bool stereoVisual, const bool exclusive, const bool is_debug, const GHOST_GPUDevice &preferred_device)
GHOST_TSuccess setWindowCustomCursorShape(uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor) override
void getWindowBounds(GHOST_Rect &bounds) const override
GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) override
const std::vector< GWL_Output * > & outputs_get()
GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) const override
GHOST_TGrabCursorMode m_cursorGrab
bool m_wantStereoVisual
int32_t m_cursorGrabInitPos[2]
GHOST_TStandardCursor getCursorShape() const override
GHOST_TSuccess setDrawingContextType(GHOST_TDrawingContextType type) override
GHOST_TStandardCursor m_cursorShape
GHOST_TSuccess setSwapInterval(int interval) override
GHOST_TSuccess releaseNativeHandles()
GHOST_TAxisFlag m_cursorGrabAxis
bool getValid() const override
GHOST_TSuccess swapBuffers() override
GHOST_Window(uint32_t width, uint32_t height, GHOST_TWindowState state, const bool wantStereoVisual=false, const bool exclusive=false)
#define this
#define output
#define LOG(severity)
Definition log.h:32
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static ulong state[N]
int context(const bContext *C, const char *member, bContextDataResult *result)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
static blender::bke::bNodeSocketTemplate outputs[]
GHOST_SystemWayland * ghost_system
std::atomic< bool > pending_actions[PENDING_NUM]
wp_fractional_scale_v1 * fractional_scale_handle
GWL_XDG_Decor_Window * xdg_decor
wl_surface * surface
struct GWL_Window::@010326315124122277324314376072022231212274370133 xdg
wp_viewport * viewport
GWL_WindowScaleParams scale_params
xdg_activation_token_v1 * activation_token
std::mutex frame_pending_mutex
GWL_WindowFrame frame_pending
struct GWL_Window::@310321162320354357253022135112114002374335277051 wp
struct GWL_Window::@337062111102176044136163167265274324172115005254 wl
GWL_WindowFrame frame
GHOST_WindowWayland * ghost_window
struct GWL_Window::@143252353312142063020215326344240270244072233310 backend
GWL_WindowCursorCustomShape cursor_custom_shape
std::vector< GWL_Output * > outputs
GHOST_TDrawingContextType ghost_context_type
zxdg_toplevel_decoration_v1 * toplevel_decor
struct GWL_XDG_Decor_Window::@375160072244110344205330257060327362123266024236 pending
enum zxdg_toplevel_decoration_v1_mode mode
i
Definition text_draw.cc:230
#define wl_display_dispatch_pending(...)
#define wl_display_dispatch(...)
#define wl_display_flush(...)
#define wl_display_roundtrip(...)
#define wl_egl_window_resize(...)
#define wl_egl_window_create(...)
#define wl_egl_window_destroy(...)
#define libdecor_frame_set_fullscreen(...)
#define libdecor_frame_map(...)
#define libdecor_state_new(...)
#define libdecor_frame_unset_fullscreen(...)
#define libdecor_configuration_get_content_size(...)
#define libdecor_state_free(...)
#define libdecor_configuration_get_window_state(...)
#define libdecor_frame_unref(...)
#define libdecor_frame_unset_maximized(...)
#define libdecor_frame_get_xdg_toplevel(...)
#define libdecor_frame_set_app_id(...)
#define libdecor_frame_set_min_content_size(...)
#define libdecor_frame_set_parent(...)
#define libdecor_frame_set_title(...)
#define libdecor_frame_set_minimized(...)
#define libdecor_frame_commit(...)
#define libdecor_decorate(...)
#define libdecor_frame_set_maximized(...)