// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
// Window.cc for Blackbox - an X11 Window manager
// Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh@debian.org>
// Copyright (c) 1997 - 2000, 2002 - 2005
//         Bradley T Hughes <bhughes at trolltech.com>
//
// 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 OR COPYRIGHT HOLDERS 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.

#include "gettext.h"
// make sure we get bt::textPropertyToString()
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "Window.hh"
#include "Screen.hh"
#include "WindowGroup.hh"
#include "Windowmenu.hh"
#include "Workspace.hh"
#include "blackbox.hh"

#include <Pen.hh>
#include <PixmapCache.hh>
#include <Unicode.hh>

#include <X11/Xatom.h>
#ifdef SHAPE
#  include <X11/extensions/shape.h>
#endif

#include <assert.h>

/*
  sometimes C++ is a pain in the ass... it gives us stuff like the
  default copy constructor and assignment operator (which does member
  my member copy/assignment), but what we don't get is a default
  comparison operator... how fucked up is that?

  the language is designed to handle this:

  struct foo { int a; int b };
  foo a = { 0 , 0 }, b = a;
  foo c;
  c = a;

  BUT, BUT, BUT, forget about doing this:

  a = findFoo();
  if (c == a)
     return; // nothing to do
  c = a;

  ARGH!@#)(!*@#)(@#*$(!@#
*/
bool operator==(const WMNormalHints &x, const WMNormalHints &y);


// Event mask used for managed client windows.
const unsigned long client_window_event_mask =
  (PropertyChangeMask | StructureNotifyMask);


/*
 * Returns the appropriate WindowType based on the _NET_WM_WINDOW_TYPE
 */
static WindowType window_type_from_atom(const bt::EWMH &ewmh, Atom atom) {
  if (atom == ewmh.wmWindowTypeDialog())
    return WindowTypeDialog;
  if (atom == ewmh.wmWindowTypeDesktop())
    return WindowTypeDesktop;
  if (atom == ewmh.wmWindowTypeDock())
    return WindowTypeDock;
  if (atom == ewmh.wmWindowTypeMenu())
    return WindowTypeMenu;
  if (atom == ewmh.wmWindowTypeNotification())
    return WindowTypeNotification;
  if (atom == ewmh.wmWindowTypeSplash())
    return WindowTypeSplash;
  if (atom == ewmh.wmWindowTypeToolbar())
    return WindowTypeToolbar;
  if (atom == ewmh.wmWindowTypeUtility())
    return WindowTypeUtility;
  if (atom == ewmh.wmWindowTypeCombo())
    return WindowTypeCombo;
  if (atom == ewmh.wmWindowTypeDnd())
    return WindowTypeDnd;
  if (atom == ewmh.wmWindowTypeDropdownMenu())
    return WindowTypeDropdownMenu;
  if (atom == ewmh.wmWindowTypePopupMenu())
    return WindowTypePopupMenu;
  if (atom == ewmh.wmWindowTypeTooltip())
    return WindowTypeTooltip;
  return WindowTypeNormal;
}


/*
 * Determine the appropriate decorations and functions based on the
 * given properties and hints.
 */
static void update_decorations(WindowDecorationFlags &decorations,
                               WindowFunctionFlags &functions,
                               bool transient,
                               const EWMH &ewmh,
                               const MotifHints &motifhints,
                               const WMNormalHints &wmnormal,
                               const WMProtocols &wmprotocols) {
  decorations = AllWindowDecorations;
  functions   = AllWindowFunctions;

  // transients should be kept on the same workspace are their parents
  if (transient)
    functions &= ~WindowFunctionChangeWorkspace;

  // modify the window decorations/functions based on window type
  switch (ewmh.window_type) {
  case WindowTypeDialog:
    decorations &= ~(WindowDecorationIconify |
                     WindowDecorationMaximize);
    functions   &= ~(WindowFunctionShade |
                     WindowFunctionIconify |
                     WindowFunctionMaximize |
                     WindowFunctionChangeLayer |
                     WindowFunctionFullScreen |
                     WindowFunctionStick);
    break;

  case WindowTypeDesktop:
  case WindowTypeDock:
  case WindowTypeSplash:
  case WindowTypeNotification:
  case WindowTypeCombo:
  case WindowTypeDnd:
  case WindowTypePopupMenu:
  case WindowTypeDropdownMenu:
  case WindowTypeTooltip:
    decorations = NoWindowDecorations;
    functions   = NoWindowFunctions;
    break;

  case WindowTypeToolbar:
  case WindowTypeMenu:
    decorations &= ~(WindowDecorationHandle |
                     WindowDecorationGrip |
                     WindowDecorationBorder |
                     WindowDecorationIconify |
                     WindowDecorationMaximize);
    functions   &= ~(WindowFunctionResize |
                     WindowFunctionShade |
                     WindowFunctionIconify |
                     WindowFunctionMaximize |
                     WindowFunctionFullScreen);
    break;

  case WindowTypeUtility:
    decorations &= ~(WindowDecorationIconify |
                     WindowDecorationMaximize);
    functions   &= ~(WindowFunctionShade |
                     WindowFunctionIconify |
                     WindowFunctionMaximize);
    break;

  default:
    break;
  }

  // mask away stuff turned off by Motif hints
  decorations &= motifhints.decorations;
  functions   &= motifhints.functions;

  // disable shade if we do not have a titlebar
  if (!(decorations & WindowDecorationTitlebar))
    functions &= ~WindowFunctionShade;
  
  // Remove decorations for windows in fullscreen mode
  if (ewmh.fullscreen) {
    decorations = NoWindowDecorations;
    functions &= ~(WindowFunctionMove |
                   WindowFunctionResize |
                   WindowFunctionShade);
  }

  // disable grips and maximize if we have a fixed size window
  if ((wmnormal.flags & (PMinSize|PMaxSize)) == (PMinSize|PMaxSize)
      && wmnormal.max_width <= wmnormal.min_width
      && wmnormal.max_height <= wmnormal.min_height) {
    decorations &= ~(WindowDecorationMaximize |
                     WindowDecorationGrip);
    functions   &= ~(WindowFunctionResize |
                     WindowFunctionMaximize);
  }

  // cannot close if client doesn't understand WM_DELETE_WINDOW
  if (!wmprotocols.wm_delete_window) {
    decorations &= ~WindowDecorationClose;
    functions   &= ~WindowFunctionClose;
  }
}


/*
 * Calculate the frame margin based on the given decorations and
 * style.
 */
static
bt::EWMH::Strut update_margin(WindowDecorationFlags decorations,
                              const WindowStyle &style) {
  bt::EWMH::Strut margin;

  const unsigned int bw = ((decorations & WindowDecorationBorder)
                           ? style.frame_border_width
                           : 0u);
  margin.top = margin.bottom = margin.left = margin.right = bw;

  if (decorations & WindowDecorationTitlebar)
    margin.top += style.title_height - bw;

  if (decorations & WindowDecorationHandle)
    margin.bottom += style.handle_height - bw;

  return margin;
}


/*
 * Add specified window to the appropriate window group, creating a
 * new group if necessary.
 */
static BWindowGroup *update_window_group(Window window_group,
                                         Blackbox *blackbox,
                                         BlackboxWindow *win) {
  BWindowGroup *group = win->findWindowGroup();
  if (!group) {
    new BWindowGroup(blackbox, window_group);
    group = win->findWindowGroup();
    assert(group != 0);
  }
  group->addWindow(win);
  return group;
}


/*
 * Calculate the size of the frame window and constrain it to the
 * size specified by the size hints of the client window.
 *
 * 'rect' refers to the geometry of the frame in pixels.
 */
enum Corner {
  TopLeft,
  TopRight,
  BottomLeft,
  BottomRight
};
static bt::Rect constrain(const bt::Rect &rect,
                          const bt::EWMH::Strut &margin,
                          const WMNormalHints &wmnormal,
                          Corner corner) {
  bt::Rect r;

  // 'rect' represents the requested frame size, we need to strip
  // 'margin' off and constrain the client size
  r.setCoords(rect.left() + static_cast<signed>(margin.left),
              rect.top() + static_cast<signed>(margin.top),
              rect.right() - static_cast<signed>(margin.right),
              rect.bottom() - static_cast<signed>(margin.bottom));

  unsigned int dw = r.width(), dh = r.height();

  const unsigned int base_width = (wmnormal.base_width
                                   ? wmnormal.base_width
                                   : wmnormal.min_width),
                    base_height = (wmnormal.base_height
                                   ? wmnormal.base_height
                                   : wmnormal.min_height);

  // fit to min/max size
  if (dw < wmnormal.min_width)
    dw = wmnormal.min_width;
  if (dh < wmnormal.min_height)
    dh = wmnormal.min_height;

  if (dw > wmnormal.max_width)
    dw = wmnormal.max_width;
  if (dh > wmnormal.max_height)
    dh = wmnormal.max_height;

  assert(dw >= base_width && dh >= base_height);

  // fit to size increments
  if (wmnormal.flags & PResizeInc) {
    dw = (((dw - base_width) / wmnormal.width_inc)
          * wmnormal.width_inc) + base_width;
    dh = (((dh - base_height) / wmnormal.height_inc)
          * wmnormal.height_inc) + base_height;
  }

  /*
   * honor aspect ratios (based on twm which is based on uwm)
   *
   * The math looks like this:
   *
   * minAspectX    dwidth     maxAspectX
   * ---------- <= ------- <= ----------
   * minAspectY    dheight    maxAspectY
   *
   * If that is multiplied out, then the width and height are
   * invalid in the following situations:
   *
   * minAspectX * dheight > minAspectY * dwidth
   * maxAspectX * dheight < maxAspectY * dwidth
   *
   */
  if (wmnormal.flags & PAspect) {
    unsigned int delta;
    const unsigned int min_asp_x = wmnormal.min_aspect_x,
                       min_asp_y = wmnormal.min_aspect_y,
                       max_asp_x = wmnormal.max_aspect_x,
                       max_asp_y = wmnormal.max_aspect_y,
                           w_inc = wmnormal.width_inc,
                           h_inc = wmnormal.height_inc;
    if (min_asp_x * dh > min_asp_y * dw) {
      delta = ((min_asp_x * dh / min_asp_y - dw) * w_inc) / w_inc;
      if (dw + delta <= wmnormal.max_width) {
        dw += delta;
      } else {
        delta = ((dh - (dw * min_asp_y) / min_asp_x) * h_inc) / h_inc;
        if (dh - delta >= wmnormal.min_height) dh -= delta;
      }
    }
    if (max_asp_x * dh < max_asp_y * dw) {
      delta = ((max_asp_y * dw / max_asp_x - dh) * h_inc) / h_inc;
      if (dh + delta <= wmnormal.max_height) {
        dh += delta;
      } else {
        delta = ((dw - (dh * max_asp_x) / max_asp_y) * w_inc) / w_inc;
        if (dw - delta >= wmnormal.min_width) dw -= delta;
      }
    }
  }

  r.setSize(dw, dh);

  // add 'margin' back onto 'r'
  r.setCoords(r.left() - margin.left, r.top() - margin.top,
              r.right() + margin.right, r.bottom() + margin.bottom);

  // move 'r' to the specified corner
  int dx = rect.right() - r.right();
  int dy = rect.bottom() - r.bottom();

  switch (corner) {
  case TopLeft:
    // nothing to do
    break;

  case TopRight:
    r.setPos(r.x() + dx, r.y());
    break;

  case BottomLeft:
    r.setPos(r.x(), r.y() + dy);
    break;

  case BottomRight:
    r.setPos(r.x() + dx, r.y() + dy);
    break;
  }

  return r;
}


/*
 * Positions 'rect' according to the client window geometry and window
 * gravity.
 */
static bt::Rect applyGravity(const bt::Rect &rect,
                             const bt::EWMH::Strut &margin,
                             int gravity) {
  bt::Rect r;

  // apply horizontal window gravity
  switch (gravity) {
  default:
  case NorthWestGravity:
  case SouthWestGravity:
  case WestGravity:
    r.setX(rect.x());
    break;

  case NorthGravity:
  case SouthGravity:
  case CenterGravity:
    r.setX(rect.x() - (margin.left + margin.right) / 2);
    break;

  case NorthEastGravity:
  case SouthEastGravity:
  case EastGravity:
    r.setX(rect.x() - (margin.left + margin.right) + 2);
    break;

  case ForgetGravity:
  case StaticGravity:
    r.setX(rect.x() - margin.left);
    break;
  }

  // apply vertical window gravity
  switch (gravity) {
  default:
  case NorthWestGravity:
  case NorthEastGravity:
  case NorthGravity:
    r.setY(rect.y());
    break;

  case CenterGravity:
  case EastGravity:
  case WestGravity:
    r.setY(rect.y() - ((margin.top + margin.bottom) / 2));
    break;

  case SouthWestGravity:
  case SouthEastGravity:
  case SouthGravity:
    r.setY(rect.y() - (margin.bottom + margin.top) + 2);
    break;

  case ForgetGravity:
  case StaticGravity:
    r.setY(rect.y() - margin.top);
    break;
  }

  r.setSize(rect.width() + margin.left + margin.right,
            rect.height() + margin.top + margin.bottom);
  return r;
}


/*
 * The reverse of the applyGravity function.
 *
 * Positions 'rect' according to the frame window geometry and window
 * gravity.
 */
static bt::Rect restoreGravity(const bt::Rect &rect,
                               const bt::EWMH::Strut &margin,
                               int gravity) {
  bt::Rect r;

  // restore horizontal window gravity
  switch (gravity) {
  default:
  case NorthWestGravity:
  case SouthWestGravity:
  case WestGravity:
    r.setX(rect.x());
    break;

  case NorthGravity:
  case SouthGravity:
  case CenterGravity:
    r.setX(rect.x() + (margin.left + margin.right) / 2);
    break;

  case NorthEastGravity:
  case SouthEastGravity:
  case EastGravity:
    r.setX(rect.x() + (margin.left + margin.right) - 2);
    break;

  case ForgetGravity:
  case StaticGravity:
    r.setX(rect.x() + margin.left);
    break;
  }

  // restore vertical window gravity
  switch (gravity) {
  default:
  case NorthWestGravity:
  case NorthEastGravity:
  case NorthGravity:
    r.setY(rect.y());
    break;

  case CenterGravity:
  case EastGravity:
  case WestGravity:
    r.setY(rect.y() + (margin.top + margin.bottom) / 2);
    break;

  case SouthWestGravity:
  case SouthEastGravity:
  case SouthGravity:
    r.setY(rect.y() + (margin.top + margin.bottom) - 2);
    break;

  case ForgetGravity:
  case StaticGravity:
    r.setY(rect.y() + margin.top);
    break;
  }

  r.setSize(rect.width() - margin.left - margin.right,
            rect.height() - margin.top - margin.bottom);
  return r;
}


static bt::ustring readWMName(Blackbox *blackbox, Window window) {
  bt::ustring name;

  if (!blackbox->ewmh().readWMName(window, name) || name.empty()) {
    XTextProperty text_prop;

    if (XGetWMName(blackbox->XDisplay(), window, &text_prop)) {
      name = bt::toUnicode(bt::textPropertyToString(blackbox->XDisplay(),
                                                    text_prop));
      XFree((char *) text_prop.value);
    }
  }

// TRANS The name to use for a window that has no name.
  if (name.empty())
    name = bt::toUnicode(gettext("Unnamed"));

  return name;
}


static bt::ustring readWMIconName(Blackbox *blackbox, Window window) {
  bt::ustring name;

  if (!blackbox->ewmh().readWMIconName(window, name) || name.empty()) {
    XTextProperty text_prop;
    if (XGetWMIconName(blackbox->XDisplay(), window, &text_prop)) {
      name = bt::toUnicode(bt::textPropertyToString(blackbox->XDisplay(),
                                                    text_prop));
      XFree((char *) text_prop.value);
    }
  }

  if (name.empty())
    return bt::ustring();

  return name;
}


