aboutsummaryrefslogblamecommitdiffstats
path: root/client.c
blob: a4982b028596955243250d71fc8dd0f9d0fcee91 (plain) (tree)
1
2
3
4
5
6
7





                                                               
                                                       















                                                              
                             






                              
                                          




















                                                         
                           

















                                                            

                                                          























                                                        
                                                                      









                                                                        
                                    




















                                                                                  
                                                             

                                                                   
                                                          
                                         

                                                           

                                                                     
                      


                              
                                                                   
 
                                                                                 
                          
                                                                          







                                                              


                                                                          



                                                 

                                    

                                   

                             

                                                    
                                               




                            
                                                    

















                                                                     
                           

                                        
                                           


                                        

                                                                         


                                                         
                                                
 

                             

                                               
                                          


























































                                                         

                                                  


























































                                                                      
                                                                        


























                                                      
                                                                   

                                                                   
                                                                 







                                             
                                    






                                                              
                                      




                                   
                                      







































                                         

                                      











                                     

                                    










                                           

                                                                           

                                                     
                                                                     


                                                          
                                   
                                                         

                                                            
                                              




















































                                                        


                                                                   



















                                                             

                                                                  



                                                        
                                            







                                     
                                             





























































































































                                                                           



                                                           













                                                                          
                                                                            













































                                                                          

                                                               














                                                            

                                                            





















































                                                             
                                                                      















































                                                               
                                                  





                                                      
                                                           





                                                             
                                                        









































                                                              
/*
 * calmwm - the calm window manager
 *
 * Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org>
 * All rights reserved.
 *
 * $Id: client.c,v 1.3 2007/05/29 22:35:04 jasper Exp $
 */

#include "headers.h"
#include "calmwm.h"

static struct client_ctx *client__cycle(struct client_ctx *cc,
    struct client_ctx *(*iter)(struct client_ctx *));
int _inwindowbounds(struct client_ctx *, int, int);

static char emptystring[] = "";

struct client_ctx *_curcc = NULL;

void
client_setup(void)
{
	TAILQ_INIT(&Clientq);
}

struct client_ctx *
client_find(Window win)
{
	struct client_ctx *cc;

	TAILQ_FOREACH(cc, &Clientq, entry)
		if (cc->pwin == win || cc->win == win)
			return (cc);

	return (NULL);
}

