/* -------------------------------
 * vim:tabstop=4:shiftwidth=4
 * ewmh.c
 * Thu, 30 Mar 2006 23:15:37 +0700
 * -------------------------------
 * EWMH/MWM support
 * ------------------------------- */
#include <X11/Xatom.h>
#include <X11/Xmd.h>

#include "common.h"
#include "debug.h"
#include "wmh.h"
#include "xutils.h"

/* Structure for Motif WM hints */
typedef struct {
  unsigned long flags;
  unsigned long functions;
  unsigned long decorations;
  long inputMode;
  unsigned long status;
} PropMotifWmHints;
/* Bit flags for fields of MWM hints data structure */
#define MWM_HINTS_FUNCTIONS (1L << 0)
#define MWM_HINTS_DECORATIONS (1L << 1)
#define MWM_HINTS_INPUT_MODE (1L << 2)
#define MWM_HINTS_STATUS (1L << 3)
/* Number of CARD32 entries in MWM hints data structure */
#define PROP_MOTIF_WM_HINTS_ELEMENTS 5

/* Check if WM that supports EWMH hints is present on given display */
int ewmh_wm_present(Display *dpy)
{
  Window *check_win, *check_win_self_ref;
  unsigned long cw_len = 0, cwsr_len = 0;
  int rc = False;
  /* see _NET_SUPPORTING_WM_CHECK in the WM spec. */
  rc = x11_get_root_winlist_prop(dpy,
      XInternAtom(dpy, _NET_SUPPORTING_WM_CHECK, False),
      (unsigned char **)&check_win, &cw_len);
  if (x11_ok() && rc && cw_len == 1) {
    LOG_TRACE(("_NET_SUPPORTING_WM_CHECK (root) = 0x%lx\n", check_win[0]));
    x11_get_window_prop32(dpy, check_win[0],
        XInternAtom(dpy, _NET_SUPPORTING_WM_CHECK, False), XA_WINDOW,
        (unsigned char **)&check_win_self_ref, &cwsr_len);
    rc = (x11_ok() && rc && cwsr_len == 1
        && check_win[0] == check_win_self_ref[0]);
    LOG_TRACE(
        ("_NET_SUPPORTING_WM_CHECK (self reference) = 0x%lx\n", check_win[0]));
  }
  if (cw_len != 0) XFree(check_win);
  if (cwsr_len != 0) XFree(check_win_self_ref);
  LOG_TRACE(("EWMH WM %sdetected\n", rc ? "" : "not "));
  return rc;
}

/* Add EWMH window state for the given window */
int ewmh_add_window_state(Display *dpy, Window wnd, char *state)
{
  Atom prop;
  Atom atom;
  XWindowAttributes xwa;
  int rc;
  prop = XInternAtom(dpy, "_NET_WM_STATE", False);
  atom = XInternAtom(dpy, state, False);
  LOG_TRACE(("adding state %s to window 0x%lx\n", state, atom));
  /* Ping the window and get its state */
  rc = XGetWindowAttributes(dpy, wnd, &xwa);
  if (!x11_ok() || !rc) return FAILURE;

  if (xwa.map_state != IsUnmapped && ewmh_wm_present(dpy)) {
    /* For mapped windows, ask WM (if it is here) to add the window state
     */
    rc = x11_send_client_msg32(
        dpy, xwa.root, wnd, prop, _NET_WM_STATE_ADD, atom, 0, 0, 0);
  } else {
    /* Else, alter the window state atom value ourselves */
    rc = XChangeProperty(
        dpy, wnd, prop, XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom, 1);
    rc = x11_ok() && rc;
  }
  return rc;
}

/* Remove a previously-added EWMH window state */
int ewmh_remove_window_state(Display *dpy, Window wnd, char *state)
{
  Atom prop;
  Atom atom;
  XWindowAttributes xwa;
  int rc;
  prop = XInternAtom(dpy, "_NET_WM_STATE", False);
  atom = XInternAtom(dpy, state, False);
  LOG_TRACE(("removing state %s from window 0x%lx\n", state, atom));
  rc = XGetWindowAttributes(dpy, wnd, &xwa);
  if (!x11_ok() || !rc) return FAILURE;

  if (xwa.map_state != IsUnmapped && ewmh_wm_present(dpy)) {
    return x11_send_client_msg32(
        dpy, xwa.root, wnd, prop, _NET_WM_STATE_REMOVE, atom, 0, 0, 0);
  }
  /* No WM to ask; rewrite the property ourselves, dropping the atom. */
  {
    Atom *states = NULL, *kept = NULL;
    unsigned long n = 0, kept_n = 0, i;
    if (!x11_get_window_prop32(
            dpy, wnd, prop, XA_ATOM, (unsigned char **)&states, &n))
      return SUCCESS; /* property absent; nothing to remove */
    if (n > 0) {
      kept = malloc(n * sizeof(Atom));
      if (kept == NULL) {
        XFree(states);
        return FAILURE;
      }
      for (i = 0; i < n; i++)
        if (states[i] != atom) kept[kept_n++] = states[i];
    }
    XChangeProperty(dpy, wnd, prop, XA_ATOM, 32, PropModeReplace,
        (unsigned char *)kept, (int)kept_n);
    free(kept);
    XFree(states);
    return x11_ok();
  }
}

/* Add EWMH window type for the given window */
int ewmh_add_window_type(Display *dpy, Window wnd, char *type)
{
  Atom prop;
  Atom atom;
  prop = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
  atom = XInternAtom(dpy, type, False);
  LOG_TRACE(("adding type %s to window 0x%lx\n", type, atom));
  /* Update property value (append) */
  XChangeProperty(
      dpy, wnd, prop, XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom, 1);
  return x11_ok();
}