static EWMH readEWMH(const bt::EWMH &bewmh,
                     Window window,
                     int currentWorkspace) {
  EWMH ewmh;
  ewmh.window_type  = WindowTypeNormal;
  ewmh.workspace    = 0; // initialized properly below
  ewmh.modal        = false;
  ewmh.maxv         = false;
  ewmh.maxh         = false;
  ewmh.shaded       = false;
  ewmh.skip_taskbar = false;
  ewmh.skip_pager   = false;
  ewmh.hidden       = false;
  ewmh.fullscreen   = false;
  ewmh.above        = false;
  ewmh.below        = false;
  ewmh.sticky       = false;
  ewmh.urgent       = false;

  // note: wm_name and wm_icon_name are read separately

  bool ret;

  bt::EWMH::AtomList atoms;
  ret = bewmh.readWMWindowType(window, atoms);
  if (ret) {
    bt::EWMH::AtomList::iterator it = atoms.begin(), end = atoms.end();
    for (; it != end; ++it) {
      if (bewmh.isSupportedWMWindowType(*it)) {
        ewmh.window_type = ::window_type_from_atom(bewmh, *it);
        break;
      }
    }
  }

  atoms.clear();
  ret = bewmh.readWMState(window, atoms);
  if (ret) {
    bt::EWMH::AtomList::iterator it = atoms.begin(), end = atoms.end();
    for (; it != end; ++it) {
      Atom state = *it;
      if (state == bewmh.wmStateModal()) {
        ewmh.modal = true;
      } else if (state == bewmh.wmStateMaximizedVert()) {
        ewmh.maxv = true;
      } else if (state == bewmh.wmStateMaximizedHorz()) {
        ewmh.maxh = true;
      } else if (state == bewmh.wmStateShaded()) {
        ewmh.shaded = true;
      } else if (state == bewmh.wmStateSkipTaskbar()) {
        ewmh.skip_taskbar = true;
      } else if (state == bewmh.wmStateSkipPager()) {
        ewmh.skip_pager = true;
      } else if (state == bewmh.wmStateHidden()) {
        /*
          ignore _NET_WM_STATE_HIDDEN if present, the wm sets this
          state, not the application
        */
      } else if (state == bewmh.wmStateFullscreen()) {
        ewmh.fullscreen = true;
      } else if (state == bewmh.wmStateAbove()) {
        ewmh.above = true;
      } else if (state == bewmh.wmStateBelow()) {
        ewmh.below = true;
      } else if (state == bewmh.wmStateSticky()) {
        ewmh.sticky = true;
      } else if (state == bewmh.wmStateDemandsAttention()) {
        ewmh.urgent = true;
      } else if (state == bewmh.wmStateFocused()) {
        /*
         * ignore _NET_WM_STATE_FOCUSED if present, the wm sets
         * this state, not the application
         */
      }
    }
  }

  switch (ewmh.window_type) {
  case WindowTypeDesktop:
  case WindowTypeDock:
    // these types should occupy all workspaces by default
    ewmh.workspace = bt::BSENTINEL;
    break;

  default:
    if (!bewmh.readWMDesktop(window, ewmh.workspace))
      ewmh.workspace = currentWorkspace;
    break;
  } //switch

  return ewmh;
}


/*
 * Returns the MotifWM hints for the specified window.
 */
static MotifHints readMotifWMHints(Blackbox *blackbox, Window window) {
  MotifHints motif;
  motif.decorations = AllWindowDecorations;
  motif.functions   = AllWindowFunctions;

  /*
    this structure only contains 3 elements, even though the Motif 2.0
    structure contains 5, because we only use the first 3
  */
  struct PropMotifhints {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
  };
  static const unsigned int PROP_MWM_HINTS_ELEMENTS = 3u;
  enum { // MWM flags
    MWM_HINTS_FUNCTIONS   = 1<<0,
    MWM_HINTS_DECORATIONS = 1<<1
  };
  enum { // MWM functions
    MWM_FUNC_ALL      = 1<<0,
    MWM_FUNC_RESIZE   = 1<<1,
    MWM_FUNC_MOVE     = 1<<2,
    MWM_FUNC_MINIMIZE = 1<<3,
    MWM_FUNC_MAXIMIZE = 1<<4,
    MWM_FUNC_CLOSE    = 1<<5
  };
  enum { // MWM decorations
    MWM_DECOR_ALL      = 1<<0,
    MWM_DECOR_BORDER   = 1<<1,
    MWM_DECOR_RESIZEH  = 1<<2,
    MWM_DECOR_TITLE    = 1<<3,
    MWM_DECOR_MENU     = 1<<4,
    MWM_DECOR_MINIMIZE = 1<<5,
    MWM_DECOR_MAXIMIZE = 1<<6
  };

  Atom atom_return;
  PropMotifhints *prop = 0;
  int format;
  unsigned long num, len;
  int ret = XGetWindowProperty(blackbox->XDisplay(), window,
                               blackbox->motifWmHintsAtom(), 0,
                               PROP_MWM_HINTS_ELEMENTS, False,
                               blackbox->motifWmHintsAtom(), &atom_return,
                               &format, &num, &len,
                               (unsigned char **) &prop);

  if (ret != Success || !prop || num != PROP_MWM_HINTS_ELEMENTS) {
    if (prop) XFree(prop);
    return motif;
  }

  if (prop->flags & MWM_HINTS_FUNCTIONS) {
    if (prop->functions & MWM_FUNC_ALL) {
      motif.functions = AllWindowFunctions;
    } else {
      // default to the functions that cannot be set through
      // _MOTIF_WM_HINTS
      motif.functions = (WindowFunctionShade
                         | WindowFunctionChangeWorkspace
                         | WindowFunctionChangeLayer
                         | WindowFunctionFullScreen);

      if (prop->functions & MWM_FUNC_RESIZE)
        motif.functions |= WindowFunctionResize;
      if (prop->functions & MWM_FUNC_MOVE)
        motif.functions |= WindowFunctionMove;
      if (prop->functions & MWM_FUNC_MINIMIZE)
        motif.functions |= WindowFunctionIconify;
      if (prop->functions & MWM_FUNC_MAXIMIZE)
        motif.functions |= WindowFunctionMaximize;
      if (prop->functions & MWM_FUNC_CLOSE)
        motif.functions |= WindowFunctionClose;
    }
  }

  if (prop->flags & MWM_HINTS_DECORATIONS) {
    if (prop->decorations & MWM_DECOR_ALL) {
      motif.decorations = AllWindowDecorations;
    } else {
      motif.decorations = NoWindowDecorations;

      if (prop->decorations & MWM_DECOR_BORDER)
        motif.decorations |= WindowDecorationBorder;
      if (prop->decorations & MWM_DECOR_RESIZEH)
        motif.decorations |= WindowDecorationHandle;
      if (prop->decorations & MWM_DECOR_TITLE)
        motif.decorations |= WindowDecorationTitlebar;
      if (prop->decorations & MWM_DECOR_MINIMIZE)
        motif.decorations |= WindowDecorationIconify;
      if (prop->decorations & MWM_DECOR_MAXIMIZE)
        motif.decorations |= WindowDecorationMaximize;
    }
  }

  if (motif.decorations & WindowDecorationHandle) {
    if (motif.functions & WindowFunctionResize)
      motif.decorations |= WindowDecorationGrip;
    else
      motif.decorations &= ~WindowDecorationGrip;
  }

  if (motif.decorations & WindowDecorationTitlebar) {
    if (motif.functions & WindowFunctionClose)
      motif.decorations |= WindowDecorationClose;
    else
      motif.decorations &= ~WindowDecorationClose;
  }

  XFree(prop);

  return motif;
}


/*
 * Returns the value of the WM_HINTS property.  If the property is not
 * set, a set of default values is returned instead.
 */
static WMHints readWMHints(Blackbox *blackbox, Window window) {
  WMHints wmh;
  wmh.accept_focus = false;
  wmh.window_group = None;
  wmh.initial_state = NormalState;
  wmh.urgency = false;

  XWMHints *wmhint = XGetWMHints(blackbox->XDisplay(), window);
  if (!wmhint) return wmh;

  if (wmhint->flags & InputHint)
    wmh.accept_focus = (wmhint->input == True);
  if (wmhint->flags & StateHint)
    wmh.initial_state = wmhint->initial_state;
  if (wmhint->flags & WindowGroupHint)
    wmh.window_group = wmhint->window_group;
  if (wmhint->flags & XUrgencyHint)
    wmh.urgency = true;

  XFree(wmhint);

  return wmh;
}


/*
 * Returns the value of the WM_NORMAL_HINTS property.  If the property
 * is not set, a set of default values is returned instead.
 */
static WMNormalHints readWMNormalHints(Blackbox *blackbox,
                                       Window window,
                                       const bt::ScreenInfo &screenInfo) {
  WMNormalHints wmnormal;
  wmnormal.flags = 0;
  wmnormal.min_width    = wmnormal.min_height   = 1u;
  wmnormal.width_inc    = wmnormal.height_inc   = 1u;
  wmnormal.min_aspect_x = wmnormal.min_aspect_y = 1u;
  wmnormal.max_aspect_x = wmnormal.max_aspect_y = 1u;
  wmnormal.base_width   = wmnormal.base_height  = 0u;
  wmnormal.win_gravity  = NorthWestGravity;

  /*
    use the full screen, not the strut modified size. otherwise when
    the availableArea changes max_width/height will be incorrect and
    lead to odd rendering bugs.
  */
  const bt::Rect &rect = screenInfo.rect();
  wmnormal.max_width = rect.width();
  wmnormal.max_height = rect.height();

  XSizeHints sizehint;
  long unused;
  if (! XGetWMNormalHints(blackbox->XDisplay(), window, &sizehint, &unused))
    return wmnormal;

  wmnormal.flags = sizehint.flags;

  if (sizehint.flags & PMinSize) {
    if (sizehint.min_width > 0)
      wmnormal.min_width  = sizehint.min_width;
    if (sizehint.min_height > 0)
      wmnormal.min_height = sizehint.min_height;

    /*
      if the minimum size is bigger then the screen, adjust the
      maximum size
    */
    if (wmnormal.min_width > wmnormal.max_width)
      wmnormal.max_width = wmnormal.min_width;
    if (wmnormal.min_height > wmnormal.max_height)
      wmnormal.max_height = wmnormal.min_height;
  }

  if (sizehint.flags & PMaxSize) {
    if (sizehint.max_width >= static_cast<signed>(wmnormal.min_width))
      wmnormal.max_width  = sizehint.max_width;
    else
      wmnormal.max_width  = wmnormal.min_width;

    if (sizehint.max_height >= static_cast<signed>(wmnormal.min_height))
      wmnormal.max_height = sizehint.max_height;
    else
      wmnormal.max_height = wmnormal.min_height;
  }

  if (sizehint.flags & PResizeInc) {
    wmnormal.width_inc  = sizehint.width_inc;
    wmnormal.height_inc = sizehint.height_inc;
  }

  if (sizehint.flags & PAspect) {
    wmnormal.min_aspect_x = sizehint.min_aspect.x;
    wmnormal.min_aspect_y = sizehint.min_aspect.y;
    wmnormal.max_aspect_x = sizehint.max_aspect.x;
    wmnormal.max_aspect_y = sizehint.max_aspect.y;
  }

  if (sizehint.flags & PBaseSize) {
    if (sizehint.base_width <= static_cast<signed>(wmnormal.min_width))
      wmnormal.base_width  = sizehint.base_width;
    if (sizehint.base_height <= static_cast<signed>(wmnormal.min_height))
      wmnormal.base_height = sizehint.base_height;
  }

  if (sizehint.flags & PWinGravity)
    wmnormal.win_gravity = sizehint.win_gravity;

  return wmnormal;
}


/*
 * Retrieve which Window Manager Protocols are supported by the client
 * window.
 */
static WMProtocols readWMProtocols(Blackbox *blackbox,
                                   Window window) {
  WMProtocols protocols;
  protocols.wm_delete_window = false;
  protocols.wm_take_focus    = false;

  Atom *proto;
  int num_return = 0;

  if (XGetWMProtocols(blackbox->XDisplay(), window,
                      &proto, &num_return)) {
    for (int i = 0; i < num_return; ++i) {
      if (proto[i] == blackbox->wmDeleteWindowAtom()) {
        protocols.wm_delete_window = true;
      } else if (proto[i] == blackbox->wmTakeFocusAtom()) {
        protocols.wm_take_focus = true;
      }
    }
    XFree(proto);
  }

  return protocols;
}


/*
 * Reads the value of the WM_TRANSIENT_FOR property and returns a
 * pointer to the transient parent for this window.  If the
 * WM_TRANSIENT_FOR is missing or invalid, this function returns 0.
 *
 * 'client.wmhints' should be properly updated before calling this
 * function.
 *
 * Note: a return value of ~0ul signifies a window that should be
 * transient but has no discernible parent.
 */
static Window readTransientInfo(Blackbox *blackbox,
                                Window window,
                                const bt::ScreenInfo &screenInfo,
                                const WMHints &wmhints) {
  Window trans_for = None;

  if (!XGetTransientForHint(blackbox->XDisplay(), window, &trans_for)) {
    // WM_TRANSIENT_FOR hint not set
    return 0;
  }

  if (trans_for == window) {
    // wierd client... treat this window as a normal window
    return 0;
  }

  if (trans_for == None || trans_for == screenInfo.rootWindow()) {
    /*
      this is a violation of the ICCCM, yet the EWMH allows this as a
      way to signify a group transient.
    */
    trans_for = wmhints.window_group;
  }

  return trans_for;
}

static bool readState(unsigned long &current_state,
                      Blackbox *blackbox,
                      Window window) {
  current_state = NormalState;

  Atom atom_return;
  bool ret = false;
  int foo;
  unsigned long *state, ulfoo, nitems;

  if ((XGetWindowProperty(blackbox->XDisplay(), window,
                          blackbox->wmStateAtom(),
                          0l, 2l, False, blackbox->wmStateAtom(),
                          &atom_return, &foo, &nitems, &ulfoo,
                          (unsigned char **) &state) != Success) ||
      (! state)) {
    return false;
  }

  if (nitems >= 1) {
    current_state = static_cast<unsigned long>(state[0]);
    ret = true;
  }

  XFree((void *) state);

  return ret;
}


static void clearState(Blackbox *blackbox, Window window) {
  XDeleteProperty(blackbox->XDisplay(), window, blackbox->wmStateAtom());

  const bt::EWMH& ewmh = blackbox->ewmh();
  ewmh.removeProperty(window, ewmh.wmDesktop());
  ewmh.removeProperty(window, ewmh.wmState());
  ewmh.removeProperty(window, ewmh.wmAllowedActions());
  ewmh.removeProperty(window, ewmh.wmVisibleName());
  ewmh.removeProperty(window, ewmh.wmVisibleIconName());
}


/*
 * Initializes the class with default values/the window's set initial values.
 */
