//-*-c++-*-
// -------------------------------------------
// RCS data:
// $Date: 2003/07/03 13:25:29 $
// $Revision: 1.2 $
// $Source: /cvsroot/miwm/miwm/miwm/manage.cc,v $
// $Id: manage.cc,v 1.2 2003/07/03 13:25:29 sgbeal Exp $
// $RCSfile: manage.cc,v $
// -------------------------------------------
// Copyright by Ben Paul Wise.
// -------------------------------------------
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// -------------------------------------------

#include	<stdio.h>
#include	<stdlib.h>

#include	<X11/X.h>
#include	<X11/Xlib.h>
#include	<X11/Xutil.h>
#include	<X11/Xatom.h>

#include "miwm.h"

void setshape(Client *);
static int getprop(Window, Atom, Atom, long, unsigned char **);
static int getstate(Window, int *);
static void apply_gravity(Client *);

//-------------------------------------------------

static void
apply_gravity(Client *c)
{
  if (c->size.flags & PWinGravity) {
    switch (c->size.win_gravity) {
    case NorthEastGravity:
      c->size.x -= 2 * border;
      break;
    case SouthWestGravity:
      c->size.y -= 2 * border;
      break;
    case SouthEastGravity:
      c->size.x -= 2 * border;
      c->size.y -= 2 * border;
      break;
    }
  }
}

void
getTransientFor(Client *c) {
  Window	trans = None;
  XGetTransientForHint(dpy, c->window, &trans);
  c->trans = trans;
}

void
withdraw(Client *c) {
  if (c->parent != root) {
    XUnmapWindow(dpy, c->parent);
    c->isMapped = 0; // is this right?
    XReparentWindow(dpy, c->parent, root, c->size.x, c->size.y);
  }

  XRemoveFromSaveSet(dpy, c->window);
  setClientState(c, WithdrawnState);


//  Flush and ignore any errors. X11 sends us an UnmapNotify before it
//  sends us a DestroyNotify. That means we can get here without knowing
//  whether the relevant window still exists.

  ignore_badwindow = 1;
  XSync(dpy, False);
  ignore_badwindow = 0;
}

static void
installcmap(Colormap cmap) {
  if (cmap == None)
    cmap = DefaultColormap(dpy, DefaultScreen(dpy));
  XInstallColormap(dpy, cmap);
}

void
cmapfocus(Client *c) {
  int	i;
  int	found;
  Client	*cc;

  if (c == 0)
    installcmap(None);
  else if (c->ncmapwins != 0) {
    found = 0;
    for (i = c->ncmapwins - 1; i >= 0; i--) {
      installcmap(c->wmcmaps[i]);
      if (c->cmapwins[i] == c->window)
	found++;
    }
    if (!found)
      installcmap(c->cmap);
  } else if (c->trans != None && (cc = getClient(c->trans)) != 0 &&
	     cc->ncmapwins != 0)
    cmapfocus(cc);
  else
    installcmap(c->cmap);
}

void
getColormaps(Client *c) {
  int	n;
  int	i;
  Window	*cw;
  XWindowAttributes	attr;

  if (c == 0)
    return;

  n = getprop(c->window, wm_colormaps, XA_WINDOW, 100L, (unsigned char **) &cw);
  if (c->ncmapwins != 0) {
    XFree(c->cmapwins);
    free(c->wmcmaps);
  }
  if (n <= 0) {
    c->ncmapwins = 0;
    return;
  }
  c->ncmapwins = n;
  c->cmapwins = cw;

  c->wmcmaps = (Colormap *) malloc(n * sizeof(Colormap));
  for (i = 0; i < n; i++) {
    if (cw[i] == c->window)
      c->wmcmaps[i] = c->cmap;
    else {
      XSelectInput(dpy, cw[i], ColormapChangeMask);
      XGetWindowAttributes(dpy, cw[i], &attr);
      c->wmcmaps[i] = attr.colormap;
    }
  }
}

void
// Terminate(int signal)
Terminate(int) { // parameter signal is unused
  cout << endl;
  cout << "Terminating MIWM ... ";

  delete theWM;
  theWM = NULL;


  // Give up the input focus and the colormap. 
  XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
  installcmap(None);

  XCloseDisplay(dpy);

  StopMemoryLeakCheck();

  cout << "Terminated MIWM at ";
  cout << __TIME__<<", "<<__DATE__<<endl<<flush;

  exit(EXIT_FAILURE);

  return;
}


static int
getprop(Window w, Atom a, Atom type, long len, unsigned char **p) {
  Atom	real_type;
  int	format;
  unsigned long	n;
  unsigned long	extra;
  int	status;

  // len is in 32-bit multiples.
  status = XGetWindowProperty(dpy, w, a, 0L, len, False, type, &real_type, &format, &n, &extra, p);
  if (status != Success || *p == 0)
    return -1;
  if (n == 0)
    XFree(*p);

  // could check real_type, format, extra here...

  return n;
}

