/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup GHOST
 */

/* For standard X11 cursors */
#include <X11/Xatom.h>
#include <X11/Xmd.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>

#include "GHOST_Debug.hh"
#include "GHOST_IconX11.hh"
#include "GHOST_SystemX11.hh"
#include "GHOST_Types.h"
#include "GHOST_WindowX11.hh"
#include "GHOST_utildefines.hh"

#ifdef WITH_XDND
#  include "GHOST_DropTargetX11.hh"
#endif

#ifdef WITH_OPENGL_BACKEND
#  include "GHOST_ContextEGL.hh"
#  include "GHOST_ContextGLX.hh"
#endif
#ifdef WITH_VULKAN_BACKEND
#  include "GHOST_ContextVK.hh"
#endif

/* For #XIWarpPointer. */
#ifdef WITH_X11_XINPUT
#  include <X11/extensions/XInput2.h>
#endif

/* For DPI value. */
#include <X11/Xresource.h>

#include <cstdio>
#include <cstring>

/* For `gethostname`. */
#include <unistd.h>

#include <algorithm>
#include <climits>
#include <cmath>
#include <string>

/* For obscure full screen mode stuff
 * lifted verbatim from blut. */

struct MotifWmHints {
  long flags;
  long functions;
  long decorations;
  long input_mode;
};

enum {
  MWM_HINTS_FUNCTIONS = (1L << 0),
  MWM_HINTS_DECORATIONS = (1L << 1),
};
enum {
  MWM_FUNCTION_ALL = (1L << 0),
  MWM_FUNCTION_RESIZE = (1L << 1),
  MWM_FUNCTION_MOVE = (1L << 2),
  MWM_FUNCTION_MINIMIZE = (1L << 3),
  MWM_FUNCTION_MAXIMIZE = (1L << 4),
  MWM_FUNCTION_CLOSE = (1L << 5),
};

#ifndef HOST_NAME_MAX
#  define HOST_NAME_MAX 64
#endif

// #define GHOST_X11_GRAB

/*
 * A Client can't change the window property, that is
 * the work of the window manager. In case, we send
 * a ClientMessage to the RootWindow with the property
 * and the Action (WM-spec define this):
 */
#define _NET_WM_STATE_REMOVE 0
#define _NET_WM_STATE_ADD 1
// #define _NET_WM_STATE_TOGGLE 2 // UNUSED

#ifdef WITH_OPENGL_BACKEND
static XVisualInfo *get_x11_visualinfo(Display *display)
{
  int num_visuals;
  XVisualInfo vinfo_template;
  vinfo_template.screen = DefaultScreen(display);
  return XGetVisualInfo(display, VisualScreenMask, &vinfo_template, &num_visuals);
}
#endif

GHOST_WindowX11::GHOST_WindowX11(GHOST_SystemX11 *system,
                                 Display *display,
                                 const char *title,
                                 int32_t left,
                                 int32_t top,
                                 uint32_t width,
                                 uint32_t height,
                                 GHOST_TWindowState state,
                                 GHOST_WindowX11 *parentWindow,
                                 GHOST_TDrawingContextType type,
                                 const bool is_dialog,
                                 const bool stereoVisual,
                                 const bool exclusive,
                                 const bool is_debug)
    : GHOST_Window(width, height, state, stereoVisual, exclusive),
      m_display(display),
      m_visualInfo(nullptr),
      m_fbconfig(nullptr),
      m_normal_state(GHOST_kWindowStateNormal),
      m_system(system),
      m_invalid_window(false),
      m_empty_cursor(None),
      m_custom_cursor(None),
      m_visible_cursor(None),
      m_taskbar("blender.desktop"),
#ifdef WITH_XDND
      m_dropTarget(nullptr),
#endif
      m_tabletData(GHOST_TABLET_DATA_NONE),
#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
      m_xic(nullptr),
