/*
 * Window management.
 */

/* Copyright (c) 1994-1996 David Hogan, see README for licence details */
#include <stdio.h>
#include <stdlib.h>
#include <X11/X.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include "dat.h"
#include "fns.h"

int isNew;

int
manage(Client *c, int mapped)
{
	int fixsize, dohide, doreshape, state;
	long msize;
	XClassHint class;
	XWMHints *hints;
	XSetWindowAttributes attrs;

	trace("manage", c, 0);
	XSelectInput(dpy, c->window, ColormapChangeMask | EnterWindowMask | PropertyChangeMask | FocusChangeMask | KeyPressMask);

	/* Get loads of hints */

	if(XGetClassHint(dpy, c->window, &class) != 0){	/* ``Success'' */
		c->instance = class.res_name;
		c->class = class.res_class;
		c->is9term = 0;
		if(isNew){
			c->is9term = strstr(c->class, "term") || strstr(c->class, "Term");
			isNew = 0;
		}
	}
	else {
		c->instance = 0;
		c->class = 0;
		c->is9term = 0;
	}
	c->iconname = getprop(c->window, XA_WM_ICON_NAME);
	c->name = getprop(c->window, XA_WM_NAME);
	setlabel(c);

	hints = XGetWMHints(dpy, c->window);
	if(XGetWMNormalHints(dpy, c->window, &c->size, &msize) == 0 || c->size.flags == 0)
		c->size.flags = PSize;		/* not specified - punt */

	getcmaps(c);
	getproto(c);
	gettrans(c);
	if(c->is9term)
		c->hold = getiprop(c->window, _rio_hold_mode);

	/* Figure out what to do with the window from hints */

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

	fixsize = 0;
	if((c->size.flags & (USSize|PSize)))
		fixsize = 1;
	if((c->size.flags & (PMinSize|PMaxSize)) == (PMinSize|PMaxSize) && c->size.min_width == c->size.max_width && c->size.min_height == c->size.max_height)
		fixsize = 1;
	doreshape = !mapped;
	if(fixsize){
		if(c->size.flags & USPosition)
			doreshape = 0;
		if(dohide && (c->size.flags & PPosition))
			doreshape = 0;
		if(c->trans != None)
			doreshape = 0;
	}
	if(c->is9term)
		fixsize = 0;
	if(c->size.flags & PBaseSize){
		c->min_dx = c->size.base_width;
		c->min_dy = c->size.base_height;
	}
	else if(c->size.flags & PMinSize){
		c->min_dx = c->size.min_width;
		c->min_dy = c->size.min_height;
	}
	else if(c->is9term){
		c->min_dx = 100;
		c->min_dy = 50;
	}
	else
		c->min_dx = c->min_dy = 0;

	if(hints)
		XFree(hints);

	/* Now do it!!! */

	if(doreshape){
		if(0) fprintf(stderr, "in doreshape is9term=%d fixsize=%d, x=%d, y=%d, min_dx=%d, min_dy=%d, dx=%d, dy=%d\n",
				c->is9term, fixsize, c->x, c->y, c->min_dx, c->min_dy, c->dx, c->dy);
		if(current && current->screen == c->screen)
			cmapnofocus(c->screen);
		if(!c->is9term && c->x==0 && c->y==0){
			static int nwin;

			c->x = 20*nwin+BORDER;
			c->y = 20*nwin+BORDER;
			nwin++;
			nwin %= 10;
		}

		if(c->is9term && !(fixsize ? drag(c, Button3) : sweep(c, Button3))){
			XKillClient(dpy, c->window);
			rmclient(c);
			if(current && current->screen == c->screen)
				cmapfocus(current);
			return 0;
		}
	} else
		gravitate(c, 0);


	attrs.border_pixel =  c->screen->black;
	attrs.background_pixel =  c->screen->white;
	attrs.colormap = c->screen->def_cmap;
	c->parent = XCreateWindow(dpy, c->screen->root,
			c->x - BORDER, c->y - BORDER,
			c->dx + 2*BORDER, c->dy + 2*BORDER,
			0,
			c->screen->depth,
			CopyFromParent,
			c->screen->vis,
			CWBackPixel | CWBorderPixel | CWColormap,
			&attrs);

	XSelectInput(dpy, c->parent, SubstructureRedirectMask | SubstructureNotifyMask|ButtonPressMask| PointerMotionMask|LeaveWindowMask|KeyPressMask);
	if(mapped)
		c->reparenting = 1;
	if(doreshape && !fixsize)
		XResizeWindow(dpy, c->window, c->dx, c->dy);
	XSetWindowBorderWidth(dpy, c->window, 0);

	/*
	  * To have something more than only a big white or black border
	  * XXX should replace this by a pattern in the white or black
	  * such that we can see the border also if all our
	  * windows are black and/or white
	  * (black (or white)  border around black (or white) window
	  *  is not very helpful.
	  */
	if(c->screen->depth <= 8){
		XSetWindowBorderWidth(dpy, c->parent, 1);
	}

	XReparentWindow(dpy, c->window, c->parent, BORDER, BORDER);
#ifdef	SHAPE
	if(shape){
		XShapeSelectInput(dpy, c->window, ShapeNotifyMask);
		ignore_badwindow = 1;		/* magic */
		setshape(c);
		ignore_badwindow = 0;
	}
#endif
	XAddToSaveSet(dpy, c->window);
	if(dohide)
		hide(c);
	else {
		XMapWindow(dpy, c->window);
		XMapWindow(dpy, c->parent);
		XUnmapWindow(dpy, c->screen->sweepwin);
		if(nostalgia || doreshape)
			active(c);
		else if(c->trans != None && current && current->window == c->trans)
			active(c);
		else
			setactive(c, 0);
		setstate(c, NormalState);
	}
	if(current && (current != c))
		cmapfocus(current);
	c->init = 1;

	/*
	 * If we swept the window, let's send a resize event to the
	 * guy who just got resized.  It's not clear whether the apps
	 * should notice their new size via other means.  Try as I might,
	 * I can't find a way to have them notice during initdraw, so
	 * I solve the problem this way instead.		-rsc
	 */
	if(c->is9term)
		sendconfig(c);
	return 1;
}