BlackboxWindow::BlackboxWindow(Blackbox *b, Window w, BScreen *s) {
  // fprintf(stderr, "BlackboxWindow size: %d bytes\n",
  //         sizeof(BlackboxWindow));

#ifdef    DEBUG
  fprintf(stderr, gettext("BlackboxWindow::BlackboxWindow(): creating 0x%lx\n"), w);
#endif // DEBUG

  /*
    set timer to zero... it is initialized properly later, so we check
    if timer is zero in the destructor, and assume that the window is not
    fully constructed if timer is zero...
  */
  timer = (bt::Timer*) 0;
  blackbox = b;
  client.window = w;
  _screen = s;
  lastButtonPressTime = 0;

  /*
    the server needs to be grabbed here to prevent client's from sending
    events while we are in the process of managing their window.
    We hold the grab until after we are done moving the window around.
  */

  blackbox->XGrabServer();

  // fetch client size and placement
  XWindowAttributes wattrib;
  if (! XGetWindowAttributes(blackbox->XDisplay(),
                             client.window, &wattrib) ||
      ! wattrib.screen || wattrib.override_redirect) {
#ifdef    DEBUG
    fprintf(stderr,
            gettext("BlackboxWindow::BlackboxWindow(): XGetWindowAttributes failed\n"));
#endif // DEBUG

    blackbox->XUngrabServer();
    delete this;
    return;
  }

  if (wattrib.depth == 32) {
    frame.depth = 32;
    frame.visual = wattrib.visual;
    frame.colormap = XCreateColormap(blackbox->XDisplay(),
                                     _screen->screenInfo().rootWindow(),
                                     frame.visual, AllocNone);
  }
  else {
    frame.depth = _screen->screenInfo().depth();
    frame.colormap = _screen->screenInfo().colormap();
    frame.visual = _screen->screenInfo().visual();
  }

  // set the eventmask early in the game so that we make sure we get
  // all the events we are interested in
  XSetWindowAttributes attrib_set;
  attrib_set.event_mask = ::client_window_event_mask;
  attrib_set.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask |
                                     ButtonMotionMask;
  XChangeWindowAttributes(blackbox->XDisplay(), client.window,
                          CWEventMask|CWDontPropagate, &attrib_set);

  client.colormap = wattrib.colormap;
  window_number = bt::BSENTINEL;
  client.strut = 0;
  /*
    set the initial size and location of client window (relative to the
    _root window_). This position is the reference point used with the
    window's gravity to find the window's initial position.
  */
  client.rect.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
  client.premax = client.rect;
  client.old_bw = wattrib.border_width;
  client.current_state = NormalState;

  frame.window = frame.plate = frame.title = frame.handle = None;
  frame.close_button = frame.iconify_button = frame.maximize_button = None;
  frame.right_grip = frame.left_grip = None;
  frame.utitle = frame.ftitle = frame.uhandle = frame.fhandle = None;
  frame.ulabel = frame.flabel = frame.ubutton = frame.fbutton = None;
  frame.pbutton = frame.ugrip = frame.fgrip = None;

  timer = new bt::Timer(blackbox, this);
  timer->setTimeout(blackbox->resource().autoRaiseDelay());

  client.title = ::readWMName(blackbox, client.window);
  client.icon_title = ::readWMIconName(blackbox, client.window);

  // get size, aspect, minimum/maximum size, ewmh and other hints set
  // by the client
  client.ewmh = ::readEWMH(blackbox->ewmh(), client.window,
                           _screen->currentWorkspace());
  client.motif = ::readMotifWMHints(blackbox, client.window);
  client.wmhints = ::readWMHints(blackbox, client.window);
  client.wmnormal = ::readWMNormalHints(blackbox, client.window,
                                        _screen->screenInfo());
  client.wmprotocols = ::readWMProtocols(blackbox, client.window);
  client.transient_for = ::readTransientInfo(blackbox, client.window,
                                             _screen->screenInfo(),
                                             client.wmhints);

  if (client.wmhints.window_group != None)
    (void) ::update_window_group(client.wmhints.window_group, blackbox, this);

  if (isTransient()) {
    // add ourselves to our transient_for
    BlackboxWindow *win = findTransientFor();
    if (win) {
      win->addTransient(this);
      client.ewmh.workspace = win->workspace();
      setLayer(win->layer());
    } else if (isGroupTransient()) {
      BWindowGroup *group = findWindowGroup();
      if (group)
        group->addTransient(this);
    } else {
      // broken client
      client.transient_for = 0;
    }
  }

  client.state.visible = false;
  client.state.iconic = false;
  client.state.moving = false;
  client.state.resizing = false;
  client.state.focused = false;

  switch (windowType()) {
  case WindowTypeDesktop:
    setLayer(StackingList::LayerDesktop);
    break;

  case WindowTypeTooltip:
  case WindowTypeDnd:
  case WindowTypeDropdownMenu:
  case WindowTypePopupMenu:
  case WindowTypeCombo:
    setLayer(StackingList::LayerAbove);
    break;

  case WindowTypeDock:
    setLayer(StackingList::LayerAbove);
    /* fall thru */

  default:
    if (client.ewmh.above)
      setLayer(StackingList::LayerAbove);
    else if (client.ewmh.below)
      setLayer(StackingList::LayerBelow);
    break;
  } // switch

  ::update_decorations(client.decorations,
                       client.functions,
                       isTransient(),
                       client.ewmh,
                       client.motif,
                       client.wmnormal,
                       client.wmprotocols);

  // sanity checks
  if (client.wmhints.initial_state == IconicState
      && !hasWindowFunction(WindowFunctionIconify))
    client.wmhints.initial_state = NormalState;
  if (isMaximized() && !hasWindowFunction(WindowFunctionMaximize))
    client.ewmh.maxv = client.ewmh.maxh = false;
  if (isFullScreen() && !hasWindowFunction(WindowFunctionFullScreen))
    client.ewmh.fullscreen = false;

  bt::EWMH::StrutPartial partial;
  if (blackbox->ewmh().readWMStrutPartial(client.window, &partial)) {
    client.strut = new bt::EWMH::Strut;
    client.strut->left = partial.left;
    client.strut->right = partial.right;
    client.strut->top = partial.top;
    client.strut->bottom = partial.bottom;
    _screen->addStrut(client.strut);
  } else {
    bt::EWMH::Strut strut;
    if (blackbox->ewmh().readWMStrut(client.window, &strut)) {
      client.strut = new bt::EWMH::Strut;
      *client.strut = strut;
      _screen->addStrut(client.strut);
    }
  }

  client.have_user_time =
    blackbox->ewmh().readWMUserTime(client.window, client.user_time);

  blackbox->ewmh().readStartupID(client.window, client.startup_id);

  /*
    if we just managed the group leader for an existing group, move
    all group transients to this window
  */
  {
    BWindowGroup *group = blackbox->findWindowGroup(client.window);
    if (group) {
      BlackboxWindowList transientList = group->transients();
      BlackboxWindowList::const_iterator it = transientList.begin();
      const BlackboxWindowList::const_iterator end = transientList.end();
      for (; it != end; ++it) {
        BlackboxWindow * const w1 = *it;
        if (w1->client.transient_for != client.window)
          continue;
        group->removeTransient(w1);
        addTransient(w1);
        w1->changeWorkspace(workspace());
        w1->changeLayer(layer());
      }
    }
  }

  // non visible decor container windows

  frame.window = createToplevelWindow();
  blackbox->insertEventHandler(frame.window, this);

  frame.plate = createChildWindow(frame.window, NoEventMask, None, frame.visual);
  blackbox->insertEventHandler(frame.plate, this);

  // create visible decor windows

  if (client.decorations & WindowDecorationTitlebar)
    createTitlebar();

  if (client.decorations & WindowDecorationHandle)
    createHandle();

  // apply the size and gravity to the frame
  const WindowStyle &style = _screen->resource().windowStyle();
  frame.margin = ::update_margin(client.decorations, style);
  blackbox->ewmh().setFrameExtents(client.window, frame.margin);
  frame.rect = ::applyGravity(client.rect,
                              frame.margin,
                              client.wmnormal.win_gravity);

  associateClientWindow();

  blackbox->insertEventHandler(client.window, this);
  blackbox->insertWindow(client.window, this);
  blackbox->insertWindow(frame.plate, this);

  // preserve the window's initial state on first map, and its current
  // state across a restart
  if (!readState(client.current_state, blackbox, client.window))
    client.current_state = client.wmhints.initial_state;

  if (client.state.iconic) {
    // prepare the window to be iconified
    client.current_state = IconicState;
    client.state.iconic = False;
  } else if (workspace() != bt::BSENTINEL &&
             workspace() != _screen->currentWorkspace()) {
    client.current_state = WithdrawnState;
  }

  blackbox->XUngrabServer();

  grabButtons();

  XMapSubwindows(blackbox->XDisplay(), frame.window);

  if (isFullScreen()) {
    client.ewmh.fullscreen = false; // trick setFullScreen into working
    setFullScreen(true);
  } else {
    if (isMaximized()) {
      reMaximize();
    } else {
      const unsigned long save_state = client.current_state;

      bt::Rect r = frame.rect;
      // trick configure into working
      frame.rect = bt::Rect();
      configure(r);

      if (isShaded()) {
        client.ewmh.shaded = false;
        setShaded(true);
      }

      if (isShaded() && save_state != IconicState) {
        /*
          At this point in the life of a window, current_state should
          only be set to IconicState if the window was an *icon*, not
          if it was shaded.
        */
        client.current_state = save_state;
      }
    }
  }
}


BlackboxWindow::~BlackboxWindow(void) {
#ifdef    DEBUG
  fprintf(stderr, gettext("BlackboxWindow::~BlackboxWindow: destroying 0x%lx\n"),
          client.window);
#endif // DEBUG

  if (! timer) // window not managed...
    return;

  if (client.state.moving || client.state.resizing) {
    _screen->hideGeometry();
    XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());
  }

  delete timer;

  if (client.strut) {
    _screen->removeStrut(client.strut);
    delete client.strut;
    client.strut = 0;
  }

  BWindowGroup *group = findWindowGroup();

  if (isTransient()) {
    // remove ourselves from our transient_for
    BlackboxWindow *win = findTransientFor();
    if (win) {
      win->removeTransient(this);
    } else if (isGroupTransient()) {
      if (group)
        group->removeTransient(this);
    }
    client.transient_for = 0;
  }

  if (group)
    group->removeWindow(this);

  if (frame.title)
    destroyTitlebar();

  if (frame.handle)
    destroyHandle();

  blackbox->removeEventHandler(client.window);
  blackbox->removeWindow(client.window);

  blackbox->removeEventHandler(frame.plate);
  blackbox->removeWindow(frame.plate);
  XDestroyWindow(blackbox->XDisplay(), frame.plate);

  blackbox->removeEventHandler(frame.window);
  XDestroyWindow(blackbox->XDisplay(), frame.window);

  if (frame.colormap)
    XFreeColormap(blackbox->XDisplay(), frame.colormap);
}


/*
 * Creates a new top level window, with a given location, size, and border
 * width.
 * Returns: the newly created window
 */
Window BlackboxWindow::createToplevelWindow(void) {
  XSetWindowAttributes attrib_create;
  unsigned long create_mask = CWColormap | CWOverrideRedirect | CWEventMask;
  if (frame.depth == 32) {
    create_mask |= CWBorderPixel;
  }

  attrib_create.override_redirect = True;
  attrib_create.event_mask = EnterWindowMask | LeaveWindowMask;

  attrib_create.colormap = frame.colormap;

  return XCreateWindow(blackbox->XDisplay(),
                       _screen->screenInfo().rootWindow(), 0, 0, 1, 1, 0,
                       frame.depth, InputOutput,
                       frame.visual,
                       create_mask, &attrib_create);
}


/*
 * Creates a child window, and optionally associates a given cursor with
 * the new window.
 */
Window BlackboxWindow::createChildWindow(Window parent,
                                         unsigned long event_mask,
                                         Cursor cursor, Visual* visual) {
  XSetWindowAttributes attrib_create;
  unsigned long create_mask = CWEventMask;
  if (visual && frame.depth == 32) {
    create_mask |= CWColormap | CWBorderPixel;
    attrib_create.colormap = frame.colormap;
  } else if (frame.depth == 32) {
    create_mask |= CWColormap | CWBorderPixel;
    attrib_create.colormap = _screen->screenInfo().colormap();
  }

  attrib_create.event_mask = event_mask;

  if (cursor) {
    create_mask |= CWCursor;
    attrib_create.cursor = cursor;
  }

  return XCreateWindow(blackbox->XDisplay(), parent, 0, 0, 1, 1, 0,
                       visual ? frame.depth : _screen->screenInfo().depth(), InputOutput,
                       visual ? frame.visual : _screen->screenInfo().visual(),
                       create_mask, &attrib_create);
}


/*
 * Reparents the client window into the newly created frame.
 *
 * Note: the server must be grabbed before calling this function.
 */
void BlackboxWindow::associateClientWindow(void) {
  XSetWindowBorderWidth(blackbox->XDisplay(), client.window, 0);
  XChangeSaveSet(blackbox->XDisplay(), client.window, SetModeInsert);

  XSelectInput(blackbox->XDisplay(), frame.plate,
               FocusChangeMask | SubstructureRedirectMask);

  XSelectInput(blackbox->XDisplay(), client.window,
               client_window_event_mask & ~StructureNotifyMask);
  /* set backing_store and win_gravity to known values */
  XSetWindowAttributes xswa;
  xswa.backing_store = NotUseful;
  xswa.win_gravity = NorthWestGravity;
  XChangeWindowAttributes(blackbox->XDisplay(), client.window,
              CWBackingStore | CWWinGravity, &xswa);
  XReparentWindow(blackbox->XDisplay(), client.window, frame.plate, 0, 0);
  XSelectInput(blackbox->XDisplay(), client.window, client_window_event_mask);

#ifdef    SHAPE
  if (blackbox->hasShapeExtensions()) {
    XShapeSelectInput(blackbox->XDisplay(), client.window,
                      ShapeNotifyMask);

    Bool shaped = False;
    int foo;
    unsigned int ufoo;

    XShapeQueryExtents(blackbox->XDisplay(), client.window, &shaped,
                       &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo,
                       &ufoo, &ufoo);
    client.state.shaped = shaped;
  }
#endif // SHAPE
}


void BlackboxWindow::decorate(void) {
  const WindowStyle &style = _screen->resource().windowStyle();
  if (client.decorations & WindowDecorationTitlebar) {
    // render focused button texture
    frame.fbutton =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.focus.button,
                            style.button_width,
                            style.button_width,
                            frame.fbutton);

    // render unfocused button texture
    frame.ubutton =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.unfocus.button,
                            style.button_width,
                            style.button_width,
                            frame.ubutton);

    // render pressed button texture
    frame.pbutton =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.pressed,
                            style.button_width,
                            style.button_width,
                            frame.pbutton);

    // render focused titlebar texture
    frame.ftitle =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.focus.title,
                            frame.rect.width(),
                            style.title_height,
                            frame.ftitle);

    // render unfocused titlebar texture
    frame.utitle =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.unfocus.title,
                            frame.rect.width(),
                            style.title_height,
                            frame.utitle);

    // render focused label texture
    frame.flabel =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.focus.label,
                            frame.label_w,
                            style.label_height,
                            frame.flabel);

    // render unfocused label texture
    frame.ulabel =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.unfocus.label,
                            frame.label_w,
                            style.label_height,
                            frame.ulabel);
  }

  if (client.decorations & WindowDecorationHandle) {
    frame.fhandle =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.focus.handle,
                            frame.rect.width(),
                            style.handle_height,
                            frame.fhandle);

    frame.uhandle =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.unfocus.handle,
                            frame.rect.width(),
                            style.handle_height,
                            frame.uhandle);
  }

  if (client.decorations & WindowDecorationGrip) {
    frame.fgrip =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.focus.grip,
                            style.grip_width,
                            style.handle_height,
                            frame.fgrip);

    frame.ugrip =
      bt::PixmapCache::find(_screen->screenNumber(),
                            style.unfocus.grip,
                            style.grip_width,
                            style.handle_height,
                            frame.ugrip);
  }
}


void BlackboxWindow::createHandle(void) {
  frame.handle = createChildWindow(frame.window,
                                   ButtonPressMask | ButtonReleaseMask |
                                   ButtonMotionMask | ExposureMask);
  blackbox->insertEventHandler(frame.handle, this);

  if (client.decorations & WindowDecorationGrip)
    createGrips();
}


void BlackboxWindow::destroyHandle(void) {
  if (frame.left_grip || frame.right_grip)
    destroyGrips();

  if (frame.fhandle) bt::PixmapCache::release(frame.fhandle);
  if (frame.uhandle) bt::PixmapCache::release(frame.uhandle);

  frame.fhandle = frame.uhandle = None;

  blackbox->removeEventHandler(frame.handle);
  XDestroyWindow(blackbox->XDisplay(), frame.handle);
  frame.handle = None;
}


void BlackboxWindow::createGrips(void) {
  frame.left_grip =
    createChildWindow(frame.handle,
                      ButtonPressMask | ButtonReleaseMask |
                      ButtonMotionMask | ExposureMask,
                      blackbox->resource().cursors().resize_bottom_left);
  blackbox->insertEventHandler(frame.left_grip, this);

  frame.right_grip =
    createChildWindow(frame.handle,
                      ButtonPressMask | ButtonReleaseMask |
                      ButtonMotionMask | ExposureMask,
                      blackbox->resource().cursors().resize_bottom_right);
  blackbox->insertEventHandler(frame.right_grip, this);
}


void BlackboxWindow::destroyGrips(void) {
  if (frame.fgrip) bt::PixmapCache::release(frame.fgrip);
  if (frame.ugrip) bt::PixmapCache::release(frame.ugrip);

  frame.fgrip = frame.ugrip = None;

  blackbox->removeEventHandler(frame.left_grip);
  blackbox->removeEventHandler(frame.right_grip);

  XDestroyWindow(blackbox->XDisplay(), frame.left_grip);
  XDestroyWindow(blackbox->XDisplay(), frame.right_grip);
  frame.left_grip = frame.right_grip = None;
}


void BlackboxWindow::createTitlebar(void) {
  frame.title = createChildWindow(frame.window,
                                  ButtonPressMask | ButtonReleaseMask |
                                  ButtonMotionMask | ExposureMask);
  frame.label = createChildWindow(frame.title,
                                  ButtonPressMask | ButtonReleaseMask |
                                  ButtonMotionMask | ExposureMask);
  blackbox->insertEventHandler(frame.title, this);
  blackbox->insertEventHandler(frame.label, this);

  if (client.decorations & WindowDecorationIconify) createIconifyButton();
  if (client.decorations & WindowDecorationMaximize) createMaximizeButton();
  if (client.decorations & WindowDecorationClose) createCloseButton();
}


void BlackboxWindow::destroyTitlebar(void) {
  if (frame.close_button)
    destroyCloseButton();

  if (frame.iconify_button)
    destroyIconifyButton();

  if (frame.maximize_button)
    destroyMaximizeButton();

  if (frame.fbutton) bt::PixmapCache::release(frame.fbutton);
  if (frame.ubutton) bt::PixmapCache::release(frame.ubutton);
  if (frame.pbutton) bt::PixmapCache::release(frame.pbutton);
  if (frame.ftitle)  bt::PixmapCache::release(frame.ftitle);
  if (frame.utitle)  bt::PixmapCache::release(frame.utitle);
  if (frame.flabel)  bt::PixmapCache::release(frame.flabel);
  if (frame.ulabel)  bt::PixmapCache::release(frame.ulabel);

  frame.fbutton = frame.ubutton = frame.pbutton =
   frame.ftitle = frame.utitle =
   frame.flabel = frame.ulabel = None;

  blackbox->removeEventHandler(frame.title);
  blackbox->removeEventHandler(frame.label);

  XDestroyWindow(blackbox->XDisplay(), frame.label);
  XDestroyWindow(blackbox->XDisplay(), frame.title);
  frame.title = frame.label = None;
}


void BlackboxWindow::createCloseButton(void) {
  if (frame.title != None) {
    frame.close_button = createChildWindow(frame.title,
                                           ButtonPressMask |
                                           ButtonReleaseMask |
                                           ButtonMotionMask | ExposureMask);
    blackbox->insertEventHandler(frame.close_button, this);
  }
}