#endif
      m_valid_setup(false),
      m_is_debug_context(is_debug)
{
#ifdef WITH_OPENGL_BACKEND
  if (type == GHOST_kDrawingContextTypeOpenGL) {
    m_visualInfo = get_x11_visualinfo(m_display);
  }
  else
#endif
  {
    XVisualInfo tmp = {nullptr};
    int n;
    m_visualInfo = XGetVisualInfo(m_display, 0, &tmp, &n);
  }

  /* caller needs to check 'getValid()' */
  if (m_visualInfo == nullptr) {
    fprintf(stderr, "initial window could not find the GLX extension\n");
    return;
  }

  uint xattributes_valuemask = 0;

  XSetWindowAttributes xattributes;
  memset(&xattributes, 0, sizeof(xattributes));

  xattributes_valuemask |= CWBorderPixel;
  xattributes.border_pixel = 0;

  /* Specify which events we are interested in hearing. */

  xattributes_valuemask |= CWEventMask;
  xattributes.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask |
                           EnterWindowMask | LeaveWindowMask | ButtonPressMask |
                           ButtonReleaseMask | PointerMotionMask | FocusChangeMask |
                           PropertyChangeMask | KeymapStateMask;

  if (exclusive) {
    xattributes_valuemask |= CWOverrideRedirect;
    xattributes.override_redirect = True;
  }

  xattributes_valuemask |= CWColormap;
  xattributes.colormap = XCreateColormap(
      m_display, RootWindow(m_display, m_visualInfo->screen), m_visualInfo->visual, AllocNone);

  /* create the window! */
  m_window = XCreateWindow(m_display,
                           RootWindow(m_display, m_visualInfo->screen),
                           left,
                           top,
                           width,
                           height,
                           0, /* no border. */
                           m_visualInfo->depth,
                           InputOutput,
                           m_visualInfo->visual,
                           xattributes_valuemask,
                           &xattributes);

#ifdef WITH_XDND
  /* initialize drop target for newly created window */
  m_dropTarget = new GHOST_DropTargetX11(this, m_system);
  GHOST_PRINT("Set drop target\n");
#endif

  if (ELEM(state, GHOST_kWindowStateMaximized, GHOST_kWindowStateFullScreen)) {
    Atom atoms[2];
    int count = 0;
    if (state == GHOST_kWindowStateMaximized) {
      atoms[count++] = m_system->m_atom._NET_WM_STATE_MAXIMIZED_VERT;
      atoms[count++] = m_system->m_atom._NET_WM_STATE_MAXIMIZED_HORZ;
    }
    else {
      atoms[count++] = m_system->m_atom._NET_WM_STATE_FULLSCREEN;
    }

    XChangeProperty(m_display,
                    m_window,
                    m_system->m_atom._NET_WM_STATE,
                    XA_ATOM,
                    32,
                    PropModeReplace,
                    (uchar *)atoms,
                    count);
    m_post_init = False;
  }
  /*
   * One of the problem with WM-spec is that can't set a property
   * to a window that isn't mapped. That is why we can't "just
   * call setState" here.
   *
   * To fix this, we first need know that the window is really
   * map waiting for the MapNotify event.
   *
   * So, m_post_init indicate that we need wait for the MapNotify
   * event and then set the Window state to the m_post_state.
   */
  else if (!ELEM(state, GHOST_kWindowStateNormal, GHOST_kWindowStateMinimized)) {
    m_post_init = True;
    m_post_state = state;
  }
  else {
    m_post_init = False;
    m_post_state = GHOST_kWindowStateNormal;
  }

  if (is_dialog && parentWindow) {
    setDialogHints(parentWindow);
  }

  /* Create some hints for the window manager on how
   * we want this window treated. */
  {
    XSizeHints *xsizehints = XAllocSizeHints();
    xsizehints->flags = PPosition | PSize | PMinSize | PMaxSize;
    xsizehints->x = left;
    xsizehints->y = top;
    xsizehints->width = width;
    xsizehints->height = height;
    xsizehints->min_width = 320;  /* size hints, could be made apart of the ghost api */
    xsizehints->min_height = 240; /* limits are also arbitrary, but should not allow 1x1 window */
    xsizehints->max_width = 65535;
    xsizehints->max_height = 65535;
    XSetWMNormalHints(m_display, m_window, xsizehints);
    XFree(xsizehints);
  }

  /* XClassHint, title */
  {
    XClassHint *xclasshint = XAllocClassHint();
    const int len = strlen(title) + 1;
    char *wmclass = (char *)malloc(sizeof(char) * len);
    memcpy(wmclass, title, len * sizeof(char));
    xclasshint->res_name = wmclass;
    xclasshint->res_class = wmclass;
    XSetClassHint(m_display, m_window, xclasshint);
    free(wmclass);
    XFree(xclasshint);
  }

  /* The basic for a good ICCCM "work" */
  if (m_system->m_atom.WM_PROTOCOLS) {
    Atom atoms[2];
    int natom = 0;

    if (m_system->m_atom.WM_DELETE_WINDOW) {
      atoms[natom] = m_system->m_atom.WM_DELETE_WINDOW;
      natom++;
    }

    if (m_system->m_atom.WM_TAKE_FOCUS && m_system->m_windowFocus) {
      atoms[natom] = m_system->m_atom.WM_TAKE_FOCUS;
      natom++;
    }

    if (natom) {
      // printf("Register atoms: %d\n", natom);
      XSetWMProtocols(m_display, m_window, atoms, natom);
    }
  }

  /* Set the window hints */
  {
    XWMHints *xwmhints = XAllocWMHints();
    xwmhints->initial_state = NormalState;
    xwmhints->input = (m_system->m_windowFocus) ? True : False;
    xwmhints->flags = InputHint | StateHint;
    XSetWMHints(display, m_window, xwmhints);
    XFree(xwmhints);
  }

  /* set the icon */
  {
    Atom _NET_WM_ICON = XInternAtom(m_display, "_NET_WM_ICON", False);
    XChangeProperty(m_display,
                    m_window,
                    _NET_WM_ICON,
                    XA_CARDINAL,
                    32,
                    PropModeReplace,
                    (uchar *)BLENDER_ICONS_WM_X11,
                    ARRAY_SIZE(BLENDER_ICONS_WM_X11));
  }

  /* set the process ID (_NET_WM_PID) */
  {
    Atom _NET_WM_PID = XInternAtom(m_display, "_NET_WM_PID", False);
    pid_t pid = getpid();
    XChangeProperty(
        m_display, m_window, _NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (uchar *)&pid, 1);
  }

  /* set the hostname (WM_CLIENT_MACHINE) */
  {
    char hostname[HOST_NAME_MAX];
    char *text_array[1];
    XTextProperty text_prop;

    gethostname(hostname, sizeof(hostname));
    hostname[sizeof(hostname) - 1] = '\0';
    text_array[0] = hostname;

    XStringListToTextProperty(text_array, 1, &text_prop);
    XSetWMClientMachine(m_display, m_window, &text_prop);
    XFree(text_prop.value);
  }

#ifdef WITH_X11_XINPUT
  refreshXInputDevices();
#endif

  /* now set up the rendering context. */
  if (setDrawingContextType(type) == GHOST_kSuccess) {
    m_valid_setup = true;
    GHOST_PRINT("Created window\n");
  }

  setTitle(title);

  if (exclusive && system->m_windowFocus) {
    XMapRaised(m_display, m_window);
  }
  else {
    XMapWindow(m_display, m_window);

    if (!system->m_windowFocus) {
      XLowerWindow(m_display, m_window);
    }
  }
  GHOST_PRINT("Mapped window\n");

  XFlush(m_display);
}

#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
static Bool destroyICCallback(XIC /*xic*/, XPointer ptr, XPointer /*data*/)
{
  GHOST_PRINT("XIM input context destroyed\n");

  if (ptr) {
    *(XIC *)ptr = nullptr;
  }
  /* Ignored by X11. */
  return True;
}

