/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* Copyright (C) 2022 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Carlos Garnacho <carlosg@gnome.org>
 *
 * Based on GTK+ code by Owen Taylor <otaylor@gtk.org>
 */

#include "config.h"

#include "na-xembed.h"

#include <mtk/mtk-x11.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/Xfixes.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

typedef struct _NaXembedPrivate NaXembedPrivate;

struct _NaXembedPrivate
{
  MetaX11Display *x11_display;
  Window socket_window;
  Window plug_window;
  Window old_parent;

  int root_x;
  int root_y;
  int request_width;
  int request_height;
  int current_width;
  int current_height;
  int available_width;
  int available_height;
  int resize_count;
  int xembed_version;

  unsigned int event_func_id;
  guint resize_id;

  XVisualInfo *xvisual_info;

  Atom atom__XEMBED;
  Atom atom__XEMBED_INFO;
  Atom atom_WM_NORMAL_HINTS;

  gboolean have_size;
  gboolean need_map;
  gboolean is_mapped;
  gboolean has_alpha;
};

/* XEMBED messages */
typedef enum _XembedMessageType XembedMessageType;

enum _XembedMessageType {
  XEMBED_EMBEDDED_NOTIFY,
  XEMBED_WINDOW_ACTIVATE,
  XEMBED_WINDOW_DEACTIVATE,
  XEMBED_REQUEST_FOCUS,
  XEMBED_FOCUS_IN,
  XEMBED_FOCUS_OUT,
  XEMBED_FOCUS_NEXT,
  XEMBED_FOCUS_PREV,
  XEMBED_GRAB_KEY,
  XEMBED_UNGRAB_KEY,
  XEMBED_MODALITY_ON,
  XEMBED_MODALITY_OFF,
};

/* Flags for _XEMBED_INFO */
#define XEMBED_MAPPED (1 << 0)

#define XEMBED_PROTOCOL_VERSION 1

G_DEFINE_TYPE_WITH_PRIVATE (NaXembed, na_xembed, G_TYPE_OBJECT)

enum {
  PLUG_ADDED,
  PLUG_REMOVED,
  RECONFIGURED,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0, };

enum {
  PROP_0,
  PROP_X11_DISPLAY,
  N_PROPS
};

static GParamSpec *props[N_PROPS] = { 0, };

static void
xembed_send_message (NaXembed          *xembed,
                     Window             recipient,
                     XembedMessageType  message,
                     glong              detail,
                     glong              data1,
                     glong              data2)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
  XClientMessageEvent xclient = {0};

  xclient.window = recipient;
  xclient.type = ClientMessage;
  xclient.message_type = priv->atom__XEMBED;
  xclient.format = 32;
  xclient.data.l[0] = 0; /* Time */
  xclient.data.l[1] = message;
  xclient.data.l[2] = detail;
  xclient.data.l[3] = data1;
  xclient.data.l[4] = data2;

  mtk_x11_error_trap_push (xdisplay);
  XSendEvent (xdisplay,
              recipient,
              False, NoEventMask, (XEvent*) &xclient);
  mtk_x11_error_trap_pop (xdisplay);
}

static void
na_xembed_end_embedding (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (priv->socket_window)
    {
      Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);

      mtk_x11_error_trap_push (xdisplay);

      if (priv->plug_window && priv->old_parent)
        XReparentWindow (xdisplay,
                         priv->plug_window,
                         priv->old_parent,
                         0, 0);

      XDestroyWindow (xdisplay, priv->socket_window);
      priv->socket_window = None;

      mtk_x11_error_trap_pop (xdisplay);
    }

  priv->plug_window = None;
  priv->old_parent = None;
  priv->current_width = 0;
  priv->current_height = 0;
  priv->resize_count = 0;
  priv->available_width = -1;
  priv->available_height = -1;
  g_clear_handle_id (&priv->resize_id, g_source_remove);
}