void
getWindowName(Client *c) {
  char * name;
  Atom actual_type;
  int format;
  unsigned long n;
  unsigned long extra;
  XClassHint * class_hints;
  int was_nameless;

  if (c == 0)
    return;

  was_nameless = (c->name == 0);

//  This hack is necessary because xwsh uses
//  COMPOUND_TEXT rather than STRING for its WM_NAME property,
//  and anonymous xwsh windows are annoying.
   
  if (XGetWindowProperty(dpy, c->window, XA_WM_NAME, 0L, 100L,
			 False, AnyPropertyType, &actual_type, &format, &n,
			 &extra, (unsigned char **) &name) == Success &&
      name && *name != '\0' && n != 0) {
    if (c->name != 0)
      XFree(c->name);
    c->name = name;
    // this pair of lines mysteriously locks up the screen,
    // keyboard, and mouse...
    //    c->name = stringCopy(name);
    //    XFree(name);
    if (!was_nameless)
      theWM->drawClientBorder(c, c == current);
  }
	
  if (c->name == 0)
    return;

  class_hints = XAllocClassHint();
  if (class_hints != 0) {
    XGetClassHint(dpy, c->window, class_hints);
    if (class_hints->res_name != 0 && !strcmp(class_hints->res_name, c->name)) {
      XFree(c->name);
      c->name = 0;
      XFree(c->fallback_name);
      c->fallback_name = stringCopy(class_hints->res_name);
    }
    XFree(class_hints->res_name);
    XFree(class_hints->res_class);
    XFree(class_hints);
  }
  return;
}

void
getNormalHints(Client *c) {
  int x, y, w, h;
  long msize;
  float innerMinimumFactor = 1.0;

// We have to be careful. The ICCCM says that the x, y
// and width, height components are not used. But we use them, so
// we need to save and restore them whenever we fill the size struct.
  x = c->size.x;
  y = c->size.y;
  w = c->size.width;
  h = c->size.height;

//  Do the get.
  if (XGetWMNormalHints(dpy, c->window, &c->size, &msize) == 0)
    c->size.flags = 0;


// Correct the minimum allowable size of this client to take account of
// the window border.

  if (c->size.flags & PMinSize) {
    c->size.min_width  += 2 * border;
    c->size.min_height += 2 * border + titleHeight();
  } else {
    c->size.flags |= PMinSize;
    c->size.min_width  = ((int) ((2 * border) + (innerMinimumFactor * border)));
    if (c->accepts_focus)
      c->size.min_height = ((int) ((2 * border + titleHeight()) + (innerMinimumFactor * border)));
    else
      c->size.min_height = ((int) ((2 * border) + (innerMinimumFactor * border)));
  }

// Correct the maximum allowable size of this client to take account of
//  the window border.
  if (c->size.flags & PMaxSize) {
    c->size.max_width  += 2 * border;
    c->size.max_height += 2 * border + titleHeight();
  }

//  Ensure that the base width & height and the width & height increments
//  are set correctly so that we do not have problems resizing
  if (!(c->size.flags & PBaseSize))
    c->size.base_width = c->size.base_height = 0;

  if (!(c->size.flags & PResizeInc))
    c->size.width_inc = c->size.height_inc = 1;

//  If the client gives identical minimum and maximum sizes, we do not
//  want the user to resize in that direction.
  if (c->size.min_width == c->size.max_width)
    c->size.width_inc = 0;

  if (c->size.min_height == c->size.max_height)
    c->size.height_inc = 0;

//  Restore the window-manager bits.
  c->size.x = x;
  c->size.y = y;
  c->size.width = w;
  c->size.height = h;
}

static int
getstate(Window w, int *state) {
  long	*p = 0;

  if (getprop(w, wm_state, wm_state, 2L, (unsigned char **) &p) <= 0)
    return 0;

  *state = (int) *p;
  XFree(p);
  return 1;
}


//-------------------------------------------
// class related stuff
//-------------------------------------------