bool GHOST_WindowX11::createX11_XIC()
{
  XIM xim = m_system->getX11_XIM();
  if (!xim) {
    return false;
  }

  XICCallback destroy;
  destroy.callback = (XICProc)destroyICCallback;
  destroy.client_data = (XPointer)&m_xic;
  m_xic = XCreateIC(xim,
                    XNClientWindow,
                    m_window,
                    XNFocusWindow,
                    m_window,
                    XNInputStyle,
                    XIMPreeditNothing | XIMStatusNothing,
                    XNResourceName,
                    GHOST_X11_RES_NAME,
                    XNResourceClass,
                    GHOST_X11_RES_CLASS,
                    XNDestroyCallback,
                    &destroy,
                    nullptr);
  if (!m_xic) {
    return false;
  }

  ulong fevent;
  XGetICValues(m_xic, XNFilterEvents, &fevent, nullptr);
  XSelectInput(m_display,
               m_window,
               ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask |
                   EnterWindowMask | LeaveWindowMask | ButtonPressMask | ButtonReleaseMask |
                   PointerMotionMask | FocusChangeMask | PropertyChangeMask | KeymapStateMask |
                   fevent);
  return true;
}
#endif

#ifdef WITH_X11_XINPUT
void GHOST_WindowX11::refreshXInputDevices()
{
  if (m_system->m_xinput_version.present) {
    std::vector<XEventClass> xevents;

    for (GHOST_SystemX11::GHOST_TabletX11 &xtablet : m_system->GetXTablets()) {
      /* With modern XInput (XLIB 1.6.2 at least and/or EVDEV 2.9.0) and some 'no-name' tablets
       * like 'UC-LOGIC Tablet WP5540U', we also need to 'select' ButtonPress for motion event,
       * otherwise we do not get any tablet motion event once pen is pressed... See #43367.
       */
      XEventClass ev;

      DeviceMotionNotify(xtablet.Device, xtablet.MotionEvent, ev);
      if (ev) {
        xevents.push_back(ev);
      }
      DeviceButtonPress(xtablet.Device, xtablet.PressEvent, ev);
      if (ev) {
        xevents.push_back(ev);
      }
      ProximityIn(xtablet.Device, xtablet.ProxInEvent, ev);
      if (ev) {
        xevents.push_back(ev);
      }
      ProximityOut(xtablet.Device, xtablet.ProxOutEvent, ev);
      if (ev) {
        xevents.push_back(ev);
      }
    }

    XSelectExtensionEvent(m_display, m_window, xevents.data(), int(xevents.size()));
  }
}

#endif /* WITH_X11_XINPUT */

Window GHOST_WindowX11::getXWindow()
{
  return m_window;
}

bool GHOST_WindowX11::getValid() const
{
  return GHOST_Window::getValid() && m_valid_setup;
}

void GHOST_WindowX11::setTitle(const char *title)
{
  Atom name = XInternAtom(m_display, "_NET_WM_NAME", 0);
  Atom utf8str = XInternAtom(m_display, "UTF8_STRING", 0);
  XChangeProperty(
      m_display, m_window, name, utf8str, 8, PropModeReplace, (const uchar *)title, strlen(title));

  /* This should convert to valid x11 string
   * and getTitle would need matching change */
  XStoreName(m_display, m_window, title);

  XFlush(m_display);
}

std::string GHOST_WindowX11::getTitle() const
{
  char *name = nullptr;

  XFetchName(m_display, m_window, &name);
  std::string title = name ? name : "untitled";
  XFree(name);
  return title;
}

void GHOST_WindowX11::getWindowBounds(GHOST_Rect &bounds) const
{
  /* Getting the window bounds under X11 is not
   * really supported (nor should it be desired). */
  getClientBounds(bounds);
}

void GHOST_WindowX11::getClientBounds(GHOST_Rect &bounds) const
{
  Window root_return;
  int x_return, y_return;
  uint w_return, h_return, border_w_return, depth_return;
  int32_t screen_x, screen_y;

  XGetGeometry(m_display,
               m_window,
               &root_return,
               &x_return,
               &y_return,
               &w_return,
               &h_return,
               &border_w_return,
               &depth_return);

  clientToScreen(0, 0, screen_x, screen_y);

  bounds.m_l = screen_x;
  bounds.m_r = bounds.m_l + w_return;
  bounds.m_t = screen_y;
  bounds.m_b = bounds.m_t + h_return;
}