void
scanwins(ScreenInfo *s)
{
	unsigned int i, nwins;
	Client *c;
	Window dw1, dw2, *wins;
	XWindowAttributes attr;

	XQueryTree(dpy, s->root, &dw1, &dw2, &wins, &nwins);
	for(i = 0; i < nwins; i++){
		XGetWindowAttributes(dpy, wins[i], &attr);
		if(attr.override_redirect || wins[i] == s->menuwin)
			continue;
		c = getclient(wins[i], 1);
		if(c != 0 && c->window == wins[i] && !c->init){
			c->x = attr.x;
			c->y = attr.y;
			c->dx = attr.width;
			c->dy = attr.height;
			c->border = attr.border_width;
			c->screen = s;
			c->parent = s->root;
			if(attr.map_state == IsViewable)
				manage(c, 1);
		}
	}
	XFree((void *) wins);	/* cast is to shut stoopid compiler up */
}

void
gettrans(Client *c)
{
	Window trans;

	trans = None;
	if(XGetTransientForHint(dpy, c->window, &trans) != 0)
		c->trans = trans;
	else
		c->trans = None;
}

void
withdraw(Client *c)
{
	XUnmapWindow(dpy, c->parent);
	gravitate(c, 1);
	XReparentWindow(dpy, c->window, c->screen->root, c->x, c->y);
	gravitate(c, 0);
	XRemoveFromSaveSet(dpy, c->window);
	setstate(c, WithdrawnState);

	/* flush any errors */
	ignore_badwindow = 1;
	XSync(dpy, False);
	ignore_badwindow = 0;
}

void
gravitate(Client *c, int invert)
{
	int gravity, dx, dy, delta;

	gravity = NorthWestGravity;
	if(c->size.flags & PWinGravity)
		gravity = c->size.win_gravity;

	delta = c->border-BORDER;
	switch (gravity){
	case NorthWestGravity:
		dx = 0;
		dy = 0;
		break;
	case NorthGravity:
		dx = delta;
		dy = 0;
		break;
	case NorthEastGravity:
		dx = 2*delta;
		dy = 0;
		break;
	case WestGravity:
		dx = 0;
		dy = delta;
		break;
	case CenterGravity:
	case StaticGravity:
		dx = delta;
		dy = delta;
		break;
	case EastGravity:
		dx = 2*delta;
		dy = delta;
		break;
	case SouthWestGravity:
		dx = 0;
		dy = 2*delta;
		break;
	case SouthGravity:
		dx = delta;
		dy = 2*delta;
		break;
	case SouthEastGravity:
		dx = 2*delta;
		dy = 2*delta;
		break;
	default:
		fprintf(stderr, "rio: bad window gravity %d for 0x%x\n", gravity, (int)c->window);
		return;
	}
	dx += BORDER;
	dy += BORDER;
	if(invert){
		dx = -dx;
		dy = -dy;
	}
	c->x += dx;
	c->y += dy;
}

