/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup GHOST */ #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 #ifdef WITH_GHOST_WAYLAND_DYNLOAD # include #endif #include #include /* For `std::find`. */ #ifdef WITH_GHOST_WAYLAND_LIBDECOR # ifdef WITH_GHOST_WAYLAND_DYNLOAD # include # endif # include #endif /* Generated by `wayland-scanner`. */ #include #include /* Logging, use `ghost.wl.*` prefix. */ #include "CLG_log.h" static constexpr size_t base_dpi = 96; #ifdef WITH_GHOST_WAYLAND_LIBDECOR /* Access `use_libdecor` in #GHOST_SystemWayland. */ # define use_libdecor GHOST_SystemWayland::use_libdecor_runtime() #endif static GHOST_WindowManager *window_manager = nullptr; #ifdef WITH_GHOST_WAYLAND_LIBDECOR struct WGL_LibDecor_Window { struct libdecor_frame *frame = nullptr; bool configured = false; }; static void gwl_libdecor_window_destroy(WGL_LibDecor_Window *decor) { libdecor_frame_unref(decor->frame); delete decor; } #endif /* WITH_GHOST_WAYLAND_LIBDECOR */ struct WGL_XDG_Decor_Window { struct xdg_surface *surface = nullptr; struct zxdg_toplevel_decoration_v1 *toplevel_decor = nullptr; struct xdg_toplevel *toplevel = nullptr; enum zxdg_toplevel_decoration_v1_mode mode = (enum zxdg_toplevel_decoration_v1_mode)0; }; static void gwl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor) { if (decor->toplevel_decor) { zxdg_toplevel_decoration_v1_destroy(decor->toplevel_decor); } xdg_toplevel_destroy(decor->toplevel); xdg_surface_destroy(decor->surface); delete decor; } struct GWL_Window { GHOST_WindowWayland *ghost_window = 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 outputs; /** The scale value written to #wl_surface_set_buffer_scale. */ int scale = 0; /** * The fractional scale used to calculate the DPI. * (always set, even when scaling is rounded to whole units). */ wl_fixed_t scale_fractional = 0; #ifdef WITH_GHOST_WAYLAND_LIBDECOR WGL_LibDecor_Window *libdecor = nullptr; #endif WGL_XDG_Decor_Window *xdg_decor = nullptr; 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 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 GWL_Output *output_a, const GWL_Output *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 &outputs, const int32_t scale_default, wl_fixed_t *r_scale_fractional) { const GWL_Output *output_max = nullptr; for (const GWL_Output *reg_output : outputs) { if (!output_max || (output_scale_cmp(output_max, reg_output) == -1)) { output_max = reg_output; } } if (output_max) { if (r_scale_fractional) { *r_scale_fractional = output_max->has_scale_fractional ? output_max->scale_fractional : wl_fixed_from_int(output_max->scale); } return output_max->scale; } if (r_scale_fractional) { *r_scale_fractional = wl_fixed_from_int(scale_default); } return scale_default; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (XDG Top Level), #xdg_toplevel_listener * \{ */ 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); GWL_Window *win = static_cast(data); 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; enum xdg_toplevel_state *state; WL_ARRAY_FOR_EACH (state, states) { switch (*state) { case XDG_TOPLEVEL_STATE_MAXIMIZED: win->is_maximised = true; break; case XDG_TOPLEVEL_STATE_FULLSCREEN: win->is_fullscreen = true; break; case XDG_TOPLEVEL_STATE_ACTIVATED: win->is_active = true; break; default: break; } } } static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/) { CLOG_INFO(LOG, 2, "close"); static_cast(data)->ghost_window->close(); } static const xdg_toplevel_listener toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; #undef LOG /** \} */ /* -------------------------------------------------------------------- */ /** \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"); GWL_Window *win = static_cast(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->ghost_window->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->ghost_window->activate() : win->ghost_window->deactivate(); state = libdecor_state_new(UNPACK2(size_next)); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); win->libdecor->configured = true; } static void frame_handle_close(struct libdecor_frame * /*frame*/, void *data) { CLOG_INFO(LOG, 2, "close"); static_cast(data)->ghost_window->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(data)->ghost_window->swapBuffers(); static_cast(data)->ghost_window->swapBuffers(); } static struct libdecor_frame_interface libdecor_frame_iface = { frame_handle_configure, frame_handle_close, frame_handle_commit, }; # undef LOG #endif /* WITH_GHOST_WAYLAND_LIBDECOR. */ /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (XDG Decoration Listener), #zxdg_toplevel_decoration_v1_listener * \{ */ 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*/, const uint32_t mode) { CLOG_INFO(LOG, 2, "configure (mode=%u)", mode); static_cast(data)->xdg_decor->mode = (zxdg_toplevel_decoration_v1_mode)mode; } static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { xdg_toplevel_decoration_handle_configure, }; #undef LOG /** \} */ /* -------------------------------------------------------------------- */ /** \name Listener (XDG Surface Handle Configure), #xdg_surface_listener * \{ */ 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) { GWL_Window *win = static_cast(data); if (win->xdg_decor->surface != xdg_surface) { CLOG_INFO(LOG, 2, "configure (skipped)"); return; } 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->ghost_window->notify_size(); } if (win->is_active) { win->ghost_window->activate(); } else { win->ghost_window->deactivate(); } xdg_surface_ack_configure(xdg_surface, serial); } static const xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; #undef LOG /** \} */ /* -------------------------------------------------------------------- */ /** \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 *wl_output) { if (!ghost_wl_output_own(wl_output)) { CLOG_INFO(LOG, 2, "enter (skipped)"); return; } CLOG_INFO(LOG, 2, "enter"); GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); GHOST_WindowWayland *win = static_cast(data); if (win->outputs_enter(reg_output)) { win->outputs_changed_update_scale(); } } static void surface_handle_leave(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *wl_output) { if (!ghost_wl_output_own(wl_output)) { CLOG_INFO(LOG, 2, "leave (skipped)"); return; } CLOG_INFO(LOG, 2, "leave"); GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); GHOST_WindowWayland *win = static_cast(data); if (win->outputs_leave(reg_output)) { win->outputs_changed_update_scale(); } } static struct wl_surface_listener wl_surface_listener = { surface_handle_enter, surface_handle_leave, }; #undef LOG /** \} */ /* -------------------------------------------------------------------- */ /** \name GHOST Implementation * * WAYLAND specific implementation of the #GHOST_Window interface. * \{ */ GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorShape) { return system_->hasCursorShape(cursorShape); } GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, const char *title, const int32_t /*left*/, const int32_t /*top*/, const uint32_t width, const uint32_t height, const GHOST_TWindowState state, const GHOST_IWindow *parentWindow, const GHOST_TDrawingContextType type, const bool is_dialog, const bool stereoVisual, const bool exclusive) : GHOST_Window(width, height, state, stereoVisual, exclusive), system_(system), window_(new GWL_Window) { /* Globally store pointer to window manager. */ if (!window_manager) { window_manager = system_->getWindowManager(); } window_->ghost_window = this; window_->size[0] = int32_t(width); window_->size[1] = int32_t(height); window_->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. */ window_->scale = outputs_max_scale_or_default(system_->outputs(), 1, &window_->scale_fractional); /* Window surfaces. */ window_->wl_surface = wl_compositor_create_surface(system_->wl_compositor()); ghost_wl_surface_tag(window_->wl_surface); wl_surface_set_buffer_scale(window_->wl_surface, window_->scale); wl_surface_add_listener(window_->wl_surface, &wl_surface_listener, this); window_->egl_window = wl_egl_window_create( window_->wl_surface, int(window_->size[0]), int(window_->size[1])); /* 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 `window_->scale` changed. */ const int32_t size_min[2] = {320, 240}; /* This value is expected to match the base name of the `.desktop` file. see T101805. * * NOTE: the XDG desktop-entry-spec defines that this should follow the "reverse DNS" convention. * For e.g. `org.blender.Blender` - however the `.desktop` file distributed with Blender is * simply called `blender.desktop`, so the it's important to follow that name. * Other distributions such as SNAP & FLATPAK may need to change this value T101779. * Currently there isn't a way to configure this, we may want to support that. */ const char *xdg_app_id = "blender"; #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { window_->libdecor = new WGL_LibDecor_Window; WGL_LibDecor_Window &decor = *window_->libdecor; /* create window decorations */ decor.frame = libdecor_decorate( system_->libdecor_context(), window_->wl_surface, &libdecor_frame_iface, window_); libdecor_frame_map(window_->libdecor->frame); libdecor_frame_set_min_content_size(decor.frame, UNPACK2(size_min)); libdecor_frame_set_app_id(decor.frame, xdg_app_id); if (parentWindow) { WGL_LibDecor_Window &decor_parent = *dynamic_cast(parentWindow)->window_->libdecor; libdecor_frame_set_parent(decor.frame, decor_parent.frame); } } else #endif { window_->xdg_decor = new WGL_XDG_Decor_Window; WGL_XDG_Decor_Window &decor = *window_->xdg_decor; decor.surface = xdg_wm_base_get_xdg_surface(system_->xdg_decor_shell(), window_->wl_surface); decor.toplevel = xdg_surface_get_toplevel(decor.surface); xdg_toplevel_set_min_size(decor.toplevel, UNPACK2(size_min)); xdg_toplevel_set_app_id(decor.toplevel, xdg_app_id); if (system_->xdg_decor_manager()) { decor.toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration( system_->xdg_decor_manager(), decor.toplevel); zxdg_toplevel_decoration_v1_add_listener( decor.toplevel_decor, &toplevel_decoration_v1_listener, window_); zxdg_toplevel_decoration_v1_set_mode(decor.toplevel_decor, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } xdg_surface_add_listener(decor.surface, &xdg_surface_listener, window_); xdg_toplevel_add_listener(decor.toplevel, &toplevel_listener, window_); if (parentWindow && is_dialog) { WGL_XDG_Decor_Window &decor_parent = *dynamic_cast(parentWindow)->window_->xdg_decor; xdg_toplevel_set_parent(decor.toplevel, decor_parent.toplevel); } } setTitle(title); wl_surface_set_user_data(window_->wl_surface, this); /* Call top-level callbacks. */ wl_surface_commit(window_->wl_surface); wl_display_roundtrip(system_->wl_display()); #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { WGL_LibDecor_Window &decor = *window_->libdecor; /* It's important not to return until the window is configured or * calls to `setState` from Blender will crash `libdecor`. */ while (!decor.configured) { if (libdecor_dispatch(system_->libdecor_context(), 0) < 0) { break; } } } #endif #ifdef GHOST_OPENGL_ALPHA setOpaque(); #endif /* Causes a glitch with `libdecor` for some reason. */ #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor == false) #endif { setState(state); } /* EGL context. */ if (setDrawingContextType(type) == GHOST_kFailure) { GHOST_PRINT("Failed to create EGL context" << std::endl); } /* Set swap interval to 0 to prevent blocking. */ setSwapInterval(0); } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { 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 (system_->window_cursor_grab_set(mode, m_cursorGrab, m_cursorGrabInitPos, bounds, m_cursorGrabAxis, window_->wl_surface, window_->scale)) { return GHOST_kSuccess; } return GHOST_kFailure; } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape) { const GHOST_TSuccess ok = system_->setCursorShape(shape); m_cursorShape = (ok == GHOST_kSuccess) ? shape : GHOST_kStandardCursorDefault; return ok; } bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay() { return 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 system_->setCustomCursorShape(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); } GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { return system_->getCursorBitmap(bitmap); } void GHOST_WindowWayland::setTitle(const char *title) { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { WGL_LibDecor_Window &decor = *window_->libdecor; libdecor_frame_set_title(decor.frame, title); } else #endif { WGL_XDG_Decor_Window &decor = *window_->xdg_decor; xdg_toplevel_set_title(decor.toplevel, title); } title_ = title; } std::string GHOST_WindowWayland::getTitle() const { return title_.empty() ? "untitled" : title_; } void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const { getClientBounds(bounds); } void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const { bounds.set(0, 0, UNPACK2(window_->size)); } GHOST_TSuccess GHOST_WindowWayland::setClientWidth(const uint32_t width) { return setClientSize(width, uint32_t(window_->size[1])); } GHOST_TSuccess GHOST_WindowWayland::setClientHeight(const uint32_t height) { return setClientSize(uint32_t(window_->size[0]), height); } GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const uint32_t height) { wl_egl_window_resize(window_->egl_window, int(width), int(height), 0, 0); /* Override any pending size that may be set. */ window_->size_pending[0] = 0; window_->size_pending[1] = 0; window_->size[0] = width; window_->size[1] = height; notify_size(); return GHOST_kSuccess; } void GHOST_WindowWayland::screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const { outX = inX; outY = inY; } void GHOST_WindowWayland::clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const { outX = inX; outY = inY; } GHOST_WindowWayland::~GHOST_WindowWayland() { releaseNativeHandles(); wl_egl_window_destroy(window_->egl_window); #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { gwl_libdecor_window_destroy(window_->libdecor); } else #endif { gwl_xdg_decor_window_destroy(window_->xdg_decor); } /* 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. */ system_->window_surface_unref(window_->wl_surface); wl_surface_destroy(window_->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(system_->wl_display()); delete window_; } uint16_t GHOST_WindowWayland::getDPIHint() { /* Using the physical DPI will cause wrong scaling of the UI * use a multiplier for the default DPI as a workaround. */ return wl_fixed_to_int(window_->scale_fractional * base_dpi); } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) { return system_->setCursorVisibility(visible); } GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state) { switch (state) { case GHOST_kWindowStateNormal: /* Unset states. */ switch (getState()) { case GHOST_kWindowStateMaximized: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_unset_maximized(window_->libdecor->frame); } else #endif { xdg_toplevel_unset_maximized(window_->xdg_decor->toplevel); } break; } case GHOST_kWindowStateFullScreen: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_unset_fullscreen(window_->libdecor->frame); } else #endif { xdg_toplevel_unset_fullscreen(window_->xdg_decor->toplevel); } break; } default: { break; } } break; case GHOST_kWindowStateMaximized: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_set_maximized(window_->libdecor->frame); } else #endif { xdg_toplevel_set_maximized(window_->xdg_decor->toplevel); } break; } case GHOST_kWindowStateMinimized: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_set_minimized(window_->libdecor->frame); } else #endif { xdg_toplevel_set_minimized(window_->xdg_decor->toplevel); } break; } case GHOST_kWindowStateFullScreen: { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_set_fullscreen(window_->libdecor->frame, nullptr); } else #endif { xdg_toplevel_set_fullscreen(window_->xdg_decor->toplevel, nullptr); } break; } case GHOST_kWindowStateEmbedded: { return GHOST_kFailure; } } return GHOST_kSuccess; } GHOST_TWindowState GHOST_WindowWayland::getState() const { if (window_->is_fullscreen) { return GHOST_kWindowStateFullScreen; } if (window_->is_maximised) { return GHOST_kWindowStateMaximized; } return GHOST_kWindowStateNormal; } GHOST_TSuccess GHOST_WindowWayland::invalidate() { return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/) { return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_set_fullscreen(window_->libdecor->frame, nullptr); } else #endif { xdg_toplevel_set_fullscreen(window_->xdg_decor->toplevel, nullptr); } return GHOST_kSuccess; } GHOST_TSuccess GHOST_WindowWayland::endFullScreen() const { #ifdef WITH_GHOST_WAYLAND_LIBDECOR if (use_libdecor) { libdecor_frame_unset_fullscreen(window_->libdecor->frame); } else #endif { xdg_toplevel_unset_fullscreen(window_->xdg_decor->toplevel); } return GHOST_kSuccess; } bool GHOST_WindowWayland::isDialog() const { return window_->is_dialog; } #ifdef GHOST_OPENGL_ALPHA void GHOST_WindowWayland::setOpaque() const { struct wl_region *region; /* Make the window opaque. */ region = wl_compositor_create_region(system_->compositor()); wl_region_add(region, 0, 0, UNPACK2(window_->size)); wl_surface_set_opaque_region(window_->surface, region); wl_region_destroy(region); } #endif /** * \param type: The type of rendering context create. * \return Indication of success. */ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType type) { GHOST_Context *context; switch (type) { case GHOST_kDrawingContextTypeNone: context = new GHOST_ContextNone(m_wantStereoVisual); break; case GHOST_kDrawingContextTypeOpenGL: for (int minor = 6; minor >= 0; --minor) { context = new GHOST_ContextEGL(system_, m_wantStereoVisual, EGLNativeWindowType(window_->egl_window), EGLNativeDisplayType(system_->wl_display()), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 4, minor, GHOST_OPENGL_EGL_CONTEXT_FLAGS, GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY, EGL_OPENGL_API); if (context->initializeDrawingContext()) { return context; } delete context; } context = new GHOST_ContextEGL(system_, m_wantStereoVisual, EGLNativeWindowType(window_->egl_window), EGLNativeDisplayType(system_->wl_display()), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 3, 3, GHOST_OPENGL_EGL_CONTEXT_FLAGS, GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY, EGL_OPENGL_API); } if (context->initializeDrawingContext()) { return context; } delete context; return nullptr; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Public WAYLAND Direct Data Access * * Expose some members via methods. * \{ */ int GHOST_WindowWayland::scale() const { return window_->scale; } wl_fixed_t GHOST_WindowWayland::scale_fractional() const { return window_->scale_fractional; } wl_surface *GHOST_WindowWayland::wl_surface() const { return window_->wl_surface; } const std::vector &GHOST_WindowWayland::outputs() { return window_->outputs; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Public WAYLAND Window Level Functions * * High Level Windowing Utilities. * \{ */ GHOST_TSuccess GHOST_WindowWayland::close() { return system_->pushEvent( new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowClose, this)); } GHOST_TSuccess GHOST_WindowWayland::activate() { if (system_->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) { return GHOST_kFailure; } return system_->pushEvent( new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowActivate, this)); } GHOST_TSuccess GHOST_WindowWayland::deactivate() { system_->getWindowManager()->setWindowInactive(this); return system_->pushEvent( new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowDeactivate, this)); } GHOST_TSuccess GHOST_WindowWayland::notify_size() { #ifdef GHOST_OPENGL_ALPHA setOpaque(); #endif return system_->pushEvent( new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowSize, this)); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Public WAYLAND Utility Functions * * Functionality only used for the WAYLAND implementation. * \{ */ /** * Return true when the windows scale or DPI changes. */ bool GHOST_WindowWayland::outputs_changed_update_scale() { wl_fixed_t scale_fractional_next = 0; const int scale_next = outputs_max_scale_or_default(outputs(), 0, &scale_fractional_next); if (UNLIKELY(scale_next == 0)) { return false; } const wl_fixed_t scale_fractional_curr = window_->scale_fractional; const int scale_curr = window_->scale; bool changed = false; if (scale_next != scale_curr) { window_->scale = scale_next; wl_surface_set_buffer_scale(window_->wl_surface, scale_next); /* It's important to resize the window immediately, to avoid the window changing size * and flickering in a constant feedback loop (in some bases). */ if ((window_->size_pending[0] != 0) && (window_->size_pending[1] != 0)) { /* Unlikely but possible there is a pending size change is set. */ window_->size[0] = window_->size_pending[0]; window_->size[1] = window_->size_pending[1]; window_->size_pending[0] = 0; window_->size_pending[1] = 0; } window_->size[0] = (window_->size[0] / scale_curr) * scale_next; window_->size[1] = (window_->size[1] / scale_curr) * scale_next; wl_egl_window_resize(window_->egl_window, UNPACK2(window_->size), 0, 0); window_->ghost_window->notify_size(); changed = true; } if (scale_fractional_next != scale_fractional_curr) { window_->scale_fractional = scale_fractional_next; changed = true; /* As this is a low-level function, we might want adding this event to be optional, * always add the event unless it causes issues. */ GHOST_System *system = (GHOST_System *)GHOST_ISystem::getSystem(); system->pushEvent( new GHOST_Event(system->getMilliSeconds(), GHOST_kEventWindowDPIHintChanged, this)); } return changed; } bool GHOST_WindowWayland::outputs_enter(GWL_Output *output) { std::vector &outputs = window_->outputs; auto it = std::find(outputs.begin(), outputs.end(), output); if (it != outputs.end()) { return false; } outputs.push_back(output); return true; } bool GHOST_WindowWayland::outputs_leave(GWL_Output *output) { std::vector &outputs = window_->outputs; auto it = std::find(outputs.begin(), outputs.end(), output); if (it == outputs.end()) { return false; } outputs.erase(it); return true; } /** \} */