struct client_ctx *
client_new(Window win, struct screen_ctx *sc, int mapped)
{
	struct client_ctx *cc;
	long tmp;
	XSetWindowAttributes pxattr;
 	XWindowAttributes wattr;
	int x, y, height, width, state;
	XWMHints *wmhints;

	if (win == None)
		return (NULL);

	XCALLOC(cc, struct client_ctx);

	XGrabServer(X_Dpy);

	cc->state = mapped ? NormalState : IconicState;
	cc->sc = sc;
	cc->win = win;
	cc->size= XAllocSizeHints();
	if (cc->size->width_inc == 0)
		cc->size->width_inc = 1;
	if (cc->size->height_inc == 0)
		cc->size->height_inc = 1;

	TAILQ_INIT(&cc->nameq);
	client_setname(cc);

	/*
	 * conf_client() needs at least cc->win and cc->name
	 */
	conf_client(cc);

	XGetWMNormalHints(X_Dpy, cc->win, cc->size, &tmp);
	XGetWindowAttributes(X_Dpy, cc->win, &wattr);

	if (cc->size->flags & PBaseSize) {
		cc->geom.min_dx = cc->size->base_width;
		cc->geom.min_dy = cc->size->base_height;
	} else if (cc->size->flags & PMinSize) {
		cc->geom.min_dx = cc->size->min_width;
		cc->geom.min_dy = cc->size->min_height;
	}

	/* Saved pointer position */
	cc->ptr.x = -1;
	cc->ptr.y = -1;

	client_gravitate(cc, 1);

	cc->geom.x = wattr.x;
	cc->geom.y = wattr.y;
	cc->geom.width = wattr.width;
	cc->geom.height = wattr.height;
	cc->geom.height = wattr.height;
	cc->cmap = wattr.colormap;

	if (wattr.map_state != IsViewable) {
		client_placecalc(cc);
		if ((wmhints = XGetWMHints(X_Dpy, cc->win)) != NULL) {
			if (wmhints->flags & StateHint)
				xu_setstate(cc, wmhints->initial_state);

			XFree(wmhints);
		}
	}

	if (xu_getstate(cc, &state) < 0)
		state = NormalState;

	XSelectInput(X_Dpy, cc->win,
	    ColormapChangeMask|EnterWindowMask|PropertyChangeMask|KeyReleaseMask);

	x = cc->geom.x - cc->bwidth;
	y = cc->geom.y - cc->bwidth;

	width = cc->geom.width;
	height = cc->geom.height;
	if (cc->bwidth > 1) {
		width += (cc->bwidth)*2;
		height += (cc->bwidth)*2;
	}

	pxattr.override_redirect = True;
	pxattr.background_pixel = sc->bgcolor.pixel;
	pxattr.event_mask =
	    ChildMask|ButtonPressMask|ButtonReleaseMask|
	    ExposureMask|EnterWindowMask;
/* 	pxattr.border_pixel = sc->blackpix; */
/* 	pxattr.background_pixel = sc->whitepix; */
	

/* 	cc->pwin = XCreateSimpleWindow(X_Dpy, sc->rootwin, */
/* 	    x, y, width, height, 1, sc->blackpix, sc->whitepix); */

	cc->pwin = XCreateWindow(X_Dpy, sc->rootwin, x, y,
	    width, height, 0,	/* XXX */
	    DefaultDepth(X_Dpy, sc->which), CopyFromParent,
	    DefaultVisual(X_Dpy, sc->which),
	    CWOverrideRedirect | CWBackPixel | CWEventMask, &pxattr);

	if (Doshape) {
		XRectangle *r;
		int n, tmp;

		XShapeSelectInput(X_Dpy, cc->win, ShapeNotifyMask);

		r = XShapeGetRectangles(X_Dpy, cc->win, ShapeBounding, &n, &tmp);
		if (n > 1)
			XShapeCombineShape(X_Dpy, cc->pwin, ShapeBounding,
			    0,	0, /* XXX border */
			    cc->win, ShapeBounding, ShapeSet);
		XFree(r);
	}

	cc->active = 0;
	client_draw_border(cc);

	XAddToSaveSet(X_Dpy, cc->win);
	XSetWindowBorderWidth(X_Dpy, cc->win, 0);
	XReparentWindow(X_Dpy, cc->win, cc->pwin, cc->bwidth, cc->bwidth);

	/* Notify client of its configuration. */
	xev_reconfig(cc);

	XMapRaised(X_Dpy, cc->pwin);
	XMapWindow(X_Dpy, cc->win);
	xu_setstate(cc, cc->state);

	XSync(X_Dpy, False);
	XUngrabServer(X_Dpy);

	TAILQ_INSERT_TAIL(&sc->mruq, cc, mru_entry);
	TAILQ_INSERT_TAIL(&Clientq, cc, entry);

	client_gethints(cc);
	client_update(cc);
	
	if (mapped) {
		if (Conf.flags & CONF_STICKY_GROUPS)
			group_sticky(cc);
		else
			group_autogroup(cc);
	}

	return (cc);
}

int
client_delete(struct client_ctx *cc, int sendevent, int ignorewindow)
{
	struct screen_ctx *sc = CCTOSC(cc);
	struct winname *wn;

	if (cc->state == IconicState && !sendevent)
		return (1);

	group_client_delete(cc);
	XGrabServer(X_Dpy);

	xu_setstate(cc, WithdrawnState);
	XRemoveFromSaveSet(X_Dpy, cc->win);

	if (!ignorewindow) {
		client_gravitate(cc, 0);
		XSetWindowBorderWidth(X_Dpy, cc->win, 1);	/* XXX */
		XReparentWindow(X_Dpy, cc->win,
		    sc->rootwin, cc->geom.x, cc->geom.y);
	}
	if (cc->pwin)
		XDestroyWindow(X_Dpy, cc->pwin);

	XSync(X_Dpy, False);
	XUngrabServer(X_Dpy);

	TAILQ_REMOVE(&sc->mruq, cc, mru_entry);
	TAILQ_REMOVE(&Clientq, cc, entry);

	if (_curcc == cc)
		_curcc = NULL;

	if (sc->cycle_client == cc)
		sc->cycle_client = NULL;

	XFree(cc->size);

	while ((wn = TAILQ_FIRST(&cc->nameq)) != NULL) {
		TAILQ_REMOVE(&cc->nameq, wn, entry);
		if (wn->name != emptystring)
			XFree(wn->name);
		xfree(wn);
	}

	client_freehints(cc);

	xfree(cc);

	return (0);
}

