/*
 * Copyright © 2009-2011 Arnaud Fontaine <arnau@debian.org>
 *
 * Permission  is  hereby  granted,  free  of charge,  to  any  person
 * obtaining  a copy  of  this software  and associated  documentation
 * files   (the  "Software"),   to  deal   in  the   Software  without
 * restriction, including without limitation  the rights to use, copy,
 * modify, merge, publish,  distribute, sublicense, and/or sell copies
 * of  the Software, and  to permit  persons to  whom the  Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above copyright  notice and  this permission  notice  shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE  IS PROVIDED  "AS IS", WITHOUT  WARRANTY OF  ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT  NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY,   FITNESS    FOR   A   PARTICULAR    PURPOSE   AND
 * NONINFRINGEMENT. IN  NO EVENT SHALL  THE AUTHORS BE LIABLE  FOR ANY
 * CLAIM,  DAMAGES  OR  OTHER  LIABILITY,  WHETHER  IN  AN  ACTION  OF
 * CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as  contained in  this notice, the  names of the  authors or
 * their institutions shall not be used in advertising or otherwise to
 * promote the  sale, use or  other dealings in this  Software without
 * prior written authorization from the authors.
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "xcb_ewmh.h"

#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <stddef.h>

#include <xcb/xcb.h>
#include <xcb/xproto.h>

#include <sys/types.h>

#define ssizeof(foo)            (ssize_t)sizeof(foo)
#define countof(foo)            (ssizeof(foo) / ssizeof(foo[0]))

/**
 * @brief The  structure used  on screen initialization  including the
 * atoms name and its length
 */
typedef struct {
  /** The Atom name length */
  uint8_t name_len;
  /** The Atom name string */
  const char *name;
  size_t m_offset;
} ewmh_atom_t;

