Files
test/intern/ghost/intern/GHOST_WindowWayland.cpp
2022-11-10 16:47:55 +11:00

1104 lines
33 KiB
C++

/* 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 <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
/* Generated by `wayland-scanner`. */
#include <xdg-decoration-unstable-v1-client-protocol.h>
#include <xdg-shell-client-protocol.h>
/* 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<GWL_Output *> 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<GWL_Output *> &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<GWL_Window *>(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<GWL_Window *>(data)->ghost_window->close();
}
static const xdg_toplevel_listener xdg_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<GWL_Window *>(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<GWL_Window *>(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<GWL_Window *>(data)->ghost_window->swapBuffers();
static_cast<GWL_Window *>(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<GWL_Window *>(data)->xdg_decor->mode = (zxdg_toplevel_decoration_v1_mode)mode;
}
static const zxdg_toplevel_decoration_v1_listener xdg_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<GWL_Window *>(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<GHOST_WindowWayland *>(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<GHOST_WindowWayland *>(data);
if (win->outputs_leave(reg_output)) {
win->outputs_changed_update_scale();
}
}
static const 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, window_);
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<const GHOST_WindowWayland *>(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, &xdg_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, &xdg_toplevel_listener, window_);
if (parentWindow && is_dialog) {
WGL_XDG_Decor_Window &decor_parent =
*dynamic_cast<const GHOST_WindowWayland *>(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<GWL_Output *> &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<GWL_Output *> &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<GWL_Output *> &outputs = window_->outputs;
auto it = std::find(outputs.begin(), outputs.end(), output);
if (it == outputs.end()) {
return false;
}
outputs.erase(it);
return true;
}
/** \} */