void
client_leave(struct client_ctx *cc)
{
	struct screen_ctx *sc;

	if (cc == NULL)
		cc = _curcc;
	if (cc == NULL)
		return;
	sc = CCTOSC(cc);

	xu_btn_ungrab(sc->rootwin, AnyModifier, Button1);
}

void
client_nocurrent(void)
{
	if (_curcc != NULL)
		client_setactive(_curcc, 0);

	_curcc = NULL;
}

void
client_setactive(struct client_ctx *cc, int fg)
{
	struct screen_ctx* sc;

	if (cc == NULL)
		cc = _curcc;
	if (cc == NULL)
		return;

	sc = CCTOSC(cc);

	if (fg) {
		XInstallColormap(X_Dpy, cc->cmap);
		XSetInputFocus(X_Dpy, cc->win,
		    RevertToPointerRoot, CurrentTime);
		xu_btn_grab(cc->pwin, Mod1Mask, AnyButton);
		xu_btn_grab(cc->pwin, ControlMask|Mod1Mask, Button1);
		/*
		 * If we're in the middle of alt-tabbing, don't change
		 * the order please.
		 */
		if (!sc->altpersist)
			client_mtf(cc);
	} else
		client_leave(cc);

	if (fg && _curcc != cc) {
		client_setactive(NULL, 0);
		_curcc = cc;
	}

	cc->active = fg;
	client_draw_border(cc);
}

struct client_ctx *
client_current(void)
{
	return (_curcc);
}

void
client_gravitate(struct client_ctx *cc, int yes)
{
        int dx = 0, dy = 0, mult = yes ? 1 : -1;
        int gravity = (cc->size->flags & PWinGravity) ?
	    cc->size->win_gravity : NorthWestGravity;

        switch (gravity) {
	case NorthWestGravity:
	case SouthWestGravity:
	case NorthEastGravity:
	case StaticGravity:
		dx = cc->bwidth;
	case NorthGravity:
		dy = cc->bwidth;
		break;
        }

        cc->geom.x += mult*dx;
        cc->geom.y += mult*dy;
}

void
client_maximize(struct client_ctx *cc)
{
	if (cc->flags & CLIENT_MAXIMIZED) {
		cc->flags &= ~CLIENT_MAXIMIZED;
		cc->geom = cc->savegeom;
	} else {
		XWindowAttributes rootwin_geom;
		struct screen_ctx *sc = CCTOSC(cc);

		XGetWindowAttributes(X_Dpy, sc->rootwin, &rootwin_geom);
		cc->savegeom = cc->geom;
		cc->geom.x = 0;
		cc->geom.y = 0;
		cc->geom.height = rootwin_geom.height;
		cc->geom.width = rootwin_geom.width;
		cc->flags |= CLIENT_MAXIMIZED;
	}

	client_resize(cc);
}

void
client_push_geometry(struct client_ctx *cc)
{
       cc->savegeom = cc->geom;
}

void
client_restore_geometry(struct client_ctx *cc)
{
       cc->geom = cc->savegeom;
       client_resize(cc);
}

void
client_resize(struct client_ctx *cc)
{
	XMoveResizeWindow(X_Dpy, cc->pwin, cc->geom.x - cc->bwidth,
	    cc->geom.y - cc->bwidth, cc->geom.width + cc->bwidth*2,
	    cc->geom.height + cc->bwidth*2);
	XMoveResizeWindow(X_Dpy, cc->win, cc->bwidth, cc->bwidth,
	    cc->geom.width, cc->geom.height);
	xev_reconfig(cc);
	client_draw_border(cc);
}