define(`DO_ENTRY', `
  { sizeof("$1") - 1, "$1", offsetof(xcb_ewmh_connection_t, $1) }ifelse(`$2', , , `,')')dnl

define(`DO', `DO_ENTRY(`$1', `$2')ifelse(`$2', , , `DO(shift($@))')')dnl

/**
 * @brief List  of atoms where each  entry contains the  Atom name and
 * its length
 */
static ewmh_atom_t ewmh_atoms[] = {dnl
  include(atomlist.m4)dnl
};

#define NB_EWMH_ATOMS countof(ewmh_atoms)

/**
 * Common functions and macro
 */

#define DO_GET_PROPERTY(fname, property, type, length)                  \
  xcb_get_property_cookie_t                                             \
  xcb_ewmh_get_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       xcb_window_t window)                             \
  {                                                                     \
    return xcb_get_property(ewmh->connection, 0, window,                \
                            ewmh->property, type, 0, length);           \
  }                                                                     \
                                                                        \
  xcb_get_property_cookie_t                                             \
  xcb_ewmh_get_##fname##_unchecked(xcb_ewmh_connection_t *ewmh,         \
                                   xcb_window_t window)                 \
  {                                                                     \
    return xcb_get_property_unchecked(ewmh->connection, 0, window,      \
                                      ewmh->property, type, 0, length); \
  }

#define DO_GET_ROOT_PROPERTY(fname, property, atype, length)            \
  xcb_get_property_cookie_t                                             \
  xcb_ewmh_get_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       int screen_nbr)                                  \
  {                                                                     \
    return xcb_get_property(ewmh->connection, 0,                        \
                            ewmh->screens[screen_nbr]->root,            \
                            ewmh->property, atype, 0, length);          \
  }                                                                     \
                                                                        \
  xcb_get_property_cookie_t                                             \
  xcb_ewmh_get_##fname##_unchecked(xcb_ewmh_connection_t *ewmh,         \
                                   int screen_nbr)                      \
  {                                                                     \
    return xcb_get_property_unchecked(ewmh->connection, 0,              \
                                      ewmh->screens[screen_nbr]->root,  \
                                      ewmh->property, atype, 0,         \
                                      length);                          \
  }

/**
 * Generic  function for  EWMH atoms  with  a single  value which  may
 * actually be either WINDOW or CARDINAL
 *
 * _NET_NUMBER_OF_DESKTOPS, CARDINAL/32
 * _NET_CURRENT_DESKTOP desktop, CARDINAL/32
 * _NET_ACTIVE_WINDOW, WINDOW/32
 * _NET_SUPPORTING_WM_CHECK, WINDOW/32
 * _NET_SHOWING_DESKTOP desktop, CARDINAL/32
 * _NET_WM_DESKTOP desktop, CARDINAL/32
 * _NET_WM_PID CARDINAL/32
 * _NET_WM_USER_TIME CARDINAL/32
 * _NET_WM_USER_TIME_WINDOW WINDOW/32
 */

/**
 * Macro defining  a generic function  for reply with a  single value,
 * considering that the  value is 32-bit long (actually  only used for
 * WINDOW and CARDINAL)
 */
#define DO_REPLY_SINGLE_VALUE(fname, atype, ctype)                      \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_from_reply(ctype *atom_value,                  \
                                    xcb_get_property_reply_t *r)        \
  {                                                                     \
    if(!r || r->type != atype || r->format != 32 ||                     \
       xcb_get_property_value_length(r) != sizeof(ctype))               \
      return 0;                                                         \
                                                                        \
    *atom_value = *((ctype *) xcb_get_property_value(r));               \
    return 1;                                                           \
  }                                                                     \
                                                                        \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_reply(xcb_ewmh_connection_t *ewmh,             \
                               xcb_get_property_cookie_t cookie,        \
                               ctype *atom_value,                       \
                               xcb_generic_error_t **e)                 \
  {                                                                     \
    xcb_get_property_reply_t *r =                                       \
      xcb_get_property_reply(ewmh->connection,                          \
                             cookie, e);                                \
                                                                        \
    const uint8_t ret = xcb_ewmh_get_##fname##_from_reply(atom_value, r); \
                                                                        \
    free(r);                                                            \
    return ret;                                                         \
  }

/** Define reply functions for common WINDOW Atom */
DO_REPLY_SINGLE_VALUE(window, XCB_ATOM_WINDOW, xcb_window_t)

/** Define reply functions for common CARDINAL Atom */
DO_REPLY_SINGLE_VALUE(cardinal, XCB_ATOM_CARDINAL, uint32_t)

#define DO_SINGLE_VALUE(fname, property, atype, ctype)                  \
  DO_GET_PROPERTY(fname, property, atype, 1L)                           \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname##_checked(xcb_ewmh_connection_t *ewmh,           \
                                 xcb_window_t window,                   \
                                 ctype value)                           \
  {                                                                     \
    return xcb_change_property_checked(ewmh->connection,                \
                                       XCB_PROP_MODE_REPLACE,           \
                                       window, ewmh->property,          \
                                       atype, 32, 1, &value);           \
  }                                                                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       xcb_window_t window,                             \
                       ctype value)                                     \
  {                                                                     \
    return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, \
                               window, ewmh->property, atype, 32, 1,    \
                               &value);                                 \
  }

#define DO_ROOT_SINGLE_VALUE(fname, property, atype, ctype)             \
  DO_GET_ROOT_PROPERTY(fname, property, atype, 1L)                      \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname##_checked(xcb_ewmh_connection_t *ewmh,           \
                                 int screen_nbr,                        \
                                 ctype value)                           \
  {                                                                     \
    return xcb_change_property_checked(ewmh->connection,                \
                                       XCB_PROP_MODE_REPLACE,           \
                                       ewmh->screens[screen_nbr]->root, \
                                       ewmh->property, atype, 32, 1,    \
                                       &value);                         \
  }                                                                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       int screen_nbr,                                  \
                       ctype value)                                     \
  {                                                                     \
    return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, \
                               ewmh->screens[screen_nbr]->root,         \
                               ewmh->property, atype,                   \
                               32, 1, &value);                          \
  }

/**
 * Generic function for EWMH atoms with  a list of values which may be
 * actually WINDOW or ATOM.
 *
 * _NET_SUPPORTED, ATOM[]/32
 * _NET_CLIENT_LIST, WINDOW[]/32
 * _NET_CLIENT_LIST_STACKING, WINDOW[]/32
 * _NET_VIRTUAL_ROOTS, WINDOW[]/32
 * _NET_WM_WINDOW_TYPE, ATOM[]/32
 * _NET_WM_ALLOWED_ACTIONS, ATOM[]
 */

/**
 * Macro defining  a generic function  for reply containing a  list of
 * values and also defines a function to wipe the reply.
 */
#define DO_REPLY_LIST_VALUES(fname, atype, ctype)                       \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_from_reply(xcb_ewmh_get_##fname##_reply_t *data, \
                                    xcb_get_property_reply_t *r)        \
  {                                                                     \
    if(!r || r->type != atype || r->format != 32)                       \
      return 0;                                                         \
                                                                        \
    data->_reply = r;                                                   \
    data->fname##_len = xcb_get_property_value_length(data->_reply) /   \
      sizeof(ctype);                                                    \
                                                                        \
    data->fname = (ctype *) xcb_get_property_value(data->_reply);       \
    return 1;                                                           \
  }                                                                     \
                                                                        \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_reply(xcb_ewmh_connection_t *ewmh,             \
                               xcb_get_property_cookie_t cookie,        \
                               xcb_ewmh_get_##fname##_reply_t *data,    \
                               xcb_generic_error_t **e)                 \
  {                                                                     \
    xcb_get_property_reply_t *r =                                       \
      xcb_get_property_reply(ewmh->connection,                          \
                             cookie, e);                                \
                                                                        \
    const uint8_t ret = xcb_ewmh_get_##fname##_from_reply(data, r);     \
                                                                        \
    /* If the  last call  was not successful  (ret equals to  0), then  \
       just free the reply as the data value is not consistent */       \
    if(!ret)                                                            \
      free(r);                                                          \
                                                                        \
    return ret;                                                         \
  }                                                                     \
                                                                        \
  void                                                                  \
  xcb_ewmh_get_##fname##_reply_wipe(xcb_ewmh_get_##fname##_reply_t *data) \
  {                                                                     \
    free(data->_reply);                                                 \
  }

#define DO_ROOT_LIST_VALUES(fname, property, atype, ctype)              \
  DO_GET_ROOT_PROPERTY(fname, property, atype, UINT_MAX)                \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname##_checked(xcb_ewmh_connection_t *ewmh,           \
                                 int screen_nbr,                        \
                                 uint32_t list_len,                     \
                                 ctype *list)                           \
  {                                                                     \
    return xcb_change_property_checked(ewmh->connection,                \
                                       XCB_PROP_MODE_REPLACE,           \
                                       ewmh->screens[screen_nbr]->root, \
                                       ewmh->property, atype, 32,       \
                                       list_len * (sizeof(ctype) >> 2), \
                                       list);                           \
  }                                                                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       int screen_nbr,                                  \
                       uint32_t list_len,                               \
                       ctype *list)                                     \
  {                                                                     \
    return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, \
                               ewmh->screens[screen_nbr]->root,         \
                               ewmh->property, atype, 32,               \
                               list_len * (sizeof(ctype) >> 2),         \
                               list);                                   \
  }

#define DO_LIST_VALUES(fname, property, atype, kind)                    \
  DO_GET_PROPERTY(fname, property, atype, UINT_MAX)                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname##_checked(xcb_ewmh_connection_t *ewmh,           \
                                 xcb_window_t window,                   \
                                 uint32_t list_len,                     \
                                 xcb_##kind##_t *list)                  \
  {                                                                     \
    return xcb_change_property_checked(ewmh->connection,                \
                                       XCB_PROP_MODE_REPLACE, window,   \
                                       ewmh->property, atype, 32,       \
                                       list_len, list);                 \
  }                                                                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       xcb_window_t window,                             \
                       uint32_t list_len,                               \
                       xcb_##kind##_t *list)                            \
  {                                                                     \
    return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, \
                               window, ewmh->property, atype, 32,       \
                               list_len, list);                         \
  }                                                                     \
                                                                        \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_from_reply(xcb_ewmh_get_##kind##s_reply_t *name, \
                                    xcb_get_property_reply_t *r)        \
  {                                                                     \
    return xcb_ewmh_get_##kind##s_from_reply(name, r);                  \
  }                                                                     \
                                                                        \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_reply(xcb_ewmh_connection_t *ewmh,             \
                               xcb_get_property_cookie_t cookie,        \
                               xcb_ewmh_get_##kind##s_reply_t *name,    \
                               xcb_generic_error_t **e)                 \
  {                                                                     \
    return xcb_ewmh_get_##kind##s_reply(ewmh, cookie, name, e);         \
  }

#define DO_REPLY_STRUCTURE(fname, ctype)                                \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_from_reply(ctype *out,                         \
                                    xcb_get_property_reply_t *r)        \
  {                                                                     \
    if(!r || r->type != XCB_ATOM_CARDINAL || r->format != 32 ||         \
       xcb_get_property_value_length(r) != sizeof(ctype))               \
      return 0;                                                         \
                                                                        \
    memcpy(out, xcb_get_property_value(r),                              \
           xcb_get_property_value_length(r));                           \
                                                                        \
    return 1;                                                           \
  }                                                                     \
                                                                        \
  uint8_t                                                               \
  xcb_ewmh_get_##fname##_reply(xcb_ewmh_connection_t *ewmh,             \
                               xcb_get_property_cookie_t cookie,        \
                               ctype *out,                              \
                               xcb_generic_error_t **e)                 \
  {                                                                     \
    xcb_get_property_reply_t *r =                                       \
      xcb_get_property_reply(ewmh->connection, cookie, e);              \
                                                                        \
    const uint8_t ret = xcb_ewmh_get_##fname##_from_reply(out, r);      \
    free(r);                                                            \
    return ret;                                                         \
  }

/**
 * UTF8_STRING handling
 */

uint8_t
xcb_ewmh_get_utf8_strings_from_reply(xcb_ewmh_connection_t *ewmh,
                                     xcb_ewmh_get_utf8_strings_reply_t *data,
                                     xcb_get_property_reply_t *r)
{
  if(!r || r->type != ewmh->UTF8_STRING || r->format != 8)
    return 0;

  data->_reply = r;
  data->strings_len = xcb_get_property_value_length(data->_reply);
  data->strings = (char *) xcb_get_property_value(data->_reply);

  return 1;
}

uint8_t
xcb_ewmh_get_utf8_strings_reply(xcb_ewmh_connection_t *ewmh,
                                xcb_get_property_cookie_t cookie,
                                xcb_ewmh_get_utf8_strings_reply_t *data,
                                xcb_generic_error_t **e)
{
  xcb_get_property_reply_t *r = xcb_get_property_reply(ewmh->connection,
                                                       cookie, e);

  const uint8_t ret = xcb_ewmh_get_utf8_strings_from_reply(ewmh, data, r);

  /* If the last call was not  successful (ret equals to 0), then just
     free the reply as the data value is not consistent */
  if(!ret)
    free(r);

  return ret;
}

void
xcb_ewmh_get_utf8_strings_reply_wipe(xcb_ewmh_get_utf8_strings_reply_t *data)
{
  free(data->_reply);
}

#define DO_ROOT_UTF8_STRING(fname, property)                            \
  DO_GET_ROOT_PROPERTY(fname, property, 0, UINT_MAX)                    \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       int screen_nbr,                                  \
                       uint32_t strings_len,                            \
                       const char *strings)                             \
  {                                                                     \
    return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, \
                               ewmh->screens[screen_nbr]->root,         \
                               ewmh->property, ewmh->UTF8_STRING, 8,    \
                               strings_len, strings);                   \
  }                                                                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname##_checked(xcb_ewmh_connection_t *ewmh,           \
                                 int screen_nbr,                        \
                                 uint32_t strings_len,                  \
                                 const char *strings)                   \
  {                                                                     \
    return xcb_change_property_checked(ewmh->connection,                \
                                       XCB_PROP_MODE_REPLACE,           \
                                       ewmh->screens[screen_nbr]->root, \
                                       ewmh->property,                  \
                                       ewmh->UTF8_STRING, 8,            \
                                       strings_len, strings);           \
  }

#define DO_UTF8_STRING(fname, property)                                 \
  DO_GET_PROPERTY(fname, property, 0, UINT_MAX)                         \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname(xcb_ewmh_connection_t *ewmh,                     \
                       xcb_window_t window,                             \
                       uint32_t strings_len,                            \
                       const char *strings)                             \
  {                                                                     \
    return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, \
                               window, ewmh->property,                  \
                               ewmh->UTF8_STRING, 8, strings_len,       \
                               strings);                                \
  }                                                                     \
                                                                        \
  xcb_void_cookie_t                                                     \
  xcb_ewmh_set_##fname##_checked(xcb_ewmh_connection_t *ewmh,           \
                                 xcb_window_t window,                   \
                                 uint32_t strings_len,                  \
                                 const char *strings)                   \
  {                                                                     \
    return xcb_change_property_checked(ewmh->connection,                \
                                       XCB_PROP_MODE_REPLACE,           \
                                       window, ewmh->property,          \
                                       ewmh->UTF8_STRING, 8,            \
                                       strings_len, strings);           \
  }

/**
 * ClientMessage generic function
 */
xcb_void_cookie_t
xcb_ewmh_send_client_message(xcb_connection_t *c,
                             xcb_window_t window,
                             xcb_window_t dest,
                             xcb_atom_t atom,
                             uint32_t data_len,
                             const uint32_t *data)
{
  xcb_client_message_event_t ev;
  memset(&ev, 0, sizeof(xcb_client_message_event_t));

  ev.response_type = XCB_CLIENT_MESSAGE;
  ev.window = window;
  ev.format = 32;
  ev.type = atom;

  assert(data_len <= (5 * sizeof(uint32_t)));

  memcpy(ev.data.data32, data, data_len);

  return xcb_send_event(c, 0, dest, XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
                        XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
                        (char *) &ev);
}

DO_REPLY_LIST_VALUES(windows, XCB_ATOM_WINDOW, xcb_window_t)
DO_REPLY_LIST_VALUES(atoms, XCB_ATOM_ATOM, xcb_atom_t)

/**
 * Atoms initialisation
 */

xcb_intern_atom_cookie_t *
xcb_ewmh_init_atoms(xcb_connection_t *c,
                    xcb_ewmh_connection_t *ewmh)
{
  int screen_nbr, atom_nbr;

  ewmh->connection = c;

  const xcb_setup_t *setup = xcb_get_setup(c);

  ewmh->nb_screens = xcb_setup_roots_length(setup);
  if(!ewmh->nb_screens)
    return NULL;

  /* Allocate the data structures depending of the number of screens */
  ewmh->screens = malloc(sizeof(xcb_screen_t *) * ewmh->nb_screens);
  ewmh->_NET_WM_CM_Sn = malloc(sizeof(xcb_atom_t) * ewmh->nb_screens);

  xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
  for(screen_iter = xcb_setup_roots_iterator(setup), screen_nbr = 0; screen_iter.rem;
      xcb_screen_next(&screen_iter))
    ewmh->screens[screen_nbr++] = screen_iter.data;

  /* _NET_WM_CM_Sn atoms  will be treated differently,  by adding them
     at the end  of this array, than other atoms as  it depends on the
     number of screens */
  xcb_intern_atom_cookie_t *ewmh_cookies = malloc(sizeof(xcb_intern_atom_cookie_t) *
                                                  (NB_EWMH_ATOMS + ewmh->nb_screens));

  /* First, send InternAtom request for all Atoms except _NET_WM_CM_Sn */
  for(atom_nbr = 0; atom_nbr < NB_EWMH_ATOMS; atom_nbr++)
    ewmh_cookies[atom_nbr] = xcb_intern_atom(ewmh->connection, 0,
                                             ewmh_atoms[atom_nbr].name_len,
                                             ewmh_atoms[atom_nbr].name);

  /* Then,  send  InternAtom requests  for  _NET_WM_CM_Sn and  compute
     _NET_WM_CM_Sn according to the screen number 'n' */
  for(screen_nbr = 0; screen_nbr < ewmh->nb_screens; screen_nbr++)
    {
      char wm_cm_sn[32];

      const int wm_cm_sn_len = snprintf(wm_cm_sn, 32, "_NET_WM_CM_S%d",
                                        screen_nbr);

      assert(wm_cm_sn_len > 0 && wm_cm_sn_len < 32);

      ewmh_cookies[atom_nbr++] = xcb_intern_atom(ewmh->connection, 0,
                                                 wm_cm_sn_len,
                                                 wm_cm_sn);
    }

  return ewmh_cookies;
}

uint8_t
xcb_ewmh_init_atoms_replies(xcb_ewmh_connection_t *ewmh,
                            xcb_intern_atom_cookie_t *ewmh_cookies,
                            xcb_generic_error_t **e)
{
  int atom_nbr;
  int screen_nbr = 0;
  uint8_t ret = 1;
  xcb_intern_atom_reply_t *reply;

  for(atom_nbr = 0; atom_nbr < NB_EWMH_ATOMS + ewmh->nb_screens; atom_nbr++)
    if((reply = xcb_intern_atom_reply(ewmh->connection, ewmh_cookies[atom_nbr], e)))
      {
        if(ret)
          {
            if(atom_nbr < NB_EWMH_ATOMS)
              *((xcb_atom_t *) (((char *) ewmh) + ewmh_atoms[atom_nbr].m_offset)) = reply->atom;
            else
              ewmh->_NET_WM_CM_Sn[screen_nbr++] = reply->atom;
          }

        free(reply);
      }
    else
      ret = 0;

  if(!ret)
    xcb_ewmh_connection_wipe(ewmh);

  free(ewmh_cookies);
  return ret;
}

/**
 * _NET_SUPPORTED
 */

DO_ROOT_LIST_VALUES(supported, _NET_SUPPORTED, XCB_ATOM_ATOM, xcb_atom_t)

/**
 * _NET_CLIENT_LIST
 * _NET_CLIENT_LIST_STACKING
 */

DO_ROOT_LIST_VALUES(client_list, _NET_CLIENT_LIST, XCB_ATOM_WINDOW, xcb_window_t)

DO_ROOT_LIST_VALUES(client_list_stacking, _NET_CLIENT_LIST_STACKING,
                    XCB_ATOM_WINDOW, xcb_window_t)

/**
 * _NET_NUMBER_OF_DESKTOPS
 */

DO_ROOT_SINGLE_VALUE(number_of_desktops, _NET_NUMBER_OF_DESKTOPS,
                     XCB_ATOM_CARDINAL, uint32_t)

/**
 * _NET_DESKTOP_GEOMETRY
 */

DO_GET_ROOT_PROPERTY(desktop_geometry, _NET_DESKTOP_GEOMETRY,
                     XCB_ATOM_CARDINAL, 2L)

xcb_void_cookie_t
xcb_ewmh_set_desktop_geometry(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                              uint32_t new_width, uint32_t new_height)
{
  const uint32_t data[] = { new_width, new_height };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE,
                             ewmh->screens[screen_nbr]->root,
                             ewmh->_NET_DESKTOP_GEOMETRY, XCB_ATOM_CARDINAL,
                             32, 2, data);
}

xcb_void_cookie_t
xcb_ewmh_set_desktop_geometry_checked(xcb_ewmh_connection_t *ewmh,
                                      int screen_nbr, uint32_t new_width,
                                      uint32_t new_height)
{
  const uint32_t data[] = { new_width, new_height };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     ewmh->screens[screen_nbr]->root,
                                     ewmh->_NET_DESKTOP_GEOMETRY,
                                     XCB_ATOM_CARDINAL, 32, 2, data);
}

xcb_void_cookie_t
xcb_ewmh_request_change_desktop_geometry(xcb_ewmh_connection_t *ewmh,
                                         int screen_nbr, uint32_t new_width,
                                         uint32_t new_height)
{
  const uint32_t data[] = { new_width, new_height };

  return xcb_ewmh_send_client_message(ewmh->connection,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_DESKTOP_GEOMETRY,
                                      sizeof(data), data);
}

uint8_t
xcb_ewmh_get_desktop_geometry_from_reply(uint32_t *width, uint32_t *height,
                                         xcb_get_property_reply_t *r)
{
  if(!r || r->type != XCB_ATOM_CARDINAL || r->format != 32 ||
     xcb_get_property_value_length(r) != (sizeof(uint32_t) * 2))
    return 0;

  uint32_t *value = (uint32_t *) xcb_get_property_value(r);

  *width = value[0];
  *height = value[1];

  return 1;
}

uint8_t
xcb_ewmh_get_desktop_geometry_reply(xcb_ewmh_connection_t *ewmh,
                                    xcb_get_property_cookie_t cookie,
                                    uint32_t *width, uint32_t *height,
                                    xcb_generic_error_t **e)
{
  xcb_get_property_reply_t *r = xcb_get_property_reply(ewmh->connection, cookie, e);
  const uint8_t ret = xcb_ewmh_get_desktop_geometry_from_reply(width, height, r);
  free(r);
  return ret;
}

/**
 * _NET_DESKTOP_VIEWPORT
 */

DO_ROOT_LIST_VALUES(desktop_viewport, _NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL,
                    xcb_ewmh_coordinates_t)

DO_REPLY_LIST_VALUES(desktop_viewport, XCB_ATOM_CARDINAL,
                     xcb_ewmh_coordinates_t)

xcb_void_cookie_t
xcb_ewmh_request_change_desktop_viewport(xcb_ewmh_connection_t *ewmh,
                                         int screen_nbr, uint32_t x,
                                         uint32_t y)
{
  const uint32_t data[] = { x, y };

  return xcb_ewmh_send_client_message(ewmh->connection,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_DESKTOP_VIEWPORT,
                                      sizeof(data), data);
}

/**
 * _NET_CURRENT_DESKTOP
 */

DO_ROOT_SINGLE_VALUE(current_desktop, _NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL,
                     uint32_t)

xcb_void_cookie_t
xcb_ewmh_request_change_current_desktop(xcb_ewmh_connection_t *ewmh,
                                        int screen_nbr, uint32_t new_desktop,
                                        xcb_timestamp_t timestamp)
{
  const uint32_t data[] = { new_desktop, timestamp };

  return xcb_ewmh_send_client_message(ewmh->connection,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_CURRENT_DESKTOP,
                                      sizeof(data), data);
}

/**
 * _NET_DESKTOP_NAMES
 */
DO_ROOT_UTF8_STRING(desktop_names, _NET_DESKTOP_NAMES)

/**
 * _NET_ACTIVE_WINDOW
 */

DO_ROOT_SINGLE_VALUE(active_window, _NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW,
                     xcb_window_t)

xcb_void_cookie_t
xcb_ewmh_request_change_active_window(xcb_ewmh_connection_t *ewmh,
                                      int screen_nbr,
                                      xcb_window_t window_to_activate,
                                      xcb_ewmh_client_source_type_t source_indication,
                                      xcb_timestamp_t timestamp,
                                      xcb_window_t current_active_window)
{
  const uint32_t data[] = { source_indication, timestamp, current_active_window };

  return xcb_ewmh_send_client_message(ewmh->connection, window_to_activate,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_ACTIVE_WINDOW, sizeof(data),
                                      data);
}

/**
 * _NET_WORKAREA
 */

DO_ROOT_LIST_VALUES(workarea, _NET_WORKAREA, XCB_ATOM_CARDINAL,
                    xcb_ewmh_geometry_t)

DO_REPLY_LIST_VALUES(workarea, XCB_ATOM_CARDINAL, xcb_ewmh_geometry_t)

/**
 * _NET_SUPPORTING_WM_CHECK
 */

DO_SINGLE_VALUE(supporting_wm_check, _NET_SUPPORTING_WM_CHECK,
                XCB_ATOM_WINDOW, xcb_window_t)

/**
 * _NET_VIRTUAL_ROOTS
 */

DO_ROOT_LIST_VALUES(virtual_roots, _NET_VIRTUAL_ROOTS, XCB_ATOM_WINDOW,
                    xcb_window_t)

/**
 * _NET_DESKTOP_LAYOUT
 */

DO_GET_ROOT_PROPERTY(desktop_layout, _NET_DESKTOP_LAYOUT, XCB_ATOM_CARDINAL, 4)
DO_REPLY_STRUCTURE(desktop_layout, xcb_ewmh_get_desktop_layout_reply_t)

xcb_void_cookie_t
xcb_ewmh_set_desktop_layout(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                            xcb_ewmh_desktop_layout_orientation_t orientation,
                            uint32_t columns, uint32_t rows,
                            xcb_ewmh_desktop_layout_starting_corner_t starting_corner)
{
  const uint32_t data[] = { orientation, columns, rows, starting_corner };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE,
                             ewmh->screens[screen_nbr]->root,
                             ewmh->_NET_DESKTOP_LAYOUT, XCB_ATOM_CARDINAL, 32,
                             countof(data), data);
}

xcb_void_cookie_t
xcb_ewmh_set_desktop_layout_checked(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                                    xcb_ewmh_desktop_layout_orientation_t orientation,
                                    uint32_t columns, uint32_t rows,
                                    xcb_ewmh_desktop_layout_starting_corner_t starting_corner)
{
  const uint32_t data[] = { orientation, columns, rows, starting_corner };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     ewmh->screens[screen_nbr]->root,
                                     ewmh->_NET_DESKTOP_LAYOUT,
                                     XCB_ATOM_CARDINAL, 32, countof(data),
                                     data);
}

/**
 * _NET_SHOWING_DESKTOP
 */

DO_ROOT_SINGLE_VALUE(showing_desktop, _NET_SHOWING_DESKTOP, XCB_ATOM_CARDINAL,
                     uint32_t)

/**
 * _NET_CLOSE_WINDOW
 */

xcb_void_cookie_t
xcb_ewmh_request_close_window(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                              xcb_window_t window_to_close,
                              xcb_timestamp_t timestamp,
                              xcb_ewmh_client_source_type_t source_indication)
{
  const uint32_t data[] = { timestamp, source_indication };

  return xcb_ewmh_send_client_message(ewmh->connection, window_to_close,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_CLOSE_WINDOW, sizeof(data),
                                      data);
}

/**
 * _NET_MOVERESIZE_WINDOW
 */

/* x, y, width, height may be equal to -1 */
xcb_void_cookie_t
xcb_ewmh_request_moveresize_window(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                                   xcb_window_t moveresize_window,
                                   xcb_gravity_t gravity,
                                   xcb_ewmh_client_source_type_t source_indication,
                                   xcb_ewmh_moveresize_window_opt_flags_t flags,
                                   uint32_t x, uint32_t y,
                                   uint32_t width, uint32_t height)
{
  const uint32_t data[] = { (gravity | flags | source_indication << 12),
                            x, y, width, height };

  return xcb_ewmh_send_client_message(ewmh->connection, moveresize_window,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_MOVERESIZE_WINDOW,
                                      sizeof(data), data);
}

/**
 * _NET_WM_MOVERESIZE
 */

xcb_void_cookie_t
xcb_ewmh_request_wm_moveresize(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                               xcb_window_t moveresize_window,
                               uint32_t x_root, uint32_t y_root,
                               xcb_ewmh_moveresize_direction_t direction,
                               xcb_button_index_t button,
                               xcb_ewmh_client_source_type_t source_indication)
{
  const uint32_t data[] = { x_root, y_root, direction, button, source_indication };

  return xcb_ewmh_send_client_message(ewmh->connection, moveresize_window,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_WM_MOVERESIZE, sizeof(data),
                                      data);
}

/**
 * _NET_RESTACK_WINDOW
 */

xcb_void_cookie_t
xcb_ewmh_request_restack_window(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                                xcb_window_t window_to_restack,
                                xcb_window_t sibling_window,
                                xcb_stack_mode_t detail)
{
  const uint32_t data[] = { XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, sibling_window,
                            detail };

  return xcb_ewmh_send_client_message(ewmh->connection, window_to_restack,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_RESTACK_WINDOW, sizeof(data),
                                      data);
}

/**
 * _NET_WM_NAME
 */

DO_UTF8_STRING(wm_name, _NET_WM_NAME)

/**
 * _NET_WM_VISIBLE_NAME
 */

DO_UTF8_STRING(wm_visible_name, _NET_WM_VISIBLE_NAME)

/**
 * _NET_WM_ICON_NAME
 */

DO_UTF8_STRING(wm_icon_name, _NET_WM_ICON_NAME)

/**
 * _NET_WM_VISIBLE_ICON_NAME
 */

DO_UTF8_STRING(wm_visible_icon_name, _NET_WM_VISIBLE_ICON_NAME)

/**
 * _NET_WM_DESKTOP
 */

DO_SINGLE_VALUE(wm_desktop, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, uint32_t)

xcb_void_cookie_t
xcb_ewmh_request_change_wm_desktop(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                                   xcb_window_t client_window,
                                   uint32_t new_desktop,
                                   xcb_ewmh_client_source_type_t source_indication)
{
  const uint32_t data[] = { new_desktop, source_indication };

  return xcb_ewmh_send_client_message(ewmh->connection, client_window,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_WM_DESKTOP, sizeof(data),
                                      data);
}

/**
 * _NET_WM_WINDOW_TYPE
 *
 * TODO: check possible atoms?
 */

DO_LIST_VALUES(wm_window_type, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, atom)

/**
 * _NET_WM_STATE
 *
 * TODO: check possible atoms?
 */

DO_LIST_VALUES(wm_state, _NET_WM_STATE, XCB_ATOM_ATOM, atom)

xcb_void_cookie_t
xcb_ewmh_request_change_wm_state(xcb_ewmh_connection_t *ewmh, int screen_nbr,
                                 xcb_window_t client_window,
                                 xcb_ewmh_wm_state_action_t action,
                                 xcb_atom_t first_property,
                                 xcb_atom_t second_property,
                                 xcb_ewmh_client_source_type_t source_indication)
{
  const uint32_t data[] = { action, first_property, second_property,
                            source_indication };

  return xcb_ewmh_send_client_message(ewmh->connection, client_window,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_WM_STATE, sizeof(data), data);
}

/**
 * _NET_WM_ALLOWED_ACTIONS
 *
 * TODO: check possible atoms?
 */

DO_LIST_VALUES(wm_allowed_actions, _NET_WM_ALLOWED_ACTIONS, XCB_ATOM_ATOM, atom)

/**
 * _NET_WM_STRUT
 */

xcb_void_cookie_t
xcb_ewmh_set_wm_strut(xcb_ewmh_connection_t *ewmh,
                      xcb_window_t window,
                      uint32_t left, uint32_t right,
                      uint32_t top, uint32_t bottom)
{
  const uint32_t data[] = { left, right, top, bottom };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, window,
                             ewmh->_NET_WM_STRUT, XCB_ATOM_CARDINAL, 32,
                             countof(data), data);
}

xcb_void_cookie_t
xcb_ewmh_set_wm_strut_checked(xcb_ewmh_connection_t *ewmh,
                              xcb_window_t window,
                              uint32_t left, uint32_t right,
                              uint32_t top, uint32_t bottom)
{
  const uint32_t data[] = { left, right, top, bottom };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     window, ewmh->_NET_WM_STRUT,
                                     XCB_ATOM_CARDINAL, 32, countof(data),
                                     data);
}

DO_GET_PROPERTY(wm_strut, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 4)
DO_REPLY_STRUCTURE(wm_strut, xcb_ewmh_get_extents_reply_t)

/*
 * _NET_WM_STRUT_PARTIAL
 */

xcb_void_cookie_t
xcb_ewmh_set_wm_strut_partial(xcb_ewmh_connection_t *ewmh,
                              xcb_window_t window,
                              xcb_ewmh_wm_strut_partial_t wm_strut)
{
  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, window,
                             ewmh->_NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32,
                             12, &wm_strut);
}

xcb_void_cookie_t
xcb_ewmh_set_wm_strut_partial_checked(xcb_ewmh_connection_t *ewmh,
                                      xcb_window_t window,
                                      xcb_ewmh_wm_strut_partial_t wm_strut)
{
  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     window, ewmh->_NET_WM_STRUT_PARTIAL,
                                     XCB_ATOM_CARDINAL, 32, 12, &wm_strut);
}

DO_GET_PROPERTY(wm_strut_partial, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 12)
DO_REPLY_STRUCTURE(wm_strut_partial, xcb_ewmh_wm_strut_partial_t)

/**
 * _NET_WM_ICON_GEOMETRY
 */

xcb_void_cookie_t
xcb_ewmh_set_wm_icon_geometry_checked(xcb_ewmh_connection_t *ewmh,
                                      xcb_window_t window,
                                      uint32_t left, uint32_t right,
                                      uint32_t top, uint32_t bottom)
{
  const uint32_t data[] = { left, right, top, bottom };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     window, ewmh->_NET_WM_ICON_GEOMETRY,
                                     XCB_ATOM_CARDINAL, 32, countof(data),
                                     data);
}

xcb_void_cookie_t
xcb_ewmh_set_wm_icon_geometry(xcb_ewmh_connection_t *ewmh,
                              xcb_window_t window,
                              uint32_t left, uint32_t right,
                              uint32_t top, uint32_t bottom)
{
  const uint32_t data[] = { left, right, top, bottom };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, window,
                             ewmh->_NET_WM_ICON_GEOMETRY, XCB_ATOM_CARDINAL, 32,
                             countof(data), data);
}

DO_GET_PROPERTY(wm_icon_geometry, _NET_WM_ICON_GEOMETRY, XCB_ATOM_CARDINAL, 4)
DO_REPLY_STRUCTURE(wm_icon_geometry, xcb_ewmh_geometry_t)

/**
 * _NET_WM_ICON
 */

static inline void
set_wm_icon_data(uint32_t data[], uint32_t width, uint32_t height,
                 uint32_t img_len, uint32_t *img)
{
  data[0] = width;
  data[1] = height;

  memcpy(data + 2, img, img_len);
}

xcb_void_cookie_t
xcb_ewmh_append_wm_icon_checked(xcb_ewmh_connection_t *ewmh,
                                xcb_window_t window,
                                uint32_t width, uint32_t height,
                                uint32_t img_len, uint32_t *img)
{
  const uint32_t data_len = img_len + 2;
  uint32_t data[data_len];

  set_wm_icon_data(data, width, height, img_len, img);

  return xcb_ewmh_set_wm_icon_checked(ewmh, XCB_PROP_MODE_APPEND, window,
                                      data_len, data);
}

xcb_void_cookie_t
xcb_ewmh_append_wm_icon(xcb_ewmh_connection_t *ewmh,
                        xcb_window_t window,
                        uint32_t width, uint32_t height,
                        uint32_t img_len, uint32_t *img)
{
  const uint32_t data_len = img_len + 2;
  uint32_t data[data_len];

  set_wm_icon_data(data, width, height, img_len, img);

  return xcb_ewmh_set_wm_icon(ewmh, XCB_PROP_MODE_APPEND, window,
                              data_len, data);
}

DO_GET_PROPERTY(wm_icon, _NET_WM_ICON, XCB_ATOM_CARDINAL, UINT_MAX)

uint8_t
xcb_ewmh_get_wm_icon_from_reply(xcb_ewmh_get_wm_icon_reply_t *wm_icon,
                                xcb_get_property_reply_t *r)
{
  if(!r || r->type != XCB_ATOM_CARDINAL || r->format != 32)
    return 0;

  uint32_t r_value_len = xcb_get_property_value_length(r);
  uint32_t *r_value = (uint32_t *) xcb_get_property_value(r);

  /* Find the number of icons in the reply. */
  wm_icon->num_icons = 0;
  while(r_value_len > (sizeof(uint32_t) * 2) && r_value && r_value[0] && r_value[1])
  {
    /* Check that the property is as long as it should be (in bytes),
       handling integer overflow. "+2" to handle the width and height
       fields. */
    const uint64_t expected_len = (r_value[0] * (uint64_t) r_value[1] + 2) * 4;
    if(expected_len > r_value_len)
      break;

    wm_icon->num_icons++;

    /* Find pointer to next icon in the reply. */
    r_value_len -= expected_len;
    r_value = (uint32_t *) (((uint8_t *) r_value) + expected_len);
  }

  if(!wm_icon->num_icons)
    return 0;

  wm_icon->_reply = r;

  return 1;
}

uint8_t
xcb_ewmh_get_wm_icon_reply(xcb_ewmh_connection_t *ewmh,
                           xcb_get_property_cookie_t cookie,
                           xcb_ewmh_get_wm_icon_reply_t *wm_icon,
                           xcb_generic_error_t **e)
{
  xcb_get_property_reply_t *r = xcb_get_property_reply(ewmh->connection, cookie, e);
  const uint8_t ret = xcb_ewmh_get_wm_icon_from_reply(wm_icon, r);
  if(!ret)
    free(r);

  return ret;
}

void
xcb_ewmh_get_wm_icon_reply_wipe(xcb_ewmh_get_wm_icon_reply_t *wm_icon)
{
  free(wm_icon->_reply);
}

xcb_ewmh_wm_icon_iterator_t
xcb_ewmh_get_wm_icon_iterator(const xcb_ewmh_get_wm_icon_reply_t *wm_icon)
{
  xcb_ewmh_wm_icon_iterator_t ret;

  ret.width = 0;
  ret.height = 0;
  ret.data = NULL;
  ret.rem = wm_icon->num_icons;
  ret.index = 0;

  if(ret.rem > 0)
  {
    uint32_t *r_value = (uint32_t *) xcb_get_property_value(wm_icon->_reply);
    ret.width = r_value[0];
    ret.height = r_value[1];
    ret.data = &r_value[2];
  }

  return ret;
}

unsigned int xcb_ewmh_get_wm_icon_length(const xcb_ewmh_get_wm_icon_reply_t *wm_icon)
{
  return wm_icon->num_icons;
}

void xcb_ewmh_get_wm_icon_next(xcb_ewmh_wm_icon_iterator_t *iterator)
{
  if(iterator->rem <= 1)
  {
    iterator->index += iterator->rem;
    iterator->rem = 0;
    iterator->width = 0;
    iterator->height = 0;
    iterator->data = NULL;
    return;
  }

  uint64_t icon_len = iterator->width * (uint64_t) iterator->height;
  uint32_t *data = iterator->data + icon_len;

  iterator->rem--;
  iterator->index++;
  iterator->width = data[0];
  iterator->height = data[1];
  iterator->data = &data[2];
}

/**
 * _NET_WM_PID
 */

DO_SINGLE_VALUE(wm_pid, _NET_WM_PID, XCB_ATOM_CARDINAL, uint32_t)

/**
 * _NET_WM_HANDLED_ICONS
 */

DO_SINGLE_VALUE(wm_handled_icons, _NET_WM_HANDLED_ICONS, XCB_ATOM_CARDINAL,
                uint32_t)

/**
 * _NET_WM_USER_TIME
 */

DO_SINGLE_VALUE(wm_user_time, _NET_WM_USER_TIME, XCB_ATOM_CARDINAL, uint32_t)

/**
 * _NET_WM_USER_TIME_WINDOW
 */

DO_SINGLE_VALUE(wm_user_time_window, _NET_WM_USER_TIME_WINDOW, XCB_ATOM_CARDINAL,
                uint32_t)

/**
 * _NET_FRAME_EXTENTS
 */

xcb_void_cookie_t
xcb_ewmh_set_frame_extents(xcb_ewmh_connection_t *ewmh,
                           xcb_window_t window,
                           uint32_t left, uint32_t right,
                           uint32_t top, uint32_t bottom)
{
  const uint32_t data[] = { left, right, top, bottom };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, window,
                             ewmh->_NET_FRAME_EXTENTS, XCB_ATOM_CARDINAL, 32,
                             countof(data), data);
}

xcb_void_cookie_t
xcb_ewmh_set_frame_extents_checked(xcb_ewmh_connection_t *ewmh,
                                   xcb_window_t window,
                                   uint32_t left, uint32_t right,
                                   uint32_t top, uint32_t bottom)
{
  const uint32_t data[] = { left, right, top, bottom };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     window, ewmh->_NET_FRAME_EXTENTS,
                                     XCB_ATOM_CARDINAL, 32, countof(data),
                                     data);
}

DO_GET_PROPERTY(frame_extents, _NET_FRAME_EXTENTS, XCB_ATOM_CARDINAL, 4)
DO_REPLY_STRUCTURE(frame_extents, xcb_ewmh_get_extents_reply_t)

/**
 * _NET_WM_PING
 *
 * TODO: client resend function?
 */

xcb_void_cookie_t
xcb_ewmh_send_wm_ping(xcb_ewmh_connection_t *ewmh,
                      xcb_window_t window,
                      xcb_timestamp_t timestamp)
{
  const uint32_t data[] = { ewmh->_NET_WM_PING, timestamp, window };

  return xcb_ewmh_send_client_message(ewmh->connection, window, window,
                                      ewmh->WM_PROTOCOLS, sizeof(data), data);
}

/**
 * _NET_WM_SYNC_REQUEST
 * _NET_WM_SYNC_REQUEST_COUNTER
 */

xcb_void_cookie_t
xcb_ewmh_set_wm_sync_request_counter(xcb_ewmh_connection_t *ewmh,
                                     xcb_window_t window,
                                     xcb_atom_t wm_sync_request_counter_atom,
                                     uint32_t low, uint32_t high)
{
  const uint32_t data[] = { low, high };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, window,
                             ewmh->_NET_WM_SYNC_REQUEST_COUNTER,
                             XCB_ATOM_CARDINAL, 32,
                             countof(data), data);
}

xcb_void_cookie_t
xcb_ewmh_set_wm_sync_request_counter_checked(xcb_ewmh_connection_t *ewmh,
                                             xcb_window_t window,
                                             xcb_atom_t wm_sync_request_counter_atom,
                                             uint32_t low, uint32_t high)
{
  const uint32_t data[] = { low, high };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     window, ewmh->_NET_WM_SYNC_REQUEST_COUNTER,
                                     XCB_ATOM_CARDINAL, 32, countof(data),
                                     data);
}

DO_GET_PROPERTY(wm_sync_request_counter, _NET_WM_SYNC_REQUEST_COUNTER,
                XCB_ATOM_CARDINAL, 2)

uint8_t
xcb_ewmh_get_wm_sync_request_counter_from_reply(uint64_t *counter,
                                                xcb_get_property_reply_t *r)
{
  /* 2 cardinals? */
  if(!r || r->type != XCB_ATOM_CARDINAL || r->format != 32 ||
     xcb_get_property_value_length(r) != sizeof(uint64_t))
    return 0;

  uint32_t *r_value = (uint32_t *) xcb_get_property_value(r);
  *counter = (r_value[0] | ((uint64_t) r_value[1]) << 32);

  return 1;
}

uint8_t
xcb_ewmh_get_wm_sync_request_counter_reply(xcb_ewmh_connection_t *ewmh,
                                           xcb_get_property_cookie_t cookie,
                                           uint64_t *counter,
                                           xcb_generic_error_t **e)
{
  xcb_get_property_reply_t *r = xcb_get_property_reply(ewmh->connection, cookie, e);
  const uint8_t ret = xcb_ewmh_get_wm_sync_request_counter_from_reply(counter, r);
  free(r);
  return ret;
}

xcb_void_cookie_t
xcb_ewmh_send_wm_sync_request(xcb_ewmh_connection_t *ewmh,
                              xcb_window_t window,
                              xcb_atom_t wm_protocols_atom,
                              xcb_atom_t wm_sync_request_atom,
                              xcb_timestamp_t timestamp,
                              uint64_t counter)
{
  const uint32_t data[] = { ewmh->_NET_WM_SYNC_REQUEST, timestamp, counter,
                            counter >> 32 };

  return xcb_ewmh_send_client_message(ewmh->connection, window, window,
                                      ewmh->WM_PROTOCOLS, sizeof(data), data);
}

/**
 * _NET_WM_FULLSCREEN_MONITORS
 */

xcb_void_cookie_t
xcb_ewmh_set_wm_fullscreen_monitors(xcb_ewmh_connection_t *ewmh,
                                    xcb_window_t window,
                                    uint32_t top, uint32_t bottom,
                                    uint32_t left, uint32_t right)
{
  const uint32_t data[] = { top, bottom, left, right };

  return xcb_change_property(ewmh->connection, XCB_PROP_MODE_REPLACE, window,
                             ewmh->_NET_WM_FULLSCREEN_MONITORS,
                             XCB_ATOM_CARDINAL, 32, countof(data), data);
}

xcb_void_cookie_t
xcb_ewmh_set_wm_fullscreen_monitors_checked(xcb_ewmh_connection_t *ewmh,
                                            xcb_window_t window,
                                            uint32_t top, uint32_t bottom,
                                            uint32_t left, uint32_t right)
{
  const uint32_t data[] = { top, bottom, left, right };

  return xcb_change_property_checked(ewmh->connection, XCB_PROP_MODE_REPLACE,
                                     window, ewmh->_NET_WM_FULLSCREEN_MONITORS,
                                     XCB_ATOM_CARDINAL, 32, countof(data),
                                     data);
}

DO_GET_PROPERTY(wm_fullscreen_monitors, _NET_WM_FULLSCREEN_MONITORS,
                XCB_ATOM_CARDINAL, 4)

DO_REPLY_STRUCTURE(wm_fullscreen_monitors,
                   xcb_ewmh_get_wm_fullscreen_monitors_reply_t)

xcb_void_cookie_t
xcb_ewmh_request_change_wm_fullscreen_monitors(xcb_ewmh_connection_t *ewmh,
                                               int screen_nbr,
                                               xcb_window_t window,
                                               uint32_t top, uint32_t bottom,
                                               uint32_t left, uint32_t right,
                                               xcb_ewmh_client_source_type_t source_indication)
{
  const uint32_t data[] = { top, bottom, left, right, source_indication };

  return xcb_ewmh_send_client_message(ewmh->connection, window,
                                      ewmh->screens[screen_nbr]->root,
                                      ewmh->_NET_WM_FULLSCREEN_MONITORS,
                                      sizeof(data), data);
}

/**
 * _NET_WM_FULL_PLACEMENT
 */

/**
 * _NET_WM_CM_Sn
 */

xcb_get_selection_owner_cookie_t
xcb_ewmh_get_wm_cm_owner(xcb_ewmh_connection_t *ewmh,
                         int screen_nbr)
{
  return xcb_get_selection_owner(ewmh->connection,
                                 ewmh->_NET_WM_CM_Sn[screen_nbr]);
}

xcb_get_selection_owner_cookie_t
xcb_ewmh_get_wm_cm_owner_unchecked(xcb_ewmh_connection_t *ewmh,
                                   int screen_nbr)
{
  return xcb_get_selection_owner_unchecked(ewmh->connection,
                                           ewmh->_NET_WM_CM_Sn[screen_nbr]);
}

uint8_t
xcb_ewmh_get_wm_cm_owner_from_reply(xcb_window_t *owner,
                                    xcb_get_selection_owner_reply_t *r)
{
  if(!r)
    return 0;

  *owner = r->owner;
  free(r);
  return 1;
}

uint8_t
xcb_ewmh_get_wm_cm_owner_reply(xcb_ewmh_connection_t *ewmh,
                               xcb_get_selection_owner_cookie_t cookie,
                               xcb_window_t *owner,
                               xcb_generic_error_t **e)
{
  xcb_get_selection_owner_reply_t *r =
    xcb_get_selection_owner_reply(ewmh->connection, cookie, e);

  return xcb_ewmh_get_wm_cm_owner_from_reply(owner, r);
}

/* TODO: section 2.1, 2.2 */
static xcb_void_cookie_t
set_wm_cm_owner_client_message(xcb_ewmh_connection_t *ewmh,
                               int screen_nbr,
                               xcb_window_t owner,
                               xcb_timestamp_t timestamp,
                               uint32_t selection_data1,
                               uint32_t selection_data2)
{
  xcb_client_message_event_t ev;
  memset(&ev, 0, sizeof(xcb_client_message_event_t));

  ev.response_type = XCB_CLIENT_MESSAGE;
  ev.format = 32;
  ev.type = ewmh->MANAGER;
  ev.data.data32[0] = timestamp;
  ev.data.data32[1] = ewmh->_NET_WM_CM_Sn[screen_nbr];
  ev.data.data32[2] = owner;
  ev.data.data32[3] = selection_data1;
  ev.data.data32[4] = selection_data2;

  return xcb_send_event(ewmh->connection, 0, ewmh->screens[screen_nbr]->root,
                        XCB_EVENT_MASK_STRUCTURE_NOTIFY,
                        (char *) &ev);
}

/* TODO: check both */
xcb_void_cookie_t
xcb_ewmh_set_wm_cm_owner(xcb_ewmh_connection_t *ewmh,
                         int screen_nbr,
                         xcb_window_t owner,
                         xcb_timestamp_t timestamp,
                         uint32_t selection_data1,
                         uint32_t selection_data2)
{
  xcb_set_selection_owner(ewmh->connection, owner,
                          ewmh->_NET_WM_CM_Sn[screen_nbr], 0);

  return set_wm_cm_owner_client_message(ewmh, screen_nbr, owner, timestamp,
                                        selection_data1, selection_data2);
}

xcb_void_cookie_t
xcb_ewmh_set_wm_cm_owner_checked(xcb_ewmh_connection_t *ewmh,
                                 int screen_nbr,
                                 xcb_window_t owner,
                                 xcb_timestamp_t timestamp,
                                 uint32_t selection_data1,
                                 uint32_t selection_data2)
{
  xcb_set_selection_owner_checked(ewmh->connection, owner,
                                  ewmh->_NET_WM_CM_Sn[screen_nbr], 0);

  return set_wm_cm_owner_client_message(ewmh, screen_nbr, owner, timestamp,
                                        selection_data1, selection_data2);
}