GHOST_TSuccess GHOST_WindowX11::setClientWidth(uint32_t width)
{
  XWindowChanges values;
  uint value_mask = CWWidth;
  values.width = width;
  XConfigureWindow(m_display, m_window, value_mask, &values);

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::setClientHeight(uint32_t height)
{
  XWindowChanges values;
  uint value_mask = CWHeight;
  values.height = height;
  XConfigureWindow(m_display, m_window, value_mask, &values);
  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::setClientSize(uint32_t width, uint32_t height)
{
  XWindowChanges values;
  uint value_mask = CWWidth | CWHeight;
  values.width = width;
  values.height = height;
  XConfigureWindow(m_display, m_window, value_mask, &values);
  return GHOST_kSuccess;
}

void GHOST_WindowX11::screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const
{
  int ax, ay;
  Window temp;

  /* Use (0, 0) instead of (inX, inY) to work around overflow of signed int16 in
   * the implementation of this function. */
  XTranslateCoordinates(
      m_display, RootWindow(m_display, m_visualInfo->screen), m_window, 0, 0, &ax, &ay, &temp);
  outX = ax + inX;
  outY = ay + inY;
}

void GHOST_WindowX11::clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const
{
  int ax, ay;
  Window temp;

  XTranslateCoordinates(
      m_display, m_window, RootWindow(m_display, m_visualInfo->screen), inX, inY, &ax, &ay, &temp);
  outX = ax;
  outY = ay;
}

GHOST_TSuccess GHOST_WindowX11::setDialogHints(GHOST_WindowX11 *parentWindow)
{

  Atom atom_window_type = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE", False);
  Atom atom_dialog = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
  MotifWmHints hints = {0};

  XChangeProperty(m_display,
                  m_window,
                  atom_window_type,
                  XA_ATOM,
                  32,
                  PropModeReplace,
                  (uchar *)&atom_dialog,
                  1);
  XSetTransientForHint(m_display, m_window, parentWindow->m_window);

  /* Disable minimizing of the window for now.
   * Actually, most window managers disable minimizing and maximizing for dialogs, ignoring this.
   * Leaving it here anyway in the hope it brings back maximizing on some window managers at least,
   * we'd preferably have it even for dialog windows (e.g. file browser). */
  hints.flags = MWM_HINTS_FUNCTIONS;
  hints.functions = MWM_FUNCTION_RESIZE | MWM_FUNCTION_MOVE | MWM_FUNCTION_MAXIMIZE |
                    MWM_FUNCTION_CLOSE;
  XChangeProperty(m_display,
                  m_window,
                  m_system->m_atom._MOTIF_WM_HINTS,
                  m_system->m_atom._MOTIF_WM_HINTS,
                  32,
                  PropModeReplace,
                  (uchar *)&hints,
                  4);

  return GHOST_kSuccess;
}

void GHOST_WindowX11::icccmSetState(int state)
{
  XEvent xev;

  if (state != IconicState) {
    return;
  }

  xev.xclient.type = ClientMessage;
  xev.xclient.serial = 0;
  xev.xclient.send_event = True;
  xev.xclient.display = m_display;
  xev.xclient.window = m_window;
  xev.xclient.format = 32;
  xev.xclient.message_type = m_system->m_atom.WM_CHANGE_STATE;
  xev.xclient.data.l[0] = state;
  XSendEvent(m_display,
             RootWindow(m_display, m_visualInfo->screen),
             False,
             SubstructureNotifyMask | SubstructureRedirectMask,
             &xev);
}

int GHOST_WindowX11::icccmGetState() const
{
  struct {
    CARD32 state;
    XID icon;
  } *prop_ret;
  ulong bytes_after, num_ret;
  Atom type_ret;
  int ret, format_ret;
  CARD32 st;

  prop_ret = nullptr;
  ret = XGetWindowProperty(m_display,
                           m_window,
                           m_system->m_atom.WM_STATE,
                           0,
                           2,
                           False,
                           m_system->m_atom.WM_STATE,
                           &type_ret,
                           &format_ret,
                           &num_ret,
                           &bytes_after,
                           ((uchar **)&prop_ret));
  if ((ret == Success) && (prop_ret != nullptr) && (num_ret == 2)) {
    st = prop_ret->state;
  }
  else {
    st = NormalState;
  }

  if (prop_ret) {
    XFree(prop_ret);
  }

  return st;
}

void GHOST_WindowX11::netwmMaximized(bool set)
{
  XEvent xev;

  xev.xclient.type = ClientMessage;
  xev.xclient.serial = 0;
  xev.xclient.send_event = True;
  xev.xclient.window = m_window;
  xev.xclient.message_type = m_system->m_atom._NET_WM_STATE;
  xev.xclient.format = 32;

  if (set == True) {
    xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
  }
  else {
    xev.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
  }

  xev.xclient.data.l[1] = m_system->m_atom._NET_WM_STATE_MAXIMIZED_HORZ;
  xev.xclient.data.l[2] = m_system->m_atom._NET_WM_STATE_MAXIMIZED_VERT;
  xev.xclient.data.l[3] = 0;
  xev.xclient.data.l[4] = 0;
  XSendEvent(m_display,
             RootWindow(m_display, m_visualInfo->screen),
             False,
             SubstructureRedirectMask | SubstructureNotifyMask,
             &xev);
}

bool GHOST_WindowX11::netwmIsMaximized() const
{
  Atom *prop_ret;
  ulong bytes_after, num_ret, i;
  Atom type_ret;
  bool st;
  int format_ret, ret, count;

  prop_ret = nullptr;
  st = False;
  ret = XGetWindowProperty(m_display,
                           m_window,
                           m_system->m_atom._NET_WM_STATE,
                           0,
                           INT_MAX,
                           False,
                           XA_ATOM,
                           &type_ret,
                           &format_ret,
                           &num_ret,
                           &bytes_after,
                           (uchar **)&prop_ret);
  if ((ret == Success) && (prop_ret) && (format_ret == 32)) {
    count = 0;
    for (i = 0; i < num_ret; i++) {
      if (prop_ret[i] == m_system->m_atom._NET_WM_STATE_MAXIMIZED_HORZ) {
        count++;
      }
      if (prop_ret[i] == m_system->m_atom._NET_WM_STATE_MAXIMIZED_VERT) {
        count++;
      }
      if (count == 2) {
        st = True;
        break;
      }
    }
  }

  if (prop_ret) {
    XFree(prop_ret);
  }
  return st;
}

void GHOST_WindowX11::netwmFullScreen(bool set)
{
  XEvent xev;

  xev.xclient.type = ClientMessage;
  xev.xclient.serial = 0;
  xev.xclient.send_event = True;
  xev.xclient.window = m_window;
  xev.xclient.message_type = m_system->m_atom._NET_WM_STATE;
  xev.xclient.format = 32;

  if (set == True) {
    xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
  }
  else {
    xev.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
  }

  xev.xclient.data.l[1] = m_system->m_atom._NET_WM_STATE_FULLSCREEN;
  xev.xclient.data.l[2] = 0;
  xev.xclient.data.l[3] = 0;
  xev.xclient.data.l[4] = 0;
  XSendEvent(m_display,
             RootWindow(m_display, m_visualInfo->screen),
             False,
             SubstructureRedirectMask | SubstructureNotifyMask,
             &xev);
}

bool GHOST_WindowX11::netwmIsFullScreen() const
{
  Atom *prop_ret;
  ulong bytes_after, num_ret, i;
  Atom type_ret;
  bool st;
  int format_ret, ret;

  prop_ret = nullptr;
  st = False;
  ret = XGetWindowProperty(m_display,
                           m_window,
                           m_system->m_atom._NET_WM_STATE,
                           0,
                           INT_MAX,
                           False,
                           XA_ATOM,
                           &type_ret,
                           &format_ret,
                           &num_ret,
                           &bytes_after,
                           (uchar **)&prop_ret);
  if ((ret == Success) && (prop_ret) && (format_ret == 32)) {
    for (i = 0; i < num_ret; i++) {
      if (prop_ret[i] == m_system->m_atom._NET_WM_STATE_FULLSCREEN) {
        st = True;
        break;
      }
    }
  }

  if (prop_ret) {
    XFree(prop_ret);
  }
  return st;
}

void GHOST_WindowX11::motifFullScreen(bool set)
{
  MotifWmHints hints;

  hints.flags = MWM_HINTS_DECORATIONS;
  if (set == True) {
    hints.decorations = 0;
  }
  else {
    hints.decorations = 1;
  }

  XChangeProperty(m_display,
                  m_window,
                  m_system->m_atom._MOTIF_WM_HINTS,
                  m_system->m_atom._MOTIF_WM_HINTS,
                  32,
                  PropModeReplace,
                  (uchar *)&hints,
                  4);
}

bool GHOST_WindowX11::motifIsFullScreen() const
{
  MotifWmHints *prop_ret;
  ulong bytes_after, num_ret;
  Atom type_ret;
  bool state;
  int format_ret, st;

  prop_ret = nullptr;
  state = False;
  st = XGetWindowProperty(m_display,
                          m_window,
                          m_system->m_atom._MOTIF_WM_HINTS,
                          0,
                          INT_MAX,
                          False,
                          m_system->m_atom._MOTIF_WM_HINTS,
                          &type_ret,
                          &format_ret,
                          &num_ret,
                          &bytes_after,
                          (uchar **)&prop_ret);
  if ((st == Success) && prop_ret) {
    if (prop_ret->flags & MWM_HINTS_DECORATIONS) {
      if (!prop_ret->decorations) {
        state = True;
      }
    }
  }

  if (prop_ret) {
    XFree(prop_ret);
  }
  return state;
}

GHOST_TWindowState GHOST_WindowX11::getState() const
{
  GHOST_TWindowState state_ret;
  int state;

  state_ret = GHOST_kWindowStateNormal;
  state = icccmGetState();
  /*
   * In the Iconic and Withdrawn state, the window
   * is unmapped, so only need return a Minimized state.
   */
  if (ELEM(state, IconicState, WithdrawnState)) {
    state_ret = GHOST_kWindowStateMinimized;
  }
  else if (netwmIsFullScreen() == True) {
    state_ret = GHOST_kWindowStateFullScreen;
  }
  else if (motifIsFullScreen() == True) {
    state_ret = GHOST_kWindowStateFullScreen;
  }
  else if (netwmIsMaximized() == True) {
    state_ret = GHOST_kWindowStateMaximized;
  }
  return state_ret;
}

GHOST_TSuccess GHOST_WindowX11::setState(GHOST_TWindowState state)
{
  GHOST_TWindowState cur_state;
  bool is_max, is_full, is_motif_full;

  cur_state = getState();
  if (state == int(cur_state)) {
    return GHOST_kSuccess;
  }

  if (cur_state != GHOST_kWindowStateMinimized) {
    /*
     * The window don't have this property's
     * if it's not mapped.
     */
    is_max = netwmIsMaximized();
    is_full = netwmIsFullScreen();
  }
  else {
    is_max = False;
    is_full = False;
  }

  is_motif_full = motifIsFullScreen();

  if (state == GHOST_kWindowStateNormal) {
    state = m_normal_state;
  }

  if (state == GHOST_kWindowStateNormal) {
    if (is_max == True) {
      netwmMaximized(False);
    }
    if (is_full == True) {
      netwmFullScreen(False);
    }
    if (is_motif_full == True) {
      motifFullScreen(False);
    }
    icccmSetState(NormalState);
    return GHOST_kSuccess;
  }

  if (state == GHOST_kWindowStateFullScreen) {
    /*
     * We can't change to full screen if the window
     * isn't mapped.
     */
    if (cur_state == GHOST_kWindowStateMinimized) {
      return GHOST_kFailure;
    }

    m_normal_state = cur_state;

    if (is_max == True) {
      netwmMaximized(False);
    }
    if (is_full == False) {
      netwmFullScreen(True);
    }
    if (is_motif_full == False) {
      motifFullScreen(True);
    }
    return GHOST_kSuccess;
  }

  if (state == GHOST_kWindowStateMaximized) {
    /*
     * We can't change to Maximized if the window
     * isn't mapped.
     */
    if (cur_state == GHOST_kWindowStateMinimized) {
      return GHOST_kFailure;
    }

    if (is_full == True) {
      netwmFullScreen(False);
    }
    if (is_motif_full == True) {
      motifFullScreen(False);
    }
    if (is_max == False) {
      netwmMaximized(True);
    }
    return GHOST_kSuccess;
  }

  if (state == GHOST_kWindowStateMinimized) {
    /*
     * The window manager need save the current state of
     * the window (maximized, full screen, etc).
     */
    icccmSetState(IconicState);
    return GHOST_kSuccess;
  }

  return GHOST_kFailure;
}

GHOST_TSuccess GHOST_WindowX11::setOrder(GHOST_TWindowOrder order)
{
  if (order == GHOST_kWindowOrderTop) {
    XWindowAttributes attr;
    Atom atom;

    /* We use both #XRaiseWindow and #_NET_ACTIVE_WINDOW, since some
     * window managers ignore the former (e.g. KWIN from KDE) and others
     * don't implement the latter (e.g. FLUXBOX before 0.9.9). */

    XRaiseWindow(m_display, m_window);

    atom = XInternAtom(m_display, "_NET_ACTIVE_WINDOW", True);

    if (atom != None) {
      Window root;
      XEvent xev;
      long eventmask;

      xev.xclient.type = ClientMessage;
      xev.xclient.serial = 0;
      xev.xclient.send_event = True;
      xev.xclient.window = m_window;
      xev.xclient.message_type = atom;

      xev.xclient.format = 32;
      xev.xclient.data.l[0] = 1;
      xev.xclient.data.l[1] = CurrentTime;
      xev.xclient.data.l[2] = m_window;
      xev.xclient.data.l[3] = 0;
      xev.xclient.data.l[4] = 0;

      root = RootWindow(m_display, m_visualInfo->screen);
      eventmask = SubstructureRedirectMask | SubstructureNotifyMask;

      XSendEvent(m_display, root, False, eventmask, &xev);
    }

    XGetWindowAttributes(m_display, m_window, &attr);

    /* Minimized windows give bad match error. */
    if (attr.map_state == IsViewable) {
      XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime);
    }
    XFlush(m_display);
  }
  else if (order == GHOST_kWindowOrderBottom) {
    XLowerWindow(m_display, m_window);
    XFlush(m_display);
  }
  else {
    return GHOST_kFailure;
  }

  return GHOST_kSuccess;
}

bool GHOST_WindowX11::isDialog() const
{
  Atom atom_window_type = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE", False);
  Atom atom_dialog = XInternAtom(m_display, "_NET_WM_WINDOW_TYPE_DIALOG", False);

  Atom *prop_ret;
  ulong bytes_after, num_ret;
  Atom type_ret;
  bool st;
  int format_ret, ret;

  prop_ret = nullptr;
  st = False;
  ret = XGetWindowProperty(m_display,
                           m_window,
                           atom_window_type,
                           0,
                           INT_MAX,
                           False,
                           XA_ATOM,
                           &type_ret,
                           &format_ret,
                           &num_ret,
                           &bytes_after,
                           (uchar **)&prop_ret);
  if ((ret == Success) && (prop_ret) && (format_ret == 32)) {
    if (prop_ret[0] == atom_dialog) {
      st = True;
    }
  }

  if (prop_ret) {
    XFree(prop_ret);
  }

  return st;
}

GHOST_TSuccess GHOST_WindowX11::invalidate()
{
  /* So the idea of this function is to generate an expose event
   * for the window.
   * Unfortunately X does not handle expose events for you and
   * it is the client's job to refresh the dirty part of the window.
   * We need to queue up invalidate calls and generate GHOST events
   * for them in the system.
   *
   * We implement this by setting a boolean in this class to concatenate
   * all such calls into a single event for this window.
   *
   * At the same time we queue the dirty windows in the system class
   * and generate events for them at the next processEvents call. */

  if (m_invalid_window == false) {
    m_system->addDirtyWindow(this);
    m_invalid_window = true;
  }

  return GHOST_kSuccess;
}

/**
 * called by the X11 system implementation when expose events
 * for the window have been pushed onto the GHOST queue
 */

void GHOST_WindowX11::validate()
{
  m_invalid_window = false;
}

GHOST_WindowX11::~GHOST_WindowX11()
{
  std::map<uint, Cursor>::iterator it = m_standard_cursors.begin();
  for (; it != m_standard_cursors.end(); ++it) {
    XFreeCursor(m_display, it->second);
  }

  if (m_empty_cursor) {
    XFreeCursor(m_display, m_empty_cursor);
  }
  if (m_custom_cursor) {
    XFreeCursor(m_display, m_custom_cursor);
  }

  if (m_valid_setup) {
    static Atom Primary_atom, Clipboard_atom;
    Window p_owner, c_owner;
    /* Change the owner of the Atoms to None if we are the owner. */
    Primary_atom = XInternAtom(m_display, "PRIMARY", False);
    Clipboard_atom = XInternAtom(m_display, "CLIPBOARD", False);

    p_owner = XGetSelectionOwner(m_display, Primary_atom);
    c_owner = XGetSelectionOwner(m_display, Clipboard_atom);

    if (p_owner == m_window) {
      XSetSelectionOwner(m_display, Primary_atom, None, CurrentTime);
    }
    if (c_owner == m_window) {
      XSetSelectionOwner(m_display, Clipboard_atom, None, CurrentTime);
    }
  }

  if (m_visualInfo) {
    XFree(m_visualInfo);
  }

#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
  if (m_xic) {
    XDestroyIC(m_xic);
  }
#endif

#ifdef WITH_XDND
  delete m_dropTarget;
#endif

  releaseNativeHandles();

  if (m_valid_setup) {
    XDestroyWindow(m_display, m_window);
  }
}

GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type)
{
  switch (type) {
#ifdef WITH_VULKAN_BACKEND
    case GHOST_kDrawingContextTypeVulkan: {
      GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual,
                                                   GHOST_kVulkanPlatformX11,
                                                   m_window,
                                                   m_display,
                                                   nullptr,
                                                   nullptr,
                                                   nullptr,
                                                   1,
                                                   2,
                                                   m_is_debug_context);
      if (context->initializeDrawingContext()) {
        return context;
      }
      delete context;
      return nullptr;
    }
#endif

#ifdef WITH_OPENGL_BACKEND
    case GHOST_kDrawingContextTypeOpenGL: {
#  ifdef USE_EGL
      /* Try to initialize an EGL context. */
      for (int minor = 6; minor >= 3; --minor) {
        GHOST_Context *context = GHOST_ContextEGL(
            this->m_system,
            m_wantStereoVisual,
            EGLNativeWindowType(m_window),
            EGLNativeDisplayType(m_display),
            EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
            4,
            minor,
            GHOST_OPENGL_EGL_CONTEXT_FLAGS |
                (m_is_debug_context ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0),
            GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY,
            EGL_OPENGL_API);
        if (context->initializeDrawingContext()) {
          return context;
        }
        delete context;
      }
      /* EGL initialization failed, try to fallback to a GLX context. */
#  endif

      for (int minor = 6; minor >= 3; --minor) {
        GHOST_Context *context = new GHOST_ContextGLX(
            m_wantStereoVisual,
            m_window,
            m_display,
            (GLXFBConfig)m_fbconfig,
            GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
            4,
            minor,
            GHOST_OPENGL_GLX_CONTEXT_FLAGS | (m_is_debug_context ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
            GHOST_OPENGL_GLX_RESET_NOTIFICATION_STRATEGY);
        if (context->initializeDrawingContext()) {
          return context;
        }
        delete context;
      }
      return nullptr;
    }
#endif

    default:
      /* Unsupported backend. */
      return nullptr;
  }
}