void BlackboxWindow::destroyCloseButton(void) {
  blackbox->removeEventHandler(frame.close_button);
  XDestroyWindow(blackbox->XDisplay(), frame.close_button);
  frame.close_button = None;
}


void BlackboxWindow::createIconifyButton(void) {
  if (frame.title != None) {
    frame.iconify_button = createChildWindow(frame.title,
                                             ButtonPressMask |
                                             ButtonReleaseMask |
                                             ButtonMotionMask | ExposureMask);
    blackbox->insertEventHandler(frame.iconify_button, this);
  }
}


void BlackboxWindow::destroyIconifyButton(void) {
  blackbox->removeEventHandler(frame.iconify_button);
  XDestroyWindow(blackbox->XDisplay(), frame.iconify_button);
  frame.iconify_button = None;
}


void BlackboxWindow::createMaximizeButton(void) {
  if (frame.title != None) {
    frame.maximize_button = createChildWindow(frame.title,
                                              ButtonPressMask |
                                              ButtonReleaseMask |
                                              ButtonMotionMask | ExposureMask);
    blackbox->insertEventHandler(frame.maximize_button, this);
  }
}


void BlackboxWindow::destroyMaximizeButton(void) {
  blackbox->removeEventHandler(frame.maximize_button);
  XDestroyWindow(blackbox->XDisplay(), frame.maximize_button);
  frame.maximize_button = None;
}


void BlackboxWindow::positionButtons(bool redecorate_label) {
  // we need to use signed ints here to detect windows that are too small
  const WindowStyle &style = _screen->resource().windowStyle();
  const int extra = style.title_margin == 0 ?
                    style.focus.button.borderWidth() : 0,
               bw = style.button_width + style.title_margin
                    - extra,
               by = style.title_margin +
                    style.focus.title.borderWidth();
  int lx = by, lw = frame.rect.width() - by;

  if (client.decorations & WindowDecorationIconify) {
    if (frame.iconify_button == None) createIconifyButton();

    XMoveResizeWindow(blackbox->XDisplay(), frame.iconify_button, by, by,
                      style.button_width, style.button_width);
    XMapWindow(blackbox->XDisplay(), frame.iconify_button);

    lx += bw;
    lw -= bw;
  } else if (frame.iconify_button) {
    destroyIconifyButton();
  }

  int bx = frame.rect.width() - bw
           - style.focus.title.borderWidth() - extra;

  if (client.decorations & WindowDecorationClose) {
    if (frame.close_button == None) createCloseButton();

    XMoveResizeWindow(blackbox->XDisplay(), frame.close_button, bx, by,
                      style.button_width, style.button_width);
    XMapWindow(blackbox->XDisplay(), frame.close_button);

    bx -= bw;
    lw -= bw;
  } else if (frame.close_button) {
    destroyCloseButton();
  }

  if (client.decorations & WindowDecorationMaximize) {
    if (frame.maximize_button == None) createMaximizeButton();

    XMoveResizeWindow(blackbox->XDisplay(), frame.maximize_button, bx, by,
                      style.button_width, style.button_width);
    XMapWindow(blackbox->XDisplay(), frame.maximize_button);

    bx -= bw;
    lw -= bw;
  } else if (frame.maximize_button) {
    destroyMaximizeButton();
  }

  if (lw > by) {
    frame.label_w = lw - by;
    XMoveResizeWindow(blackbox->XDisplay(), frame.label, lx, by,
                      frame.label_w, style.label_height);
    XMapWindow(blackbox->XDisplay(), frame.label);

    if (redecorate_label) {
      frame.flabel =
        bt::PixmapCache::find(_screen->screenNumber(),
                              style.focus.label,
                              frame.label_w, style.label_height,
                              frame.flabel);
      frame.ulabel =
        bt::PixmapCache::find(_screen->screenNumber(),
                              style.unfocus.label,
                              frame.label_w, style.label_height,
                              frame.ulabel);
    }

    const bt::ustring ellided =
      bt::ellideText(client.title, frame.label_w, bt::toUnicode("..."),
                     _screen->screenNumber(), style.font);

    if (ellided != client.visible_title) {
      client.visible_title = ellided;
      blackbox->ewmh().setWMVisibleName(client.window, client.visible_title);
    }
  } else {
    frame.label_w = 1;
    XUnmapWindow(blackbox->XDisplay(), frame.label);
  }

  redrawLabel();
  redrawAllButtons();
}


void BlackboxWindow::reconfigure(void) {
  const WindowStyle &style = _screen->resource().windowStyle();
  if (isMaximized()) {
    // update the frame margin in case the style has changed
    frame.margin = ::update_margin(client.decorations, style);
    blackbox->ewmh().setFrameExtents(client.window, frame.margin);

    // make sure maximized windows have the correct size after a style
    // change
    reMaximize();
  } else {
    // get the client window geometry as if it was unmanaged
    bt::Rect r = frame.rect;
    if (client.ewmh.shaded) {
      r.setHeight(client.rect.height() + frame.margin.top
                  + frame.margin.bottom);
    }
    r = ::restoreGravity(r, frame.margin, client.wmnormal.win_gravity);

    // update the frame margin in case the style has changed
    frame.margin = ::update_margin(client.decorations, style);
    blackbox->ewmh().setFrameExtents(client.window, frame.margin);

    // get the frame window geometry from the client window geometry
    // calculated above
    r = ::applyGravity(r, frame.margin, client.wmnormal.win_gravity);
    if (client.ewmh.shaded) {
      frame.rect = r;

      positionWindows();
      decorate();

      // keep the window shaded
      frame.rect.setHeight(style.title_height);
      XResizeWindow(blackbox->XDisplay(), frame.window,
                    frame.rect.width(), frame.rect.height());
    } else {
      // trick configure into working
      frame.rect = bt::Rect();
      configure(r);
    }
  }

  ungrabButtons();
  grabButtons();
}


void BlackboxWindow::grabButtons(void) {
  if (blackbox->resource().focusModel() == ClickToFocusModel
      || blackbox->resource().clickRaise())
    // grab button 1 for changing focus/raising
    blackbox->grabButton(Button1, 0, frame.plate, True, ButtonPressMask,
                         GrabModeSync, GrabModeSync, frame.plate, None,
                         blackbox->resource().allowScrollLock());

  if (hasWindowFunction(WindowFunctionMove))
    blackbox->grabButton(Button1, Mod1Mask, frame.window, True,
                         ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
                         GrabModeAsync, frame.window,
                         blackbox->resource().cursors().move,
                         blackbox->resource().allowScrollLock());
  if (hasWindowFunction(WindowFunctionResize))
    blackbox->grabButton(Button3, Mod1Mask, frame.window, True,
                         ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
                         GrabModeAsync, frame.window,
                         None, blackbox->resource().allowScrollLock());
  // alt+middle lowers the window
  blackbox->grabButton(Button2, Mod1Mask, frame.window, True,
                       ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
                       frame.window, None,
                       blackbox->resource().allowScrollLock());

  blackbox->grabButton(Button3, Mod4Mask, frame.window, True,
                       ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
                       frame.window, None,
                       blackbox->resource().allowScrollLock());
}


void BlackboxWindow::ungrabButtons(void) {
  blackbox->ungrabButton(Button1, 0, frame.plate);
  blackbox->ungrabButton(Button1, Mod1Mask, frame.window);
  blackbox->ungrabButton(Button2, Mod1Mask, frame.window);
  blackbox->ungrabButton(Button3, Mod1Mask, frame.window);
  blackbox->ungrabButton(Button3, Mod4Mask, frame.window);
}


void BlackboxWindow::positionWindows(void) {
  const WindowStyle &style = _screen->resource().windowStyle();
  const unsigned int bw = (hasWindowDecoration(WindowDecorationBorder)
                           ? style.frame_border_width
                           : 0);

  XMoveResizeWindow(blackbox->XDisplay(), frame.plate,
                    frame.margin.left - bw,
                    frame.margin.top - bw,
                    client.rect.width(), client.rect.height());
  XSetWindowBorderWidth(blackbox->XDisplay(), frame.plate, bw);
  XMoveResizeWindow(blackbox->XDisplay(), client.window,
                    0, 0, client.rect.width(), client.rect.height());
  // ensure client.rect contains the real location
  client.rect.setPos(frame.rect.left() + frame.margin.left,
                     frame.rect.top() + frame.margin.top);

  if (client.decorations & WindowDecorationTitlebar) {
    if (frame.title == None) createTitlebar();

    XMoveResizeWindow(blackbox->XDisplay(), frame.title,
                      0, 0, frame.rect.width(), style.title_height);

    positionButtons();
    XMapSubwindows(blackbox->XDisplay(), frame.title);
    XMapWindow(blackbox->XDisplay(), frame.title);
  } else if (frame.title) {
    destroyTitlebar();
  }

  if (client.decorations & WindowDecorationHandle) {
    if (frame.handle == None) createHandle();

    // use client.rect here so the value is correct even if shaded
    XMoveResizeWindow(blackbox->XDisplay(), frame.handle,
                      0, client.rect.height() + frame.margin.top,
                      frame.rect.width(), style.handle_height);

    if (client.decorations & WindowDecorationGrip) {
      if (frame.left_grip == None || frame.right_grip == None) createGrips();

      XMoveResizeWindow(blackbox->XDisplay(), frame.left_grip, 0, 0,
                        style.grip_width, style.handle_height);

      const int nx = frame.rect.width() - style.grip_width;
      XMoveResizeWindow(blackbox->XDisplay(), frame.right_grip, nx, 0,
                        style.grip_width, style.handle_height);

      XMapSubwindows(blackbox->XDisplay(), frame.handle);
    } else {
      destroyGrips();
    }

    XMapWindow(blackbox->XDisplay(), frame.handle);
  } else if (frame.handle) {
    destroyHandle();
  }
}


/*
 * This function is responsible for updating both the client and the
 * frame rectangles.  According to the ICCCM a client message is not
 * sent for a resize, only a move.
 */
void BlackboxWindow::configure(int dx, int dy,
                               unsigned int dw, unsigned int dh) {
  bool send_event = ((frame.rect.x() != dx || frame.rect.y() != dy) &&
                     ! client.state.moving);

  if (dw != frame.rect.width() || dh != frame.rect.height()) {
    frame.rect.setRect(dx, dy, dw, dh);

    if (frame.rect.right() <= 0 || frame.rect.bottom() <= 0)
      frame.rect.setPos(0, 0);

    client.rect.setCoords(frame.rect.left() + frame.margin.left,
                          frame.rect.top() + frame.margin.top,
                          frame.rect.right() - frame.margin.right,
                          frame.rect.bottom() - frame.margin.bottom);

#ifdef SHAPE
    if (client.state.shaped)
      configureShape();
#endif // SHAPE

    XMoveResizeWindow(blackbox->XDisplay(), frame.window,
                      frame.rect.x(), frame.rect.y(),
                      frame.rect.width(), frame.rect.height());

    positionWindows();
    decorate();
    redrawWindowFrame();
  } else {
    frame.rect.setPos(dx, dy);

    XMoveWindow(blackbox->XDisplay(), frame.window,
                frame.rect.x(), frame.rect.y());
    /*
      we may have been called just after an opaque window move, so
      even though the old coords match the new ones no ConfigureNotify
      has been sent yet.  There are likely other times when this will
      be relevant as well.
    */
    if (! client.state.moving) send_event = True;
  }

  if (send_event) {
    // if moving, the update and event will occur when the move finishes
    client.rect.setPos(frame.rect.left() + frame.margin.left,
                       frame.rect.top() + frame.margin.top);

    XEvent event;
    event.type = ConfigureNotify;

    event.xconfigure.display = blackbox->XDisplay();
    event.xconfigure.event = client.window;
    event.xconfigure.window = client.window;
    event.xconfigure.x = client.rect.x();
    event.xconfigure.y = client.rect.y();
    event.xconfigure.width = client.rect.width();
    event.xconfigure.height = client.rect.height();
    event.xconfigure.border_width = client.old_bw;
    event.xconfigure.above = frame.window;
    event.xconfigure.override_redirect = False;

    XSendEvent(blackbox->XDisplay(), client.window, False,
               StructureNotifyMask, &event);
  }
}


#ifdef SHAPE
void BlackboxWindow::configureShape(void) {
  XShapeCombineShape(blackbox->XDisplay(), frame.window, ShapeBounding,
                     frame.margin.left, frame.margin.top,
                     client.window, ShapeBounding, ShapeSet);

  int num = 0;
  XRectangle xrect[2];

  const WindowStyle &style = _screen->resource().windowStyle();
  if (client.decorations & WindowDecorationTitlebar) {
    xrect[0].x = xrect[0].y = 0;
    xrect[0].width = frame.rect.width();
    xrect[0].height = style.title_height;
    ++num;
  }

  if (client.decorations & WindowDecorationHandle) {
    xrect[1].x = 0;
    xrect[1].y = client.rect.height() + frame.margin.top;
    xrect[1].width = frame.rect.width();
    xrect[1].height = style.handle_height;
    ++num;
  }

  XShapeCombineRectangles(blackbox->XDisplay(), frame.window,
                          ShapeBounding, 0, 0, xrect, num,
                          ShapeUnion, Unsorted);
}
#endif // SHAPE


void BlackboxWindow::addTransient(BlackboxWindow *win)
{ client.transientList.push_front(win); }


void BlackboxWindow::removeTransient(BlackboxWindow *win)
{ client.transientList.remove(win); }


BlackboxWindow *BlackboxWindow::findTransientFor(void) const {
  BlackboxWindow *win = 0;
  if (isTransient()) {
    win = blackbox->findWindow(client.transient_for);
    if (win && win->_screen != _screen)
      win = 0;
  }
  return win;
}


/*
  walk up to either 1) a non-transient window 2) a group transient,
  watching out for a circular chain

  this function returns zero for non-transient windows
*/
BlackboxWindow *BlackboxWindow::findNonTransientParent(void) const {
  BlackboxWindowList seen;
  seen.push_back(const_cast<BlackboxWindow *>(this));

  BlackboxWindow *w = findTransientFor();
  if (!w)
    return 0;

  while (w->isTransient() && !w->isGroupTransient()) {
    seen.push_back(w);
    BlackboxWindow * const tmp = w->findTransientFor();
    if (!tmp)
      break;
    if (std::find(seen.begin(), seen.end(), tmp) != seen.end()) {
      // circular transient chain
      break;
    }
    w = tmp;
  }
  return w;
}


/*
  Returns a list of all transients.  This is recursive, so it returns
  all transients of transients as well.
*/
BlackboxWindowList BlackboxWindow::buildFullTransientList(void) const {
  BlackboxWindowList all = client.transientList;
  BlackboxWindowList::const_iterator it = client.transientList.begin(),
                                    end = client.transientList.end();
  for (; it != end; ++it) {
    BlackboxWindowList x = (*it)->buildFullTransientList();
    all.splice(all.end(), x);
  }
  return all;
}


BWindowGroup *BlackboxWindow::findWindowGroup(void) const {
  BWindowGroup *group = 0;
  if (client.wmhints.window_group)
    group = blackbox->findWindowGroup(client.wmhints.window_group);
  return group;
}


void BlackboxWindow::setWorkspace(unsigned int new_workspace) {
  client.ewmh.workspace = new_workspace;
  blackbox->ewmh().setWMDesktop(client.window, client.ewmh.workspace);
}


void BlackboxWindow::changeWorkspace(unsigned int new_workspace,
                                     ChangeWorkspaceOption how) {
  if (client.ewmh.workspace == new_workspace)
    return;

  if (isTransient()) {
    BlackboxWindow *win = findTransientFor();
    if (win) {
      if (win->workspace() != new_workspace) {
        win->changeWorkspace(new_workspace, how);
        return;
      }
    }
  } else {
    assert(hasWindowFunction(WindowFunctionChangeWorkspace));
  }

  Workspace *ws;
  if (workspace() != bt::BSENTINEL) {
    ws = _screen->findWorkspace(workspace());
    assert(ws != 0);
    ws->removeWindow(this);
  }

  if (new_workspace != bt::BSENTINEL) {
    ws = _screen->findWorkspace(new_workspace);
    assert(ws != 0);
    ws->addWindow(this);
  }

  switch (how) {
  case StayOnCurrentWorkspace:
    if (isVisible() && workspace() != bt::BSENTINEL
        && workspace() != _screen->currentWorkspace()) {
      hide();
    } else if (!isVisible()
               && (workspace() == bt::BSENTINEL
                   || workspace() == _screen->currentWorkspace())) {
      show();
    }
    break;

  case SwitchToNewWorkspace:
    /*
      we will change to the new workspace soon, so force this window
      to be visible
    */
    show();
    break;
  }

  // change workspace on all transients
  if (!client.transientList.empty()) {
    BlackboxWindowList::iterator it = client.transientList.begin(),
                                end = client.transientList.end();
    for (; it != end; ++it)
      (*it)->changeWorkspace(new_workspace, how);
  }
}