/* Set data for _NET_WM_STRUT{,_PARTIAL} hints */
int ewmh_set_window_strut(Display *dpy, Window wnd, wm_strut_t wm_strut)
{
  Atom prop_strut;
  Atom prop_strut_partial;
  prop_strut = XInternAtom(dpy, _NET_WM_STRUT, False);
  prop_strut_partial = XInternAtom(dpy, _NET_WM_STRUT_PARTIAL, False);
  XChangeProperty(dpy, wnd, prop_strut, XA_CARDINAL, 32, PropModeReplace,
      (unsigned char *)wm_strut, _NET_WM_STRUT_SZ);
  XChangeProperty(dpy, wnd, prop_strut_partial, XA_CARDINAL, 32,
      PropModeReplace, (unsigned char *)wm_strut, _NET_WM_STRUT_PARTIAL_SZ);
  return x11_ok();
}

/* Set CARD32 value of EWMH atom for a given window */
int ewmh_set_window_atom32(
    Display *dpy, Window wnd, char *prop_name, CARD32 value)
{
  Atom prop;
  XWindowAttributes xwa;
  int rc;
  prop = XInternAtom(dpy, prop_name, False);
  LOG_TRACE(("0x%lx: setting atom %s to 0x%x\n", wnd, prop_name, value));
  /* Ping the window and get its state */
  rc = XGetWindowAttributes(dpy, wnd, &xwa);
  if (!x11_ok() || !rc) return FAILURE;
  if (xwa.map_state != IsUnmapped && ewmh_wm_present(dpy)) {
    /* For mapped windows, ask WM (if it is here) to add the window state
     */
    return x11_send_client_msg32(dpy, DefaultRootWindow(dpy), wnd, prop, value,
        2 /* source indication */, 0, 0, 0);
  } else {
    Atom atom = value;
    /* Else, alter the window state atom value ourselves */
    XChangeProperty(
        dpy, wnd, prop, XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom, 1);
    return x11_ok();
  }
}

/* Set MWM hints */
int mwm_set_hints(Display *dpy, Window wnd, unsigned long decorations,
    unsigned long functions)
{
  PropMotifWmHints *prop = NULL, new_prop;
  int act_fmt;
  unsigned long nitems, bytes_after;
  static Atom atom = None, act_type;
  /* Check if WM supports Motif WM hints */
  if (atom == None) atom = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
  if (atom == None) return FAILURE;
  /* Get current hints */
  XGetWindowProperty(dpy, wnd, atom, 0, 5, False, atom, &act_type, &act_fmt,
      &nitems, &bytes_after, (unsigned char **)&prop);
  if ((act_type == None && act_fmt == 0 && bytes_after == 0) || nitems == 0) {
    /* Hints are either not set or have some other type.
     * Reset all values. */
    memset(&new_prop, 0, sizeof(PropMotifWmHints));
    if (prop != NULL) XFree(prop);
    prop = &new_prop;
  } else if (prop != NULL) {
    /* Copy value */
    new_prop = *prop;
    XFree(prop);
    prop = &new_prop;
  } else {
    /* Something is broken */
    x11_ok(); /* Reset x11 error status */
    return FAILURE;
  }
  /* Update value */
  prop->flags |= MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS;
  prop->decorations = decorations;
  prop->functions = functions;
  XChangeProperty(dpy, wnd, atom, atom, 32, PropModeReplace,
      (unsigned char *)prop, PROP_MOTIF_WM_HINTS_ELEMENTS);
  return x11_ok();
}

#ifdef DEBUG
/* Dumps EWMH atoms supported by WM */
int ewmh_list_supported_atoms(Display *dpy)
{
  Atom *atom_list;
  unsigned long atom_list_len, i;
  char *atom_name;
  if (ewmh_wm_present(dpy)) {
    if (x11_get_window_prop32(dpy, DefaultRootWindow(dpy),
            XInternAtom(dpy, _NET_SUPPORTED, False), XA_ATOM,
            (unsigned char **)&atom_list, &atom_list_len))
      if (atom_list_len) {
        for (i = 0; i < atom_list_len; i++) {
          atom_name = XGetAtomName(dpy, atom_list[i]);
          if (atom_name != NULL) {
            LOG_TRACE(("_NET_SUPPORTED[%ld]: %s (0x%lx)\n", i, atom_name,
                atom_list[i]));
          } else
            LOG_TRACE((
                "_NET_SUPPORTED[%ld]: bogus value (0x%lx)\n", i, atom_list[i]));
          XFree(atom_name);
          x11_ok();
        }
        free(atom_list);
        return SUCCESS;
      }
  }
  return FAILURE;
}

/* List EWMH states that are set for the given window */
int ewmh_dump_window_states(Display *dpy, Window wnd)
{
  Atom prop, *data;
  unsigned long prop_len;
  int j;
  char *tmp;
  /* Check if WM supports _NET_WM_STATE */
  prop = XInternAtom(tray_data.dpy, "_NET_WM_STATE", True);
  if (prop == None) return FAILURE;
  /* Retrive the list of states */
  if (x11_get_window_prop32(
          dpy, wnd, prop, XA_ATOM, (unsigned char **)&data, &prop_len)) {
    for (j = 0; j < prop_len; j++) {
      tmp = XGetAtomName(tray_data.dpy, data[j]);
      if (x11_ok() && tmp != NULL) {
        LOG_TRACE(("0x%lx:_NET_WM_STATE[%d] = %s\n", wnd, j, tmp));
        XFree(tmp);
      }
    }
    return SUCCESS;
  }
  return FAILURE;
}
#endif