GHOST_TSuccess GHOST_WindowX11::getStandardCursor(GHOST_TStandardCursor g_cursor, Cursor &xcursor)
{
  uint xcursor_id;

  switch (g_cursor) {
    case GHOST_kStandardCursorHelp:
      xcursor_id = XC_question_arrow;
      break;
    case GHOST_kStandardCursorWait:
      xcursor_id = XC_watch;
      break;
    case GHOST_kStandardCursorText:
      xcursor_id = XC_xterm;
      break;
    case GHOST_kStandardCursorCrosshair:
      xcursor_id = XC_crosshair;
      break;
    case GHOST_kStandardCursorUpDown:
      xcursor_id = XC_sb_v_double_arrow;
      break;
    case GHOST_kStandardCursorLeftRight:
      xcursor_id = XC_sb_h_double_arrow;
      break;
    case GHOST_kStandardCursorTopSide:
      xcursor_id = XC_top_side;
      break;
    case GHOST_kStandardCursorBottomSide:
      xcursor_id = XC_bottom_side;
      break;
    case GHOST_kStandardCursorLeftSide:
      xcursor_id = XC_left_side;
      break;
    case GHOST_kStandardCursorRightSide:
      xcursor_id = XC_right_side;
      break;
    case GHOST_kStandardCursorTopLeftCorner:
      xcursor_id = XC_top_left_corner;
      break;
    case GHOST_kStandardCursorTopRightCorner:
      xcursor_id = XC_top_right_corner;
      break;
    case GHOST_kStandardCursorBottomRightCorner:
      xcursor_id = XC_bottom_right_corner;
      break;
    case GHOST_kStandardCursorBottomLeftCorner:
      xcursor_id = XC_bottom_left_corner;
      break;
    case GHOST_kStandardCursorDefault:
      xcursor = None;
      return GHOST_kSuccess;
    default:
      xcursor = None;
      return GHOST_kFailure;
  }

  xcursor = m_standard_cursors[xcursor_id];

  if (!xcursor) {
    xcursor = XCreateFontCursor(m_display, xcursor_id);

    m_standard_cursors[xcursor_id] = xcursor;
  }

  return GHOST_kSuccess;
}