void BlackboxWindow::changeLayer(StackingList::Layer new_layer) {
  if (layer() == new_layer)
    return;

  bool restack = false;
  if (isTransient()) {
    BlackboxWindow *win = findTransientFor();
    if (win) {
      if (win->layer() != new_layer) {
        win->changeLayer(new_layer);
        return;
      } else {
        restack = true;
      }
    }
  } else {
    assert(hasWindowFunction(WindowFunctionChangeLayer));
    restack = true;
  }

  _screen->stackingList().changeLayer(this, new_layer);

  if (!client.transientList.empty()) {
    BlackboxWindowList::iterator it = client.transientList.begin();
    const BlackboxWindowList::iterator end = client.transientList.end();
    for (; it != end; ++it)
      (*it)->changeLayer(new_layer);
  }

  if (restack)
    _screen->restackWindows();

  updateEWMHState();
}


bool BlackboxWindow::setInputFocus(void) {
  if (!isVisible())
    return false;
  if (client.state.focused)
    return true;

  switch (windowType()) {
  case WindowTypeDock:
    /*
      Many docks have auto-hide features similar to the toolbar and
      slit... we don't want these things to be moved to the center of
      the screen when switching to an empty workspace.
    */
    break;

  default:
    {  const bt::Rect &scr = _screen->screenInfo().rect();
      if (!frame.rect.intersects(scr)) {
        // client is outside the screen, move it to the center
        configure(scr.x() + (scr.width() - frame.rect.width()) / 2,
                  scr.y() + (scr.height() - frame.rect.height()) / 2,
                  frame.rect.width(), frame.rect.height());
      }
      break;
    }
  }

  /*
    pass focus to any modal transients, giving modal group transients
    higher priority
  */
  BWindowGroup *group = findWindowGroup();
  if (group && !group->transients().empty()) {
    BlackboxWindowList::const_iterator it = group->transients().begin(),
                                      end = group->transients().end();
    for (; it != end; ++it) {
      BlackboxWindow * const tmp = *it;
      if (!tmp->isVisible() || !tmp->isModal())
        continue;
      if (tmp == this) {
        // we are the newest modal group transient
        break;
      }
      if (isTransient()) {
        if (tmp == findNonTransientParent()) {
          // we are a transient of the modal group transient
          break;
        }
      }
      return tmp->setInputFocus();
    }
  }

  if (!client.transientList.empty()) {
    BlackboxWindowList::const_iterator it = client.transientList.begin(),
                                      end = client.transientList.end();
    for (; it != end; ++it) {
      BlackboxWindow * const tmp = *it;
      if (tmp->isVisible() && tmp->isModal())
        return tmp->setInputFocus();
    }
  }

  switch (windowType()) {
  case WindowTypeDock:
    return false;
  default:
    break;
  }

  XSetInputFocus(blackbox->XDisplay(), client.window,
                 RevertToPointerRoot, blackbox->XTime());

  if (client.wmprotocols.wm_take_focus) {
    XEvent ce;
    ce.xclient.type = ClientMessage;
    ce.xclient.message_type = blackbox->wmProtocolsAtom();
    ce.xclient.display = blackbox->XDisplay();
    ce.xclient.window = client.window;
    ce.xclient.format = 32;
    ce.xclient.data.l[0] = blackbox->wmTakeFocusAtom();
    ce.xclient.data.l[1] = blackbox->XTime();
    ce.xclient.data.l[2] = 0l;
    ce.xclient.data.l[3] = 0l;
    ce.xclient.data.l[4] = 0l;
    XSendEvent(blackbox->XDisplay(), client.window, False, NoEventMask, &ce);
  }

  return true;
}


void BlackboxWindow::show(void) {
  if (client.state.visible)
    return;

  if (client.state.iconic)
    _screen->removeIcon(this);

  client.state.iconic = false;
  client.state.visible = true;
  setState(isShaded() ? IconicState : NormalState);

  XMapWindow(blackbox->XDisplay(), client.window);
  XMapSubwindows(blackbox->XDisplay(), frame.window);
  XMapWindow(blackbox->XDisplay(), frame.window);

  if (!client.transientList.empty()) {
    BlackboxWindowList::iterator it = client.transientList.begin(),
                                end = client.transientList.end();
    for (; it != end; ++it)
      (*it)->show();
  }

#ifdef DEBUG
  int real_x, real_y;
  Window child;
  XTranslateCoordinates(blackbox->XDisplay(), client.window,
                        _screen->screenInfo().rootWindow(),
                        0, 0, &real_x, &real_y, &child);
  fprintf(stderr, gettext("%s -- assumed: (%d, %d), real: (%d, %d)\n"), title().c_str(),
          client.rect.left(), client.rect.top(), real_x, real_y);
  assert(client.rect.left() == real_x && client.rect.top() == real_y);
#endif
}


void BlackboxWindow::hide(void) {
  if (!client.state.visible)
    return;

  client.state.visible = false;
  setState(client.state.iconic ? IconicState : client.current_state);

  XUnmapWindow(blackbox->XDisplay(), frame.window);

  /*
   * we don't want this XUnmapWindow call to generate an UnmapNotify
   * event, so we need to clear the event mask on client.window for a
   * split second.  HOWEVER, since X11 is asynchronous, the window
   * could be destroyed in that split second, leaving us with a ghost
   * window... so, we need to do this while the X server is grabbed
   */
  blackbox->XGrabServer();
  XSelectInput(blackbox->XDisplay(), client.window,
               client_window_event_mask & ~StructureNotifyMask);
  XUnmapWindow(blackbox->XDisplay(), client.window);
  XSelectInput(blackbox->XDisplay(), client.window, client_window_event_mask);
  blackbox->XUngrabServer();
}


void BlackboxWindow::close(void) {
  assert(hasWindowFunction(WindowFunctionClose));

  XEvent ce;
  ce.xclient.type = ClientMessage;
  ce.xclient.message_type = blackbox->wmProtocolsAtom();
  ce.xclient.display = blackbox->XDisplay();
  ce.xclient.window = client.window;
  ce.xclient.format = 32;
  ce.xclient.data.l[0] = blackbox->wmDeleteWindowAtom();
  ce.xclient.data.l[1] = blackbox->XTime();
  ce.xclient.data.l[2] = 0l;
  ce.xclient.data.l[3] = 0l;
  ce.xclient.data.l[4] = 0l;
  XSendEvent(blackbox->XDisplay(), client.window, False, NoEventMask, &ce);
}


void BlackboxWindow::activate(void) {
  if (workspace() != bt::BSENTINEL
      && workspace() != _screen->currentWorkspace())
    _screen->setCurrentWorkspace(workspace());
  if (client.state.iconic)
    show();
  if (client.ewmh.shaded)
    setShaded(false);
  if (client.ewmh.urgent)
    setUrgent(false);
  if (setInputFocus())
    _screen->raiseWindow(this);
}


void BlackboxWindow::iconify(void) {
  if (client.state.iconic)
    return;

  if (isTransient()) {
    BlackboxWindow *win = findTransientFor();
    if (win) {
      if (!win->isIconic()) {
        win->iconify();
        return;
      }
    }
  } else {
    assert(hasWindowFunction(WindowFunctionIconify));
  }

  _screen->addIcon(this);

  client.state.iconic = true;
  hide();

  // iconify all transients
  if (!client.transientList.empty()) {
    BlackboxWindowList::iterator it = client.transientList.begin(),
                                end = client.transientList.end();
    for (; it != end; ++it)
      (*it)->iconify();
  }
}


void BlackboxWindow::setMaximized(bool maxv, bool maxh) {
  assert(hasWindowFunction(WindowFunctionMaximize));

  // any maximize operation always unshades
  client.ewmh.shaded = false;
  frame.rect.setHeight(client.rect.height() + frame.margin.top
                       + frame.margin.bottom);

  bool was_max = isMaximized();
    
  client.ewmh.maxv = maxv;
  client.ewmh.maxh = maxh;

  if (!maxv && !maxh) {
    if (was_max) {
      // restore from maximized

      if (!isFullScreen()) {
        /*
          when a resize is begun, maximize(0) is called to clear any
          maximization flags currently set.  Otherwise it still thinks
          it is maximized.  so we do not need to call configure()
          because resizing will handle it
        */
        if (! client.state.resizing) {
          bt::Rect r = ::applyGravity(client.premax,
                                      frame.margin,
                                      client.wmnormal.win_gravity);
          r = ::constrain(r, frame.margin, client.wmnormal, TopLeft);
          // trick configure into working
          frame.rect = bt::Rect();
          configure(r);
        }

        redrawAllButtons(); // in case it is not called in configure()
      }
    }

  } else {

    if (!isFullScreen()) {
      // go go gadget-maximize!
      bt::Rect r = _screen->availableArea();

      if (!maxh) {
        if (was_max) {
          r.setX(client.premax.x());
          r.setWidth(client.premax.width());
        } else {
          r.setX(frame.rect.x());
          r.setWidth(frame.rect.width());
        }
      }

      if (!maxv) {
        if (was_max) {
          r.setY(client.premax.y());
          r.setHeight(client.premax.height());
        } else {
          r.setY(frame.rect.y());
          r.setHeight(frame.rect.height());
        }
      }
      
      if (!was_max) {
        // store the current frame geometry, so that we can restore it later
        client.premax = ::restoreGravity(frame.rect,
                                         frame.margin,
                                         client.wmnormal.win_gravity);
      }

      r = ::constrain(r, frame.margin, client.wmnormal, TopLeft);
      // trick configure into working
      frame.rect = bt::Rect();
      configure(r);
    }
  }

  updateEWMHState();
  updateEWMHAllowedActions();
}

void BlackboxWindow::toggleMaximized(bool maxv, bool maxh) {
  assert(hasWindowFunction(WindowFunctionMaximize));

  if (!maxv && !maxh)
    return;
  maxv = maxv ? (client.ewmh.maxv ? false : true) : client.ewmh.maxv;
  maxh = maxh ? (client.ewmh.maxh ? false : true) : client.ewmh.maxh;
  setMaximized(maxv, maxh);
}


void BlackboxWindow::buttonMaximize(unsigned int button) {
  assert(hasWindowFunction(WindowFunctionMaximize));

  switch (button) {
  case 0:
    setMaximized(false, false);
    break;

  case 1:
    if (!isMaximizedHorz() || !isMaximizedVert())
      setMaximized(true, true);
    else
      setMaximized(false, false);
    break;

  case 2:
    toggleMaximized(true, false);
    break;

  case 3:
    toggleMaximized(false, true);
    break;

  default:
    assert(0);
    break;
  }

}

/*
  re-maximizes the window to take into account availableArea changes.

  note that unlike maximize(), the shaded state is preserved.
*/
void BlackboxWindow::reMaximize(void) {
  if (isShaded()) {
    bt::Rect r = _screen->availableArea();

    if (!client.ewmh.maxh) {
      r.setX(frame.rect.x());
      r.setWidth(frame.rect.width());
    }
    if (!client.ewmh.maxv) {
      r.setY(frame.rect.y());
      r.setHeight(frame.rect.height());
    }

    frame.rect = ::constrain(r, frame.margin, client.wmnormal, TopLeft);

    positionWindows();
    decorate();

    // set the frame rect to the shaded size
    const WindowStyle &style = _screen->resource().windowStyle();
    frame.rect.setHeight(style.title_height);
    XResizeWindow(blackbox->XDisplay(), frame.window,
                  frame.rect.width(), frame.rect.height());
    return;
  }
  setMaximized(client.ewmh.maxv, client.ewmh.maxh);
}


void BlackboxWindow::setShaded(bool shaded) {
  assert(hasWindowFunction(WindowFunctionShade));

  if (client.ewmh.shaded == shaded)
    return;

  client.ewmh.shaded = shaded;
  if (!isShaded()) {
    if (isMaximized()) {
      reMaximize();
    } else {
      // set the frame rect to the normal size
      frame.rect.setHeight(client.rect.height() + frame.margin.top +
                           frame.margin.bottom);

      XResizeWindow(blackbox->XDisplay(), frame.window,
                    frame.rect.width(), frame.rect.height());
    }

    setState(NormalState);
  } else {
    // set the frame rect to the shaded size
    const WindowStyle &style = _screen->resource().windowStyle();
    frame.rect.setHeight(style.title_height);

    XResizeWindow(blackbox->XDisplay(), frame.window,
                  frame.rect.width(), frame.rect.height());

    setState(IconicState);
  }
}


void BlackboxWindow::setFullScreen(bool b) {
  assert(hasWindowFunction(WindowFunctionFullScreen));

  if (client.ewmh.fullscreen == b)
    return;

  // any fullscreen operation always unshades
  client.ewmh.shaded = false;
  frame.rect.setHeight(client.rect.height() + frame.margin.top
                       + frame.margin.bottom);

  bool refocus = isFocused();
  client.ewmh.fullscreen = b;
  if (isFullScreen()) {
    // go go gadget-fullscreen!
    if (!isMaximized())
      client.premax = ::restoreGravity(frame.rect,
                                       frame.margin,
                                       client.wmnormal.win_gravity);

    // modify decorations, functions and frame margin
    client.decorations = NoWindowDecorations;
    client.functions &= ~(WindowFunctionMove |
                          WindowFunctionResize |
                          WindowFunctionShade);
    const WindowStyle &style = _screen->resource().windowStyle();
    frame.margin = ::update_margin(client.decorations, style);
    blackbox->ewmh().setFrameExtents(client.window, frame.margin);

    /*
     * Note: we don't call ::constrain() here, because many
     * applications are broken in this respect.  Some specify a
     * max-size or aspect-ratio that simply doesn't cover the entire
     * screen.  Let's try to be smarter than such applications and
     * simply cover the entire screen.
     */

    // trick configure() into working
    frame.rect = bt::Rect();
    configure(_screen->screenInfo().rect());

    if (isVisible())
      changeLayer(StackingList::LayerFullScreen);

    updateEWMHState();
    updateEWMHAllowedActions();
  } else {
    // restore from fullscreen
    ::update_decorations(client.decorations,
                         client.functions,
                         isTransient(),
                         client.ewmh,
                         client.motif,
                         client.wmnormal,
                         client.wmprotocols);
    const WindowStyle &style = _screen->resource().windowStyle();
    frame.margin = ::update_margin(client.decorations, style);
    blackbox->ewmh().setFrameExtents(client.window, frame.margin);

    if (isVisible())
      changeLayer(StackingList::LayerNormal);

    if (isMaximized()) {
      reMaximize();
    } else {
      bt::Rect r = ::applyGravity(client.premax,
                                  frame.margin,
                                  client.wmnormal.win_gravity);
      r = ::constrain(r, frame.margin, client.wmnormal, TopLeft);

      // trick configure into working
      frame.rect = bt::Rect();
      configure(r);

      updateEWMHState();
      updateEWMHAllowedActions();
    }
  }

  ungrabButtons();
  grabButtons();

  if (refocus)
    (void) setInputFocus();
}


void BlackboxWindow::setSticky(bool b) {
  assert(hasWindowFunction(WindowFunctionStick));

  if (client.ewmh.sticky == b)
    return;

  client.ewmh.sticky = b;

  updateEWMHState();
}


void BlackboxWindow::setUrgent(bool b) {

  if (client.ewmh.urgent == b)
    return;

  client.ewmh.urgent = b;

  updateEWMHState();
}


void BlackboxWindow::redrawWindowFrame(void) const {
  if (client.decorations & WindowDecorationTitlebar) {
    redrawTitle();
    redrawLabel();
    redrawAllButtons();
  }

  if (client.decorations & WindowDecorationBorder) {
    const WindowStyle &style = _screen->resource().windowStyle();
    const bt::Color &c = (isFocused()
                          ? style.focus.frame_border
                          : style.unfocus.frame_border);
    // If we have a ARGB visual, the ColorCache assumption about a single
    // colormaps per screen is no longer valid. Just quickly custom alloc.
    if (frame.depth < 32) {
      XSetWindowBorder(blackbox->XDisplay(), frame.plate,
                       c.pixel(_screen->screenNumber()));
    }
    else {
      XColor xcol;
      xcol.red   = c.red() | c.red() << 8;
      xcol.green = c.green() | c.green() << 8;
      xcol.blue  = c.blue() | c.blue() << 8;
      xcol.pixel = 0;
      xcol.flags = DoRed | DoGreen | DoBlue;
      XAllocColor(blackbox->XDisplay(), frame.colormap, &xcol);
      XSetWindowBorder(blackbox->XDisplay(), frame.plate,
                       xcol.pixel);
      XFreeColors(blackbox->XDisplay(), frame.colormap, &xcol.pixel, 1, 0);
    }
  }

  if (client.decorations & WindowDecorationHandle) {
    redrawHandle();

    if (client.decorations & WindowDecorationGrip)
      redrawGrips();
  }
}