void
client_move(struct client_ctx *cc)
{
	XMoveWindow(X_Dpy, cc->pwin,
	    cc->geom.x - cc->bwidth, cc->geom.y - cc->bwidth);
	xev_reconfig(cc);
}

void
client_lower(struct client_ctx *cc)
{
	XLowerWindow(X_Dpy, cc->pwin);
}

void
client_raise(struct client_ctx *cc)
{
	XRaiseWindow(X_Dpy, cc->pwin);
	client_draw_border(cc);
}

void
client_warp(struct client_ctx *cc)
{
	client_raise(cc);
	xu_ptr_setpos(cc->pwin, 0, 0);
}

void
client_ptrwarp(struct client_ctx *cc)
{
	int x = cc->ptr.x, y = cc->ptr.y;

	if (x == -1 || y == -1) {
		x = cc->geom.width / 2;
		y = cc->geom.height / 2;
	}

	client_raise(cc);
	xu_ptr_setpos(cc->pwin, x, y);
}

void
client_ptrsave(struct client_ctx *cc)
{
	int x, y;

	xu_ptr_getpos(cc->pwin, &x, &y);
        if (_inwindowbounds(cc, x, y)) {
		cc->ptr.x = x;
		cc->ptr.y = y;
        }
}

void
client_hide(struct client_ctx *cc)
{
	/* XXX - add wm_state stuff */
	XUnmapWindow(X_Dpy, cc->pwin);
	XUnmapWindow(X_Dpy, cc->win);

	cc->active = 0;
	cc->flags |= CLIENT_HIDDEN;
	xu_setstate(cc, IconicState);

	if (cc == _curcc)
		_curcc = NULL;
}

void
client_unhide(struct client_ctx *cc)
{
	XMapWindow(X_Dpy, cc->win);
	XMapRaised(X_Dpy, cc->pwin);

	cc->flags &= ~CLIENT_HIDDEN;
	xu_setstate(cc, NormalState);
}

void
client_draw_border(struct client_ctx *cc)
{
	struct screen_ctx *sc = CCTOSC(cc);

	if (cc->active) {
		XSetWindowBackground(X_Dpy, cc->pwin, client_bg_pixel(cc));
		XClearWindow(X_Dpy, cc->pwin);

		if (!cc->highlight && cc->bwidth > 1)
			XDrawRectangle(X_Dpy, cc->pwin, sc->gc, 1, 1,
			    cc->geom.width + cc->bwidth,
			    cc->geom.height + cc->bwidth);
	} else {
		if (cc->bwidth > 1)
			XSetWindowBackgroundPixmap(X_Dpy,
			    cc->pwin, client_bg_pixmap(cc));

		XClearWindow(X_Dpy, cc->pwin);
	}
}

u_long
client_bg_pixel(struct client_ctx *cc)
{
	struct screen_ctx *sc = CCTOSC(cc);
	u_long pixl;

	switch (cc->highlight) {
	case CLIENT_HIGHLIGHT_BLUE:
		pixl = sc->bluepixl;
		break;
	case CLIENT_HIGHLIGHT_RED:
		pixl = sc->redpixl;
		break;
	default:
		pixl = sc->blackpixl;
		break;
	}

	return (pixl);
}

Pixmap
client_bg_pixmap(struct client_ctx *cc)
{
	struct screen_ctx *sc = CCTOSC(cc);
	Pixmap pix;

	switch (cc->highlight) {
	case CLIENT_HIGHLIGHT_BLUE:
		pix = sc->blue;
		break;
	case CLIENT_HIGHLIGHT_RED:
		pix = sc->red;
		break;
	default:
		pix = sc->gray;
		break;
	}

	return (pix);
}

void
client_update(struct client_ctx *cc)
{
	Atom *p, wm_delete, wm_protocols, wm_take_focus;
	int i;
	long n;

	/* XXX cache these. */
	wm_delete = XInternAtom(X_Dpy, "WM_DELETE_WINDOW", False);
	wm_protocols = XInternAtom(X_Dpy, "WM_PROTOCOLS", False);
	wm_take_focus = XInternAtom(X_Dpy, "WM_TAKE_FOCUS", False);

	if ((n = xu_getprop(cc, wm_protocols,
		 XA_ATOM, 20L, (u_char **)&p)) <= 0)
		return;

	for (i = 0; i < n; i++)
		if (p[i] == wm_delete)
			cc->xproto |= CLIENT_PROTO_DELETE;
		else if (p[i] == wm_take_focus)
			cc->xproto |= CLIENT_PROTO_TAKEFOCUS;

	XFree(p);
}

