diff options
Diffstat (limited to 'intern/ghost/intern/GHOST_WindowWayland.cpp')
-rw-r--r-- | intern/ghost/intern/GHOST_WindowWayland.cpp | 795 |
1 files changed, 620 insertions, 175 deletions
diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 7f2a2c992d9..e303bd5b6aa 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -6,61 +6,170 @@ #include "GHOST_WindowWayland.h" #include "GHOST_SystemWayland.h" +#include "GHOST_WaylandUtils.h" #include "GHOST_WindowManager.h" +#include "GHOST_utildefines.h" #include "GHOST_Event.h" #include "GHOST_ContextEGL.h" #include "GHOST_ContextNone.h" +#include <wayland-client-protocol.h> + +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include <wayland_dynload_egl.h> +#endif #include <wayland-egl.h> +#include <algorithm> /* For `std::find`. */ + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR +# ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include <wayland_dynload_libdecor.h> +# endif +# include <libdecor.h> +#endif + +/* Logging, use `ghost.wl.*` prefix. */ +#include "CLG_log.h" + static constexpr size_t base_dpi = 96; +static GHOST_WindowManager *window_manager = nullptr; + struct window_t { - GHOST_WindowWayland *w; - wl_surface *surface; - /* Outputs on which the window is currently shown on. */ - std::unordered_set<const output_t *> outputs; - uint16_t dpi = 0; - int scale = 1; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; + GHOST_WindowWayland *w = nullptr; + struct wl_surface *wl_surface = nullptr; + /** + * Outputs on which the window is currently shown on. + * + * This is an ordered set (whoever adds to this is responsible for keeping members unique). + * In practice this is rarely manipulated and is limited by the number of physical displays. + */ + std::vector<output_t *> outputs; + + /** The scale value written to #wl_surface_set_buffer_scale. */ + int scale = 0; + /** + * The DPI, either: + * - `scale * base_dpi` + * - `wl_fixed_to_int(scale_fractional * base_dpi)` + * When fractional scaling is available. + */ + uint32_t dpi = 0; + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + struct libdecor_frame *decor_frame = nullptr; + bool decor_configured = false; +#else + struct xdg_surface *xdg_surface = nullptr; struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr; - enum zxdg_toplevel_decoration_v1_mode decoration_mode; - wl_egl_window *egl_window; - int32_t pending_width, pending_height; - bool is_maximised; - bool is_fullscreen; - bool is_active; - bool is_dialog; - int32_t width, height; + struct xdg_toplevel *xdg_toplevel = nullptr; + + enum zxdg_toplevel_decoration_v1_mode decoration_mode = (enum zxdg_toplevel_decoration_v1_mode)0; +#endif + + wl_egl_window *egl_window = nullptr; + bool is_maximised = false; + bool is_fullscreen = false; + bool is_active = false; + bool is_dialog = false; + + int32_t size[2] = {0, 0}; + int32_t size_pending[2] = {0, 0}; }; /* -------------------------------------------------------------------- */ -/** \name Wayland Interface Callbacks - * - * These callbacks are registered for Wayland interfaces and called when - * an event is received from the compositor. +/** \name Internal Utilities + * \{ */ + +/** + * Return -1 if `output_a` has a scale smaller than `output_b`, 0 when there equal, otherwise 1. + */ +static int output_scale_cmp(const output_t *output_a, const output_t *output_b) +{ + if (output_a->scale < output_b->scale) { + return -1; + } + if (output_a->scale > output_b->scale) { + return 1; + } + if (output_a->has_scale_fractional || output_b->has_scale_fractional) { + const wl_fixed_t scale_fractional_a = output_a->has_scale_fractional ? + output_a->scale_fractional : + wl_fixed_from_int(output_a->scale); + const wl_fixed_t scale_fractional_b = output_b->has_scale_fractional ? + output_b->scale_fractional : + wl_fixed_from_int(output_b->scale); + if (scale_fractional_a < scale_fractional_b) { + return -1; + } + if (scale_fractional_a > scale_fractional_b) { + return 1; + } + } + return 0; +} + +static int outputs_max_scale_or_default(const std::vector<output_t *> &outputs, + const int32_t scale_default, + uint32_t *r_dpi) +{ + const output_t *output_max = nullptr; + for (const output_t *reg_output : outputs) { + if (!output_max || (output_scale_cmp(output_max, reg_output) == -1)) { + output_max = reg_output; + } + } + + if (output_max) { + if (r_dpi) { + *r_dpi = output_max->has_scale_fractional ? + /* Fractional DPI. */ + wl_fixed_to_int(output_max->scale_fractional * base_dpi) : + /* Simple non-fractional DPI. */ + (output_max->scale * base_dpi); + } + return output_max->scale; + } + + if (r_dpi) { + *r_dpi = scale_default * base_dpi; + } + return scale_default; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Top Level), #xdg_toplevel_listener * \{ */ -static void toplevel_configure( - void *data, xdg_toplevel * /*xdg_toplevel*/, int32_t width, int32_t height, wl_array *states) +#ifndef WITH_GHOST_WAYLAND_LIBDECOR + +static CLG_LogRef LOG_WL_XDG_TOPLEVEL = {"ghost.wl.handle.xdg_toplevel"}; +# define LOG (&LOG_WL_XDG_TOPLEVEL) + +static void xdg_toplevel_handle_configure(void *data, + xdg_toplevel * /*xdg_toplevel*/, + const int32_t width, + const int32_t height, + wl_array *states) { + /* TODO: log `states`, not urgent. */ + CLOG_INFO(LOG, 2, "configure (size=[%d, %d])", width, height); + window_t *win = static_cast<window_t *>(data); - win->pending_width = width; - win->pending_height = height; + win->size_pending[0] = win->scale * width; + win->size_pending[1] = win->scale * height; win->is_maximised = false; win->is_fullscreen = false; win->is_active = false; - /* Note that the macro 'wl_array_for_each' would typically be used to simplify this logic, - * however it's not compatible with C++, so perform casts instead. - * If this needs to be done more often we could define our own C++ compatible macro. */ - for (enum xdg_toplevel_state *state = static_cast<xdg_toplevel_state *>(states->data); - reinterpret_cast<uint8_t *>(state) < (static_cast<uint8_t *>(states->data) + states->size); - state++) { + enum xdg_toplevel_state *state; + WL_ARRAY_FOR_EACH (state, states) { switch (*state) { case XDG_TOPLEVEL_STATE_MAXIMIZED: win->is_maximised = true; @@ -77,42 +186,157 @@ static void toplevel_configure( } } -static void toplevel_close(void *data, xdg_toplevel * /*xdg_toplevel*/) +static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/) { + CLOG_INFO(LOG, 2, "close"); static_cast<window_t *>(data)->w->close(); } static const xdg_toplevel_listener toplevel_listener = { - toplevel_configure, - toplevel_close, + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, +}; + +# undef LOG + +#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (LibDecor Frame), #libdecor_frame_interface + * \{ */ + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + +static CLG_LogRef LOG_WL_LIBDECOR_FRAME = {"ghost.wl.handle.libdecor_frame"}; +# define LOG (&LOG_WL_LIBDECOR_FRAME) + +static void frame_handle_configure(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + void *data) +{ + CLOG_INFO(LOG, 2, "configure"); + + window_t *win = static_cast<window_t *>(data); + + int size_next[2]; + enum libdecor_window_state window_state; + struct libdecor_state *state; + + if (!libdecor_configuration_get_content_size( + configuration, frame, &size_next[0], &size_next[1])) { + size_next[0] = win->size[0] / win->scale; + size_next[1] = win->size[1] / win->scale; + } + + win->size[0] = win->scale * size_next[0]; + win->size[1] = win->scale * size_next[1]; + + wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); + win->w->notify_size(); + + if (!libdecor_configuration_get_window_state(configuration, &window_state)) { + window_state = LIBDECOR_WINDOW_STATE_NONE; + } + + win->is_maximised = window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED; + win->is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN; + win->is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE; + + win->is_active ? win->w->activate() : win->w->deactivate(); + + state = libdecor_state_new(UNPACK2(size_next)); + libdecor_frame_commit(frame, state, configuration); + libdecor_state_free(state); + + win->decor_configured = true; +} + +static void frame_handle_close(struct libdecor_frame * /*frame*/, void *data) +{ + CLOG_INFO(LOG, 2, "close"); + + static_cast<window_t *>(data)->w->close(); +} + +static void frame_handle_commit(struct libdecor_frame * /*frame*/, void *data) +{ + CLOG_INFO(LOG, 2, "commit"); + + /* We have to swap twice to keep any pop-up menus alive. */ + static_cast<window_t *>(data)->w->swapBuffers(); + static_cast<window_t *>(data)->w->swapBuffers(); +} + +static struct libdecor_frame_interface libdecor_frame_iface = { + frame_handle_configure, + frame_handle_close, + frame_handle_commit, }; -static void toplevel_decoration_configure( +# undef LOG + +#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Decoration Listener), #zxdg_toplevel_decoration_v1_listener + * \{ */ + +#ifndef WITH_GHOST_WAYLAND_LIBDECOR + +static CLG_LogRef LOG_WL_XDG_TOPLEVEL_DECORATION = {"ghost.wl.handle.xdg_toplevel_decoration"}; +# define LOG (&LOG_WL_XDG_TOPLEVEL_DECORATION) + +static void xdg_toplevel_decoration_handle_configure( void *data, struct zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/, - uint32_t mode) + const uint32_t mode) { - static_cast<window_t *>(data)->decoration_mode = zxdg_toplevel_decoration_v1_mode(mode); + CLOG_INFO(LOG, 2, "configure (mode=%u)", mode); + static_cast<window_t *>(data)->decoration_mode = (zxdg_toplevel_decoration_v1_mode)mode; } static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { - toplevel_decoration_configure, + xdg_toplevel_decoration_handle_configure, }; -static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t serial) +# undef LOG + +#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Surface Handle Configure), #xdg_surface_listener + * \{ */ + +#ifndef WITH_GHOST_WAYLAND_LIBDECOR + +static CLG_LogRef LOG_WL_XDG_SURFACE = {"ghost.wl.handle.xdg_surface"}; +# define LOG (&LOG_WL_XDG_SURFACE) + +static void xdg_surface_handle_configure(void *data, + xdg_surface *xdg_surface, + const uint32_t serial) { window_t *win = static_cast<window_t *>(data); if (win->xdg_surface != xdg_surface) { + CLOG_INFO(LOG, 2, "configure (skipped)"); return; } - - if (win->pending_width != 0 && win->pending_height != 0) { - win->width = win->scale * win->pending_width; - win->height = win->scale * win->pending_height; - wl_egl_window_resize(win->egl_window, win->width, win->height, 0, 0); - win->pending_width = 0; - win->pending_height = 0; + const bool do_resize = win->size_pending[0] != 0 && win->size_pending[1] != 0; + CLOG_INFO(LOG, 2, "configure (do_resize=%d)", do_resize); + + if (do_resize) { + win->size[0] = win->size_pending[0]; + win->size[1] = win->size_pending[1]; + wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); + win->size_pending[0] = 0; + win->size_pending[1] = 0; win->w->notify_size(); } @@ -126,62 +350,70 @@ static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t ser xdg_surface_ack_configure(xdg_surface, serial); } -static const xdg_surface_listener surface_listener = { - surface_configure, +static const xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, }; -static bool update_scale(GHOST_WindowWayland *window) +# undef LOG + +#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Surface), #wl_surface_listener + * \{ */ + +static CLG_LogRef LOG_WL_SURFACE = {"ghost.wl.handle.surface"}; +#define LOG (&LOG_WL_SURFACE) + +static void surface_handle_enter(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) { - int scale = 0; - for (const output_t *output : window->outputs_active()) { - if (output->scale > scale) - scale = output->scale; + if (!ghost_wl_output_own(output)) { + CLOG_INFO(LOG, 2, "enter (skipped)"); + return; } + CLOG_INFO(LOG, 2, "enter"); - if (scale > 0 && window->scale() != scale) { - window->scale() = scale; - /* Using the real DPI will cause wrong scaling of the UI - * use a multiplier for the default DPI as workaround. */ - window->dpi() = scale * base_dpi; - wl_surface_set_buffer_scale(window->surface(), scale); - return true; + output_t *reg_output = ghost_wl_output_user_data(output); + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(data); + if (win->outputs_enter(reg_output)) { + win->outputs_changed_update_scale(); } - return false; } -static void surface_enter(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) +static void surface_handle_leave(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) { - GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); - for (const output_t *reg_output : w->outputs()) { - if (reg_output->output == output) { - w->outputs_active().insert(reg_output); - } + if (!ghost_wl_output_own(output)) { + CLOG_INFO(LOG, 2, "leave (skipped)"); + return; } - update_scale(w); -} + CLOG_INFO(LOG, 2, "leave"); -static void surface_leave(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) -{ - GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); - for (const output_t *reg_output : w->outputs()) { - if (reg_output->output == output) { - w->outputs_active().erase(reg_output); - } + output_t *reg_output = ghost_wl_output_user_data(output); + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(data); + if (win->outputs_leave(reg_output)) { + win->outputs_changed_update_scale(); } - update_scale(w); } -struct wl_surface_listener wl_surface_listener = { - surface_enter, - surface_leave, +static struct wl_surface_listener wl_surface_listener = { + surface_handle_enter, + surface_handle_leave, }; +#undef LOG + /** \} */ /* -------------------------------------------------------------------- */ -/** \name Ghost Implementation +/** \name GHOST Implementation * - * Wayland specific implementation of the GHOST_Window interface. + * WAYLAND specific implementation of the #GHOST_Window interface. * \{ */ GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorShape) @@ -191,13 +423,13 @@ GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorS GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, const char *title, - int32_t /*left*/, - int32_t /*top*/, - uint32_t width, - uint32_t height, - GHOST_TWindowState state, + const int32_t /*left*/, + const int32_t /*top*/, + const uint32_t width, + const uint32_t height, + const GHOST_TWindowState state, const GHOST_IWindow *parentWindow, - GHOST_TDrawingContextType type, + const GHOST_TDrawingContextType type, const bool is_dialog, const bool stereoVisual, const bool exclusive) @@ -205,34 +437,73 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, m_system(system), w(new window_t) { + /* Globally store pointer to window manager. */ + if (!window_manager) { + window_manager = m_system->getWindowManager(); + } + w->w = this; - w->width = int32_t(width); - w->height = int32_t(height); + w->size[0] = int32_t(width); + w->size[1] = int32_t(height); w->is_dialog = is_dialog; + /* NOTE(@campbellbarton): The scale set here to avoid flickering on startup. + * When all monitors use the same scale (which is quite common) there aren't any problems. + * + * When monitors have different scales there may still be a visible window resize on startup. + * Ideally it would be possible to know the scale this window will use however that's only + * known once #surface_enter callback runs (which isn't guaranteed to run at all). + * + * Using the maximum scale is best as it results in the window first being smaller, + * avoiding a large window flashing before it's made smaller. */ + w->scale = outputs_max_scale_or_default(this->m_system->outputs(), 1, &w->dpi); + /* Window surfaces. */ - w->surface = wl_compositor_create_surface(m_system->compositor()); - wl_surface_add_listener(w->surface, &wl_surface_listener, this); + w->wl_surface = wl_compositor_create_surface(m_system->compositor()); + ghost_wl_surface_tag(w->wl_surface); + + wl_surface_set_buffer_scale(w->wl_surface, w->scale); + + wl_surface_add_listener(w->wl_surface, &wl_surface_listener, this); - w->egl_window = wl_egl_window_create(w->surface, int(width), int(height)); + w->egl_window = wl_egl_window_create(w->wl_surface, int(w->size[0]), int(w->size[1])); - w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->shell(), w->surface); + /* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels. + * This has the advantage that the size limit is the same when moving the window between monitors + * with different scales set. If it was important to limit in pixels it could be re-calculated + * when the `w->scale` changed. */ + const int32_t size_min[2] = {320, 240}; + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + /* create window decorations */ + w->decor_frame = libdecor_decorate( + m_system->decor_context(), w->wl_surface, &libdecor_frame_iface, w); + libdecor_frame_map(w->decor_frame); + + libdecor_frame_set_min_content_size(w->decor_frame, UNPACK2(size_min)); + + if (parentWindow) { + libdecor_frame_set_parent( + w->decor_frame, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->decor_frame); + } +#else + w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->xdg_shell(), w->wl_surface); w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface); - if (m_system->decoration_manager()) { + xdg_toplevel_set_min_size(w->xdg_toplevel, UNPACK2(size_min)); + + if (m_system->xdg_decoration_manager()) { w->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - m_system->decoration_manager(), w->xdg_toplevel); + m_system->xdg_decoration_manager(), w->xdg_toplevel); zxdg_toplevel_decoration_v1_add_listener( w->xdg_toplevel_decoration, &toplevel_decoration_v1_listener, w); zxdg_toplevel_decoration_v1_set_mode(w->xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } - wl_surface_set_user_data(w->surface, this); - - xdg_surface_add_listener(w->xdg_surface, &surface_listener, w); + xdg_surface_add_listener(w->xdg_surface, &xdg_surface_listener, w); xdg_toplevel_add_listener(w->xdg_toplevel, &toplevel_listener, w); if (parentWindow && is_dialog) { @@ -240,87 +511,63 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, w->xdg_toplevel, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->xdg_toplevel); } +#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */ + + setTitle(title); + + wl_surface_set_user_data(w->wl_surface, this); + /* Call top-level callbacks. */ - wl_surface_commit(w->surface); + wl_surface_commit(w->wl_surface); wl_display_roundtrip(m_system->display()); +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + /* It's important not to return until the window is configured or + * calls to `setState` from Blender will crash `libdecor`. */ + while (!w->decor_configured) { + if (libdecor_dispatch(m_system->decor_context(), 0) < 0) { + break; + } + } +#endif + #ifdef GHOST_OPENGL_ALPHA setOpaque(); #endif +#ifndef WITH_GHOST_WAYLAND_LIBDECOR /* Causes a glitch with `libdecor` for some reason. */ setState(state); - - setTitle(title); +#endif /* EGL context. */ if (setDrawingContextType(type) == GHOST_kFailure) { GHOST_PRINT("Failed to create EGL context" << std::endl); } - /* set swap interval to 0 to prevent blocking */ + /* Set swap interval to 0 to prevent blocking. */ setSwapInterval(0); } -GHOST_TSuccess GHOST_WindowWayland::close() -{ - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowClose, this)); -} - -GHOST_TSuccess GHOST_WindowWayland::activate() -{ - if (m_system->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) { - return GHOST_kFailure; - } - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowActivate, this)); -} - -GHOST_TSuccess GHOST_WindowWayland::deactivate() -{ - m_system->getWindowManager()->setWindowInactive(this); - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowDeactivate, this)); -} - -GHOST_TSuccess GHOST_WindowWayland::notify_size() -{ -#ifdef GHOST_OPENGL_ALPHA - setOpaque(); -#endif - - return m_system->pushEvent( - new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this)); -} - -wl_surface *GHOST_WindowWayland::surface() const -{ - return w->surface; -} - -const std::vector<output_t *> &GHOST_WindowWayland::outputs() const -{ - return m_system->outputs(); -} - -std::unordered_set<const output_t *> &GHOST_WindowWayland::outputs_active() -{ - return w->outputs; -} - -uint16_t &GHOST_WindowWayland::dpi() -{ - return w->dpi; -} - -int &GHOST_WindowWayland::scale() -{ - return w->scale; -} - GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { - return m_system->setCursorGrab(mode, m_cursorGrab, w->surface); + GHOST_Rect bounds_buf; + GHOST_Rect *bounds = nullptr; + if (m_cursorGrab == GHOST_kGrabWrap) { + if (getCursorGrabBounds(bounds_buf) == GHOST_kFailure) { + getClientBounds(bounds_buf); + } + bounds = &bounds_buf; + } + if (m_system->window_cursor_grab_set(mode, + m_cursorGrab, + m_cursorGrabInitPos, + bounds, + m_cursorGrabAxis, + w->wl_surface, + w->scale)) { + return GHOST_kSuccess; + } + return GHOST_kFailure; } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape) @@ -330,16 +577,32 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor s return ok; } +bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay() +{ + return m_system->getCursorGrabUseSoftwareDisplay(m_cursorGrab); +} + GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape( uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor) { return m_system->setCustomCursorShape(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); } +GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) +{ + return m_system->getCursorBitmap(bitmap); +} + void GHOST_WindowWayland::setTitle(const char *title) { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_set_app_id(w->decor_frame, title); + libdecor_frame_set_title(w->decor_frame, title); +#else xdg_toplevel_set_title(w->xdg_toplevel, title); xdg_toplevel_set_app_id(w->xdg_toplevel, title); +#endif + this->title = title; } @@ -355,22 +618,32 @@ void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const { - bounds.set(0, 0, w->width, w->height); + bounds.set(0, 0, UNPACK2(w->size)); } -GHOST_TSuccess GHOST_WindowWayland::setClientWidth(uint32_t width) +GHOST_TSuccess GHOST_WindowWayland::setClientWidth(const uint32_t width) { - return setClientSize(width, uint32_t(w->height)); + return setClientSize(width, uint32_t(w->size[1])); } -GHOST_TSuccess GHOST_WindowWayland::setClientHeight(uint32_t height) +GHOST_TSuccess GHOST_WindowWayland::setClientHeight(const uint32_t height) { - return setClientSize(uint32_t(w->width), height); + return setClientSize(uint32_t(w->size[0]), height); } -GHOST_TSuccess GHOST_WindowWayland::setClientSize(uint32_t width, uint32_t height) +GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const uint32_t height) { wl_egl_window_resize(w->egl_window, int(width), int(height), 0, 0); + + /* Override any pending size that may be set. */ + w->size_pending[0] = 0; + w->size_pending[1] = 0; + + w->size[0] = width; + w->size[1] = height; + + notify_size(); + return GHOST_kSuccess; } @@ -397,12 +670,27 @@ GHOST_WindowWayland::~GHOST_WindowWayland() releaseNativeHandles(); wl_egl_window_destroy(w->egl_window); + +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_unref(w->decor_frame); +#else if (w->xdg_toplevel_decoration) { zxdg_toplevel_decoration_v1_destroy(w->xdg_toplevel_decoration); } xdg_toplevel_destroy(w->xdg_toplevel); xdg_surface_destroy(w->xdg_surface); - wl_surface_destroy(w->surface); +#endif + + /* Clear any pointers to this window. This is needed because there are no guarantees + * that flushing the display will the "leave" handlers before handling events. */ + m_system->window_surface_unref(w->wl_surface); + + wl_surface_destroy(w->wl_surface); + + /* NOTE(@campbellbarton): Flushing will often run the appropriate handlers event + * (#wl_surface_listener.leave in particular) to avoid attempted access to the freed surfaces. + * This is not fool-proof though, hence the call to #window_surface_unref, see: T99078. */ + wl_display_flush(m_system->display()); delete w; } @@ -424,23 +712,43 @@ GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state) /* Unset states. */ switch (getState()) { case GHOST_kWindowStateMaximized: +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_unset_maximized(w->decor_frame); +#else xdg_toplevel_unset_maximized(w->xdg_toplevel); +#endif break; case GHOST_kWindowStateFullScreen: +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_unset_fullscreen(w->decor_frame); +#else xdg_toplevel_unset_fullscreen(w->xdg_toplevel); +#endif break; default: break; } break; case GHOST_kWindowStateMaximized: +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_set_maximized(w->decor_frame); +#else xdg_toplevel_set_maximized(w->xdg_toplevel); +#endif break; case GHOST_kWindowStateMinimized: +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_set_minimized(w->decor_frame); +#else xdg_toplevel_set_minimized(w->xdg_toplevel); +#endif break; case GHOST_kWindowStateFullScreen: +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_set_fullscreen(w->decor_frame, nullptr); +#else xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr); +#endif break; case GHOST_kWindowStateEmbedded: return GHOST_kFailure; @@ -453,12 +761,10 @@ GHOST_TWindowState GHOST_WindowWayland::getState() const if (w->is_fullscreen) { return GHOST_kWindowStateFullScreen; } - else if (w->is_maximised) { + if (w->is_maximised) { return GHOST_kWindowStateMaximized; } - else { - return GHOST_kWindowStateNormal; - } + return GHOST_kWindowStateNormal; } GHOST_TSuccess GHOST_WindowWayland::invalidate() @@ -473,13 +779,21 @@ GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/) GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_set_fullscreen(w->decor_frame, nullptr); +#else xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr); +#endif return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWayland::endFullScreen() const { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + libdecor_frame_unset_fullscreen(w->decor_frame); +#else xdg_toplevel_unset_fullscreen(w->xdg_toplevel); +#endif return GHOST_kSuccess; } @@ -495,7 +809,7 @@ void GHOST_WindowWayland::setOpaque() const /* Make the window opaque. */ region = wl_compositor_create_region(m_system->compositor()); - wl_region_add(region, 0, 0, w->width, w->height); + wl_region_add(region, 0, 0, UNPACK2(w->size)); wl_surface_set_opaque_region(w->surface, region); wl_region_destroy(region); } @@ -525,10 +839,10 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY, EGL_OPENGL_API); - if (context->initializeDrawingContext()) + if (context->initializeDrawingContext()) { return context; - else - delete context; + } + delete context; } context = new GHOST_ContextEGL(this->m_system, m_wantStereoVisual, @@ -546,3 +860,134 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public WAYLAND Direct Data Access + * + * Expose some members via methods. + * \{ */ + +uint16_t GHOST_WindowWayland::dpi() const +{ + return w->dpi; +} + +int GHOST_WindowWayland::scale() const +{ + return w->scale; +} + +wl_surface *GHOST_WindowWayland::surface() const +{ + return w->wl_surface; +} + +const std::vector<output_t *> &GHOST_WindowWayland::outputs() +{ + return w->outputs; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public WAYLAND Window Level Functions + * + * High Level Windowing Utilities. + * \{ */ + +GHOST_TSuccess GHOST_WindowWayland::close() +{ + return m_system->pushEvent( + new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowClose, this)); +} + +GHOST_TSuccess GHOST_WindowWayland::activate() +{ + if (m_system->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) { + return GHOST_kFailure; + } + return m_system->pushEvent( + new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowActivate, this)); +} + +GHOST_TSuccess GHOST_WindowWayland::deactivate() +{ + m_system->getWindowManager()->setWindowInactive(this); + return m_system->pushEvent( + new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowDeactivate, this)); +} + +GHOST_TSuccess GHOST_WindowWayland::notify_size() +{ +#ifdef GHOST_OPENGL_ALPHA + setOpaque(); +#endif + + return m_system->pushEvent( + new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public WAYLAND Utility Functions + * + * Functionality only used for the WAYLAND implementation. + * \{ */ + +bool GHOST_WindowWayland::outputs_changed_update_scale() +{ + uint32_t dpi_next; + const int scale_next = outputs_max_scale_or_default(this->outputs(), 0, &dpi_next); + if (UNLIKELY(scale_next == 0)) { + return false; + } + + window_t *win = this->w; + const uint32_t dpi_curr = win->dpi; + const int scale_curr = win->scale; + bool changed = false; + + if (scale_next != scale_curr) { + /* Unlikely but possible there is a pending size change is set. */ + win->size_pending[0] = (win->size_pending[0] / scale_curr) * scale_next; + win->size_pending[1] = (win->size_pending[1] / scale_curr) * scale_next; + + win->scale = scale_next; + wl_surface_set_buffer_scale(w->wl_surface, scale_next); + changed = true; + } + + if (dpi_next != dpi_curr) { + /* Using the real DPI will cause wrong scaling of the UI + * use a multiplier for the default DPI as workaround. */ + win->dpi = dpi_next; + changed = true; + } + + return changed; +} + +bool GHOST_WindowWayland::outputs_enter(output_t *reg_output) +{ + std::vector<output_t *> &outputs = w->outputs; + auto it = std::find(outputs.begin(), outputs.end(), reg_output); + if (it != outputs.end()) { + return false; + } + outputs.push_back(reg_output); + return true; +} + +bool GHOST_WindowWayland::outputs_leave(output_t *reg_output) +{ + std::vector<output_t *> &outputs = w->outputs; + auto it = std::find(outputs.begin(), outputs.end(), reg_output); + if (it == outputs.end()) { + return false; + } + outputs.erase(it); + return true; +} + +/** \} */ |