void BlackboxWindow::setFocused(bool focused) {
  if (focused == client.state.focused)
    return;

  client.state.focused = isVisible() ? focused : false;

  if (isVisible()) {
    redrawWindowFrame();

    if (client.state.focused) {
      XInstallColormap(blackbox->XDisplay(), client.colormap);
    } else {
      if (client.ewmh.fullscreen && layer() != StackingList::LayerBelow)
        changeLayer(StackingList::LayerBelow);
    }
  }

  updateEWMHState();
}


void BlackboxWindow::setState(unsigned long new_state) {
  client.current_state = new_state;

  unsigned long state[2];
  state[0] = client.current_state;
  state[1] = None;
  XChangeProperty(blackbox->XDisplay(), client.window,
                  blackbox->wmStateAtom(), blackbox->wmStateAtom(), 32,
                  PropModeReplace, (unsigned char *) state, 2);

  updateEWMHState();
  updateEWMHAllowedActions();
}


void BlackboxWindow::updateEWMHState() {
  const bt::EWMH& ewmh = blackbox->ewmh();

  // set _NET_WM_STATE
  bt::EWMH::AtomList atoms;
  if (isModal())
    atoms.push_back(ewmh.wmStateModal());
  if (isShaded())
    atoms.push_back(ewmh.wmStateShaded());
  if (isIconic())
    atoms.push_back(ewmh.wmStateHidden());
  if (isFullScreen())
    atoms.push_back(ewmh.wmStateFullscreen());
  if (client.ewmh.maxh)
    atoms.push_back(ewmh.wmStateMaximizedHorz());
  if (client.ewmh.maxv)
    atoms.push_back(ewmh.wmStateMaximizedVert());
  if (client.ewmh.skip_taskbar)
    atoms.push_back(ewmh.wmStateSkipTaskbar());
  if (client.ewmh.skip_pager)
    atoms.push_back(ewmh.wmStateSkipPager());
  if (client.ewmh.sticky)
    atoms.push_back(ewmh.wmStateSticky());
  if (client.ewmh.urgent)
    atoms.push_back(ewmh.wmStateDemandsAttention());
  if (client.state.focused)
    atoms.push_back(ewmh.wmStateFocused());

  switch (layer()) {
  case StackingList::LayerAbove:
    atoms.push_back(ewmh.wmStateAbove());
    break;
  case StackingList::LayerBelow:
    atoms.push_back(ewmh.wmStateBelow());
    break;
  default:
    break;
  }

  if (atoms.empty())
    ewmh.removeProperty(client.window, ewmh.wmState());
  else
    ewmh.setWMState(client.window, atoms);
}


void BlackboxWindow::updateEWMHAllowedActions() {
  const bt::EWMH& ewmh = blackbox->ewmh();

  // set _NET_WM_ALLOWED_ACTIONS
  bt::EWMH::AtomList atoms;
  if (hasWindowFunction(WindowFunctionChangeWorkspace))
    atoms.push_back(ewmh.wmActionChangeDesktop());

  if (hasWindowFunction(WindowFunctionIconify))
    atoms.push_back(ewmh.wmActionMinimize());

  if (hasWindowFunction(WindowFunctionShade))
    atoms.push_back(ewmh.wmActionShade());

  if (hasWindowFunction(WindowFunctionMove))
    atoms.push_back(ewmh.wmActionMove());

  if (hasWindowFunction(WindowFunctionResize))
    atoms.push_back(ewmh.wmActionResize());

  if (hasWindowFunction(WindowFunctionMaximize)) {
    atoms.push_back(ewmh.wmActionMaximizeHorz());
    atoms.push_back(ewmh.wmActionMaximizeVert());
  }

  if (hasWindowFunction(WindowFunctionChangeLayer)) {
    atoms.push_back(ewmh.wmActionAbove());
    atoms.push_back(ewmh.wmActionBelow());
  }

  if (hasWindowFunction(WindowFunctionFullScreen))
    atoms.push_back(ewmh.wmActionFullscreen());

  if (hasWindowFunction(WindowFunctionStick))
    atoms.push_back(ewmh.wmActionStick());

  if (hasWindowFunction(WindowFunctionClose))
    atoms.push_back(ewmh.wmActionClose());

  if (atoms.empty())
    ewmh.removeProperty(client.window, ewmh.wmAllowedActions());
  else
    ewmh.setWMAllowedActions(client.window, atoms);
}


void BlackboxWindow::redrawTitle(void) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  const bt::Rect u(0, 0, frame.rect.width(), style.title_height);
  bt::drawTexture(_screen->screenNumber(),
                  (client.state.focused
                   ? style.focus.title
                   : style.unfocus.title),
                  frame.title, u, u,
                  (client.state.focused
                   ? frame.ftitle
                   : frame.utitle));
}


void BlackboxWindow::redrawLabel(void) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  bt::Rect u(0, 0, frame.label_w, style.label_height);
  Pixmap p = (client.state.focused ? frame.flabel : frame.ulabel);
  if (p == ParentRelative) {
    const bt::Texture &texture =
      (isFocused() ? style.focus.title : style.unfocus.title);
    int offset = texture.borderWidth();
    if (client.decorations & WindowDecorationIconify)
      offset += style.button_width + style.title_margin;

    const bt::Rect t(-(style.title_margin + offset),
                     -(style.title_margin + texture.borderWidth()),
                     frame.rect.width(), style.title_height);
    bt::drawTexture(_screen->screenNumber(), texture, frame.label, t, u,
                    (client.state.focused ? frame.ftitle : frame.utitle));
  } else {
    bt::drawTexture(_screen->screenNumber(),
                    (client.state.focused
                     ? style.focus.label
                     : style.unfocus.label),
                    frame.label, u, u, p);
  }

  const bt::Pen pen(_screen->screenNumber(),
                    ((client.state.focused)
                     ? style.focus.text
                     : style.unfocus.text));
  u.setCoords(u.left()  + style.label_margin,
              u.top() + style.label_margin,
              u.right() - style.label_margin,
              u.bottom() - style.label_margin);
  bt::drawText(style.font, pen, frame.label, u,
               style.alignment, client.visible_title);
}


void BlackboxWindow::redrawAllButtons(void) const {
  if (frame.iconify_button) redrawIconifyButton();
  if (frame.maximize_button) redrawMaximizeButton();
  if (frame.close_button) redrawCloseButton();
}


void BlackboxWindow::redrawIconifyButton(bool pressed) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  const bt::Rect u(0, 0, style.button_width, style.button_width);
  Pixmap p = (pressed ? frame.pbutton :
              (client.state.focused ? frame.fbutton : frame.ubutton));
  if (p == ParentRelative) {
    const bt::Texture &texture =
      (isFocused() ? style.focus.title : style.unfocus.title);
    const bt::Rect t(-(style.title_margin + texture.borderWidth()),
                     -(style.title_margin + texture.borderWidth()),
                     frame.rect.width(), style.title_height);
    bt::drawTexture(_screen->screenNumber(), texture, frame.iconify_button,
                    t, u, (client.state.focused
                           ? frame.ftitle
                           : frame.utitle));
  } else {
    bt::drawTexture(_screen->screenNumber(),
                    (pressed ? style.pressed :
                     (client.state.focused ? style.focus.button :
                      style.unfocus.button)),
                    frame.iconify_button, u, u, p);
  }

  const bt::Pen pen(_screen->screenNumber(),
                    (client.state.focused
                     ? style.focus.foreground
                     : style.unfocus.foreground));
  bt::drawBitmap(style.iconify, pen, frame.iconify_button, u);
}


void BlackboxWindow::redrawMaximizeButton(bool pressed) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  const bt::Rect u(0, 0, style.button_width, style.button_width);
  Pixmap p = (pressed ? frame.pbutton :
              (client.state.focused ? frame.fbutton : frame.ubutton));
  if (p == ParentRelative) {
    const bt::Texture &texture =
      (isFocused() ? style.focus.title : style.unfocus.title);
    int button_w = style.button_width
                   + style.title_margin + texture.borderWidth();
    if (client.decorations & WindowDecorationClose)
      button_w *= 2;
    const bt::Rect t(-(frame.rect.width() - button_w),
                     -(style.title_margin + texture.borderWidth()),
                     frame.rect.width(), style.title_height);
    bt::drawTexture(_screen->screenNumber(), texture, frame.maximize_button,
                    t, u, (client.state.focused
                           ? frame.ftitle
                           : frame.utitle));
  } else {
    bt::drawTexture(_screen->screenNumber(),
                    (pressed ? style.pressed :
                     (client.state.focused ? style.focus.button :
                      style.unfocus.button)),
                    frame.maximize_button, u, u, p);
  }

  const bt::Pen pen(_screen->screenNumber(),
                    (client.state.focused
                     ? style.focus.foreground
                     : style.unfocus.foreground));
  bt::drawBitmap(isMaximized() ? style.restore : style.maximize,
                 pen, frame.maximize_button, u);
}


void BlackboxWindow::redrawCloseButton(bool pressed) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  const bt::Rect u(0, 0, style.button_width, style.button_width);
  Pixmap p = (pressed ? frame.pbutton :
              (client.state.focused ? frame.fbutton : frame.ubutton));
  if (p == ParentRelative) {
    const bt::Texture &texture =
      (isFocused() ? style.focus.title : style.unfocus.title);
    const int button_w = style.button_width +
                         style.title_margin +
                         texture.borderWidth();
    const bt::Rect t(-(frame.rect.width() - button_w),
                     -(style.title_margin + texture.borderWidth()),
                     frame.rect.width(), style.title_height);
    bt::drawTexture(_screen->screenNumber(),texture, frame.close_button, t, u,
                    (client.state.focused ? frame.ftitle : frame.utitle));
  } else {
    bt::drawTexture(_screen->screenNumber(),
                    (pressed ? style.pressed :
                     (client.state.focused ? style.focus.button :
                      style.unfocus.button)),
                    frame.close_button, u, u, p);
  }

  const bt::Pen pen(_screen->screenNumber(),
                    (client.state.focused
                     ? style.focus.foreground
                     : style.unfocus.foreground));
  bt::drawBitmap(style.close, pen, frame.close_button, u);
}


void BlackboxWindow::redrawHandle(void) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  const bt::Rect u(0, 0, frame.rect.width(), style.handle_height);
  bt::drawTexture(_screen->screenNumber(),
                  (client.state.focused ? style.focus.handle :
                                          style.unfocus.handle),
                  frame.handle, u, u,
                  (client.state.focused ? frame.fhandle : frame.uhandle));
}


void BlackboxWindow::redrawGrips(void) const {
  const WindowStyle &style = _screen->resource().windowStyle();
  const bt::Rect u(0, 0, style.grip_width, style.handle_height);
  Pixmap p = (client.state.focused ? frame.fgrip : frame.ugrip);
  if (p == ParentRelative) {
    bt::Rect t(0, 0, frame.rect.width(), style.handle_height);
    bt::drawTexture(_screen->screenNumber(),
                    (client.state.focused ? style.focus.handle :
                                            style.unfocus.handle),
                    frame.right_grip, t, u, p);

    t.setPos(-(frame.rect.width() - style.grip_width), 0);
    bt::drawTexture(_screen->screenNumber(),
                    (client.state.focused ? style.focus.handle :
                                            style.unfocus.handle),
                    frame.right_grip, t, u, p);
  } else {
    bt::drawTexture(_screen->screenNumber(),
                    (client.state.focused ? style.focus.grip :
                                            style.unfocus.grip),
                    frame.left_grip, u, u, p);

    bt::drawTexture(_screen->screenNumber(),
                    (client.state.focused ? style.focus.grip :
                                            style.unfocus.grip),
                    frame.right_grip, u, u, p);
  }
}


void
BlackboxWindow::clientMessageEvent(const XClientMessageEvent * const event) {
  if (event->format != 32)
    return;

  const bt::EWMH& ewmh = blackbox->ewmh();

  if (event->message_type == blackbox->wmChangeStateAtom()) {
    if (event->data.l[0] == IconicState) {
      if (hasWindowFunction(WindowFunctionIconify))
        iconify();
    } else if (event->data.l[0] == NormalState) {
      activate();
    }
  } else if (event->message_type == ewmh.activeWindow()) {
    activate();
  } else if (event->message_type == ewmh.closeWindow()) {
    if (hasWindowFunction(WindowFunctionClose))
      close();
  } else if (event->message_type == ewmh.moveresizeWindow()) {
    XConfigureRequestEvent request;
    request.window = event->window;
    request.value_mask =
      (event->data.l[0] >> 8) & (CWX | CWY | CWWidth | CWHeight);
    request.x = event->data.l[1];
    request.y = event->data.l[2];
    request.width = event->data.l[3];
    request.height = event->data.l[4];

    const int gravity = (event->data.l[0] & 0xff);
    const int old_gravity = client.wmnormal.win_gravity;
    if (event->data.l[0] != 0)
      client.wmnormal.win_gravity = gravity;

    configureRequestEvent(&request);

    client.wmnormal.win_gravity = old_gravity;
  } else if (event->message_type == ewmh.wmDesktop()) {
    if (hasWindowFunction(WindowFunctionChangeWorkspace)) {
      const unsigned int new_workspace = event->data.l[0];
      changeWorkspace(new_workspace);
    }
  } else if (event->message_type == ewmh.wmState()) {
    Atom action = event->data.l[0],
          first = event->data.l[1],
         second = event->data.l[2];

    if (first == ewmh.wmStateModal() || second == ewmh.wmStateModal()) {
      if ((action == ewmh.wmStateAdd() ||
           (action == ewmh.wmStateToggle() && ! client.ewmh.modal)) &&
          isTransient())
        client.ewmh.modal = true;
      else
        client.ewmh.modal = false;
    }

    if (hasWindowFunction(WindowFunctionMaximize)) {
      bool maxh = client.ewmh.maxh;
      bool maxv = client.ewmh.maxv;

      if (first == ewmh.wmStateMaximizedHorz() ||
          second == ewmh.wmStateMaximizedHorz()) {
        if (action == ewmh.wmStateAdd() ||
            (action == ewmh.wmStateToggle() && !maxh))
          maxh = true;
        else if (action == ewmh.wmStateRemove() ||
            (action == ewmh.wmStateToggle() && maxh))
          maxh = false;
      }

      if (first == ewmh.wmStateMaximizedVert() ||
          second == ewmh.wmStateMaximizedVert()) {
        if (action == ewmh.wmStateAdd() ||
            (action == ewmh.wmStateToggle() && !maxv))
          maxv = true;
        else if (action == ewmh.wmStateRemove() ||
            (action == ewmh.wmStateToggle() && maxv))
          maxv = false;
      }
      if (maxh != client.ewmh.maxh ||
          maxv != client.ewmh.maxv)
        setMaximized(maxv, maxh);
    }

    if (hasWindowFunction(WindowFunctionShade)) {
      if (first == ewmh.wmStateShaded() ||
          second == ewmh.wmStateShaded()) {
        if (action == ewmh.wmStateRemove())
          setShaded(false);
        else if (action == ewmh.wmStateAdd())
          setShaded(true);
        else if (action == ewmh.wmStateToggle())
          setShaded(!isShaded());
      }
    }

    if (first == ewmh.wmStateSkipTaskbar()
        || second == ewmh.wmStateSkipTaskbar()
        || first == ewmh.wmStateSkipPager()
        || second == ewmh.wmStateSkipPager()) {
      if (first == ewmh.wmStateSkipTaskbar()
          || second == ewmh.wmStateSkipTaskbar()) {
        client.ewmh.skip_taskbar = (action == ewmh.wmStateAdd()
                                    || (action == ewmh.wmStateToggle()
                                        && !client.ewmh.skip_taskbar));
      }
      if (first == ewmh.wmStateSkipPager()
          || second == ewmh.wmStateSkipPager()) {
        client.ewmh.skip_pager = (action == ewmh.wmStateAdd()
                                  || (action == ewmh.wmStateToggle()
                                      && !client.ewmh.skip_pager));
      }
      // we do nothing with skip_*, but others might... we should at
      // least make sure these are present in _NET_WM_STATE
      updateEWMHState();
    }

    if (first == ewmh.wmStateHidden() ||
        second == ewmh.wmStateHidden()) {
      /*
        ignore _NET_WM_STATE_HIDDEN, the wm sets this state, not the
        application
      */
    }

    if (hasWindowFunction(WindowFunctionFullScreen)) {
      if (first == ewmh.wmStateFullscreen() ||
          second == ewmh.wmStateFullscreen()) {
        if (action == ewmh.wmStateAdd() ||
            (action == ewmh.wmStateToggle() &&
             ! client.ewmh.fullscreen)) {
          setFullScreen(true);
        } else if (action == ewmh.wmStateToggle() ||
                   action == ewmh.wmStateRemove()) {
          setFullScreen(false);
        }
      }
    }

    if (hasWindowFunction(WindowFunctionChangeLayer)) {
      if (first == ewmh.wmStateAbove() ||
          second == ewmh.wmStateAbove()) {
        if (action == ewmh.wmStateAdd() ||
            (action == ewmh.wmStateToggle() &&
             layer() != StackingList::LayerAbove)) {
          changeLayer(StackingList::LayerAbove);
        } else if (action == ewmh.wmStateToggle() ||
                   action == ewmh.wmStateRemove()) {
          changeLayer(StackingList::LayerNormal);
        }
      }

      if (first == ewmh.wmStateBelow() ||
          second == ewmh.wmStateBelow()) {
        if (action == ewmh.wmStateAdd() ||
            (action == ewmh.wmStateToggle() &&
             layer() != StackingList::LayerBelow)) {
          changeLayer(StackingList::LayerBelow);
        } else if (action == ewmh.wmStateToggle() ||
                   action == ewmh.wmStateRemove()) {
          changeLayer(StackingList::LayerNormal);
        }
      }
    }

    if (hasWindowFunction(WindowFunctionStick)) {
      if (first == ewmh.wmStateSticky() ||
          second == ewmh.wmStateSticky()) {
        if (action == ewmh.wmStateAdd() ||
            (action == ewmh.wmStateToggle() &&
             ! client.ewmh.sticky)) {
          setSticky(true);
        } else if (action == ewmh.wmStateToggle() ||
                   action == ewmh.wmStateRemove()) {
          setSticky(false);
        }
      }
    }
  }
}