void
client_send_delete(struct client_ctx *cc)
{
	Atom wm_delete, wm_protocols;

	/* XXX - cache */
	wm_delete = XInternAtom(X_Dpy, "WM_DELETE_WINDOW", False);
	wm_protocols = XInternAtom(X_Dpy, "WM_PROTOCOLS", False);

	if (cc->xproto & CLIENT_PROTO_DELETE)
		xu_sendmsg(cc, wm_protocols, wm_delete);
	else
		XKillClient(X_Dpy, cc->win);
}

void
client_setname(struct client_ctx *cc)
{
	char *newname;
	struct winname *wn;

	XFetchName(X_Dpy, cc->win, &newname);
	if (newname == NULL)
		newname = emptystring;

	TAILQ_FOREACH(wn, &cc->nameq, entry)
		if (strcmp(wn->name, newname) == 0) {
			/* Move to the last since we got a hit. */
			TAILQ_REMOVE(&cc->nameq, wn, entry);
			TAILQ_INSERT_TAIL(&cc->nameq, wn, entry);
			goto match;
		}

	XMALLOC(wn, struct winname);
	wn->name = newname;
	TAILQ_INSERT_TAIL(&cc->nameq, wn, entry);
	cc->nameqlen++;

match:
	cc->name = wn->name;

	/* Now, do some garbage collection. */
	if (cc->nameqlen > CLIENT_MAXNAMEQLEN) {
		wn = TAILQ_FIRST(&cc->nameq);
		assert(wn != NULL);
		TAILQ_REMOVE(&cc->nameq, wn, entry);
		if (wn->name != emptystring)
			XFree(wn->name);
		xfree(wn);
		cc->nameqlen--;
	}

	return;	
}

/*
 * TODO: seems to have some issues still on the first invocation
 * (globally the first)
 */

struct client_ctx *
client_cyclenext(struct client_ctx *cc, int reverse)
{
	struct screen_ctx *sc = CCTOSC(cc);
	struct client_ctx *(*iter)(struct client_ctx *) =
	    reverse ? &client_mruprev : &client_mrunext;

	/* TODO: maybe this should just be a CIRCLEQ. */

	/* if altheld; then reset the iterator to the beginning */
	if (!sc->altpersist || sc->cycle_client == NULL)
		sc->cycle_client = TAILQ_FIRST(&sc->mruq);

	if (sc->cycle_client == NULL)
		return (NULL);

	/*
	 * INVARIANT: as long as sc->cycle_client != NULL here, we
	 * won't exit with sc->cycle_client == NULL
	 */

	if ((sc->cycle_client = client__cycle(cc, iter)) == NULL)
		sc->cycle_client = cc;

	/* Do the actual warp. */
	client_ptrsave(cc);
	client_ptrwarp(sc->cycle_client);
	sc->altpersist = 1;   /* This is reset when alt is let go... */

	/* Draw window. */
	client_cycleinfo(sc->cycle_client);

	return (sc->cycle_client);
}

/*
 * XXX - have to have proper exposure handling here.  we will probably
 * have to do this by registering with the event loop a function to
 * redraw, then match that on windows.
 */