static void
na_xembed_send_configure_event (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  XConfigureEvent xconfigure = {0};
  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);

  xconfigure.type = ConfigureNotify;

  xconfigure.event = priv->plug_window;
  xconfigure.window = priv->plug_window;

  xconfigure.x = priv->root_x;
  xconfigure.y = priv->root_y;
  xconfigure.width = priv->current_width;
  xconfigure.height = priv->current_height;

  xconfigure.border_width = 0;
  xconfigure.above = None;
  xconfigure.override_redirect = False;

  mtk_x11_error_trap_push (xdisplay);
  XSendEvent (xdisplay,
              priv->plug_window,
              False, NoEventMask,
              (XEvent*) &xconfigure);
  mtk_x11_error_trap_pop (xdisplay);
}

static void
na_xembed_synchronize_size (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
  int x, y, width, height;

  x = priv->root_x;
  y = priv->root_y;
  width = priv->request_width;
  height = priv->request_height;

  if (priv->available_width >= 0)
    width = priv->available_width;
  if (priv->available_height >= 0)
    height = priv->available_height;

  XMoveResizeWindow (xdisplay,
                     priv->socket_window,
                     x, y,
                     width, height);

  if (priv->plug_window)
    {
      mtk_x11_error_trap_push (xdisplay);

      if (width != priv->current_width ||
          height != priv->current_height)
        {
          XMoveResizeWindow (xdisplay,
                             priv->plug_window,
                             0, 0,
                             width, height);
          if (priv->resize_count)
            priv->resize_count--;

          priv->current_width = width;
          priv->current_height = height;
        }

      if (priv->need_map)
        {
          XMapWindow (xdisplay, priv->plug_window);
          priv->need_map = FALSE;
        }

      while (priv->resize_count)
        {
          na_xembed_send_configure_event (xembed);
          priv->resize_count--;
        }

      mtk_x11_error_trap_pop (xdisplay);
    }
}

static void
synchronize_size_cb (gpointer user_data)
{
  NaXembed *xembed = user_data;
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  na_xembed_synchronize_size (xembed);
  priv->resize_id = 0;
}

static void
na_xembed_resize (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
  XSizeHints hints;
  long supplied;

  g_clear_handle_id (&priv->resize_id, g_source_remove);

  mtk_x11_error_trap_push (xdisplay);

  priv->request_width = 1;
  priv->request_height = 1;

  if (XGetWMNormalHints (xdisplay,
                         priv->plug_window,
                         &hints, &supplied))
    {
      if (hints.flags & PMinSize)
        {
          priv->request_width = MAX (hints.min_width, 1);
          priv->request_height = MAX (hints.min_height, 1);
        }
      else if (hints.flags & PBaseSize)
        {
          priv->request_width = MAX (hints.base_width, 1);
          priv->request_height = MAX (hints.base_height, 1);
        }
    }

  priv->have_size = TRUE;
  mtk_x11_error_trap_pop (xdisplay);

  priv->resize_id = g_idle_add_once (synchronize_size_cb, xembed);
}

static gboolean
na_xembed_get_info (NaXembed      *xembed,
                    Window         xwindow,
                    unsigned long *version,
                    unsigned long *flags)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  MetaX11Display *x11_display = priv->x11_display;
  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
  Atom type;
  int format;
  unsigned long nitems, bytes_after;
  unsigned char *data;
  unsigned long *data_long;
  int status;

  mtk_x11_error_trap_push (xdisplay);
  status = XGetWindowProperty (xdisplay,
                               xwindow,
                               priv->atom__XEMBED_INFO,
                               0, 2, False,
                               priv->atom__XEMBED_INFO,
                               &type, &format,
                               &nitems, &bytes_after, &data);
  mtk_x11_error_trap_pop (xdisplay);

  if (status != Success)
    return FALSE;
  if (type == None)
    return FALSE;

  if (type != priv->atom__XEMBED_INFO)
    {
      g_warning ("_XEMBED_INFO property has wrong type");
      XFree (data);
      return FALSE;
    }

  if (nitems < 2)
    {
      g_warning ("_XEMBED_INFO too short");
      XFree (data);
      return FALSE;
    }

  data_long = (unsigned long *)data;
  if (version)
    *version = data_long[0];
  if (flags)
    *flags = data_long[1] & XEMBED_MAPPED;

  XFree (data);
  return TRUE;
}