void BlackboxWindow::unmapNotifyEvent(const XUnmapEvent * const event) {
  if (event->window != client.window)
    return;

#ifdef    DEBUG
  fprintf(stderr, gettext("BlackboxWindow::unmapNotifyEvent() for 0x%lx\n"),
          client.window);
#endif // DEBUG

  _screen->releaseWindow(this);
}


void
BlackboxWindow::destroyNotifyEvent(const XDestroyWindowEvent * const event) {
  if (event->window != client.window)
    return;

#ifdef    DEBUG
  fprintf(stderr, gettext("BlackboxWindow::destroyNotifyEvent() for 0x%lx\n"),
          client.window);
#endif // DEBUG

  _screen->releaseWindow(this);
}


void BlackboxWindow::reparentNotifyEvent(const XReparentEvent * const event) {
  if (event->window != client.window || event->parent == frame.plate)
    return;

#ifdef    DEBUG
  fprintf(stderr, gettext("BlackboxWindow::reparentNotifyEvent(): reparent 0x%lx to "
          "0x%lx.\n"), client.window, event->parent);
#endif // DEBUG

  /*
    put the ReparentNotify event back into the queue so that
    BlackboxWindow::restore(void) can do the right thing
  */
  XEvent replay;
  replay.xreparent = *event;
  XPutBackEvent(blackbox->XDisplay(), &replay);

  _screen->releaseWindow(this);
}


void BlackboxWindow::propertyNotifyEvent(const XPropertyEvent * const event) {
#ifdef    DEBUG
  fprintf(stderr, gettext("BlackboxWindow::propertyNotifyEvent(): for 0x%lx\n"),
          client.window);
#endif

  switch(event->atom) {
  case XA_WM_TRANSIENT_FOR: {
    if (isTransient()) {
      // remove ourselves from our transient_for
      BlackboxWindow *win = findTransientFor();
      if (win) {
        win->removeTransient(this);
      } else if (isGroupTransient()) {
        BWindowGroup *group = findWindowGroup();
        if (group)
          group->removeTransient(this);
      }
    }

    // determine if this is a transient window
    client.transient_for = ::readTransientInfo(blackbox,
                                               client.window,
                                               _screen->screenInfo(),
                                               client.wmhints);

    if (isTransient()) {
      BlackboxWindow *win = findTransientFor();
      if (win) {
        // add ourselves to our new transient_for
        win->addTransient(this);
        changeWorkspace(win->workspace());
        changeLayer(win->layer());
      } else if (isGroupTransient()) {
        BWindowGroup *group = findWindowGroup();
        if (group)
          group->addTransient(this);
      } else {
        // broken client
        client.transient_for = 0;
      }
    }

    ::update_decorations(client.decorations,
                         client.functions,
                         isTransient(),
                         client.ewmh,
                         client.motif,
                         client.wmnormal,
                         client.wmprotocols);

    reconfigure();
    break;
  }

  case XA_WM_HINTS: {
    // remove from current window group
    BWindowGroup *group = findWindowGroup();
    if (group) {
      if (isTransient() && !findTransientFor() && isGroupTransient())
        group->removeTransient(this);
      group->removeWindow(this);
      group = 0;
    }

    client.wmhints = ::readWMHints(blackbox, client.window);

    if (client.wmhints.window_group != None) {
      // add to new window group
      group = ::update_window_group(client.wmhints.window_group,
                                    blackbox,
                                    this);
      if (isTransient() && !findTransientFor() && isGroupTransient()) {
        if (group)
          group->addTransient(this);
      }
    }
    setUrgent(client.wmhints.urgency);
    break;
  }

  case XA_WM_ICON_NAME: {
    client.icon_title = ::readWMIconName(blackbox, client.window);
    if (client.state.iconic)
      _screen->propagateWindowName(this);
    break;
  }

  case XA_WM_NAME: {
    client.title = ::readWMName(blackbox, client.window);

    client.visible_title =
      bt::ellideText(client.title, frame.label_w, bt::toUnicode("..."),
                     _screen->screenNumber(),
                     _screen->resource().windowStyle().font);
    blackbox->ewmh().setWMVisibleName(client.window, client.visible_title);

    if (client.decorations & WindowDecorationTitlebar)
      redrawLabel();

    _screen->propagateWindowName(this);
    break;
  }

  case XA_WM_NORMAL_HINTS: {
    WMNormalHints wmnormal = ::readWMNormalHints(blackbox, client.window,
                                                 _screen->screenInfo());
    if (wmnormal == client.wmnormal) {
      // apps like xv and GNU emacs seem to like to repeatedly set
      // this property over and over
      break;
    }

    client.wmnormal = wmnormal;

    ::update_decorations(client.decorations,
                         client.functions,
                         isTransient(),
                         client.ewmh,
                         client.motif,
                         client.wmnormal,
                         client.wmprotocols);

    reconfigure();
    break;
  }

  default: {
    if (event->atom == blackbox->wmProtocolsAtom()) {
      client.wmprotocols = ::readWMProtocols(blackbox, client.window);

      ::update_decorations(client.decorations,
                           client.functions,
                           isTransient(),
                           client.ewmh,
                           client.motif,
                           client.wmnormal,
                           client.wmprotocols);

      reconfigure();
    } else if (event->atom == blackbox->motifWmHintsAtom()) {
      client.motif = ::readMotifWMHints(blackbox, client.window);

      ::update_decorations(client.decorations,
                           client.functions,
                           isTransient(),
                           client.ewmh,
                           client.motif,
                           client.wmnormal,
                           client.wmprotocols);

      reconfigure();
    } else if (event->atom == blackbox->ewmh().wmStrut()) {
      if (! client.strut) {
        client.strut = new bt::EWMH::Strut;
        _screen->addStrut(client.strut);
      }

      blackbox->ewmh().readWMStrut(client.window, client.strut);
      if (client.strut->left || client.strut->right ||
          client.strut->top || client.strut->bottom) {
        _screen->updateStrut();
      } else {
        _screen->removeStrut(client.strut);
        delete client.strut;
        client.strut = 0;
      }
    } else if (event->atom == blackbox->ewmh().wmStrutPartial()) {
      if (! client.strut) {
        client.strut = new bt::EWMH::Strut;
        _screen->addStrut(client.strut);
      }

      bt::EWMH::StrutPartial partial;
      blackbox->ewmh().readWMStrutPartial(client.window, &partial);
      client.strut->left = partial.left;
      client.strut->right = partial.right;
      client.strut->top = partial.top;
      client.strut->bottom = partial.bottom;
      if (client.strut->left || client.strut->right ||
          client.strut->top || client.strut->bottom) {
        _screen->updateStrut();
      } else {
        _screen->removeStrut(client.strut);
        delete client.strut;
        client.strut = 0;
      }
    }

    break;
  }
  } // switch
}


void BlackboxWindow::exposeEvent(const XExposeEvent * const event) {
#ifdef DEBUG
  fprintf(stderr, gettext("BlackboxWindow::exposeEvent() for 0x%lx\n"), client.window);
#endif

  if (frame.title == event->window)
    redrawTitle();
  else if (frame.label == event->window)
    redrawLabel();
  else if (frame.close_button == event->window)
    redrawCloseButton();
  else if (frame.maximize_button == event->window)
    redrawMaximizeButton();
  else if (frame.iconify_button == event->window)
    redrawIconifyButton();
  else if (frame.handle == event->window)
    redrawHandle();
  else if (frame.left_grip == event->window ||
           frame.right_grip == event->window)
    redrawGrips();
}


void BlackboxWindow::configureRequestEvent(const XConfigureRequestEvent *
                                           const event) {
  if (event->window != client.window || client.state.iconic)
    return;

  if (event->value_mask & CWBorderWidth)
    client.old_bw = event->border_width;

  if (event->value_mask & (CWX | CWY | CWWidth | CWHeight)) {
    bt::Rect req = frame.rect;

    if (event->value_mask & (CWX | CWY)) {
      req = ::restoreGravity(req, frame.margin, client.wmnormal.win_gravity);

      if (event->value_mask & CWX)
        req.setX(event->x);
      if (event->value_mask & CWY)
        req.setY(event->y);

      req = ::applyGravity(req, frame.margin, client.wmnormal.win_gravity);
    }

    if (event->value_mask & (CWWidth | CWHeight)) {
      if (event->value_mask & CWWidth)
        req.setWidth(event->width + frame.margin.left + frame.margin.right);
      if (event->value_mask & CWHeight)
        req.setHeight(event->height + frame.margin.top + frame.margin.bottom);
    }

    configure(req);
  }

  if (event->value_mask & CWStackMode) {
    switch (event->detail) {
    case Below:
    case BottomIf:
      _screen->lowerWindow(this);
      break;

    case Above:
    case TopIf:
    default:
      _screen->raiseWindow(this);
      break;
    }
  }
}


void BlackboxWindow::buttonPressEvent(const XButtonEvent * const event) {
#ifdef DEBUG
  fprintf(stderr, gettext("BlackboxWindow::buttonPressEvent() for 0x%lx\n"),
          client.window);
#endif

  if (frame.maximize_button == event->window) {
    if (event->button < 4)
      redrawMaximizeButton(true);
  } else if (frame.iconify_button == event->window) {
    if (event->button == 1)
      redrawIconifyButton(true);
  } else if (frame.close_button == event->window) {
    if (event->button == 1)
      redrawCloseButton(true);
  } else {
    if (event->button == 1
        || (event->button == 3 && event->state == Mod1Mask)) {
      frame.grab_x = event->x_root - frame.rect.x();
      frame.grab_y = event->y_root - frame.rect.y();

      _screen->raiseWindow(this);

      if (! client.state.focused)
        (void) setInputFocus();
      else
        XInstallColormap(blackbox->XDisplay(), client.colormap);

      if (frame.plate == event->window) {
        XAllowEvents(blackbox->XDisplay(), ReplayPointer, event->time);
      } else if ((frame.title == event->window
                  || frame.label == event->window)
                 && hasWindowFunction(WindowFunctionShade)) {
        if ((event->time - lastButtonPressTime <=
             blackbox->resource().doubleClickInterval()) ||
            event->state == ControlMask) {
          lastButtonPressTime = 0;
          setShaded(!isShaded());
        } else {
          lastButtonPressTime = event->time;
        }
      }
    } else if (event->button == 2) {
      _screen->lowerWindow(this);
    } else if (event->button == 3
               || (event->button == 3 && event->state == Mod4Mask)) {
      const int extra = _screen->resource().windowStyle().frame_border_width;
      const bt::Rect rect(client.rect.x() - extra,
                          client.rect.y() - extra,
                          client.rect.width() + (extra * 2),
                          client.rect.height() + (extra * 2));

      Windowmenu *windowmenu = _screen->windowmenu(this);
      windowmenu->popup(event->x_root, event->y_root, rect);
    } else if (blackbox->resource().shadeWindowWithMouseWheel()) {
      if (event->button == 4
       && hasWindowFunction(WindowFunctionShade)
       && !isShaded()) {
        setShaded(true);
      } else if (event->button == 5
       && hasWindowFunction(WindowFunctionShade)
       && isShaded()) {
        setShaded(false);
      }
    }
  }
}


void BlackboxWindow::buttonReleaseEvent(const XButtonEvent * const event) {
#ifdef DEBUG
  fprintf(stderr, gettext("BlackboxWindow::buttonReleaseEvent() for 0x%lx\n"),
          client.window);
#endif

  const WindowStyle &style = _screen->resource().windowStyle();
  if (event->window == frame.maximize_button) {
    if (event->button < 4) {
      if (bt::within(event->x, event->y,
                     style.button_width, style.button_width)) {
        buttonMaximize(event->button);
        _screen->raiseWindow(this);
      } else {
        redrawMaximizeButton();
      }
    }
  } else if (event->window == frame.iconify_button) {
    if (event->button == 1) {
      if (bt::within(event->x, event->y,
                     style.button_width, style.button_width))
        iconify();
      else
        redrawIconifyButton();
    }
  } else if (event->window == frame.close_button) {
    if (event->button == 1) {
      if (bt::within(event->x, event->y,
                     style.button_width, style.button_width))
        close();
      redrawCloseButton();
    }
  } else if (client.state.moving) {
    finishMove();
  } else if (client.state.resizing) {
    finishResize();
  } else if (event->window == frame.window) {
    if (event->button == 2 && event->state == Mod1Mask)
      XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());
  }
}


void BlackboxWindow::motionNotifyEvent(const XMotionEvent * const event) {
#ifdef DEBUG
  fprintf(stderr, gettext("BlackboxWindow::motionNotifyEvent() for 0x%lx\n"),
          client.window);
#endif

  if (hasWindowFunction(WindowFunctionMove)
      && !client.state.resizing
      && event->state & Button1Mask
      && (frame.title == event->window || frame.label == event->window
          || frame.handle == event->window || frame.window == event->window)) {
    if (! client.state.moving)
      startMove();
    else
      continueMove(event->x_root, event->y_root);
  } else if (hasWindowFunction(WindowFunctionResize)
             && ((event->state & Button1Mask
                 && (event->window == frame.right_grip
                     || event->window == frame.left_grip))
             || (event->state & Button3Mask
                 && event->state & Mod1Mask
                 && event->window == frame.window))) {
    if (!client.state.resizing)
      startResize(event->window);
    else
      continueResize(event->x_root, event->y_root);
  }
}


void BlackboxWindow::enterNotifyEvent(const XCrossingEvent * const event) {
  if (event->window != frame.window || event->mode != NotifyNormal)
    return;

  if (blackbox->resource().focusModel() == ClickToFocusModel || !isVisible())
    return;

  switch (windowType()) {
  case WindowTypeDesktop:
  case WindowTypeDock:
  case WindowTypeCombo:
  case WindowTypeDropdownMenu:
  case WindowTypePopupMenu:
  case WindowTypeDnd:
  case WindowTypeTooltip:
    // these types cannot be focused w/ sloppy focus
    return;

  default:
    break;
  }

  XEvent next;
  bool leave = False, inferior = False;

  while (XCheckTypedWindowEvent(blackbox->XDisplay(), event->window,
                                LeaveNotify, &next)) {
    if (next.type == LeaveNotify && next.xcrossing.mode == NotifyNormal) {
      leave = True;
      inferior = (next.xcrossing.detail == NotifyInferior);
    }
  }

  if ((! leave || inferior) && ! isFocused())
    (void) setInputFocus();

  if (blackbox->resource().autoRaise())
    timer->start();
}


void
BlackboxWindow::leaveNotifyEvent(const XCrossingEvent * const /*unused*/) {
  if (!(blackbox->resource().focusModel() == SloppyFocusModel
        && blackbox->resource().autoRaise()))
    return;

  if (timer->isTiming())
    timer->stop();
}


#ifdef    SHAPE
void BlackboxWindow::shapeEvent(const XEvent * const /*unused*/)
{ if (client.state.shaped) configureShape(); }
#endif // SHAPE


/*
 *
 */