Cursor GHOST_WindowX11::getEmptyCursor()
{
  if (!m_empty_cursor) {
    Pixmap blank;
    XColor dummy = {0};
    char data[1] = {0};

    /* make a blank cursor */
    blank = XCreateBitmapFromData(
        m_display, RootWindow(m_display, m_visualInfo->screen), data, 1, 1);

    m_empty_cursor = XCreatePixmapCursor(m_display, blank, blank, &dummy, &dummy, 0, 0);
    XFreePixmap(m_display, blank);
  }

  return m_empty_cursor;
}

GHOST_TSuccess GHOST_WindowX11::setWindowCursorVisibility(bool visible)
{
  Cursor xcursor;

  if (visible) {
    if (m_visible_cursor) {
      xcursor = m_visible_cursor;
    }
    else if (getStandardCursor(getCursorShape(), xcursor) == GHOST_kFailure) {
      getStandardCursor(getCursorShape(), xcursor);
    }
  }
  else {
    xcursor = getEmptyCursor();
  }

  XDefineCursor(m_display, m_window, xcursor);
  XFlush(m_display);

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
{
  if (mode != GHOST_kGrabDisable) {
    if (mode != GHOST_kGrabNormal) {
      m_system->getCursorPosition(UNPACK2(m_cursorGrabInitPos));
      setCursorGrabAccum(0, 0);

      if (mode == GHOST_kGrabHide) {
        setWindowCursorVisibility(false);
      }
    }
#ifdef GHOST_X11_GRAB
    XGrabPointer(m_display,
                 m_window,
                 False,
                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
                 GrabModeAsync,
                 GrabModeAsync,
                 None,
                 None,
                 CurrentTime);
#endif
  }
  else {
    if (m_cursorGrab == GHOST_kGrabHide) {
      m_system->setCursorPosition(UNPACK2(m_cursorGrabInitPos));
    }

    if (m_cursorGrab != GHOST_kGrabNormal) {
      /* use to generate a mouse move event, otherwise the last event
       * blender gets can be outside the screen causing menus not to show
       * properly unless the user moves the mouse */

#if defined(WITH_X11_XINPUT) && defined(USE_X11_XINPUT_WARP)
      if ((m_system->m_xinput_version.present) && (m_system->m_xinput_version.major_version >= 2))
      {
        int device_id;
        if (XIGetClientPointer(m_display, None, &device_id) != False) {
          XIWarpPointer(m_display, device_id, None, None, 0, 0, 0, 0, 0, 0);
        }
      }
      else
#endif
      {
        XWarpPointer(m_display, None, None, 0, 0, 0, 0, 0, 0);
      }
    }

    /* Perform this last so to workaround XWayland bug, see: #53004. */
    if (m_cursorGrab == GHOST_kGrabHide) {
      setWindowCursorVisibility(true);
    }

    /* Almost works without but important
     * otherwise the mouse GHOST location can be incorrect on exit. */
    setCursorGrabAccum(0, 0);
    m_cursorGrabBounds.m_l = m_cursorGrabBounds.m_r = -1; /* disable */
#ifdef GHOST_X11_GRAB
    XUngrabPointer(m_display, CurrentTime);
#endif
  }

  XFlush(m_display);

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::setWindowCursorShape(GHOST_TStandardCursor shape)
{
  Cursor xcursor;
  if (getStandardCursor(shape, xcursor) == GHOST_kFailure) {
    getStandardCursor(GHOST_kStandardCursorDefault, xcursor);
  }

  m_visible_cursor = xcursor;

  XDefineCursor(m_display, m_window, xcursor);
  XFlush(m_display);

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::hasCursorShape(GHOST_TStandardCursor shape)
{
  Cursor xcursor;
  return getStandardCursor(shape, xcursor);
}

GHOST_TSuccess GHOST_WindowX11::setWindowCustomCursorShape(uint8_t *bitmap,
                                                           uint8_t *mask,
                                                           int sizex,
                                                           int sizey,
                                                           int hotX,
                                                           int hotY,
                                                           bool /*canInvertColor*/)
{
  Colormap colormap = DefaultColormap(m_display, m_visualInfo->screen);
  Pixmap bitmap_pix, mask_pix;
  XColor fg, bg;

  if (XAllocNamedColor(m_display, colormap, "White", &fg, &fg) == 0) {
    return GHOST_kFailure;
  }
  if (XAllocNamedColor(m_display, colormap, "Black", &bg, &bg) == 0) {
    return GHOST_kFailure;
  }

  if (m_custom_cursor) {
    XFreeCursor(m_display, m_custom_cursor);
  }

  bitmap_pix = XCreateBitmapFromData(m_display, m_window, (char *)bitmap, sizex, sizey);
  mask_pix = XCreateBitmapFromData(m_display, m_window, (char *)mask, sizex, sizey);

  m_custom_cursor = XCreatePixmapCursor(m_display, bitmap_pix, mask_pix, &fg, &bg, hotX, hotY);
  XDefineCursor(m_display, m_window, m_custom_cursor);
  XFlush(m_display);

  m_visible_cursor = m_custom_cursor;

  XFreePixmap(m_display, bitmap_pix);
  XFreePixmap(m_display, mask_pix);

  XFreeColors(m_display, colormap, &fg.pixel, 1, 0L);
  XFreeColors(m_display, colormap, &bg.pixel, 1, 0L);

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::beginFullScreen() const
{
  {
    Window root_return;
    int x_return, y_return;
    uint w_return, h_return, border_w_return, depth_return;

    XGetGeometry(m_display,
                 m_window,
                 &root_return,
                 &x_return,
                 &y_return,
                 &w_return,
                 &h_return,
                 &border_w_return,
                 &depth_return);

    m_system->setCursorPosition(w_return / 2, h_return / 2);
  }

  /* Grab Keyboard & Mouse */
  int err;

  err = XGrabKeyboard(m_display, m_window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
  if (err != GrabSuccess) {
    printf("XGrabKeyboard failed %d\n", err);
  }

  err = XGrabPointer(m_display,
                     m_window,
                     False,
                     PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
                     GrabModeAsync,
                     GrabModeAsync,
                     m_window,
                     None,
                     CurrentTime);
  if (err != GrabSuccess) {
    printf("XGrabPointer failed %d\n", err);
  }

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_WindowX11::endFullScreen() const
{
  XUngrabKeyboard(m_display, CurrentTime);
  XUngrabPointer(m_display, CurrentTime);

  return GHOST_kSuccess;
}

uint16_t GHOST_WindowX11::getDPIHint()
{
  /* Try to read DPI setting set using xrdb */
  char *resMan = XResourceManagerString(m_display);
  if (resMan) {
    XrmDatabase xrdb = XrmGetStringDatabase(resMan);
    if (xrdb) {
      char *type = nullptr;
      XrmValue val;

      int success = XrmGetResource(xrdb, "Xft.dpi", "Xft.Dpi", &type, &val);
      if (success && type) {
        if (STREQ(type, "String")) {
          return atoi((char *)val.addr);
        }
      }
    }
    XrmDestroyDatabase(xrdb);
  }

  /* Fallback to calculating DPI using X reported DPI, set using `xrandr --dpi`. */
  XWindowAttributes attr;
  if (!XGetWindowAttributes(m_display, m_window, &attr)) {
    /* Failed to get window attributes, return X11 default DPI */
    return 96;
  }

  Screen *screen = attr.screen;
  int pixelWidth = WidthOfScreen(screen);
  int pixelHeight = HeightOfScreen(screen);
  int mmWidth = WidthMMOfScreen(screen);
  int mmHeight = HeightMMOfScreen(screen);

  double pixelDiagonal = sqrt((pixelWidth * pixelWidth) + (pixelHeight * pixelHeight));
  double mmDiagonal = sqrt((mmWidth * mmWidth) + (mmHeight * mmHeight));
  float inchDiagonal = mmDiagonal * 0.039f;
  int dpi = pixelDiagonal / inchDiagonal;
  return dpi;
}

GHOST_TSuccess GHOST_WindowX11::setProgressBar(float progress)
{
  if (m_taskbar.is_valid()) {
    m_taskbar.set_progress(progress);
    m_taskbar.set_progress_enabled(true);
    return GHOST_kSuccess;
  }

  return GHOST_kFailure;
}

GHOST_TSuccess GHOST_WindowX11::endProgressBar()
{
  if (m_taskbar.is_valid()) {
    m_taskbar.set_progress_enabled(false);
    return GHOST_kSuccess;
  }

  return GHOST_kFailure;
}
