Blender V5.0
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(const 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, const 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, const 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{
286 cg->free_fn(cg);
287}
288
290{
291 if (*cg == nullptr) {
292 return;
293 }
295 *cg = nullptr;
296}
297
303
305 const GHOST_CursorGenerator *cg,
306 GHOST_SystemWayland *system)
307{
308#ifdef USE_EVENT_BACKGROUND_THREAD
309 GHOST_ASSERT(system->main_thread_id == std::this_thread::get_id(), "Only from main thread!");
310#endif
311
312 if (shape == GHOST_kStandardCursorCustom) {
313 const GHOST_TSuccess ok = gwl_window_cursor_custom_load(*cg, system);
314 if (ok == GHOST_kSuccess) {
315 return ok;
316 }
318 system->cursor_shape_set(shape);
319 return GHOST_kFailure;
320 }
321
322 return system->cursor_shape_set(shape);
323}
324
326
327/* -------------------------------------------------------------------- */
330
331#ifdef USE_EVENT_BACKGROUND_THREAD
332
368# define PENDING_NUM (PENDING_WINDOW_CURSOR_SHAPE_REFRESH + 1)
369
370#endif /* USE_EVENT_BACKGROUND_THREAD */
371
379 int32_t size[2] = {0, 0};
380 bool is_maximised = false;
381 bool is_fullscreen = false;
382 bool is_active = false;
392
394 bool is_scale_init = false;
395};
396
398
400 struct {
401 wl_surface *surface = nullptr;
402 } wl;
403
405 struct {
406 wp_viewport *viewport = nullptr;
407
412 wp_fractional_scale_v1 *fractional_scale_handle = nullptr;
413 } wp;
414
416 struct {
418 xdg_activation_token_v1 *activation_token = nullptr;
420
421 struct {
422#ifdef WITH_OPENGL_BACKEND
423 wl_egl_window *egl_window = nullptr;
424#endif
425#ifdef WITH_VULKAN_BACKEND
426 GHOST_ContextVK_WindowInfo *vulkan_window_info = nullptr;
427#endif
429
433
440 std::vector<GWL_Output *> outputs;
441
442#ifdef WITH_GHOST_WAYLAND_LIBDECOR
443 GWL_LibDecor_Window *libdecor = nullptr;
444#endif
446
453
454#ifdef USE_EVENT_BACKGROUND_THREAD
460#endif
461
463
464 std::string title;
465
466 bool is_dialog = false;
467
469 bool is_init = false;
470
472 bool is_valid_setup = false;
473
476
477#ifdef USE_EVENT_BACKGROUND_THREAD
482 std::atomic<bool> pending_actions[PENDING_NUM] = {false};
483#endif /* USE_EVENT_BACKGROUND_THREAD */
484};
485
487{
488#ifdef WITH_OPENGL_BACKEND
489 if (win->ghost_context_type == GHOST_kDrawingContextTypeOpenGL) {
490 /* Null on window initialization. */
491 if (win->backend.egl_window) {
492 wl_egl_window_resize(win->backend.egl_window, UNPACK2(size), 0, 0);
493 }
494 }
495#endif
496#ifdef WITH_VULKAN_BACKEND
497 if (win->ghost_context_type == GHOST_kDrawingContextTypeVulkan) {
498 /* FIXME: unlike EGL, the underlying surface is not resized
499 * which is needed in situations where the buffer scale changes
500 * (moving the window to an output with a different scale for e.g.).
501 * Failing to do so can exit with a protocol error, see: #148243. */
502 win->backend.vulkan_window_info->size[0] = size[0];
503 win->backend.vulkan_window_info->size[1] = size[1];
504 }
505#endif
506}
507
508static void gwl_window_title_set(GWL_Window *win, const char *title)
509{
510#ifdef WITH_GHOST_WAYLAND_LIBDECOR
511 if (use_libdecor) {
512 GWL_LibDecor_Window &decor = *win->libdecor;
513 libdecor_frame_set_title(decor.frame, title);
514 }
515 else
516#endif
517 {
518 GWL_XDG_Decor_Window &decor = *win->xdg_decor;
519 xdg_toplevel_set_title(decor.toplevel, title);
520 }
521
522 win->title = title;
523}
524
526{
527 if (win->frame.is_fullscreen) {
529 }
530 if (win->frame.is_maximised) {
532 }
534}
535
536#ifdef WITH_GHOST_WAYLAND_LIBDECOR
540static bool gwl_window_state_set_for_libdecor(libdecor_frame *frame,
542 const GHOST_TWindowState state_current)
543{
544 switch (state) {
546 /* Unset states. */
547 switch (state_current) {
550 break;
551 }
554 break;
555 }
556 default: {
557 break;
558 }
559 }
560 break;
563 break;
564 }
567 break;
568 }
570 libdecor_frame_set_fullscreen(frame, nullptr);
571 break;
572 }
573 }
574 return true;
575}
576
577#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
578
582static bool gwl_window_state_set_for_xdg(xdg_toplevel *toplevel,
584 const GHOST_TWindowState state_current)
585{
586 switch (state) {
588 /* Unset states. */
589 switch (state_current) {
591 xdg_toplevel_unset_maximized(toplevel);
592 break;
593 }
595 xdg_toplevel_unset_fullscreen(toplevel);
596 break;
597 }
598 default: {
599 break;
600 }
601 }
602 break;
604 xdg_toplevel_set_maximized(toplevel);
605 break;
606 }
608 xdg_toplevel_set_minimized(toplevel);
609 break;
610 }
612 xdg_toplevel_set_fullscreen(toplevel, nullptr);
613 break;
614 }
615 }
616 return true;
617}
618
620{
621 const GHOST_TWindowState state_current = gwl_window_state_get(win);
622 bool result;
623#ifdef WITH_GHOST_WAYLAND_LIBDECOR
624 if (use_libdecor) {
625 result = gwl_window_state_set_for_libdecor(win->libdecor->frame, state, state_current);
626 }
627 else
628#endif
629 {
631 }
632 return result;
633}
634
636
637/* -------------------------------------------------------------------- */
643
648static int gwl_window_fractional_to_viewport(const GWL_WindowFrame &frame, const int value)
649{
650 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
651 return (value * frame.fractional_scale) / FRACTIONAL_DENOMINATOR;
652}
653
658static int gwl_window_fractional_from_viewport(const GWL_WindowFrame &frame, const int value)
659{
660 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
661 return (value * FRACTIONAL_DENOMINATOR) / frame.fractional_scale;
662}
663
664/* NOTE: rounded versions are needed for window-frame dimensions conversions.
665 * (rounding is part of the WAYLAND spec). All other conversions such as cursor coordinates
666 * can used simple integer division as rounding is not defined in this case. */
667
668static int gwl_window_fractional_to_viewport_round(const GWL_WindowFrame &frame, const int value)
669{
670 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
671 return lroundf(double(value * frame.fractional_scale) / double(FRACTIONAL_DENOMINATOR));
672}
673
674static int gwl_window_fractional_from_viewport_round(const GWL_WindowFrame &frame, const int value)
675{
676 GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initialized!");
677 return lroundf(double(value * FRACTIONAL_DENOMINATOR) / double(frame.fractional_scale));
678}
679
681 bool *r_surface_needs_commit,
682 bool *r_surface_needs_buffer_scale)
683{
684 if (win->wp.viewport != nullptr) {
685 return false;
686 }
687 wp_viewporter *viewporter = win->ghost_system->wp_viewporter_get();
688 if (viewporter == nullptr) {
689 return false;
690 }
691 win->wp.viewport = wp_viewporter_get_viewport(viewporter, win->wl.surface);
692 if (win->wp.viewport == nullptr) {
693 return false;
694 }
695
696 /* Set the buffer scale to 1 since a viewport will be used. */
697 if (win->frame.buffer_scale != 1) {
698 win->frame.buffer_scale = 1;
699
700 if (r_surface_needs_buffer_scale) {
701 *r_surface_needs_buffer_scale = true;
702 }
703 else {
704 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
705 }
706
707 if (r_surface_needs_commit) {
708 *r_surface_needs_commit = true;
709 }
710 else {
711 wl_surface_commit(win->wl.surface);
712 }
713 }
714
715 return true;
716}
717
719 bool *r_surface_needs_commit,
720 bool *r_surface_needs_buffer_scale)
721{
722 if (win->wp.viewport == nullptr) {
723 return false;
724 }
725
726 wp_viewport_destroy(win->wp.viewport);
727 win->wp.viewport = nullptr;
728
729 GHOST_ASSERT(win->frame.buffer_scale == 1, "Unexpected scale!");
730 if (win->frame_pending.buffer_scale != win->frame.buffer_scale) {
732
733 if (r_surface_needs_buffer_scale) {
734 *r_surface_needs_buffer_scale = true;
735 }
736 else {
737 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
738 }
739
740 if (r_surface_needs_commit) {
741 *r_surface_needs_commit = true;
742 }
743 else {
744 wl_surface_commit(win->wl.surface);
745 }
746 }
747 return true;
748}
749
751{
752 if (win->wp.viewport == nullptr) {
753 return false;
754 }
755
756 /* Setting `wp_viewport_set_source` isn't necessary as an unset value is ensured on creation
757 * and documented to use the entire buffer, further this can crash with NVIDIA, see: #117531. */
758
759 wp_viewport_set_destination(
760 win->wp.viewport,
763 return true;
764}
765
767
768/* -------------------------------------------------------------------- */
771
779{
780 GHOST_SystemWayland *system = win->ghost_system;
781 xdg_activation_v1 *activation_manager = system->xdg_activation_manager_get();
782 if (UNLIKELY(activation_manager == nullptr)) {
783 return;
784 }
785
786 if (win->xdg.activation_token) {
787 /* We're about to overwrite this with a new request. */
788 xdg_activation_token_v1_destroy(win->xdg.activation_token);
789 }
790 win->xdg.activation_token = xdg_activation_v1_get_activation_token(activation_manager);
791
792 xdg_activation_token_v1_add_listener(
794
795 xdg_activation_token_v1_set_app_id(win->xdg.activation_token,
797
798 /* The serial of the input device requesting activation. */
799 {
800 uint32_t serial = 0;
801 wl_seat *seat = system->wl_seat_active_get_with_input_serial(serial);
802 if (seat) {
803 xdg_activation_token_v1_set_serial(win->xdg.activation_token, serial, seat);
804 }
805 }
806
807 /* The surface of the window requesting activation. */
808 {
809 GHOST_WindowWayland *ghost_window_active = static_cast<GHOST_WindowWayland *>(
810 system->getWindowManager()->getActiveWindow());
811 if (ghost_window_active) {
812 wl_surface *surface = ghost_window_active->wl_surface_get();
813 if (surface) {
814 xdg_activation_token_v1_set_surface(win->xdg.activation_token, surface);
815 }
816 }
817 }
818
819 xdg_activation_token_v1_commit(win->xdg.activation_token);
820}
821
823
824/* -------------------------------------------------------------------- */
827
829 GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
830{
833 gwl_window_viewport_set(win, r_surface_needs_commit, r_surface_needs_buffer_scale);
835 }
836 else {
837 if (win->wp.viewport) {
838 gwl_window_viewport_unset(win, r_surface_needs_commit, r_surface_needs_buffer_scale);
839 }
840 else {
842 if (r_surface_needs_buffer_scale) {
843 *r_surface_needs_buffer_scale = true;
844 }
845 else {
846 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
847 }
848 if (r_surface_needs_commit) {
849 *r_surface_needs_commit = true;
850 }
851 else {
852 wl_surface_commit(win->wl.surface);
853 }
854 }
855 }
856}
857
859 bool *r_surface_needs_commit,
860 bool *r_surface_needs_buffer_scale)
861{
864 {
865 return;
866 }
868 win, r_surface_needs_commit, r_surface_needs_buffer_scale);
869}
870
872 bool *r_surface_needs_commit,
873 bool *r_surface_needs_resize_for_backend,
874 bool *r_surface_needs_buffer_scale)
875{
876 if (win->frame_pending.size[0] == 0 || win->frame_pending.size[1] == 0) {
877 return;
878 }
879
880 win->frame.size[0] = win->frame_pending.size[0];
881 win->frame.size[1] = win->frame_pending.size[1];
882
885 {
887 win, r_surface_needs_commit, r_surface_needs_buffer_scale);
888 }
889 else {
891 }
892
893 if (r_surface_needs_resize_for_backend) {
894 *r_surface_needs_resize_for_backend = true;
895 }
896 else {
898 }
899
901
902 win->frame_pending.size[0] = 0;
903 win->frame_pending.size[1] = 0;
904}
905
907
908#ifdef USE_EVENT_BACKGROUND_THREAD
909
911{
912 win->pending_actions[int(type)].store(true);
914}
915
917{
918 /* Ensure pending actions always use the state when the function starts
919 * because one actions may trigger other pending actions an in that case
920 * exact behavior depends on the order functions are called here.
921 * Without this, configuring the frame will trigger the surface
922 * commit immediately instead of the next time pending actions are handled. */
923 bool actions[PENDING_NUM];
924 for (size_t i = 0; i < ARRAY_SIZE(actions); i++) {
925 actions[i] = win->pending_actions[i].exchange(false);
926 }
927
928 if (actions[PENDING_WINDOW_FRAME_CONFIGURE]) {
930 }
933 /* Force postponing scale update to ensure all scale information has been taken into account
934 * before the actual update is performed. Failing to do so tends to cause flickering. */
935 actions[PENDING_OUTPUT_SCALE_UPDATE] = false;
936 }
937 if (actions[PENDING_OUTPUT_SCALE_UPDATE]) {
939 }
940 if (actions[PENDING_WINDOW_SURFACE_COMMIT]) {
941 wl_surface_commit(win->wl.surface);
942 }
946 }
947}
948
949#endif /* USE_EVENT_BACKGROUND_THREAD */
950
956{
957#ifdef USE_EVENT_BACKGROUND_THREAD
958 GHOST_ASSERT(win->ghost_system->main_thread_id == std::this_thread::get_id(),
959 "Only from main thread!");
960
961#endif
962
963 const bool dpi_changed = win->frame_pending.fractional_scale != win->frame.fractional_scale;
964 bool surface_needs_commit = false;
965 bool surface_needs_resize_for_backend = false;
966 bool surface_needs_buffer_scale = false;
967
968 if (win->frame_pending.size[0] != 0 && win->frame_pending.size[1] != 0) {
969 if ((win->frame.size[0] != win->frame_pending.size[0]) ||
970 (win->frame.size[1] != win->frame_pending.size[1]))
971 {
973 &surface_needs_commit,
974 &surface_needs_resize_for_backend,
975 &surface_needs_buffer_scale);
976 }
977 }
978
981 win, &surface_needs_commit, &surface_needs_buffer_scale);
982 }
983 else {
984 if (win->frame_pending.buffer_scale != win->frame.buffer_scale) {
986 surface_needs_buffer_scale = true;
987 }
988 }
989
990 if (surface_needs_resize_for_backend) {
992 }
993
994 if (surface_needs_buffer_scale) {
995 wl_surface_set_buffer_scale(win->wl.surface, win->frame.buffer_scale);
996 }
997
998#ifdef WITH_GHOST_WAYLAND_LIBDECOR
999 if (use_libdecor) {
1000 GWL_LibDecor_Window &decor = *win->libdecor;
1001 if (decor.pending.ack_configure) {
1002 surface_needs_commit = true;
1003
1004 decor.pending.ack_configure = false;
1005
1006 libdecor_state *state = libdecor_state_new(UNPACK2(decor.pending.size));
1007
1008# ifdef USE_LIBDECOR_CONFIG_COPY_QUEUE
1009 GHOST_ASSERT(decor.pending.size[0] != 0 && decor.pending.size[1] != 0, "Invalid size");
1010 for (libdecor_configuration *configuration : decor.pending.configuration_queue) {
1011 libdecor_frame_commit(decor.frame, state, configuration);
1012 ghost_wl_libdecor_configuration_free(configuration);
1013 }
1014 decor.pending.configuration_queue.clear();
1015# endif
1016
1017 libdecor_frame_commit(decor.frame, state, decor.pending.configuration);
1018
1020
1021 decor.pending.size[0] = 0;
1022 decor.pending.size[1] = 0;
1023
1024 if (decor.initial_configure_seen == false) {
1025 decor.initial_configure_seen = true;
1026
1027 if (decor.initial_configure_state) {
1028 xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame);
1030 toplevel, decor.initial_configure_state.value(), gwl_window_state_get(win));
1031 decor.initial_configure_state = std::nullopt;
1032 }
1033 }
1034
1035# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1036 if (decor.pending.configuration_needs_free) {
1037 ghost_wl_libdecor_configuration_free(decor.pending.configuration);
1038 decor.pending.configuration_needs_free = false;
1039 }
1040# endif
1041
1042 decor.pending.configuration = nullptr;
1043 }
1044 }
1045 else
1046#endif
1047 if (win->xdg_decor)
1048 {
1049 GWL_XDG_Decor_Window &decor = *win->xdg_decor;
1050 if (decor.pending.ack_configure) {
1051 xdg_surface_ack_configure(decor.surface, decor.pending.ack_configure_serial);
1052 /* The XDG spec states a commit event is required after ACK configure. */
1053 surface_needs_commit = true;
1054
1055 decor.pending.ack_configure = false;
1056 decor.pending.ack_configure_serial = 0;
1057
1058 decor.initial_configure_seen = true;
1059 }
1060 }
1061
1062 if (surface_needs_commit) {
1063#ifdef USE_EVENT_BACKGROUND_THREAD
1064 /* Postponing the commit avoids flickering when moving between monitors of different scale. */
1066#else
1067 wl_surface_commit(win->wl.surface);
1068#endif
1069 }
1070
1071 if (dpi_changed) {
1072 GHOST_SystemWayland *system = win->ghost_system;
1073 system->pushEvent(new GHOST_Event(
1075 }
1076
1077 if (win->frame.is_active != win->frame_pending.is_active) {
1078 if (win->frame_pending.is_active) {
1079 win->ghost_window->activate();
1080 }
1081 else {
1082 win->ghost_window->deactivate();
1083 }
1084 }
1085 else if (false) {
1086 /* Disabled, this can happen during debugging
1087 * when the window changed while the process has been paused. */
1089 win->frame.is_active ==
1091 "GHOST internal active state does not match WAYLAND!");
1092 }
1093
1094 win->frame_pending.size[0] = win->frame.size[0];
1095 win->frame_pending.size[1] = win->frame.size[1];
1096
1097 win->frame = win->frame_pending;
1098
1099 /* Signal not to apply the scale unless it's configured. */
1100 win->frame_pending.size[0] = 0;
1101 win->frame_pending.size[1] = 0;
1102}
1103
1104[[maybe_unused]] static void gwl_window_frame_update_from_pending(GWL_Window *win)
1105{
1106#ifdef USE_EVENT_BACKGROUND_THREAD
1107 std::lock_guard lock_frame_guard{win->frame_pending_mutex};
1108#endif
1110}
1111
1113
1114/* -------------------------------------------------------------------- */
1117
1121static int output_scale_cmp(const GWL_Output *output_a, const GWL_Output *output_b)
1122{
1123 if (output_a->scale < output_b->scale) {
1124 return -1;
1125 }
1126 if (output_a->scale > output_b->scale) {
1127 return 1;
1128 }
1129 if (output_a->has_scale_fractional || output_b->has_scale_fractional) {
1130 const int scale_fractional_a = output_a->has_scale_fractional ?
1131 output_a->scale_fractional :
1132 (output_a->scale * FRACTIONAL_DENOMINATOR);
1133 const int scale_fractional_b = output_b->has_scale_fractional ?
1134 output_b->scale_fractional :
1135 (output_b->scale * FRACTIONAL_DENOMINATOR);
1136 if (scale_fractional_a < scale_fractional_b) {
1137 return -1;
1138 }
1139 if (scale_fractional_a > scale_fractional_b) {
1140 return 1;
1141 }
1142 }
1143 return 0;
1144}
1145
1146static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs,
1147 const int32_t scale_default,
1148 int *r_scale_fractional)
1149{
1150 const GWL_Output *output_max = nullptr;
1151 for (const GWL_Output *reg_output : outputs) {
1152 if (!output_max || (output_scale_cmp(output_max, reg_output) == -1)) {
1153 output_max = reg_output;
1154 }
1155 }
1156
1157 if (output_max) {
1158 if (r_scale_fractional) {
1159 *r_scale_fractional = output_max->has_scale_fractional ?
1160 output_max->scale_fractional :
1161 (output_max->scale * FRACTIONAL_DENOMINATOR);
1162 }
1163 return output_max->scale;
1164 }
1165 if (r_scale_fractional) {
1166 *r_scale_fractional = scale_default * FRACTIONAL_DENOMINATOR;
1167 }
1168 return scale_default;
1169}
1170
1171static int outputs_uniform_scale_or_default(const std::vector<GWL_Output *> &outputs,
1172 const int32_t scale_default,
1173 int *r_scale_fractional)
1174{
1175 const GWL_Output *output_uniform = nullptr;
1176 for (const GWL_Output *reg_output : outputs) {
1177 if (!output_uniform) {
1178 output_uniform = reg_output;
1179 }
1180 else if (output_scale_cmp(output_uniform, reg_output) != 0) {
1181 /* Non-uniform. */
1182 output_uniform = nullptr;
1183 break;
1184 }
1185 }
1186
1187 if (output_uniform) {
1188 if (r_scale_fractional) {
1189 *r_scale_fractional = output_uniform->has_scale_fractional ?
1190 output_uniform->scale_fractional :
1191 (output_uniform->scale * FRACTIONAL_DENOMINATOR);
1192 }
1193 return output_uniform->scale;
1194 }
1195 if (r_scale_fractional) {
1196 *r_scale_fractional = scale_default * FRACTIONAL_DENOMINATOR;
1197 }
1198 return scale_default;
1199}
1200
1202
1203/* -------------------------------------------------------------------- */
1208
1209#ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1210
1211static libdecor_configuration *ghost_wl_libdecor_configuration_copy(
1212 const libdecor_configuration *configuration)
1213{
1214 size_t configuration_size = malloc_usable_size((void *)configuration);
1215 libdecor_configuration *configuration_copy = (libdecor_configuration *)malloc(
1216 configuration_size);
1217 memcpy((void *)configuration_copy, (const void *)configuration, configuration_size);
1218 return configuration_copy;
1219}
1220
1221static void ghost_wl_libdecor_configuration_free(libdecor_configuration *configuration)
1222{
1223 free((void *)configuration);
1224}
1225
1226#endif /* USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
1227
1229
1230/* -------------------------------------------------------------------- */
1233
1234static CLG_LogRef LOG_WL_XDG_TOPLEVEL = {"ghost.wl.handle.xdg_toplevel"};
1235#define LOG (&LOG_WL_XDG_TOPLEVEL)
1236
1238 xdg_toplevel * /*xdg_toplevel*/,
1239 const int32_t width,
1240 const int32_t height,
1241 wl_array *states)
1242{
1243 /* TODO: log `states`, not urgent. */
1244 CLOG_DEBUG(LOG, "configure (size=[%d, %d])", width, height);
1245
1246 GWL_Window *win = static_cast<GWL_Window *>(data);
1247
1248#ifdef USE_EVENT_BACKGROUND_THREAD
1249 std::lock_guard lock_frame_guard{win->frame_pending_mutex};
1250#endif
1251
1252 const int32_t size[2] = {width, height};
1253 for (int i = 0; i < 2; i++) {
1254 if (size[i] == 0) {
1255 /* Values may be zero, in this case the client should choose. */
1256 continue;
1257 }
1260 (size[i] * win->frame.buffer_scale);
1261 }
1262
1263 win->frame_pending.is_maximised = false;
1264 win->frame_pending.is_fullscreen = false;
1265 win->frame_pending.is_active = false;
1266
1267 enum xdg_toplevel_state *state;
1268 WL_ARRAY_FOR_EACH (state, states) {
1269 switch (*state) {
1270 case XDG_TOPLEVEL_STATE_MAXIMIZED:
1271 win->frame_pending.is_maximised = true;
1272 break;
1273 case XDG_TOPLEVEL_STATE_FULLSCREEN:
1274 win->frame_pending.is_fullscreen = true;
1275 break;
1276 case XDG_TOPLEVEL_STATE_ACTIVATED:
1277 win->frame_pending.is_active = true;
1278 break;
1279 default:
1280 break;
1281 }
1282 }
1283}
1284
1285static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/)
1286{
1287 CLOG_DEBUG(LOG, "close");
1288
1289 GWL_Window *win = static_cast<GWL_Window *>(data);
1290
1291 win->ghost_window->close();
1292}
1293
1295 xdg_toplevel * /*xdg_toplevel*/,
1296 const int32_t width,
1297 const int32_t height)
1298{
1299 /* Only available in interface version 4. */
1300 CLOG_DEBUG(LOG, "configure_bounds (size=[%d, %d])", width, height);
1301
1302 /* No need to lock as this only runs on window creation. */
1303 GWL_Window *win = static_cast<GWL_Window *>(data);
1304 GWL_XDG_Decor_Window &decor = *win->xdg_decor;
1305 if (decor.initial_configure_seen == false) {
1306 decor.initial_bounds[0] = width;
1307 decor.initial_bounds[1] = height;
1308 }
1309}
1310static void xdg_toplevel_handle_wm_capabilities(void * /*data*/,
1311 xdg_toplevel * /*xdg_toplevel*/,
1312 wl_array * /*capabilities*/)
1313{
1314 /* Only available in interface version 5. */
1315 CLOG_DEBUG(LOG, "wm_capabilities");
1316
1317 /* NOTE: this would be useful if blender had CSD. */
1318}
1319
1321 /*configure*/ xdg_toplevel_handle_configure,
1322 /*close*/ xdg_toplevel_handle_close,
1323 /*configure_bounds*/ xdg_toplevel_handle_configure_bounds,
1324 /*wm_capabilities*/ xdg_toplevel_handle_wm_capabilities,
1325};
1326
1327#undef LOG
1328
1330
1331/* -------------------------------------------------------------------- */
1336
1338 xdg_activation_token_v1 *xdg_activation_token_v1,
1339 const char *token)
1340{
1341 GWL_Window *win = static_cast<GWL_Window *>(data);
1342 if (xdg_activation_token_v1 != win->xdg.activation_token) {
1343 return;
1344 }
1345
1346 GHOST_SystemWayland *system = win->ghost_system;
1347 xdg_activation_v1 *activation_manager = system->xdg_activation_manager_get();
1348 xdg_activation_v1_activate(activation_manager, token, win->wl.surface);
1349 xdg_activation_token_v1_destroy(win->xdg.activation_token);
1350 win->xdg.activation_token = nullptr;
1351}
1352
1353static const xdg_activation_token_v1_listener xdg_activation_listener = {
1355};
1356
1357static const xdg_activation_token_v1_listener *xdg_activation_listener_get()
1358{
1360}
1361
1363
1364/* -------------------------------------------------------------------- */
1369
1370static CLG_LogRef LOG_WL_FRACTIONAL_SCALE = {"ghost.wl.handle.fractional_scale"};
1371#define LOG (&LOG_WL_FRACTIONAL_SCALE)
1372
1374 void *data, wp_fractional_scale_v1 * /*wp_fractional_scale_v1*/, uint preferred_scale)
1375{
1376#ifdef USE_EVENT_BACKGROUND_THREAD
1377 std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
1378#endif
1380 "preferred_scale (preferred_scale=%.6f)",
1381 double(preferred_scale) / FRACTIONAL_DENOMINATOR);
1382
1383 GWL_Window *win = static_cast<GWL_Window *>(data);
1384
1385 if (win->frame_pending.fractional_scale_preferred != int(preferred_scale)) {
1386 win->frame_pending.fractional_scale_preferred = preferred_scale;
1388 }
1389}
1390
1391static const wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
1393};
1394
1395#undef LOG
1396
1398
1399/* -------------------------------------------------------------------- */
1402
1403#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1404
1405static CLG_LogRef LOG_WL_LIBDECOR_FRAME = {"ghost.wl.handle.libdecor_frame"};
1406# define LOG (&LOG_WL_LIBDECOR_FRAME)
1407
1408static void libdecor_frame_handle_configure(libdecor_frame *frame,
1409 libdecor_configuration *configuration,
1410 void *data)
1411{
1412 CLOG_DEBUG(LOG, "configure");
1413
1414# ifdef USE_EVENT_BACKGROUND_THREAD
1415 std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
1416 const bool is_main_thread = [data] {
1417 const GWL_Window *win = static_cast<GWL_Window *>(data);
1418 const GHOST_SystemWayland *system = win->ghost_system;
1419 return system->main_thread_id == std::this_thread::get_id();
1420 }();
1421# endif
1422
1423 GWL_WindowFrame &frame_pending = static_cast<GWL_Window *>(data)->frame_pending;
1424
1425 /* Set the size. */
1426 int size_next[2] = {0, 0};
1427
1428 {
1429 GWL_Window *win = static_cast<GWL_Window *>(data);
1430 const int fractional_scale = win->frame.fractional_scale;
1431 /* It's important `fractional_scale` has a fractional component or rounding up will fail
1432 * to produce the correct whole-number scale. */
1433 GHOST_ASSERT((fractional_scale == 0) ||
1434 (gwl_round_int_test(fractional_scale, FRACTIONAL_DENOMINATOR) == false),
1435 "Fractional scale has no fractional component!");
1436 /* The size from LIBDECOR wont use the GHOST windows buffer size.
1437 * so it's important to calculate the buffer size that would have been used
1438 * if fractional scaling wasn't supported. */
1439 const int scale = fractional_scale ? (fractional_scale / FRACTIONAL_DENOMINATOR) + 1 :
1440 win->frame.buffer_scale;
1441 const int scale_as_fractional = scale * FRACTIONAL_DENOMINATOR;
1443 configuration, frame, &size_next[0], &size_next[1]))
1444 {
1445 if (fractional_scale) {
1446 frame_pending.size[0] = gwl_window_fractional_to_viewport_round(win->frame, size_next[0]);
1447 frame_pending.size[1] = gwl_window_fractional_to_viewport_round(win->frame, size_next[1]);
1448 }
1449 else if (fractional_scale && (fractional_scale != (scale * FRACTIONAL_DENOMINATOR))) {
1450 /* The windows `preferred_scale` is not yet available,
1451 * set the size as if fractional scale is available. */
1452 frame_pending.size[0] = ((size_next[0] * scale) * fractional_scale) / scale_as_fractional;
1453 frame_pending.size[1] = ((size_next[1] * scale) * fractional_scale) / scale_as_fractional;
1454 }
1455 else {
1456 frame_pending.size[0] = size_next[0] * scale;
1457 frame_pending.size[1] = size_next[1] * scale;
1458 }
1459
1460 /* Account for buffer rounding requirement, once fractional scaling is enabled
1461 * the buffer scale will be 1, rounding is a requirement until then. */
1462 gwl_round_int2_by(frame_pending.size, win->frame.buffer_scale);
1463 }
1464 else {
1465 /* These values are cleared after use & will practically always be zero.
1466 * Read them because it's possible multiple configure calls run before they can be handled.
1467 */
1468 const GWL_LibDecor_Window &decor = *win->libdecor;
1469 size_next[0] = decor.pending.size[0];
1470 size_next[1] = decor.pending.size[1];
1471 }
1472 }
1473
1474 /* Set the state. */
1475 {
1476 enum libdecor_window_state window_state;
1477 if (libdecor_configuration_get_window_state(configuration, &window_state)) {
1478 frame_pending.is_maximised = window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED;
1479 frame_pending.is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN;
1480 frame_pending.is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE;
1481 }
1482 }
1483
1484 {
1485 GWL_Window *win = static_cast<GWL_Window *>(data);
1486 GWL_LibDecor_Window &decor = *win->libdecor;
1487
1488# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1489 /* Unlikely but possible a previous configuration is unhandled. */
1490 if (decor.pending.configuration_needs_free) {
1491 ghost_wl_libdecor_configuration_free(decor.pending.configuration);
1492 decor.pending.configuration_needs_free = false;
1493 }
1494# endif /* USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
1495
1496 decor.pending.size[0] = size_next[0];
1497 decor.pending.size[1] = size_next[1];
1498 decor.pending.configuration = configuration;
1499 decor.pending.ack_configure = true;
1500
1501# ifdef USE_EVENT_BACKGROUND_THREAD
1502 if (!is_main_thread) {
1503# ifdef USE_LIBDECOR_CONFIG_COPY_WORKAROUND
1504 decor.pending.configuration = ghost_wl_libdecor_configuration_copy(configuration);
1505 decor.pending.configuration_needs_free = true;
1506# else
1507 /* Without a way to copy the configuration,
1508 * the configuration will be ignored as it can't be postponed. */
1509 decor.pending.configuration = nullptr;
1510# endif /* !USE_LIBDECOR_CONFIG_COPY_WORKAROUND */
1511 }
1512# endif
1513
1514# ifdef USE_LIBDECOR_CONFIG_COPY_QUEUE
1515 if (!(size_next[0] && size_next[1])) {
1516 /* Always copy. */
1517 if (decor.pending.configuration_needs_free == false) {
1518 decor.pending.configuration = ghost_wl_libdecor_configuration_copy(
1519 decor.pending.configuration);
1520 decor.pending.configuration_needs_free = true;
1521 }
1522 /* Transfer ownership to the queue. */
1523 decor.pending.configuration_queue.push_back(decor.pending.configuration);
1524 decor.pending.configuration = nullptr;
1525 decor.pending.configuration_needs_free = false;
1526 /* Wait until we have a valid size. */
1527 decor.pending.ack_configure = false;
1528 }
1529# endif /* USE_LIBDECOR_CONFIG_COPY_QUEUE */
1530 }
1531
1532 /* Apply & commit the changes. */
1533 {
1534 GWL_Window *win = static_cast<GWL_Window *>(data);
1535# ifdef USE_EVENT_BACKGROUND_THREAD
1536 if (!is_main_thread) {
1538 }
1539 else
1540# endif
1541 {
1543 }
1544 }
1545}
1546
1547static void libdecor_frame_handle_close(libdecor_frame * /*frame*/, void *data)
1548{
1549 CLOG_DEBUG(LOG, "close");
1550
1551 GWL_Window *win = static_cast<GWL_Window *>(data);
1552
1553 win->ghost_window->close();
1554}
1555
1556static void libdecor_frame_handle_commit(libdecor_frame * /*frame*/, void *data)
1557{
1558 CLOG_DEBUG(LOG, "commit");
1559
1560# if 0
1561 GWL_Window *win = static_cast<GWL_Window *>(data);
1563# else
1564 (void)data;
1565# endif
1566}
1567
1568/* NOTE: cannot be `const` because of the LIBDECOR API. */
1569static libdecor_frame_interface libdecor_frame_iface = {
1570 /*configure*/ libdecor_frame_handle_configure,
1571 /*close*/ libdecor_frame_handle_close,
1572 /*commit*/ libdecor_frame_handle_commit,
1573};
1574
1575# undef LOG
1576
1577#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
1578
1580
1581/* -------------------------------------------------------------------- */
1584
1585static CLG_LogRef LOG_WL_XDG_TOPLEVEL_DECORATION = {"ghost.wl.handle.xdg_toplevel_decoration"};
1586#define LOG (&LOG_WL_XDG_TOPLEVEL_DECORATION)
1587
1589 void *data, zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/, const uint32_t mode)
1590{
1591 CLOG_DEBUG(LOG, "configure (mode=%u)", mode);
1592
1593 GWL_Window *win = static_cast<GWL_Window *>(data);
1594
1595 win->xdg_decor->mode = (zxdg_toplevel_decoration_v1_mode)mode;
1596}
1597
1598static const zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_v1_listener = {
1600};
1601
1602#undef LOG
1603
1605
1606/* -------------------------------------------------------------------- */
1609
1610static CLG_LogRef LOG_WL_XDG_SURFACE = {"ghost.wl.handle.xdg_surface"};
1611#define LOG (&LOG_WL_XDG_SURFACE)
1612
1614 xdg_surface *xdg_surface,
1615 const uint32_t serial)
1616{
1617 GWL_Window *win = static_cast<GWL_Window *>(data);
1618
1619 if (win->xdg_decor->surface != xdg_surface) {
1620 CLOG_DEBUG(LOG, "configure (skipped)");
1621 return;
1622 }
1623 CLOG_DEBUG(LOG, "configure");
1624
1625#ifdef USE_EVENT_BACKGROUND_THREAD
1626 std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
1627#endif
1628 win->xdg_decor->pending.ack_configure = true;
1629 win->xdg_decor->pending.ack_configure_serial = serial;
1630
1631#ifdef USE_EVENT_BACKGROUND_THREAD
1632 const GHOST_SystemWayland *system = win->ghost_system;
1633 const bool is_main_thread = system->main_thread_id == std::this_thread::get_id();
1634 if (!is_main_thread) {
1635 /* NOTE(@ideasman42): this only gets one redraw,
1636 * I could not find a case where this causes problems. */
1638 }
1639 else
1640#endif
1641 {
1643 }
1644}
1645
1649
1650#undef LOG
1651
1653
1654/* -------------------------------------------------------------------- */
1657
1658static CLG_LogRef LOG_WL_SURFACE = {"ghost.wl.handle.surface"};
1659#define LOG (&LOG_WL_SURFACE)
1660
1661static void surface_handle_enter(void *data, wl_surface * /*wl_surface*/, wl_output *wl_output)
1662{
1663 if (!ghost_wl_output_own(wl_output)) {
1664 CLOG_DEBUG(LOG, "enter (skipped)");
1665 return;
1666 }
1667 CLOG_DEBUG(LOG, "enter");
1668
1669 GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
1670 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(data);
1671 if (win->outputs_enter(reg_output)) {
1673 }
1674}
1675
1676static void surface_handle_leave(void *data, wl_surface * /*wl_surface*/, wl_output *wl_output)
1677{
1678 if (!ghost_wl_output_own(wl_output)) {
1679 CLOG_DEBUG(LOG, "leave (skipped)");
1680 return;
1681 }
1682 CLOG_DEBUG(LOG, "leave");
1683
1684 GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
1685 GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(data);
1686 if (win->outputs_leave(reg_output)) {
1688 }
1689}
1690
1691#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
1692 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
1693static void surface_handle_preferred_buffer_scale(void * /*data*/,
1694 wl_surface * /*wl_surface*/,
1695 const int32_t factor)
1696{
1697 /* Only available in interface version 6. */
1698 CLOG_DEBUG(LOG, "handle_preferred_buffer_scale (factor=%d)", factor);
1699}
1700
1701static void surface_handle_preferred_buffer_transform(void * /*data*/,
1702 wl_surface * /*wl_surface*/,
1703 const uint32_t transform)
1704{
1705 /* Only available in interface version 6. */
1706 CLOG_DEBUG(LOG, "handle_preferred_buffer_transform (transform=%u)", transform);
1707}
1708#endif /* WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION && \
1709 * WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION */
1710
1712 /*enter*/ surface_handle_enter,
1713 /*leave*/ surface_handle_leave,
1714#if defined(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION) && \
1715 defined(WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION)
1716 /*preferred_buffer_scale*/ surface_handle_preferred_buffer_scale,
1717 /*preferred_buffer_transform*/ surface_handle_preferred_buffer_transform,
1718#endif
1719};
1720
1721#undef LOG
1722
1724
1725/* -------------------------------------------------------------------- */
1730
1732 const char *title,
1733 const int32_t /*left*/,
1734 const int32_t /*top*/,
1735 const uint32_t width,
1736 const uint32_t height,
1738 const GHOST_IWindow *parent_window,
1739 const GHOST_TDrawingContextType type,
1740 const bool is_dialog,
1741 const GHOST_ContextParams &context_params,
1742 const bool exclusive,
1743 const GHOST_GPUDevice &preferred_device)
1744 : GHOST_Window(width, height, state, context_params, exclusive),
1745 system_(system),
1746 window_(new GWL_Window),
1747 preferred_device_(preferred_device)
1748{
1749#ifdef USE_EVENT_BACKGROUND_THREAD
1750 std::lock_guard lock_server_guard{*system->server_mutex};
1751#endif
1752
1753 window_->ghost_window = this;
1754 window_->ghost_system = system;
1755 window_->ghost_context_type = type;
1756
1757 wl_display *display = system->wl_display_get();
1758
1759 /* NOTE(@ideasman42): The scale set here to avoid flickering on startup.
1760 * When all monitors use the same scale (which is quite common) there aren't any problems.
1761 *
1762 * When monitors have different scales there may still be a visible window resize on startup.
1763 * Ideally it would be possible to know the scale this window will use however that's only
1764 * known once #surface_enter callback runs (which isn't guaranteed to run at all).
1765 *
1766 * Using the maximum scale is best as it results in the window first being smaller,
1767 * avoiding a large window flashing before it's made smaller.
1768 *
1769 * For fractional scaling the buffer will eventually be 1. Setting it to 1 now
1770 * (to avoid window size rounding and buffer size switching) has some down-sides.
1771 * It means the window will be drawn larger for a moment then smaller once fractional scaling
1772 * is detected and enabled. Unfortunately, it doesn't seem possible to receive the
1773 * #wp_fractional_scale_v1_listener::preferred_scale information before the window is created
1774 * So leave the buffer scaled up because there is no *guarantee* the fractional scaling support
1775 * will run which could result in an incorrect buffer scale. */
1776 int scale_fractional_from_output;
1777 const int buffer_scale_from_output = outputs_uniform_scale_or_default(
1778 system_->outputs_get(), 0, &scale_fractional_from_output);
1779
1780 window_->frame.size[0] = int32_t(width);
1781 window_->frame.size[1] = int32_t(height);
1782
1783 window_->is_dialog = is_dialog;
1784
1785 /* Window surfaces. */
1786 window_->wl.surface = wl_compositor_create_surface(system_->wl_compositor_get());
1787 ghost_wl_surface_tag(window_->wl.surface);
1788
1789 wl_surface_add_listener(window_->wl.surface, &wl_surface_listener, window_);
1790
1791 wp_fractional_scale_manager_v1 *fractional_scale_manager =
1793 if (fractional_scale_manager) {
1794 window_->wp.fractional_scale_handle = wp_fractional_scale_manager_v1_get_fractional_scale(
1795 fractional_scale_manager, window_->wl.surface);
1796 wp_fractional_scale_v1_add_listener(
1797 window_->wp.fractional_scale_handle, &wp_fractional_scale_listener, window_);
1798 }
1799
1800 /* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels.
1801 * This has the advantage that the size limit is the same when moving the window between monitors
1802 * with different scales set. If it was important to limit in pixels it could be re-calculated
1803 * when the `window_->frame.buffer_scale` changed. */
1804 const int32_t size_min[2] = {320, 240};
1805
1806 const char *xdg_app_id = GHOST_SystemWayland::xdg_app_id_get();
1807
1808#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1809 if (use_libdecor) {
1810 window_->libdecor = new GWL_LibDecor_Window;
1811 GWL_LibDecor_Window &decor = *window_->libdecor;
1812
1813 /* create window decorations */
1814 decor.frame = libdecor_decorate(
1815 system_->libdecor_context_get(), window_->wl.surface, &libdecor_frame_iface, window_);
1816
1817 libdecor_frame_set_min_content_size(decor.frame, UNPACK2(size_min));
1818 libdecor_frame_set_app_id(decor.frame, xdg_app_id);
1819
1820 if (parent_window) {
1821 GWL_LibDecor_Window &decor_parent =
1822 *dynamic_cast<const GHOST_WindowWayland *>(parent_window)->window_->libdecor;
1823 libdecor_frame_set_parent(decor.frame, decor_parent.frame);
1824 }
1825 }
1826 else
1827#endif
1828 {
1829 window_->xdg_decor = new GWL_XDG_Decor_Window;
1830 GWL_XDG_Decor_Window &decor = *window_->xdg_decor;
1831 decor.surface = xdg_wm_base_get_xdg_surface(system_->xdg_decor_shell_get(),
1832 window_->wl.surface);
1833 decor.toplevel = xdg_surface_get_toplevel(decor.surface);
1834
1835 xdg_toplevel_set_min_size(decor.toplevel, UNPACK2(size_min));
1836 xdg_toplevel_set_app_id(decor.toplevel, xdg_app_id);
1837
1838 xdg_surface_add_listener(decor.surface, &xdg_surface_listener, window_);
1839 xdg_toplevel_add_listener(decor.toplevel, &xdg_toplevel_listener, window_);
1840
1841 if (parent_window && is_dialog) {
1842 GWL_XDG_Decor_Window &decor_parent =
1843 *dynamic_cast<const GHOST_WindowWayland *>(parent_window)->window_->xdg_decor;
1844 xdg_toplevel_set_parent(decor.toplevel, decor_parent.toplevel);
1845 }
1846 }
1847
1848 gwl_window_title_set(window_, title);
1849
1850#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1851 if (use_libdecor) {
1852 /* Postpone mapping the window until after the app-id & title have been set.
1853 * While this doesn't seem to be a requirement, LIBDECOR example code does this. */
1854 libdecor_frame_map(window_->libdecor->frame);
1855 }
1856#endif
1857
1858 wl_surface_set_user_data(window_->wl.surface, this);
1859
1860 /* NOTE: the method used for XDG & LIBDECOR initialization (using `initial_configure_seen`)
1861 * follows the method used in SDL 3.16. */
1862
1863 /* Causes a glitch with `libdecor` for some reason. */
1864#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1865 if (use_libdecor) {
1866 /* Pass. */
1867 }
1868 else
1869#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
1870 {
1871 GWL_XDG_Decor_Window &decor = *window_->xdg_decor;
1872
1873 if (system_->xdg_decor_manager_get()) {
1874 const bool use_window_frame = system_->use_window_frame_get();
1875 decor.toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration(
1876 system_->xdg_decor_manager_get(), decor.toplevel);
1877 zxdg_toplevel_decoration_v1_add_listener(
1879 /* Request client side decorations as a way of disabling decorations. */
1880 zxdg_toplevel_decoration_v1_set_mode(decor.toplevel_decor,
1881 use_window_frame ?
1882 ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE :
1883 ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
1884 }
1885
1886 /* Commit needed to so configure callback runs. */
1887 wl_surface_commit(window_->wl.surface);
1888
1889 /* Failure exits with an error, simply prevent an eternal loop. */
1891 wl_display_flush(display);
1892 wl_display_dispatch(display);
1893 }
1894 }
1895
1896 /* If the scale is known early, setup the window scale.
1897 * Otherwise accept an unsightly flicker once the outputs scale can be found. */
1898 int early_buffer_scale = 0;
1899 int early_fractional_scale = 0;
1900
1901 if (const int test_fractional_scale =
1902 fractional_scale_manager ? (window_->frame_pending.fractional_scale_preferred ?
1903 window_->frame_pending.fractional_scale_preferred :
1904 scale_fractional_from_output) :
1905 0)
1906 {
1907 if (!gwl_round_int_test(test_fractional_scale, FRACTIONAL_DENOMINATOR)) {
1908 early_fractional_scale = test_fractional_scale;
1909 }
1910 else {
1911 /* Rounded, use simple integer buffer scaling. */
1912 early_buffer_scale = test_fractional_scale / FRACTIONAL_DENOMINATOR;
1913 early_fractional_scale = 0;
1914 }
1915 }
1916 else if (buffer_scale_from_output) {
1917 early_buffer_scale = buffer_scale_from_output;
1918 }
1919
1920 if (early_fractional_scale != 0) {
1921 /* Fractional scale is known. */
1922
1923 window_->frame.fractional_scale_preferred = early_fractional_scale;
1924 window_->frame.fractional_scale = early_fractional_scale;
1925 window_->frame.buffer_scale = 1;
1926
1927 window_->frame_pending.fractional_scale_preferred = early_fractional_scale;
1928 window_->frame_pending.fractional_scale = early_fractional_scale;
1929 window_->frame_pending.buffer_scale = 1;
1930
1931 /* The scale is considered initialized now. */
1932 window_->frame_pending.is_scale_init = true;
1933
1934 /* Always commit and set the scale. */
1935 bool surface_needs_commit_dummy = false, surface_needs_buffer_scale_dummy = false;
1937 window_, &surface_needs_commit_dummy, &surface_needs_buffer_scale_dummy);
1938 }
1939 else if (early_buffer_scale != 0) {
1940 /* Non-fractional scale is known. */
1941
1942 /* No fractional scale, simple initialization. */
1943 window_->frame.buffer_scale = early_buffer_scale;
1944 window_->frame_pending.buffer_scale = early_buffer_scale;
1945
1946 /* The scale is considered initialized now. */
1947 window_->frame_pending.is_scale_init = true;
1948
1949 /* The window surface must be rounded to the scale,
1950 * failing to do so causes the WAYLAND-server to close the window immediately. */
1951 gwl_round_int2_by(window_->frame.size, window_->frame.buffer_scale);
1952 }
1953 else {
1954 /* Scale isn't known (the windows size may flicker when #outputs_changed_update_scale runs). */
1955 window_->frame.buffer_scale = 1;
1956 window_->frame_pending.buffer_scale = 1;
1957 GHOST_ASSERT(window_->frame_pending.is_scale_init == false,
1958 "An initialized scale is not expected");
1959 }
1960
1961 if (window_->frame_pending.is_scale_init) {
1962 /* If the output scale changes here it means the scale settings were not properly set. */
1964 "Fractional scale was not properly initialized");
1965 }
1966
1967 wl_surface_set_buffer_scale(window_->wl.surface, window_->frame.buffer_scale);
1968
1969 /* Apply Bounds.
1970 * Important to run after the buffer scale is known & before the buffer is created. */
1971#ifdef WITH_GHOST_WAYLAND_LIBDECOR
1972 if (use_libdecor) {
1973 /* Pass (unsupported). */
1974 }
1975 else
1976#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
1977 {
1978 const GWL_XDG_Decor_Window &decor = *window_->xdg_decor;
1979 if (decor.initial_bounds[0] && decor.initial_bounds[1]) {
1980 window_->frame.size[0] = std::min(window_->frame.size[0],
1981 decor.initial_bounds[0] * window_->frame.buffer_scale);
1982 window_->frame.size[1] = std::min(window_->frame.size[1],
1983 decor.initial_bounds[1] * window_->frame.buffer_scale);
1984 }
1985 }
1986
1987/* Postpone binding the buffer until after it's decor has been configured:
1988 * - Ensure the window is sized properly (with XDG window decorations), see: #113059.
1989 * - Avoids flickering on startup.
1990 */
1991#ifdef WITH_OPENGL_BACKEND
1992 if (type == GHOST_kDrawingContextTypeOpenGL) {
1993 window_->backend.egl_window = wl_egl_window_create(
1994 window_->wl.surface, int(window_->frame.size[0]), int(window_->frame.size[1]));
1995 }
1996#endif
1997#ifdef WITH_VULKAN_BACKEND
1998 if (type == GHOST_kDrawingContextTypeVulkan) {
1999 window_->backend.vulkan_window_info = new GHOST_ContextVK_WindowInfo;
2000 window_->backend.vulkan_window_info->size[0] = window_->frame.size[0];
2001 window_->backend.vulkan_window_info->size[1] = window_->frame.size[1];
2002
2003 /* There is no HDR on/off settings as on Windows, so from the Window side
2004 * consider it always enabled. But may still get disabled if Vulkan has no
2005 * appropriate surface format. */
2006 hdr_info_.hdr_enabled = true;
2007 hdr_info_.wide_gamut_enabled = true;
2008 hdr_info_.sdr_white_level = 1.0f;
2009 }
2010#endif
2011
2012#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2013# ifdef WITH_VULKAN_BACKEND
2014 const bool libdecor_wait_for_window_init = (type == GHOST_kDrawingContextTypeVulkan);
2015# else
2016 const bool libdecor_wait_for_window_init = false;
2017# endif
2018#endif
2019
2020 /* Drawing context. */
2022 /* This can happen when repeatedly creating windows, see #123096.
2023 * In this case #GHOST_WindowWayland::getValid will return false. */
2024 GHOST_PRINT("Failed to create drawing context" << std::endl);
2025 }
2026 else {
2027 window_->is_valid_setup = true;
2028 }
2029
2030 if (window_->is_valid_setup == false) {
2031 /* Don't attempt to setup the window if there is no context.
2032 * This window is considered invalid and will be removed. */
2033 }
2034 else
2035#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2036 if (use_libdecor && libdecor_wait_for_window_init)
2037 {
2038 /* Ensuring the XDG window has been created is *not* supported by VULKAN.
2039 *
2040 * Although this was once supported using a temporary SHM buffer,
2041 * a DMA buffer is now required by some drivers which turns out to be
2042 * impractical to create here, specially since it's only for a temporary buffer.
2043 *
2044 * Workaround the problem by postponing changes to the window state.
2045 * This causes minor but noticeable glitch when starting maximized,
2046 * where a rectangle is first shown before maximizing.
2047 * With EGL this also happens however maximizing is almost immediate.
2048 *
2049 * This can't be avoided at the moment since LIBDECOR requires the window
2050 * to be created before it's configured (sigh!).
2051 * This can be removed if CSD are implemented, see: #113795. */
2052 GWL_LibDecor_Window &decor = *window_->libdecor;
2053 decor.initial_configure_state = state;
2054 }
2055 else if (use_libdecor) {
2056 /* Commit needed so the top-level callbacks run (and `toplevel` can be accessed). */
2057 wl_surface_commit(window_->wl.surface);
2058 GWL_LibDecor_Window &decor = *window_->libdecor;
2059
2060 /* Additional round-trip is needed to ensure `xdg_toplevel` is set. */
2061 wl_display_roundtrip(display);
2062
2063 /* NOTE: LIBDECOR requires the window to be created & configured before the state can be set.
2064 * Workaround this by using the underlying `xdg_toplevel` */
2065
2066 /* Failure exits with an error, simply prevent an eternal loop. */
2067 while (!decor.initial_configure_seen && !ghost_wl_display_report_error_if_set(display)) {
2068 wl_display_flush(display);
2069 wl_display_dispatch(display);
2070 }
2071
2072 xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame);
2074
2075 /* NOTE(@ideasman42): Round trips are necessary with LIBDECOR on GNOME
2076 * because resizing later on and redrawing does *not* update as it should, see #119871.
2077 *
2078 * Without the round-trip here:
2079 * - The window will be created and this function will return using the requested buffer size,
2080 * instead of the window size which ends up being used (causing a visible flicker).
2081 * This has the down side that Blender's internal window state has the outdated size
2082 * which then gets immediately resized, causing a noticeable glitch.
2083 * - The window decorations will be displayed at the wrong size before refreshing
2084 * at the new size.
2085 * - On GNOME-Shell 46 shows the previous buffer-size under some conditions.
2086 *
2087 * In principle this could be used with XDG too however it causes problems with KDE
2088 * and some WLROOTS based compositors.
2089 */
2090 wl_display_roundtrip(display);
2091 }
2092 else
2093#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
2094 {
2095 gwl_window_state_set(window_, state);
2096 }
2097
2098 /* Commit after setting the buffer.
2099 * While postponing until after the buffer drawing is context is set
2100 * isn't essential, it reduces flickering. */
2101 wl_surface_commit(window_->wl.surface);
2102
2103#ifdef WITH_OPENGL_BACKEND
2104 if (type == GHOST_kDrawingContextTypeOpenGL) {
2105 /* NOTE(@ideasman42): Set the swap interval to 0 (disable VSync) to prevent blocking.
2106 * This was reported for SDL in 2021 so it may be good to revisit this decision
2107 * at some point since forcing the VSync setting seems heavy-handed,
2108 * especially if the issue gets resolved up-stream.
2109 *
2110 * For reference: https://github.com/libsdl-org/SDL/issues/4335
2111 * From the report the compositor causing problems was GNOME's Mutter. */
2112 setSwapInterval(0);
2113 }
2114#endif
2115
2116 window_->is_init = true;
2117}
2118
2120{
2121#ifdef USE_EVENT_BACKGROUND_THREAD
2122 std::lock_guard lock_server_guard{*system_->server_mutex};
2123#endif
2124
2126
2127#ifdef WITH_OPENGL_BACKEND
2128 if (window_->ghost_context_type == GHOST_kDrawingContextTypeOpenGL) {
2129 wl_egl_window_destroy(window_->backend.egl_window);
2130 }
2131#endif
2132#ifdef WITH_VULKAN_BACKEND
2133 if (window_->ghost_context_type == GHOST_kDrawingContextTypeVulkan) {
2134 delete window_->backend.vulkan_window_info;
2135 }
2136#endif
2137
2138 if (window_->xdg.activation_token) {
2139 xdg_activation_token_v1_destroy(window_->xdg.activation_token);
2140 window_->xdg.activation_token = nullptr;
2141 }
2142
2143 if (window_->wp.fractional_scale_handle) {
2144 wp_fractional_scale_v1_destroy(window_->wp.fractional_scale_handle);
2145 window_->wp.fractional_scale_handle = nullptr;
2146 }
2147
2148 if (window_->wp.viewport) {
2149 wp_viewport_destroy(window_->wp.viewport);
2150 window_->wp.viewport = nullptr;
2151 }
2152
2153#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2154 if (use_libdecor) {
2155 gwl_libdecor_window_destroy(window_->libdecor);
2156 }
2157 else
2158#endif
2159 {
2160 gwl_xdg_decor_window_destroy(window_->xdg_decor);
2161 }
2162
2163 /* Clear any pointers to this window. This is needed because there are no guarantees
2164 * that flushing the display will the "leave" handlers before handling events. */
2165 system_->window_surface_unref(window_->wl.surface);
2166
2167 wl_surface_destroy(window_->wl.surface);
2168
2169 /* NOTE(@ideasman42): Flushing will often run the appropriate handlers event
2170 * (#wl_surface_listener.leave in particular) to avoid attempted access to the freed surfaces.
2171 * This is not fool-proof though, hence the call to #window_surface_unref, see: #99078. */
2172 wl_display_flush(system_->wl_display_get());
2173
2174 if (window_->cursor_generator) {
2175 gwl_window_cursor_custom_free(window_->cursor_generator);
2176 }
2177
2178 delete window_;
2179}
2180
2181#ifdef USE_EVENT_BACKGROUND_THREAD
2183{
2184 GHOST_ASSERT(system_->main_thread_id == std::this_thread::get_id(), "Only from main thread!");
2186}
2187#endif /* USE_EVENT_BACKGROUND_THREAD */
2188
2190{
2191 return system_->cursor_shape_check(cursor_shape);
2192}
2193
2195{
2196#ifdef USE_EVENT_BACKGROUND_THREAD
2197 std::lock_guard lock_server_guard{*system_->server_mutex};
2198#endif
2199
2200 GHOST_Rect bounds_buf;
2201 const GHOST_Rect *bounds = nullptr;
2203 if (getCursorGrabBounds(bounds_buf) == GHOST_kFailure) {
2204 getClientBounds(bounds_buf);
2205 }
2206 bounds = &bounds_buf;
2207 }
2208
2209 if (system_->window_cursor_grab_set(mode,
2212 bounds,
2214 window_->wl.surface,
2215 this->scale_params_get()))
2216 {
2217 return GHOST_kSuccess;
2218 }
2219 return GHOST_kFailure;
2220}
2221
2223{
2224#ifdef USE_EVENT_BACKGROUND_THREAD
2225 std::lock_guard lock_server_guard{*system_->server_mutex};
2226#endif
2227
2228 const bool is_active = this == static_cast<const GHOST_WindowWayland *>(
2229 system_->getWindowManager()->getActiveWindow());
2230 gwl_window_cursor_custom_clear(&window_->cursor_generator);
2231 cursor_shape_ = shape;
2232
2233 GHOST_TSuccess ok;
2234 if (is_active) {
2235 ok = system_->cursor_shape_set(cursor_shape_);
2236 GHOST_TSuccess ok_test = ok;
2237 if (ok == GHOST_kFailure) {
2238 /* Failed, try again with the default cursor. */
2240 ok_test = system_->cursor_shape_set(cursor_shape_);
2241 }
2242
2243 wl_display *display = system_->wl_display_get();
2244#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
2245 if (ok == GHOST_kSuccess || ok_test == GHOST_kSuccess) {
2246 wl_display_flush(display);
2248 }
2249 else
2250#endif /* USE_CURSOR_IMMEDIATE_DISPATCH */
2251 if (ok_test == GHOST_kFailure) {
2252 /* For the cursor to display when the event queue isn't being handled. */
2253 wl_display_flush(display);
2254 }
2255 }
2256 else {
2257 /* Set later when activating the window. */
2258 ok = system_->cursor_shape_check(shape);
2259 if (ok == GHOST_kFailure) {
2261 }
2262 }
2263 return ok;
2264}
2265
2267{
2268#ifdef USE_EVENT_BACKGROUND_THREAD
2269 std::lock_guard lock_server_guard{*system_->server_mutex};
2270#endif
2271 return system_->cursor_grab_use_software_display_get(cursor_grab_);
2272}
2273
2275 GHOST_CursorGenerator *cursor_generator)
2276{
2277 /* Before this, all logic is just setting up the cursor. */
2278#ifdef USE_EVENT_BACKGROUND_THREAD
2279 std::lock_guard lock_server_guard{*system_->server_mutex};
2280#endif
2282 if (window_->cursor_generator) {
2283 gwl_window_cursor_custom_free(window_->cursor_generator);
2284 }
2285 window_->cursor_generator = cursor_generator;
2286
2288
2289 /* Let refresh handle applying the changes. */
2290 if (success == GHOST_kSuccess) {
2291 wl_display *display = system_->wl_display_get();
2292 /* For the cursor to display when the event queue isn't being handled. */
2293 wl_display_flush(display);
2294#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
2296#endif
2297 }
2298 return success;
2299}
2300
2302 const uint8_t *mask,
2303 const int size[2],
2304 const int hot_spot[2],
2305 const bool can_invert_color)
2306{
2307 /* This is no longer needed as all cursors are generated on demand. */
2308 GHOST_ASSERT(false, "All cursors must be generated!");
2309 (void)bitmap;
2310 (void)mask;
2311 (void)size;
2312 (void)hot_spot;
2313 (void)can_invert_color;
2314
2315 return GHOST_kFailure;
2316}
2317
2319{
2320#ifdef USE_EVENT_BACKGROUND_THREAD
2321 std::lock_guard lock_server_guard{*system_->server_mutex};
2322#endif
2323 return system_->cursor_bitmap_get(bitmap);
2324}
2325
2327{
2328 return GHOST_Window::getValid() && window_->is_valid_setup;
2329}
2330
2331void GHOST_WindowWayland::setTitle(const char *title)
2332{
2333#ifdef USE_EVENT_BACKGROUND_THREAD
2334 std::lock_guard lock_server_guard{*system_->server_mutex};
2335#endif
2336 gwl_window_title_set(window_, title);
2337}
2338
2340{
2341 /* No need to lock `server_mutex` (WAYLAND never changes this). */
2342 return window_->title.empty() ? "untitled" : window_->title;
2343}
2344
2349
2351{
2352 /* No need to lock `server_mutex` (WAYLAND never changes this in a thread). */
2353 bounds.set(0, 0, UNPACK2(window_->frame.size));
2354}
2355
2357{
2358 return setClientSize(width, uint32_t(window_->frame.size[1]));
2359}
2360
2362{
2363 return setClientSize(uint32_t(window_->frame.size[0]), height);
2364}
2365
2366GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const uint32_t height)
2367{
2368#ifdef USE_EVENT_BACKGROUND_THREAD
2369 std::lock_guard lock_server_guard{*system_->server_mutex};
2370 std::lock_guard lock_frame_guard{window_->frame_pending_mutex};
2371#endif
2372
2373 GWL_WindowFrame &frame_pending = window_->frame_pending;
2374
2375 frame_pending.size[0] = width;
2376 frame_pending.size[1] = height;
2377 gwl_round_int2_by(frame_pending.size, frame_pending.buffer_scale);
2378
2379 gwl_window_frame_pending_size_set(window_, nullptr, nullptr, nullptr);
2380
2381 return GHOST_kSuccess;
2382}
2383
2385 const int32_t inY,
2386 int32_t &outX,
2387 int32_t &outY) const
2388{
2389 outX = inX;
2390 outY = inY;
2391}
2392
2394 const int32_t inY,
2395 int32_t &outX,
2396 int32_t &outY) const
2397{
2398 outX = inX;
2399 outY = inY;
2400}
2401
2403{
2404 /* No need to lock `server_mutex`
2405 * (`outputs_changed_update_scale` never changes values in a non-main thread). */
2406
2407 if (window_->frame.fractional_scale) {
2408 return gwl_window_fractional_to_viewport(window_->frame, base_dpi);
2409 }
2410
2411 return window_->frame.buffer_scale * base_dpi;
2412}
2413
2415{
2416#ifdef USE_EVENT_BACKGROUND_THREAD
2417 std::lock_guard lock_server_guard{*system_->server_mutex};
2418#endif
2419 const GHOST_TSuccess ok = system_->cursor_visibility_set(visible);
2420 if (ok == GHOST_kSuccess) {
2421 wl_display *display = system_->wl_display_get();
2422 /* For the cursor to display when the event queue isn't being handled. */
2423 wl_display_flush(display);
2424#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
2426#endif
2427 }
2428 return ok;
2429}
2430
2432{
2433#ifdef USE_EVENT_BACKGROUND_THREAD
2434 std::lock_guard lock_server_guard{*system_->server_mutex};
2435#endif
2437}
2438
2440{
2441#ifdef USE_EVENT_BACKGROUND_THREAD
2442 std::lock_guard lock_server_guard{*system_->server_mutex};
2443#endif
2444 return gwl_window_state_get(window_);
2445}
2446
2451
2453{
2454 /* NOTE(@ideasman42): only activation is supported (on X11 & Cocoa for example)
2455 * both activation and raising is performed. Since WAYLAND only supports activation,
2456 * do that as the compositor will likely raise the window as well.
2457 * Although it's not ideal that raising does something but lowering a window is ignored. */
2458 if (order == GHOST_kWindowOrderTop) {
2459 gwl_window_activate(window_);
2460 }
2461
2462 return GHOST_kSuccess;
2463}
2464
2466{
2467 return window_->is_dialog;
2468}
2469
2470GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType type)
2471{
2472 switch (type) {
2475 return context;
2476 }
2477
2478#ifdef WITH_VULKAN_BACKEND
2479 case GHOST_kDrawingContextTypeVulkan: {
2480 GHOST_ContextVK *context = new GHOST_ContextVK(want_context_params_,
2481 GHOST_kVulkanPlatformWayland,
2482 0,
2483 nullptr,
2484 window_->wl.surface,
2485 system_->wl_display_get(),
2486 window_->backend.vulkan_window_info,
2487 1,
2488 2,
2489 preferred_device_,
2490 &hdr_info_);
2491 if (context->initializeDrawingContext()) {
2492 return context;
2493 }
2494 delete context;
2495 return nullptr;
2496 }
2497#endif
2498
2499#ifdef WITH_OPENGL_BACKEND
2500 case GHOST_kDrawingContextTypeOpenGL: {
2501 for (int minor = 6; minor >= 3; --minor) {
2502 GHOST_Context *context = new GHOST_ContextEGL(
2503 system_,
2505 EGLNativeWindowType(window_->backend.egl_window),
2506 EGLNativeDisplayType(system_->wl_display_get()),
2507 EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
2508 4,
2509 minor,
2511 (want_context_params_.is_debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
2513 EGL_OPENGL_API);
2514
2515 if (context->initializeDrawingContext()) {
2516 return context;
2517 }
2518 delete context;
2519 }
2520 return nullptr;
2521 }
2522#endif
2523
2524 default:
2525 /* Unsupported backend. */
2526 return nullptr;
2527 }
2528}
2529
2530#ifdef WITH_INPUT_IME
2531
2532void GHOST_WindowWayland::beginIME(
2533 const int32_t x, const int32_t y, const int32_t w, const int32_t h, const bool completed)
2534{
2535 system_->ime_begin(this, x, y, w, h, completed);
2536}
2537
2538void GHOST_WindowWayland::endIME()
2539{
2540 system_->ime_end(this);
2541}
2542
2543#endif
2544
2546
2547/* -------------------------------------------------------------------- */
2552
2554{
2555 return window_->frame.buffer_scale;
2556}
2557
2559{
2560 /* NOTE(@ideasman42): This could be kept initialized,
2561 * since it's such a small struct it's not so important. */
2562 GWL_WindowScaleParams *scale_params = &window_->scale_params;
2563 scale_params->is_fractional = (window_->frame.fractional_scale != 0);
2564 scale_params->scale = scale_params->is_fractional ? window_->frame.fractional_scale :
2565 window_->frame.buffer_scale;
2566 return *scale_params;
2567}
2568
2569wl_fixed_t GHOST_WindowWayland::wl_fixed_from_window(wl_fixed_t value) const
2570{
2571 if (window_->frame.fractional_scale) {
2572 return gwl_window_fractional_from_viewport(window_->frame, value);
2573 }
2574 return value / window_->frame.buffer_scale;
2575}
2576
2577wl_fixed_t GHOST_WindowWayland::wl_fixed_to_window(wl_fixed_t value) const
2578{
2579 if (window_->frame.fractional_scale) {
2580 return gwl_window_fractional_to_viewport(window_->frame, value);
2581 }
2582 return value * window_->frame.buffer_scale;
2583}
2584
2586{
2587 return window_->wl.surface;
2588}
2589
2590const std::vector<GWL_Output *> &GHOST_WindowWayland::outputs_get()
2591{
2592 return window_->outputs;
2593}
2594
2596
2597/* -------------------------------------------------------------------- */
2602
2604{
2605 return system_->pushEvent_maybe_pending(
2606 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowClose, this));
2607}
2608
2610{
2611 /* When first initializing from the main thread, activation is called directly,
2612 * otherwise activation is performed when processing pending events. */
2613#ifdef USE_EVENT_BACKGROUND_THREAD
2614 const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
2615 if (is_main_thread)
2616#endif
2617 {
2618 /* This can run while the window being initialized.
2619 * In this case, skip setting the window active but add the event, see: #120465. */
2620 if (window_->is_init) {
2621 if (system_->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
2622 return GHOST_kFailure;
2623 }
2624 }
2625 }
2626 const GHOST_TSuccess success = system_->pushEvent_maybe_pending(
2627 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowActivate, this));
2628#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2629 if (success == GHOST_kSuccess) {
2630 if (use_libdecor) {
2631 /* Ensure there is a swap-buffers, needed for the updated window borders to refresh. */
2633 }
2634 }
2635#endif
2636 return success;
2637}
2638
2640{
2641 /* When first initializing from the main thread, deactivation is called directly,
2642 * otherwise deactivation is performed when processing pending events. */
2643#ifdef USE_EVENT_BACKGROUND_THREAD
2644 const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
2645 if (is_main_thread)
2646#endif
2647 {
2648 /* See code comments for #GHOST_WindowWayland::activate. */
2649 if (window_->is_init) {
2650 system_->getWindowManager()->setWindowInactive(this);
2651 }
2652 }
2653 const GHOST_TSuccess success = system_->pushEvent_maybe_pending(
2654 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowDeactivate, this));
2655#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2656 if (success == GHOST_kSuccess) {
2657 if (use_libdecor) {
2658 /* Ensure there is a swap-buffers, needed for the updated window borders to refresh. */
2660 }
2661 }
2662#endif
2663 return success;
2664}
2665
2667{
2668 return system_->pushEvent_maybe_pending(
2669 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowSize, this));
2670}
2671
2673{
2674 /* NOTE: we want to `swapBuffers`, however this may run from a thread and
2675 * when this windows OpenGL context is not active, so send and update event instead. */
2676 return system_->pushEvent_maybe_pending(
2677 new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowUpdateDecor, this));
2678}
2679
2681
2682/* -------------------------------------------------------------------- */
2687
2689{
2690#ifdef USE_EVENT_BACKGROUND_THREAD
2691 if (system_->main_thread_id != std::this_thread::get_id()) {
2693 return GHOST_kSuccess;
2694 }
2695#endif
2696 return gwl_window_cursor_shape_refresh(cursor_shape_, window_->cursor_generator, system_);
2697}
2698
2700{
2701#ifdef USE_EVENT_BACKGROUND_THREAD
2702
2703 /* NOTE: if deferring causes problems, it could be isolated to the first scale initialization
2704 * See: #GWL_WindowFrame::is_scale_init. */
2706#else
2708#endif
2709}
2710
2712{
2713#ifdef USE_EVENT_BACKGROUND_THREAD
2714 if (system_->main_thread_id != std::this_thread::get_id()) {
2716 return false;
2717 }
2718#endif
2719 int fractional_scale_next = -1;
2720 int fractional_scale_from_output = 0;
2721
2722 int scale_next = outputs_max_scale_or_default(outputs_get(), 0, &fractional_scale_from_output);
2723
2724 if (UNLIKELY(scale_next == 0)) {
2725 return false;
2726 }
2727
2728#ifdef USE_EVENT_BACKGROUND_THREAD
2729 std::lock_guard lock_frame_guard{window_->frame_pending_mutex};
2730#endif
2731
2732 if (window_->wp.fractional_scale_handle) {
2733 /* Let the #wp_fractional_scale_v1_listener::preferred_scale callback handle
2734 * changes to the windows scale. */
2735 if (window_->frame_pending.fractional_scale_preferred != 0) {
2736 fractional_scale_next = window_->frame_pending.fractional_scale_preferred;
2737 scale_next = fractional_scale_next / FRACTIONAL_DENOMINATOR;
2738 }
2739 }
2740
2741 if (fractional_scale_next == -1) {
2742 fractional_scale_next = fractional_scale_from_output;
2743 scale_next = fractional_scale_next / FRACTIONAL_DENOMINATOR;
2744 }
2745
2746 bool changed = false;
2747
2748 const bool is_fractional_prev = window_->frame.fractional_scale != 0;
2749 const bool is_fractional_next = (fractional_scale_next % FRACTIONAL_DENOMINATOR) != 0;
2750
2751 /* When non-fractional, never use fractional scaling! */
2752 window_->frame_pending.fractional_scale = is_fractional_next ? fractional_scale_next : 0;
2753 window_->frame_pending.buffer_scale = is_fractional_next ?
2754 1 :
2755 fractional_scale_next / FRACTIONAL_DENOMINATOR;
2756
2757 const int fractional_scale_prev = window_->frame.fractional_scale ?
2758 window_->frame.fractional_scale :
2759 window_->frame.buffer_scale * FRACTIONAL_DENOMINATOR;
2760 const int scale_prev = fractional_scale_prev / FRACTIONAL_DENOMINATOR;
2761
2762 /* Resizing implies updating. */
2763 bool do_frame_resize = false;
2764 bool do_frame_update = false;
2765
2766 if (window_->frame_pending.is_scale_init == false) {
2767 window_->frame_pending.is_scale_init = true;
2768
2769 /* NOTE(@ideasman42): Needed because new windows are created at their previous pixel-dimensions
2770 * as the window doesn't save it's DPI. Restore the window size under the assumption it's
2771 * opening on the same monitor so a window keeps it's previous size on a users system.
2772 *
2773 * To support anything more sophisticated, windows would need to be created with a scale
2774 * argument (representing the scale used when the window was stored, for example). */
2775
2776#ifdef WITH_GHOST_WAYLAND_LIBDECOR
2777 if (use_libdecor) {
2778 /* LIBDECOR needs its own logic. Failing to do this causes the window border
2779 * not to follow the GHOST window on startup - with multiple monitors,
2780 * each with different fractional scale, see: #109194.
2781 *
2782 * Note that the window will show larger, then resize to be smaller soon
2783 * after opening. This would be nice to avoid but would require DPI
2784 * to be stored in the window (as noted above). */
2785 int size_next[2] = {0, 0};
2786 int size_orig[2] = {0, 0};
2787
2788 /* Leave `window_->frame_pending` as-is, only change the window frame. */
2789 for (size_t i = 0; i < ARRAY_SIZE(window_->frame_pending.size); i++) {
2790 const int value = size_next[i] ? window_->frame_pending.size[i] : window_->frame.size[i];
2791 size_orig[i] = value;
2792 if (is_fractional_prev || is_fractional_next) {
2793 size_next[i] = lroundf((value * double(FRACTIONAL_DENOMINATOR)) /
2794 double(fractional_scale_next));
2795 }
2796 else {
2797 size_next[i] = value / scale_prev;
2798 }
2799 if (window_->frame_pending.buffer_scale > 1) {
2800 gwl_round_int_by(&size_next[i], window_->frame_pending.buffer_scale);
2801 }
2802 }
2803
2804 if (size_orig[0] != size_next[0] || size_orig[1] != size_next[1]) {
2805 GWL_LibDecor_Window &decor = *window_->libdecor;
2806 libdecor_state *state = libdecor_state_new(UNPACK2(size_next));
2807 libdecor_frame_commit(decor.frame, state, nullptr);
2809 }
2810 }
2811#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
2812 /* Leave `window_->frame_pending` as-is, so changes are detected and updates are applied. */
2813 do_frame_resize = false;
2814 do_frame_update = true;
2815
2816 /* If the buffer scale changes, the window size (and underlying buffer-size)
2817 * must always be a multiple of the buffer size. Resizing ensures this is the case.
2818 * See replies to #135764 for details.
2819 *
2820 * NOTE: We could skip resize if the current window size is a multiple of the buffer scale,
2821 * avoids this as it will result in unpredictable behavior based on single pixel differences
2822 * in window size. */
2823 if (window_->frame_pending.buffer_scale != window_->frame.buffer_scale) {
2824 do_frame_resize = true;
2825 }
2826 }
2827 else {
2828 /* Test if the scale changed. */
2829 if ((fractional_scale_prev != fractional_scale_next) ||
2830 (window_->frame_pending.buffer_scale != window_->frame.buffer_scale))
2831 {
2832 do_frame_resize = true;
2833 }
2834 }
2835
2836 if (do_frame_resize) {
2837 /* Resize the window failing to do so results in severe flickering with a
2838 * multi-monitor setup when multiple monitors have different scales.
2839 *
2840 * NOTE: some flickering is still possible even when resizing this
2841 * happens when dragging the right hand side of the title-bar in KDE
2842 * as expanding changed the size on the RHS, this may be up to the compositor to fix. */
2843 for (size_t i = 0; i < ARRAY_SIZE(window_->frame_pending.size); i++) {
2844 const int value = window_->frame_pending.size[i] ? window_->frame_pending.size[i] :
2845 window_->frame.size[i];
2846 if (is_fractional_prev || is_fractional_next) {
2847 window_->frame_pending.size[i] = lroundf((value * double(fractional_scale_next)) /
2848 double(fractional_scale_prev));
2849 }
2850 else {
2851 window_->frame_pending.size[i] = (value * scale_next) / scale_prev;
2852 }
2853 if (window_->frame_pending.buffer_scale > 1) {
2854 gwl_round_int_by(&window_->frame_pending.size[i], window_->frame_pending.buffer_scale);
2855 }
2856 }
2857 do_frame_update = true;
2858 }
2859
2860 if (do_frame_update) {
2862 changed = true;
2863 }
2864
2865 return changed;
2866}
2867
2869{
2870 std::vector<GWL_Output *> &outputs = window_->outputs;
2871 auto it = std::find(outputs.begin(), outputs.end(), output);
2872 if (it != outputs.end()) {
2873 return false;
2874 }
2875 outputs.push_back(output);
2876 return true;
2877}
2878
2880{
2881 std::vector<GWL_Output *> &outputs = window_->outputs;
2882 auto it = std::find(outputs.begin(), outputs.end(), output);
2883 if (it == outputs.end()) {
2884 return false;
2885 }
2886 outputs.erase(it);
2887 return true;
2888}
2889
2890#ifdef USE_EVENT_BACKGROUND_THREAD
2891
2893{
2894 /* Caller must lock `server_mutex`, while individual actions could lock,
2895 * it's simpler to lock once when handling all window actions. */
2896 GWL_Window *win = window_;
2897 GHOST_ASSERT(win->ghost_system->main_thread_id == std::this_thread::get_id(),
2898 "Run from main thread!");
2899
2901}
2902
2903#endif /* USE_EVENT_BACKGROUND_THREAD */
2904
KDTree *BLI_kdtree_nd_ new(unsigned int nodes_len_capacity)
Definition kdtree_impl.h:98
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
#define UNPACK2(a)
#define ARRAY_SIZE(arr)
#define UNLIKELY(x)
#define CLOG_DEBUG(clg_ref,...)
Definition CLG_log.h:191
#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:57
@ GHOST_kFailure
Definition GHOST_Types.h:57
@ GHOST_kSuccess
Definition GHOST_Types.h:57
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)
static bool gwl_round_int_test(const int value, const int round_value)
static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shape, const GHOST_CursorGenerator *cg, GHOST_SystemWayland *system)
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_window_cursor_custom_clear(GHOST_CursorGenerator **cg)
static void gwl_round_int2_by(int value_p[2], const int round_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)
static int gwl_window_fractional_from_viewport(const GWL_WindowFrame &frame, const int value)
static bool gwl_window_viewport_unset(GWL_Window *win, bool *r_surface_needs_commit, bool *r_surface_needs_buffer_scale)
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 int gwl_window_fractional_to_viewport_round(const GWL_WindowFrame &frame, const int value)
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 GHOST_CursorGenerator &cg, GHOST_SystemWayland *system)
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, const int value)
static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state)
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)
static CLG_LogRef LOG_WL_FRACTIONAL_SCALE
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_pending_actions_handle(GWL_Window *win)
static const xdg_surface_listener xdg_surface_listener
static int gwl_window_fractional_from_viewport_round(const GWL_WindowFrame &frame, const 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 bool gwl_window_viewport_size_update(GWL_Window *win)
static void xdg_toplevel_handle_configure_bounds(void *data, xdg_toplevel *, const int32_t width, const int32_t height)
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)
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, const int value)
static int gwl_window_fractional_to_viewport(const GWL_WindowFrame &frame, const int 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_cursor_custom_free(GHOST_CursorGenerator *cg)
static void gwl_window_title_set(GWL_Window *win, const char *title)
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 GHOST_CursorGenerator &cg)
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
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 setWindowCursorGrab(GHOST_TGrabCursorMode mode) override
GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursor_shape) override
GHOST_TSuccess setState(GHOST_TWindowState state) override
GHOST_TSuccess setWindowCursorVisibility(bool visible) 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 *parent_window, GHOST_TDrawingContextType type, const bool is_dialog, const GHOST_ContextParams &context_params, const bool exclusive, const GHOST_GPUDevice &preferred_device)
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 swapBufferRelease() 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 setWindowCustomCursorShape(const uint8_t *bitmap, const uint8_t *mask, const int size[2], const int hot_spot[2], bool can_invert_color) override
GHOST_TSuccess invalidate() override
GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override
GHOST_TSuccess setWindowCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) override
void setTitle(const char *title) override
void getWindowBounds(GHOST_Rect &bounds) const override
GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) override
const std::vector< GWL_Output * > & outputs_get()
GHOST_ContextParams want_context_params_
GHOST_TSuccess swapBufferRelease() override
GHOST_TStandardCursor cursor_shape_
GHOST_TAxisFlag cursor_grab_axis_
GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) const override
GHOST_TStandardCursor getCursorShape() const override
GHOST_TSuccess setDrawingContextType(GHOST_TDrawingContextType type) override
int32_t cursor_grab_init_pos_[2]
GHOST_TSuccess setSwapInterval(int interval) override
GHOST_TSuccess releaseNativeHandles()
bool getValid() const override
GHOST_Window(uint32_t width, uint32_t height, GHOST_TWindowState state, const GHOST_ContextParams &context_params, const bool exclusive=false)
GHOST_WindowHDRInfo hdr_info_
GHOST_TGrabCursorMode cursor_grab_
#define output
#define LOG(level)
Definition log.h:97
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[]
void(* free_fn)(struct GHOST_CursorGenerator *cursor_generator)
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
wp_viewport * viewport
GWL_WindowScaleParams scale_params
struct GWL_Window::@211324370331073216033106070037277062312046022064 wp
xdg_activation_token_v1 * activation_token
std::mutex frame_pending_mutex
struct GWL_Window::@226144076170122005126305352224000061130331357311 wl
GWL_WindowFrame frame_pending
struct GWL_Window::@052270163354376334004337043250357320164157112151 backend
GWL_WindowFrame frame
GHOST_WindowWayland * ghost_window
struct GWL_Window::@040024232136114172255107327327361000017072123125 xdg
GHOST_CursorGenerator * cursor_generator
std::vector< GWL_Output * > outputs
GHOST_TDrawingContextType ghost_context_type
zxdg_toplevel_decoration_v1 * toplevel_decor
struct GWL_XDG_Decor_Window::@000320341361074024320003302145125362246371366227 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(...)