void BlackboxWindow::restore(void) {
  XChangeSaveSet(blackbox->XDisplay(), client.window, SetModeDelete);
  XSelectInput(blackbox->XDisplay(), client.window, NoEventMask);
  XSelectInput(blackbox->XDisplay(), frame.plate, NoEventMask);

  client.state.visible = false;

  /*
    remove WM_STATE unless the we are shutting down (in which case we
    want to make sure we preserve the state across restarts).
  */
  if (!blackbox->shuttingDown()) {
    clearState(blackbox, client.window);
  } else if (isShaded() && !isIconic()) {
    // do not leave a shaded window as an icon unless it was an icon
    setState(NormalState);
  }

  client.rect = ::restoreGravity(frame.rect, frame.margin,
                                 client.wmnormal.win_gravity);

  blackbox->XGrabServer();

  XUnmapWindow(blackbox->XDisplay(), frame.window);
  XUnmapWindow(blackbox->XDisplay(), client.window);

  XSetWindowBorderWidth(blackbox->XDisplay(), client.window, client.old_bw);
  if (isMaximized()) {
    // preserve the original size
    client.rect = client.premax;
    XMoveResizeWindow(blackbox->XDisplay(), client.window,
                      client.premax.x(),
                      client.premax.y(),
                      client.premax.width(),
                      client.premax.height());
  } else {
    XMoveWindow(blackbox->XDisplay(), client.window,
                client.rect.x() - frame.rect.x(),
                client.rect.y() - frame.rect.y());
  }

  blackbox->XUngrabServer();

  XEvent unused;
  if (!XCheckTypedWindowEvent(blackbox->XDisplay(), client.window,
                              ReparentNotify, &unused)) {
    /*
      according to the ICCCM, the window manager is responsible for
      reparenting the window back to root... however, we don't want to
      do this if the window has been reparented by someone else
      (i.e. not us).
    */
    XReparentWindow(blackbox->XDisplay(), client.window,
                    _screen->screenInfo().rootWindow(),
                    client.rect.x(), client.rect.y());
  }

  if (blackbox->shuttingDown())
    XMapWindow(blackbox->XDisplay(), client.window);
}


// timer for autoraise
void BlackboxWindow::timeout(bt::Timer *)
{ _screen->raiseWindow(this); }


void BlackboxWindow::startMove() {
  // begin a move
  XGrabPointer(blackbox->XDisplay(), frame.window, false,
               Button1MotionMask | ButtonReleaseMask,
               GrabModeAsync, GrabModeAsync, None,
               blackbox->resource().cursors().move, blackbox->XTime());

  client.state.moving = true;

  if (! blackbox->resource().opaqueMove()) {
    blackbox->XGrabServer();

    frame.changing = frame.rect;
    _screen->showGeometry(BScreen::Position, frame.changing);

    bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
    const int bw = _screen->resource().windowStyle().frame_border_width,
              hw = bw / 2;
    pen.setGCFunction(GXxor);
    pen.setLineWidth(bw);
    pen.setSubWindowMode(IncludeInferiors);
    XDrawRectangle(blackbox->XDisplay(), _screen->screenInfo().rootWindow(),
                   pen.gc(),
                   frame.changing.x() + hw,
                   frame.changing.y() + hw,
                   frame.changing.width() - bw,
                   frame.changing.height() - bw);
  }
}


static
void collisionAdjust(int *dx, int *dy, int x, int y,
                     unsigned int width, unsigned int height,
                     const bt::Rect& rect, int snap_distance,
                     bool snapCenter = false)
{
  // window corners
  const int wleft = x,
           wright = x + width - 1,
             wtop = y,
          wbottom = y + height - 1,
  // left, right, top + bottom are for rect, douterleft = left border of rect
       dinnerleft = abs(wleft - rect.left()),
      dinnerright = abs(wright - rect.right()),
        dinnertop = abs(wtop - rect.top()),
     dinnerbottom = abs(wbottom - rect.bottom()),
       douterleft = abs(wright - rect.left()),
      douterright = abs(wleft - rect.right()),
        doutertop = abs(wbottom - rect.top()),
     douterbottom = abs(wtop - rect.bottom());

  if ((wtop <= rect.bottom() && wbottom >= rect.top())
      || doutertop <= snap_distance
      || douterbottom <= snap_distance) {
    // snap left or right
    if (douterleft <= dinnerleft && douterleft <= snap_distance)
      // snap outer left
      *dx = (x - (rect.left() - width));
    else if (douterright <= dinnerright && douterright <= snap_distance)
      // snap outer right
      *dx = (x - rect.right() - 1);
    else if (dinnerleft <= dinnerright && dinnerleft < snap_distance)
      // snap inner left
      *dx = (x - rect.left());
    else if (dinnerright < snap_distance)
      // snap inner right
      *dx = (x - (rect.right() - width + 1));
  }

  if ((wleft <= rect.right() && wright >= rect.left())
      || douterleft <= snap_distance
      || douterright <= snap_distance) {
    // snap top or bottom
    if (doutertop <= dinnertop && doutertop <= snap_distance)
      // snap outer top
      *dy = (y - (rect.top() - height));
    else if (douterbottom <= dinnerbottom && douterbottom <= snap_distance)
      // snap outer bottom
      *dy = (y - rect.bottom() - 1);
    else if (dinnertop <= dinnerbottom && dinnertop < snap_distance)
      // snap inner top
      *dy = (y - rect.top());
    else if (dinnerbottom < snap_distance)
      // snap inner bottom
      *dy = (y - (rect.bottom() - height + 1));
  }

  if (snapCenter) {
    const int cwx = x + width / 2;
    const int cwy = y + height / 2;
    const int crx = rect.x() + rect.width() / 2;
    const int cry = rect.y() + rect.height() / 2;
    const int cdx = abs(cwx - crx);
    const int cdy = abs(cwy - cry);
    if (cdx <= snap_distance)
      // snap to horizontal center
      *dx = x - (rect.x() + ((rect.width() - width) / 2));
    if (cdy <= snap_distance)
      // snap to vertical center
      *dy = y - (rect.y() + ((rect.height() - height) / 2));
  }
}


void BlackboxWindow::snapAdjust(int *x, int *y) {
  int nx, ny, dx, dy, init_dx, init_dy;
  const int edge_distance = blackbox->resource().edgeSnapThreshold();
  const int win_distance  = blackbox->resource().windowSnapThreshold();

  nx = (win_distance > edge_distance) ? win_distance : edge_distance;
  ny = (win_distance > edge_distance) ? win_distance : edge_distance;
  dx = init_dx = ++nx; dy = init_dy = ++ny;

  if (edge_distance) {
    collisionAdjust(&dx, &dy, *x, *y, frame.rect.width(), frame.rect.height(),
                    _screen->availableArea(), edge_distance, true);
    nx = (dx != init_dx && abs(dx) < abs(nx)) ? dx : nx; dx = init_dx;
    ny = (dy != init_dy && abs(dy) < abs(ny)) ? dy : ny; dy = init_dy;
    if (!blackbox->resource().fullMaximization()) {
      collisionAdjust(&dx, &dy, *x, *y, frame.rect.width(), frame.rect.height(),
                      _screen->screenInfo().rect(), edge_distance);
      nx = (dx != init_dx && abs(dx) < abs(nx)) ? dx : nx; dx = init_dx;
      ny = (dy != init_dy && abs(dy) < abs(ny)) ? dy : ny; dy = init_dy;
    }
  }
  if (win_distance) {
    StackingList::const_iterator it = _screen->stackingList().begin(),
                                end = _screen->stackingList().end();
    for (; it != end; ++it) {
      BlackboxWindow * const win = dynamic_cast<BlackboxWindow *>(*it);
      if (win && win != this &&
          (win->workspace() == _screen->currentWorkspace()
           || win->workspace() == bt::BSENTINEL)) {
        collisionAdjust(&dx, &dy, *x, *y, frame.rect.width(),
                        frame.rect.height(), win->frame.rect, win_distance);
        nx = (dx != init_dx && abs(dx) < abs(nx)) ? dx : nx; dx = init_dx;
        ny = (dy != init_dy && abs(dy) < abs(ny)) ? dy : ny; dy = init_dy;
      }
    }
  }

  *x = (nx != init_dx) ? (*x - nx) : *x;
  *y = (ny != init_dy) ? (*y - ny) : *y;
}


void BlackboxWindow::continueMove(int x_root, int y_root) {
  int dx = x_root - frame.grab_x, dy = y_root - frame.grab_y;

  snapAdjust(&dx, &dy);

  if (blackbox->resource().opaqueMove()) {
    configure(dx, dy, frame.rect.width(), frame.rect.height());
  } else {
    bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
    const int bw = _screen->resource().windowStyle().frame_border_width,
              hw = bw / 2;
    pen.setGCFunction(GXxor);
    pen.setLineWidth(bw);
    pen.setSubWindowMode(IncludeInferiors);
    XDrawRectangle(blackbox->XDisplay(), _screen->screenInfo().rootWindow(),
                   pen.gc(),
                   frame.changing.x() + hw,
                   frame.changing.y() + hw,
                   frame.changing.width() - bw,
                   frame.changing.height() - bw);

    frame.changing.setPos(dx, dy);

    XDrawRectangle(blackbox->XDisplay(), _screen->screenInfo().rootWindow(),
                   pen.gc(),
                   frame.changing.x() + hw,
                   frame.changing.y() + hw,
                   frame.changing.width() - bw,
                   frame.changing.height() - bw);
  }

  _screen->showGeometry(BScreen::Position, bt::Rect(dx, dy, 0, 0));
}


void BlackboxWindow::finishMove() {
  XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());

  client.state.moving = false;

  if (!blackbox->resource().opaqueMove()) {
    bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
    const int bw = _screen->resource().windowStyle().frame_border_width,
              hw = bw / 2;
    pen.setGCFunction(GXxor);
    pen.setLineWidth(bw);
    pen.setSubWindowMode(IncludeInferiors);
    XDrawRectangle(blackbox->XDisplay(),
                   _screen->screenInfo().rootWindow(),
                   pen.gc(),
                   frame.changing.x() + hw,
                   frame.changing.y() + hw,
                   frame.changing.width() - bw,
                   frame.changing.height() - bw);
    blackbox->XUngrabServer();

    configure(frame.changing);
  } else {
    configure(frame.rect);
  }

  _screen->hideGeometry();
}


void BlackboxWindow::startResize(Window window) {
  if (frame.grab_x < (signed) frame.rect.width() / 2) {
    if (frame.grab_y < (signed) frame.rect.height() / 2)
      frame.corner = BottomRight;
    else
      frame.corner = TopRight;
  } else {
    if (frame.grab_y < (signed) frame.rect.height() / 2)
      frame.corner = BottomLeft;
    else
      frame.corner = TopLeft;
  }

  Cursor cursor = None;
  switch (frame.corner) {
  case TopLeft:
    cursor = blackbox->resource().cursors().resize_bottom_right;
    frame.grab_x = frame.rect.width() - frame.grab_x;
    frame.grab_y = frame.rect.height() - frame.grab_y;
    break;
  case BottomLeft:
    cursor = blackbox->resource().cursors().resize_top_right;
    frame.grab_x = frame.rect.width() - frame.grab_x;
    break;
  case TopRight:
    cursor = blackbox->resource().cursors().resize_bottom_left;
    frame.grab_y = frame.rect.height() - frame.grab_y;
    break;
  case BottomRight:
    cursor = blackbox->resource().cursors().resize_top_left;
    break;
  }

  // begin a resize
  XGrabPointer(blackbox->XDisplay(), window, False,
               ButtonMotionMask | ButtonReleaseMask,
               GrabModeAsync, GrabModeAsync, None, cursor, blackbox->XTime());

  client.state.resizing = true;

  frame.changing = constrain(frame.rect, frame.margin, client.wmnormal,
                             Corner(frame.corner));

  if (!blackbox->resource().opaqueResize()) {
    blackbox->XGrabServer();

    bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
    const int bw = _screen->resource().windowStyle().frame_border_width,
              hw = bw / 2;
    pen.setGCFunction(GXxor);
    pen.setLineWidth(bw);
    pen.setSubWindowMode(IncludeInferiors);
    XDrawRectangle(blackbox->XDisplay(),
                   _screen->screenInfo().rootWindow(),
                   pen.gc(),
                   frame.changing.x() + hw,
                   frame.changing.y() + hw,
                   frame.changing.width() - bw,
                   frame.changing.height() - bw);
  } else {
    // unset maximized state when resized
    if (isMaximized())
      setMaximized(false, false);
  }

  showGeometry(frame.changing);
}


void BlackboxWindow::continueResize(int x_root, int y_root) {
  // continue a resize
  const bt::Rect curr = frame.changing;

  switch (frame.corner) {
  case TopLeft:
  case BottomLeft:
    frame.changing.setCoords(frame.changing.left(),
                             frame.changing.top(),
                             std::max<signed>(x_root + frame.grab_x,
                                              frame.changing.left()
                                              + (frame.margin.left
                                                 + frame.margin.right + 1)),
                             frame.changing.bottom());
    break;
  case TopRight:
  case BottomRight:
    frame.changing.setCoords(std::min<signed>(x_root - frame.grab_x,
                                              frame.changing.right()
                                              - (frame.margin.left
                                                 + frame.margin.right + 1)),
                             frame.changing.top(),
                             frame.changing.right(),
                             frame.changing.bottom());
    break;
  }

  switch (frame.corner) {
  case TopLeft:
  case TopRight:
    frame.changing.setCoords(frame.changing.left(),
                             frame.changing.top(),
                             frame.changing.right(),
                             std::max<signed>(y_root + frame.grab_y,
                                              frame.changing.top()
                                              + (frame.margin.top
                                                 + frame.margin.bottom + 1)));
    break;
  case BottomLeft:
  case BottomRight:
    frame.changing.setCoords(frame.changing.left(),
                             std::min<signed>(y_root - frame.grab_y,
                                              frame.rect.bottom()
                                              - (frame.margin.top
                                                 + frame.margin.bottom + 1)),
                             frame.changing.right(),
                             frame.changing.bottom());
    break;
  }

  frame.changing = constrain(frame.changing, frame.margin, client.wmnormal,
                             Corner(frame.corner));

  if (curr != frame.changing) {
    if (blackbox->resource().opaqueResize()) {
      configure(frame.changing);
    } else {
      bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
      const int bw = _screen->resource().windowStyle().frame_border_width,
                hw = bw / 2;
      pen.setGCFunction(GXxor);
      pen.setLineWidth(bw);
      pen.setSubWindowMode(IncludeInferiors);
      XDrawRectangle(blackbox->XDisplay(),
                     _screen->screenInfo().rootWindow(),
                     pen.gc(),
                     curr.x() + hw,
                     curr.y() + hw,
                     curr.width() - bw,
                     curr.height() - bw);

      XDrawRectangle(blackbox->XDisplay(),
                     _screen->screenInfo().rootWindow(),
                     pen.gc(),
                     frame.changing.x() + hw,
                     frame.changing.y() + hw,
                     frame.changing.width() - bw,
                     frame.changing.height() - bw);
    }

    showGeometry(frame.changing);
  }
}


void BlackboxWindow::finishResize() {

  if (!blackbox->resource().opaqueResize()) {
    bt::Pen pen(_screen->screenNumber(), bt::Color(0xff, 0xff, 0xff));
    const int bw = _screen->resource().windowStyle().frame_border_width,
              hw = bw / 2;
    pen.setGCFunction(GXxor);
    pen.setLineWidth(bw);
    pen.setSubWindowMode(IncludeInferiors);
    XDrawRectangle(blackbox->XDisplay(),
                   _screen->screenInfo().rootWindow(),
                   pen.gc(),
                   frame.changing.x() + hw,
                   frame.changing.y() + hw,
                   frame.changing.width() - bw,
                   frame.changing.height() - bw);

    blackbox->XUngrabServer();

    // unset maximized state when resized
    if (isMaximized())
      setMaximized(false, false);
  }

  client.state.resizing = false;

  XUngrabPointer(blackbox->XDisplay(), blackbox->XTime());

  _screen->hideGeometry();

  frame.changing = constrain(frame.changing, frame.margin, client.wmnormal,
                             Corner(frame.corner));
  configure(frame.changing);
}


/*
 * show the geometry of the window based on rectangle r.
 * The logical width and height are used here.  This refers to the user's
 * perception of the window size (for example an xterm resizes in cells,
 * not in pixels).  No extra work is needed if there is no difference between
 * the logical and actual dimensions.
 */
void BlackboxWindow::showGeometry(const bt::Rect &r) const {
  unsigned int w = r.width(), h = r.height();

  // remove the window frame
  w -= frame.margin.left + frame.margin.right;
  h -= frame.margin.top + frame.margin.bottom;

  if (client.wmnormal.flags & PResizeInc) {
    if (client.wmnormal.flags & (PMinSize|PBaseSize)) {
      w -= ((client.wmnormal.base_width)
            ? client.wmnormal.base_width
            : client.wmnormal.min_width);
      h -= ((client.wmnormal.base_height)
            ? client.wmnormal.base_height
            : client.wmnormal.min_height);
    }

    w /= client.wmnormal.width_inc;
    h /= client.wmnormal.height_inc;
  }

  _screen->showGeometry(BScreen::Size, bt::Rect(0, 0, w, h));
}


// see my rant above for an explanation of this operator
bool operator==(const WMNormalHints &x, const WMNormalHints &y) {
  return (x.flags == y.flags
          && x.min_width == y.min_width
          && x.min_height == y.min_height
          && x.max_width == y.max_width
          && x.max_height == y.max_height
          && x.width_inc == y.width_inc
          && x.height_inc == y.height_inc
          && x.min_aspect_x == y.min_aspect_x
          && x.min_aspect_y == y.min_aspect_y
          && x.max_aspect_x == y.max_aspect_x
          && x.max_aspect_y == y.max_aspect_y
          && x.base_width == y.base_width
          && x.base_height == y.base_height
          && x.win_gravity == y.win_gravity);
}