static void
na_xembed_add_window (NaXembed  *xembed,
                      Window     xid,
                      gboolean   need_reparent)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
  XVisualInfo template = { 0, };
  int n_xvisuals;
  unsigned long version;
  unsigned long flags;

  priv->plug_window = xid;

  mtk_x11_error_trap_push (xdisplay);

  XSelectInput (xdisplay,
                priv->plug_window,
                StructureNotifyMask | PropertyChangeMask);

  if (mtk_x11_error_trap_pop_with_return (xdisplay))
    {
      priv->plug_window = None;
      return;
    }

  /* OK, we now will reliably get destroy notification on socket->plug_window */

  mtk_x11_error_trap_push (xdisplay);

  if (need_reparent)
    {
      XSetWindowAttributes socket_attrs;
      XWindowAttributes plug_attrs;
      int shape_major, shape_minor;
      int result;

      result = XGetWindowAttributes (xdisplay, priv->plug_window, &plug_attrs);
      if (result == 0)
        {
          mtk_x11_error_trap_pop (xdisplay);
          priv->plug_window = None;
          return;
        }

      template.visualid = plug_attrs.visual->visualid;
      priv->xvisual_info = XGetVisualInfo (xdisplay,
                                           VisualIDMask,
                                           &template,
                                           &n_xvisuals);

      if (!priv->xvisual_info)
        {
          mtk_x11_error_trap_pop (xdisplay);
          priv->plug_window = None;
          return;
        }

      priv->has_alpha = (priv->xvisual_info->depth >
                         __builtin_popcount (priv->xvisual_info->red_mask |
                                             priv->xvisual_info->green_mask |
                                             priv->xvisual_info->blue_mask));

      socket_attrs.override_redirect = True;

      priv->socket_window =
        XCreateWindow (xdisplay,
                       meta_x11_display_get_xroot (priv->x11_display),
                       -1, -1, 1, 1, 0,
                       priv->xvisual_info->depth,
                       InputOutput,
                       plug_attrs.visual,
                       CWOverrideRedirect,
                       &socket_attrs);

      XUnmapWindow (xdisplay, priv->plug_window); /* Shouldn't actually be necessary for XEMBED, but just in case */

      if (!priv->old_parent)
        {
          g_autofree Window *children = NULL;
          unsigned int n_children;
          Window root;

          if (!XQueryTree (xdisplay, priv->plug_window, &root, &priv->old_parent,
                           &children, &n_children))
            priv->old_parent = meta_x11_display_get_xroot (priv->x11_display);
        }

      XReparentWindow (xdisplay,
                       priv->plug_window,
                       priv->socket_window,
                       0, 0);

      /* Set an empty input shape on the window so that the socket does not
       * get any input. Without this the tray may still get events and - for
       * instance - show tooltips on hover which we don't want.
       * This is the quickest way to achieve this, without having to deal these
       * windows with specific code in mutter.
       */
      if (XShapeQueryExtension (xdisplay, &shape_major, &shape_minor))
        {
          XShapeSelectInput (xdisplay, priv->socket_window, NoEventMask);
          XShapeCombineRectangles (xdisplay, priv->socket_window, ShapeInput,
                                   0, 0, NULL, 0, ShapeSet, 0);
        }
    }

  priv->have_size = FALSE;

  priv->xembed_version = -1;
  if (na_xembed_get_info (xembed, priv->plug_window, &version, &flags))
    {
      priv->xembed_version = MIN (XEMBED_PROTOCOL_VERSION, version);
      priv->is_mapped = (flags & XEMBED_MAPPED) != 0;
    }
  else
    {
      /* FIXME, we should probably actually check the state before we started */
      priv->is_mapped = TRUE;
    }

  priv->need_map = priv->is_mapped;

  mtk_x11_error_trap_pop (xdisplay);

  mtk_x11_error_trap_push (xdisplay);
  XFixesChangeSaveSet (xdisplay, priv->plug_window,
                       SetModeInsert, SaveSetRoot, SaveSetUnmap);
  mtk_x11_error_trap_pop (xdisplay);

  xembed_send_message (xembed,
		       priv->plug_window,
		       XEMBED_EMBEDDED_NOTIFY, 0,
		       priv->socket_window,
		       priv->xembed_version);

  na_xembed_resize (xembed);

  g_signal_emit (xembed, signals[PLUG_ADDED], 0);

  XMapWindow (xdisplay, priv->socket_window);
}