void
client_cycleinfo(struct client_ctx *cc)
{
	int w, h, nlines, i, n, oneh, curn = -1, x, y, diff;
	struct client_ctx *ccc, *list[3];
	struct screen_ctx *sc = CCTOSC(cc);
	struct fontdesc *font = DefaultFont;

	memset(list, 0, sizeof(list));

	nlines = 0;
	TAILQ_FOREACH(ccc, &sc->mruq, mru_entry)
		nlines++;
	nlines = MIN(nlines, 3);

	oneh = font_ascent(font) + font_descent(font) + 1;
	h = nlines*oneh;

	list[1] = cc;

	if (nlines > 1)
		list[2] = client__cycle(cc, &client_mrunext);
	if (nlines > 2)
		list[0] = client__cycle(cc, &client_mruprev);

	w = 0;
	for (i = 0; i < sizeof(list)/sizeof(list[0]); i++) {
		if ((ccc = list[i]) == NULL)
			continue;		
		w = MAX(w, font_width(font, ccc->name, strlen(ccc->name)));
	}

	w += 4;

	/* try to fit. */

	if ((x = cc->ptr.x) < 0 || (y = cc->ptr.y) < 0) {
		x = cc->geom.width / 2;
		y = cc->geom.height / 2;
	}

	if ((diff = cc->geom.width - (x + w)) < 0)
		x += diff;

	if ((diff = cc->geom.height - (y + h)) < 0)
		y += diff;

	XReparentWindow(X_Dpy, sc->infowin, cc->win, 0, 0);
	XMoveResizeWindow(X_Dpy, sc->infowin, x, y, w, h);
	XMapRaised(X_Dpy, sc->infowin);
	XClearWindow(X_Dpy, sc->infowin);

	for (i = 0, n = 0; i < sizeof(list)/sizeof(list[0]); i++) {
		if ((ccc = list[i]) == NULL)
			continue;
		font_draw(font, ccc->name, strlen(ccc->name), sc->infowin,
		    2, n*oneh + font_ascent(font) + 1);
		if (i == 1)
			curn = n;
		n++;
	}

	assert(curn != -1);

	/* Highlight the current entry. */
	XFillRectangle(X_Dpy, sc->infowin, sc->hlgc, 0, curn*oneh, w, oneh);
}

struct client_ctx *
client_mrunext(struct client_ctx *cc)
{
	struct screen_ctx *sc = CCTOSC(cc);
	struct client_ctx *ccc;

	return ((ccc = TAILQ_NEXT(cc, mru_entry)) != NULL ?
	    ccc : TAILQ_FIRST(&sc->mruq));
}

struct client_ctx *
client_mruprev(struct client_ctx *cc)
{
	struct screen_ctx *sc = CCTOSC(cc);
	struct client_ctx *ccc;

	return ((ccc = TAILQ_PREV(cc, cycle_entry_q, mru_entry)) != NULL ?
	    ccc : TAILQ_LAST(&sc->mruq, cycle_entry_q));
}

static struct client_ctx *
client__cycle(struct client_ctx *cc,
    struct client_ctx *(*iter)(struct client_ctx *))
{
	struct client_ctx *save = cc;

	do {
		if (!((cc = (*iter)(cc))->flags & CLIENT_HIDDEN))
			break;
	} while (cc != save);

	return cc != save ? cc : NULL;
}

void
client_altrelease()
{
	struct client_ctx *cc = _curcc;
	struct screen_ctx *sc;

	if (cc == NULL)
		return;
	sc = CCTOSC(cc);

	XUnmapWindow(X_Dpy, sc->infowin);
	XReparentWindow(X_Dpy, sc->infowin, sc->rootwin, 0, 0);
}

void
client_placecalc(struct client_ctx *cc)
{
	struct screen_ctx *sc = CCTOSC(cc);
	int yslack, xslack;
	int x, y, height, width, ymax, xmax, mousex, mousey;

	y = cc->geom.y;
	x = cc->geom.x;

	height = cc->geom.height;
	width = cc->geom.width;

	ymax = DisplayHeight(X_Dpy, sc->which) - cc->bwidth;
	xmax = DisplayWidth(X_Dpy, sc->which) - cc->bwidth;

	yslack = ymax - cc->geom.height;
	xslack = xmax - cc->geom.width;

	xu_ptr_getpos(sc->rootwin, &mousex, &mousey);

	mousex = MAX(mousex, cc->bwidth) - cc->geom.width/2;
	mousey = MAX(mousey, cc->bwidth) - cc->geom.height/2;

	mousex = MAX(mousex, (int)cc->bwidth);
	mousey = MAX(mousey, (int)cc->bwidth);

	if (cc->size->flags & USPosition) {
		x = cc->size->x;
		if (x <= 0 || x >= xmax)
			x = cc->bwidth;
		y = cc->size->y;
		if (y <= 0 || y >= ymax)
			y = cc->bwidth;
	} else {
		if (yslack < 0) {
			y = cc->bwidth;
			height = ymax;
		} else {
			if (y == 0 || y > yslack)
				y = MIN(mousey, yslack);
			height = cc->geom.height;
		}

		if (xslack < 0) {
			x = cc->bwidth;
			width = xmax;
		} else {
			if (x == 0 || x > xslack)
				x = MIN(mousex, xslack);
			width = cc->geom.width;
		}
	}

	cc->geom.y = y;
	cc->geom.x = x;

	cc->geom.height = height;
	cc->geom.width = width;
}