// manage takes a new client (see scanWindowTree)
void
WindowManager::manageClient(Client * c, bool preexisting )  {
  int dohide;
  int state;
  int th = titleHeightc(c);
  XWMHints * hints;
  XWindowAttributes current_attr;
  XSetWindowAttributes attr;

  // For WM_PROTOCOLS handling. 
  Atom	*protocols;
  int	n;
  int	p;

  // Get the hints, window name, and normal hints (see ICCCM
  // section 4.1.2.3).
  hints = XGetWMHints(dpy, c->window);

  getWindowName(c);
  getNormalHints(c);

//  Get the colormaps associated with this window. Get the window
//  attribute colormap first, then look to see if the
//  WM_COLORMAP_WINDOWS property has been used to specify windows
//  needing colormaps that differ from the top-level colormap.
//  (See ICCCM section 4.1.8.)
  XGetWindowAttributes(dpy, c->window, &current_attr);
  c->cmap = current_attr.colormap;

  getColormaps(c);


//  Scan the list of atoms on WM_PROTOCOLS to see which of the
//  protocols that we understand the client is prepared to
//  participate in. (See ICCCM section 4.1.2.7.)
  if (XGetWMProtocols(dpy, c->window, &protocols, &n) != 0) {
    for (p = 0; p < n; p++) {
      if (protocols[p] == wm_delete)
	c->proto |= Pdelete;
      else if (protocols[p] == wm_take_focus)
	c->proto |= Ptakefocus;
    }

    XFree(protocols);
  }

  // Get the WM_TRANSIENT_FOR property (see ICCCM section 4.1.2.6). 
  getTransientFor(c);

//  Work out details for the Client structure from the hints. 
  if (hints && (hints->flags & InputHint))
    c->accepts_focus = hints->input;

  if (!getstate(c->window, &state))
    state = hints ? hints->initial_state : NormalState;
  dohide = (state == IconicState);


// Sort out window position.
  {
    Window root_window;
    int x, y;
    unsigned int w, h;
    unsigned int border_width, depth;

    XGetGeometry(dpy, c->window, &root_window, &x, &y, &w, &h,
		 &border_width, &depth);

//  Do the size first. Note that
//  "The size specifiers refer to the width and height of the
//  client excluding borders" -- ICCCM 4.1.2.3.
    c->size.width  = w + 2 * border;
    c->size.height = h + 2 * border;

//  OpenGL programs often appear smaller than their
//  minimum sizes, which they do not handle well.
    if (c->size.width < c->size.min_width)
      c->size.width = c->size.min_width;
    if (c->size.height < c->size.min_height)
      c->size.height = c->size.min_height;


    //  Do the position next. 
    // assign to the current workspace
    c->workSpace = theWM->workSpace;


//  If we have a user-specified position for a top-level window,
//  or a program-specified position for a dialogue box, we'll
//  take it.

    if (c->trans != None && c->size.flags & PPosition) {
      // It is  a "dialogue box". Trust it. 
      c->size.x = x;
      c->size.y = y;
    } else if (c->size.flags & USPosition) {
      // Use the specified window position.
      c->size.x = x;
      c->size.y = y;


//  We need to be careful of the right-hand edge and
//  bottom. We can use the window gravity (if specified)
//  to handle this. (See section 4.1.2.3 of the ICCCM.)
      apply_gravity(c);
    } else 
      { // No position was specified: use the auto-placement heuristics.
	if( ! preexisting ) theWM->autoPlaceCoords(c);
      } 
  }

  // absolutely ensure the top-left corner is on the screen.
  c->size.x = imax(0 , c->size.x);
  c->size.x = imin(c->size.x, display_width - 2 * border);
  c->size.y = imax(th , c->size.y);
  c->size.y = imin(c->size.y, display_height - 2 * border);

  if (hints)
    XFree(hints);

  // all the reparenting stuff.

  // I could ignore the shaped windows, but that makes
  // it impossible to move/resize/hide/kill them. So
  // I go ahead and do it, but it is hard to get hold of
  // shaped clients like xeyes or oclock.

//   if (!isshaped(c->window)) 
  if (1)
    {
    c->parent = XCreateSimpleWindow(dpy, root, c->size.x, c->size.y,
				    c->size.width, c->size.height, 1, 
				    theWM->parentWindowColor.pixel, theWM->inactiveBorderColor.pixel);

    attr.event_mask = ExposureMask | EnterWindowMask | ButtonMask |
      SubstructureRedirectMask | SubstructureNotifyMask;
    attr.cursor = frame_cursor;
    XChangeWindowAttributes(dpy, c->parent, CWCursor | CWEventMask,
			    &attr);

    XResizeWindow(dpy, c->window, c->size.width - 2 * border,
		  c->size.height - 2 * border);
  }

  //  X11 doe no't let us change border width. It's a window
  // attribute, but it is somehow different.
  XSetWindowBorderWidth(dpy, c->window, 0);

  attr.event_mask = ColormapChangeMask | EnterWindowMask | //LeaveWindowMask |
    PropertyChangeMask;
  attr.win_gravity = StaticGravity;
  attr.do_not_propagate_mask =  ButtonMask;
  XChangeWindowAttributes(dpy, c->window, CWEventMask | CWWinGravity |
			  CWDontPropagate, &attr);

  XReparentWindow(dpy, c->window, c->parent, border, border);

  setshape(c);

  XAddToSaveSet(dpy, c->window);
  if (dohide)
    hide(c);
  else {
    // Map the new window in the relevant state.
    c->hidden = False;
    XMapWindow(dpy, c->parent);
    XMapWindow(dpy, c->window);
    c->isMapped = 1;
    setactive(c, 0, 0L);
    setClientState(c, NormalState);
  }

  if (current != c)
    cmapfocus(current);

  return;
}

// -------------------------------------------
// end of manage.cc
// -------------------------------------------