static void
installcmap(ScreenInfo *s, Colormap cmap)
{
	if(cmap == None)
		XInstallColormap(dpy, s->def_cmap);
	else
		XInstallColormap(dpy, cmap);
}

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

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

void
cmapnofocus(ScreenInfo *s)
{
	installcmap(s, None);
}

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

	if(!c->init){
		ignore_badwindow = 1;
		XGetWindowAttributes(dpy, c->window, &attr);
		c->cmap = attr.colormap;
		XSync(dpy, False);
		ignore_badwindow = 0;
	}

	n = _getprop(c->window, wm_colormaps, XA_WINDOW, 100L, (void*)&cw);
	if(c->ncmapwins != 0){
		XFree((char *)c->cmapwins);
		free((char *)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 {
			/* flush any errors (e.g., caused by mozilla tabs) */
			ignore_badwindow = 1;
			XSelectInput(dpy, cw[i], ColormapChangeMask);
			XGetWindowAttributes(dpy, cw[i], &attr);
			c->wmcmaps[i] = attr.colormap;
			XSync(dpy, False);
			ignore_badwindow = 0;
		}
	}
}

void
setlabel(Client *c)
{
	char *label, *p;

	if(c->iconname != 0)
		label = c->iconname;
	else if(c->name != 0)
		label = c->name;
	else if(c->instance != 0)
		label = c->instance;
	else if(c->class != 0)
		label = c->class;
	else
		label = "no label";
	if((p = index(label, ':')) != 0)
		*p = '\0';
	c->label = label;
}

#ifdef	SHAPE
void
setshape(Client *c)
{
	int n, order;
	XRectangle *rect;

	/* don't try to add a border if the window is non-rectangular */
	rect = XShapeGetRectangles(dpy, c->window, ShapeBounding, &n, &order);
	if(n > 1)
		XShapeCombineShape(dpy, c->parent, ShapeBounding, BORDER, BORDER,
			c->window, ShapeBounding, ShapeSet);
	XFree((void*)rect);
}
#endif

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

	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((void*) *p);
	/* could check real_type, format, extra here... */
	return n;
}

char *
getprop(Window w, Atom a)
{
	unsigned char *p;

	if(_getprop(w, a, XA_STRING, 100L, &p) <= 0)
		return 0;
	return (char *)p;
}

int
get1prop(Window w, Atom a, Atom type)
{
	char **p, *x;

	if(_getprop(w, a, type, 1L, (void*)&p) <= 0)
		return 0;
	x = *p;
	XFree((void*) p);
	return (int)x;
}

Window
getwprop(Window w, Atom a)
{
	return get1prop(w, a, XA_WINDOW);
}

int
getiprop(Window w, Atom a)
{
	return get1prop(w, a, XA_INTEGER);
}

void
setstate(Client *c, int state)
{
	long data[2];

	data[0] = (long) state;
	data[1] = (long) None;

	c->state = state;
	XChangeProperty(dpy, c->window, wm_state, wm_state, 32,
		PropModeReplace, (unsigned char *)data, 2);
}

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

	if(_getprop(w, wm_state, wm_state, 2L, (void*)&p) <= 0)
		return 0;

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

void
getproto(Client *c)
{
	Atom *p;
	int i;
	long n;
	Window w;

	w = c->window;
	c->proto = 0;
	if((n = _getprop(w, wm_protocols, XA_ATOM, 20L, (void*)&p)) <= 0)
		return;

	for(i = 0; i < n; i++)
		if(p[i] == wm_delete)
			c->proto |= Pdelete;
		else if(p[i] == wm_take_focus)
			c->proto |= Ptakefocus;
		else if(p[i] == wm_lose_focus)
			c->proto |= Plosefocus;

	XFree((char *) p);
}