void
client_vertmaximize(struct client_ctx *cc)
{
	if (cc->flags & CLIENT_MAXIMIZED) {
		cc->flags &= ~CLIENT_MAXIMIZED;
		cc->geom = cc->savegeom;
	} else {
		struct screen_ctx *sc = CCTOSC(cc);
		int display_height = DisplayHeight(X_Dpy, sc->which) -
		    cc->bwidth*2;
        
		cc->savegeom = cc->geom;
		cc->geom.y = cc->bwidth;
		if (cc->geom.min_dx == 0)
			cc->geom.height = display_height; 
		else
			cc->geom.height = display_height -
			    (display_height % cc->geom.min_dx);
		cc->flags |= CLIENT_MAXIMIZED;
	}

	client_resize(cc);
}

void
client_map(struct client_ctx *cc)
{
	/* mtf? */
	client_ptrwarp(cc);
}

void
client_mtf(struct client_ctx *cc)
{
	struct screen_ctx *sc;

	if (cc == NULL)
		cc = _curcc;
	if (cc == NULL)
		return;

	sc = CCTOSC(cc);

	/* Move to front. */
	TAILQ_REMOVE(&sc->mruq, cc, mru_entry);
	TAILQ_INSERT_HEAD(&sc->mruq, cc, mru_entry);
}

void
client_gethints(struct client_ctx *cc)
{
	XClassHint xch;
	int argc;
	char **argv;
	Atom mha;
	struct mwm_hints *mwmh;

	if (XGetClassHint(X_Dpy, cc->win, &xch)) {
		if (xch.res_name != NULL)
			cc->app_name = xch.res_name;
		if (xch.res_class != NULL)
			cc->app_class = xch.res_class;
	}

	mha = XInternAtom(X_Dpy, "_MOTIF_WM_HINTS", False);
	if (xu_getprop(cc, mha, mha, PROP_MWM_HINTS_ELEMENTS,
		(u_char **)&mwmh) == MWM_NUMHINTS)
		if (mwmh->flags & MWM_HINTS_DECORATIONS &&
		    !(mwmh->decorations & MWM_DECOR_ALL) &&
		    !(mwmh->decorations & MWM_DECOR_BORDER))
			cc->bwidth = 0;
	if (XGetCommand(X_Dpy, cc->win, &argv, &argc)) {
#define MAX_ARGLEN 512
#define ARG_SEP_ " "
		int len = MAX_ARGLEN;
		int i, o;
		char *buf;

		buf = xmalloc(len);
		buf[0] = '\0';

		for (o = 0, i = 0; o < len && i < argc; i++) {
			if (argv[i] == NULL)
				break;
			strlcat(buf, argv[i], len);
			o += strlen(buf);
			strlcat(buf,  ARG_SEP_, len);
			o += strlen(ARG_SEP_);
		}

		if (strlen(buf) > 0)
			cc->app_cliarg = buf;

		XFreeStringList(argv);
	}
}

void
client_freehints(struct client_ctx *cc)
{
	if (cc->app_name != NULL)
		XFree(cc->app_name);
	if (cc->app_class != NULL)
		XFree(cc->app_class);
	if (cc->app_cliarg != NULL)
		xfree(cc->app_cliarg);
}

int
_inwindowbounds(struct client_ctx *cc, int x, int y)
{
	return (x < cc->geom.width && x >= 0 &&
	    y < cc->geom.height && y >= 0);
}