static void
na_xembed_handle_map_request (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (!priv->is_mapped)
    {
      priv->is_mapped = TRUE;
      priv->need_map = TRUE;
      na_xembed_resize (xembed);
    }
}

static void
na_xembed_handle_unmap_notify (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (priv->is_mapped)
    {
      priv->is_mapped = FALSE;
      na_xembed_resize (xembed);
    }
}

static void
xembed_filter_func (MetaX11Display *x11_display,
                    XEvent         *xevent,
                    gpointer        user_data)
{
  NaXembed *xembed = user_data;
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);

  if (priv->socket_window == None)
    return;
  if (xevent->xany.window != priv->socket_window &&
      xevent->xany.window != priv->plug_window)
    return;

  switch (xevent->type)
    {
    case ClientMessage:
      /* We choose to ignore all _XEMBED ClientEvent messages
       * addressed to the socket.
       */
      break;
    case CreateNotify:
      {
        XCreateWindowEvent *xcwe = &xevent->xcreatewindow;

        if (!priv->plug_window)
          na_xembed_add_window (xembed, xcwe->window, FALSE);

        break;
      }
    case ConfigureRequest:
      {
        XConfigureRequestEvent *xcre = &xevent->xconfigurerequest;

        if (!priv->plug_window)
          na_xembed_add_window (xembed, xcre->window, FALSE);

        if (priv->plug_window)
          {
            if (xcre->value_mask & (CWWidth | CWHeight))
              {
                priv->resize_count++;
                na_xembed_resize (xembed);
              }
            else if (xcre->value_mask & (CWX | CWY))
              {
                na_xembed_send_configure_event (xembed);
              }

            /* Ignore stacking requests. */
          }
        break;
      }
    case ConfigureNotify:
      {
        XConfigureEvent *xce = &xevent->xconfigure;

        if (xce->window == priv->socket_window)
          g_signal_emit (xembed, signals[RECONFIGURED], 0);

        break;
      }
    case DestroyNotify:
      {
        XDestroyWindowEvent *xdwe = &xevent->xdestroywindow;

        /* Note that we get destroy notifies both from SubstructureNotify on
         * our window and StructureNotify on socket->plug_window
         */
        if (priv->socket_window && xdwe->window == priv->socket_window)
          priv->socket_window = None;

        if (priv->plug_window && (xdwe->window == priv->plug_window))
          {
            g_object_ref (xembed);
            g_signal_emit (xembed, signals[PLUG_REMOVED], 0);
            priv->plug_window = None;
            na_xembed_end_embedding (xembed);
            g_object_unref (xembed);
          }
        break;
      }
    case MapRequest:
      if (!priv->plug_window)
        na_xembed_add_window (xembed, xevent->xmaprequest.window, FALSE);

      if (priv->plug_window == xevent->xmaprequest.window)
        na_xembed_handle_map_request (xembed);
      break;
    case PropertyNotify:
      if (priv->plug_window &&
          xevent->xproperty.window == priv->plug_window)
        {
          if (xevent->xproperty.atom == priv->atom_WM_NORMAL_HINTS)
            {
              priv->have_size = FALSE;
              na_xembed_resize (xembed);
            }
          else if (xevent->xproperty.atom == priv->atom__XEMBED_INFO)
            {
              unsigned long flags;

              if (na_xembed_get_info (xembed, priv->plug_window, NULL, &flags))
                {
                  gboolean was_mapped = priv->is_mapped;
                  gboolean is_mapped = (flags & XEMBED_MAPPED) != 0;

                  if (was_mapped != is_mapped)
                    {
                      if (is_mapped)
                        {
                          na_xembed_handle_map_request (xembed);
                        }
                      else
                        {
                          mtk_x11_error_trap_push (xdisplay);
                          XMapWindow (xdisplay, priv->plug_window);
                          mtk_x11_error_trap_pop (xdisplay);

                          na_xembed_handle_unmap_notify (xembed);
                        }
                    }
                }
            }
        }
      break;
    case ReparentNotify:
      {
        XReparentEvent *xre = &xevent->xreparent;

        if (priv->plug_window == None &&
            xre->parent == priv->socket_window)
          {
            na_xembed_add_window (xembed, xre->window, FALSE);
          }
        else if (priv->plug_window != None &&
                 xre->window == priv->plug_window &&
                 xre->parent != priv->socket_window)
          {
            g_object_ref (xembed);
            g_signal_emit (xembed, signals[PLUG_REMOVED], 0);
            priv->old_parent = None;
            na_xembed_end_embedding (xembed);
            g_object_unref (xembed);
          }

        break;
      }
    case UnmapNotify:
      if (priv->plug_window != None &&
          xevent->xunmap.window == priv->plug_window)
        na_xembed_handle_unmap_notify (xembed);
      break;
    }
}

static void
na_xembed_set_property (GObject      *object,
                        guint         prop_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  NaXembed *xembed = NA_XEMBED (object);
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  switch (prop_id)
    {
    case PROP_X11_DISPLAY:
      priv->x11_display = g_value_get_object (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
na_xembed_get_property (GObject    *object,
                        guint       prop_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  NaXembed *xembed = NA_XEMBED (object);
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  switch (prop_id)
    {
    case PROP_X11_DISPLAY:
      g_value_set_object (value, priv->x11_display);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
na_xembed_finalize (GObject *object)
{
  NaXembed *xembed = NA_XEMBED (object);
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  g_clear_pointer (&priv->xvisual_info, XFree);

  if (priv->x11_display && priv->event_func_id)
    meta_x11_display_remove_event_func (priv->x11_display, priv->event_func_id);

  if (priv->plug_window || priv->socket_window)
    na_xembed_end_embedding (xembed);

  G_OBJECT_CLASS (na_xembed_parent_class)->finalize (object);
}

static void
na_xembed_constructed (GObject *object)
{
  NaXembed *xembed = NA_XEMBED (object);
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  Display *xdisplay;

  G_OBJECT_CLASS (na_xembed_parent_class)->constructed (object);

  xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);

  priv->event_func_id =
    meta_x11_display_add_event_func (priv->x11_display,
                                     xembed_filter_func,
                                     object,
                                     NULL);

  priv->atom__XEMBED = XInternAtom (xdisplay, "_XEMBED", False);
  priv->atom__XEMBED_INFO = XInternAtom (xdisplay, "_XEMBED_INFO", False);
  priv->atom_WM_NORMAL_HINTS = XInternAtom (xdisplay, "WM_NORMAL_HINTS", False);
}

static void
na_xembed_class_init (NaXembedClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->set_property = na_xembed_set_property;
  object_class->get_property = na_xembed_get_property;
  object_class->finalize = na_xembed_finalize;
  object_class->constructed = na_xembed_constructed;

  signals[PLUG_ADDED] =
    g_signal_new ("plug-added",
                  G_OBJECT_CLASS_TYPE (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (NaXembedClass, plug_added),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  signals[PLUG_REMOVED] =
    g_signal_new ("plug-removed",
                  G_OBJECT_CLASS_TYPE (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (NaXembedClass, plug_removed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  signals[RECONFIGURED] =
    g_signal_new ("reconfigured",
                  G_OBJECT_CLASS_TYPE (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (NaXembedClass, reconfigured),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  props[PROP_X11_DISPLAY] =
    g_param_spec_object ("x11-display", NULL, NULL,
                         META_TYPE_X11_DISPLAY,
                         G_PARAM_READWRITE |
                         G_PARAM_CONSTRUCT_ONLY);

  g_object_class_install_properties (object_class, N_PROPS, props);
}

static void
na_xembed_init (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  priv->available_width = -1;
  priv->available_height = -1;
}

void
na_xembed_add_id (NaXembed *xembed,
                  Window    window)
{
  na_xembed_add_window (xembed, window, TRUE);
}

Window
na_xembed_get_plug_window (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  return priv->plug_window;
}

Window
na_xembed_get_socket_window (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  return priv->socket_window;
}

void
na_xembed_set_root_position (NaXembed *xembed,
                             int       x,
                             int       y)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (priv->root_x == x &&
      priv->root_y == y)
    return;

  priv->root_x = x;
  priv->root_y = y;

  if (priv->resize_id == 0)
    priv->resize_id = g_idle_add_once (synchronize_size_cb, xembed);
}

MetaX11Display *
na_xembed_get_x11_display (NaXembed *xembed)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  return priv->x11_display;
}

void
na_xembed_get_size (NaXembed *xembed,
                    int      *width,
                    int      *height)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (width)
    *width = priv->request_width;
  if (height)
    *height = priv->request_height;
}

void
na_xembed_set_available_size (NaXembed *xembed,
                              int       width,
                              int       height)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (priv->available_width == width && priv->available_height == height)
    return;

  priv->available_width = width;
  priv->available_height = height;

  na_xembed_resize (xembed);
}

void
na_xembed_get_root_position (NaXembed *xembed,
                             int      *x,
                             int      *y)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);

  if (x)
    *x = priv->root_x;
  if (y)
    *y = priv->root_y;
}

static void
get_pixel_details (unsigned long  pixel_mask,
                   int           *shift,
                   int           *precision)
{
  unsigned long m = 0;
  int s = 0;
  int p = 0;

  if (pixel_mask != 0)
    {
      m = pixel_mask;
      while (!(m & 0x1))
        {
          s++;
          m >>= 1;
        }

      while (m & 0x1)
        {
          p++;
          m >>= 1;
        }
    }

  if (shift)
    *shift = s;
  if (precision)
    *precision = p;
}

void
na_xembed_set_background_color (NaXembed        *xembed,
                                const CoglColor *color)
{
  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
  XVisualInfo *xvisual_info;
  Display *xdisplay;
  unsigned long pixel;

  if (!priv->socket_window)
    return;
  if (!priv->xvisual_info)
    return;

  xvisual_info = priv->xvisual_info;

  if (priv->has_alpha)
    {
      pixel = 0;
    }
  else
    {
      double red, green, blue;
      int red_shift, red_prec, green_shift, green_prec, blue_shift, blue_prec;
      unsigned int padding;

      /* Shifting by >= width-of-type isn't defined in C */
      if (xvisual_info->depth >= 32)
        padding = 0;
      else
        padding = ((~(uint32_t)0)) << xvisual_info->depth;

      red = (double) color->red / 255.0;
      green = (double) color->green / 255.0;
      blue = (double) color->blue / 255.0;

      get_pixel_details (xvisual_info->red_mask, &red_shift, &red_prec);
      get_pixel_details (xvisual_info->green_mask, &green_shift, &green_prec);
      get_pixel_details (xvisual_info->blue_mask, &blue_shift, &blue_prec);

      pixel = ~(xvisual_info->red_mask |
                xvisual_info->green_mask |
                xvisual_info->blue_mask |
                padding);

      pixel += (((int) (red * ((1 << red_prec) - 1))) << red_shift) +
        (((int) (green * ((1 << green_prec) - 1))) << green_shift) +
        (((int) (blue * ((1 << blue_prec) - 1))) << blue_shift);
    }

  xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);

  XSetWindowBackground (xdisplay, priv->socket_window, pixel);
  XClearWindow (xdisplay, priv->socket_window);
}
