Edit

IABSD.fr/xenocara/app/cwm/client.c

Branch :

  • Show log

    Commit

  • Author : op
    Date : 2023-03-22 08:27:36
    Hash : 2c64aaa9
    Message : cwm: fix a semi-transparency border issue with some applications When running with a compositor the border of some applications (firefox, chromium, zathura...) is not rendered correctly. Initializing the highest significant bits of the color fixes it. diff from Julien Blanchard (julien at typed-hole [dot] org); ok okan@

  • app/cwm/client.c
  • /*
     * calmwm - the calm window manager
     *
     * Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     *
     * $OpenBSD: client.c,v 1.267 2023/03/22 08:27:36 op Exp $
     */
    
    #include <sys/types.h>
    #include <sys/queue.h>
    
    #include <err.h>
    #include <errno.h>
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #include "calmwm.h"
    
    static void			 client_class_hint(struct client_ctx *);
    static void			 client_placement(struct client_ctx *);
    static void			 client_mwm_hints(struct client_ctx *);
    static void			 client_wm_protocols(struct client_ctx *);
    
    struct client_ctx *
    client_init(Window win, struct screen_ctx *sc)
    {
    	struct client_ctx	*cc;
    	XWindowAttributes	 wattr;
    	int			 mapped;
    	long			 state;
    
    	if (win == None)
    		return NULL;
    	if (!XGetWindowAttributes(X_Dpy, win, &wattr))
    		return NULL;
    
    	if (sc == NULL) {
    		if ((sc = screen_find(wattr.root)) == NULL)
    			return NULL;
    		mapped = 1;
    	} else {
    		if (wattr.override_redirect || wattr.map_state != IsViewable)
    			return NULL;
    		mapped = wattr.map_state != IsUnmapped;
    	}
    
    	XGrabServer(X_Dpy);
    
    	cc = xmalloc(sizeof(*cc));
    	cc->sc = sc;
    	cc->win = win;
    	cc->name = NULL;
    	cc->label = NULL;
    	cc->gc = NULL;
    	cc->res_class = NULL;
    	cc->res_name = NULL;
    	cc->flags = 0;
    	cc->stackingorder = 0;
    	cc->initial_state = 0;
    	memset(&cc->hint, 0, sizeof(cc->hint));
    	TAILQ_INIT(&cc->nameq);
    
    	cc->geom.x = wattr.x;
    	cc->geom.y = wattr.y;
    	cc->geom.w = wattr.width;
    	cc->geom.h = wattr.height;
    	cc->colormap = wattr.colormap;
    	cc->obwidth = wattr.border_width;
    	cc->bwidth = Conf.bwidth;
    
    	client_set_name(cc);
    	conf_client(cc);
    
    	client_wm_hints(cc);
    	client_class_hint(cc);
    	client_wm_protocols(cc);
    	client_get_sizehints(cc);
    	client_transient(cc);
    	client_mwm_hints(cc);
    
    	if ((cc->flags & CLIENT_IGNORE))
    		cc->bwidth = 0;
    	cc->dim.w = (cc->geom.w - cc->hint.basew) / cc->hint.incw;
    	cc->dim.h = (cc->geom.h - cc->hint.baseh) / cc->hint.inch;
    	cc->ptr.x = cc->geom.w / 2;
    	cc->ptr.y = cc->geom.h / 2;
    
    	if (wattr.map_state != IsViewable) {
    		client_placement(cc);
    		client_resize(cc, 0);
    		if (cc->initial_state)
    			xu_set_wm_state(cc->win, cc->initial_state);
    	}
    
    	XSelectInput(X_Dpy, cc->win,
    	    EnterWindowMask | PropertyChangeMask | KeyReleaseMask);
    
    	XAddToSaveSet(X_Dpy, cc->win);
    
    	/* Notify client of its configuration. */
    	client_config(cc);
    
    	TAILQ_INSERT_TAIL(&sc->clientq, cc, entry);
    
    	xu_ewmh_net_client_list(sc);
    	xu_ewmh_net_client_list_stacking(sc);
    	xu_ewmh_restore_net_wm_state(cc);
    
    	xu_get_wm_state(cc->win, &state);
    	if (state == IconicState)
    		client_hide(cc);
    	else
    		client_show(cc);
    
    	if (mapped) {
    		if (cc->gc) {
    			group_movetogroup(cc, cc->gc->num);
    			goto out;
    		}
    		if (group_restore(cc))
    			goto out;
    		if (group_autogroup(cc))
    			goto out;
    		if (Conf.stickygroups)
    			group_assign(sc->group_active, cc);
    		else
    			group_assign(NULL, cc);
    	}
    out:
    	XSync(X_Dpy, False);
    	XUngrabServer(X_Dpy);
    
    	return cc;
    }
    
    struct client_ctx *
    client_current(struct screen_ctx *sc)
    {
    	struct screen_ctx	*_sc;
    	struct client_ctx	*cc;
    
    	if (sc) {
    		TAILQ_FOREACH(cc, &sc->clientq, entry) {
    			if (cc->flags & CLIENT_ACTIVE)
    				return cc;
    		}
    	} else {
    		TAILQ_FOREACH(_sc, &Screenq, entry) {
    			TAILQ_FOREACH(cc, &_sc->clientq, entry) {
    				if (cc->flags & CLIENT_ACTIVE)
    					return cc;
    			}
    		}
    	}
    	return NULL;
    }
    
    struct client_ctx *
    client_find(Window win)
    {
    	struct screen_ctx	*sc;
    	struct client_ctx	*cc;
    
    	TAILQ_FOREACH(sc, &Screenq, entry) {
    		TAILQ_FOREACH(cc, &sc->clientq, entry) {
    			if (cc->win == win)
    				return cc;
    		}
    	}
    	return NULL;
    }
    
    struct client_ctx *
    client_next(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct client_ctx	*newcc;
    
    	return(((newcc = TAILQ_NEXT(cc, entry)) != NULL) ?
    	    newcc : TAILQ_FIRST(&sc->clientq));
    }
    
    struct client_ctx *
    client_prev(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct client_ctx	*newcc;
    
    	return(((newcc = TAILQ_PREV(cc, client_q, entry)) != NULL) ?
    	    newcc : TAILQ_LAST(&sc->clientq, client_q));
    }
    
    void
    client_remove(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct winname		*wn;
    
    	TAILQ_REMOVE(&sc->clientq, cc, entry);
    
    	xu_ewmh_net_client_list(sc);
    	xu_ewmh_net_client_list_stacking(sc);
    
    	if (cc->flags & CLIENT_ACTIVE)
    		xu_ewmh_net_active_window(sc, None);
    
    	while ((wn = TAILQ_FIRST(&cc->nameq)) != NULL) {
    		TAILQ_REMOVE(&cc->nameq, wn, entry);
    		free(wn->name);
    		free(wn);
    	}
    
    	free(cc->name);
    	free(cc->label);
    	free(cc->res_class);
    	free(cc->res_name);
    	free(cc);
    }
    
    void
    client_set_active(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct client_ctx	*oldcc;
    
    	if (cc->flags & CLIENT_HIDDEN)
    		return;
    
    	XInstallColormap(X_Dpy, cc->colormap);
    
    	if ((cc->flags & CLIENT_INPUT) ||
    	    (!(cc->flags & CLIENT_WM_TAKE_FOCUS))) {
    		XSetInputFocus(X_Dpy, cc->win,
    		    RevertToPointerRoot, CurrentTime);
    	}
    	if (cc->flags & CLIENT_WM_TAKE_FOCUS)
    		xu_send_clientmsg(cc->win, cwmh[WM_TAKE_FOCUS], Last_Event_Time);
    
    	if ((oldcc = client_current(sc)) != NULL) {
    		oldcc->flags &= ~CLIENT_ACTIVE;
    		client_draw_border(oldcc);
    	}
    
    	/* If we're in the middle of cycling, don't change the order. */
    	if (!sc->cycling)
    		client_mtf(cc);
    
    	cc->flags |= CLIENT_ACTIVE;
    	cc->flags &= ~CLIENT_URGENCY;
    	client_draw_border(cc);
    	conf_grab_mouse(cc->win);
    	xu_ewmh_net_active_window(sc, cc->win);
    }
    
    void
    client_toggle_freeze(struct client_ctx *cc)
    {
    	if (cc->flags & CLIENT_FULLSCREEN)
    		return;
    
    	cc->flags ^= CLIENT_FREEZE;
    	xu_ewmh_set_net_wm_state(cc);
    }
    
    void
    client_toggle_hidden(struct client_ctx *cc)
    {
    	cc->flags ^= CLIENT_HIDDEN;
    	xu_ewmh_set_net_wm_state(cc);
    }
    
    void
    client_toggle_skip_pager(struct client_ctx *cc)
    {
    	cc->flags ^= CLIENT_SKIP_PAGER;
    	xu_ewmh_set_net_wm_state(cc);
    }
    
    void
    client_toggle_skip_taskbar(struct client_ctx *cc)
    {
    	cc->flags ^= CLIENT_SKIP_TASKBAR;
    	xu_ewmh_set_net_wm_state(cc);
    }
    
    void
    client_toggle_sticky(struct client_ctx *cc)
    {
    	cc->flags ^= CLIENT_STICKY;
    	xu_ewmh_set_net_wm_state(cc);
    }
    
    void
    client_toggle_fullscreen(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct geom		 area;
    
    	if ((cc->flags & CLIENT_FREEZE) &&
    	    !(cc->flags & CLIENT_FULLSCREEN))
    		return;
    
    	if (cc->flags & CLIENT_FULLSCREEN) {
    		if (!(cc->flags & CLIENT_IGNORE))
    			cc->bwidth = Conf.bwidth;
    		cc->geom = cc->fullgeom;
    		cc->flags &= ~(CLIENT_FULLSCREEN | CLIENT_FREEZE);
    		goto resize;
    	}
    
    	cc->fullgeom = cc->geom;
    
    	area = screen_area(sc,
    	    cc->geom.x + cc->geom.w / 2,
    	    cc->geom.y + cc->geom.h / 2, 0);
    
    	cc->bwidth = 0;
    	cc->geom = area;
    	cc->flags |= (CLIENT_FULLSCREEN | CLIENT_FREEZE);
    
    resize:
    	client_resize(cc, 0);
    	xu_ewmh_set_net_wm_state(cc);
    	client_ptr_inbound(cc, 1);
    }
    
    void
    client_toggle_maximize(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct geom		 area;
    
    	if (cc->flags & CLIENT_FREEZE)
    		return;
    
    	if ((cc->flags & CLIENT_MAXFLAGS) == CLIENT_MAXIMIZED) {
    		cc->geom = cc->savegeom;
    		cc->flags &= ~CLIENT_MAXIMIZED;
    		goto resize;
    	}
    
    	if (!(cc->flags & CLIENT_VMAXIMIZED)) {
    		cc->savegeom.h = cc->geom.h;
    		cc->savegeom.y = cc->geom.y;
    	}
    
    	if (!(cc->flags & CLIENT_HMAXIMIZED)) {
    		cc->savegeom.w = cc->geom.w;
    		cc->savegeom.x = cc->geom.x;
    	}
    
    	area = screen_area(sc,
    	    cc->geom.x + cc->geom.w / 2,
    	    cc->geom.y + cc->geom.h / 2, 1);
    
    	cc->geom.x = area.x;
    	cc->geom.y = area.y;
    	cc->geom.w = area.w - (cc->bwidth * 2);
    	cc->geom.h = area.h - (cc->bwidth * 2);
    	cc->flags |= CLIENT_MAXIMIZED;
    
    resize:
    	client_resize(cc, 0);
    	xu_ewmh_set_net_wm_state(cc);
    	client_ptr_inbound(cc, 1);
    }
    
    void
    client_toggle_vmaximize(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct geom		 area;
    
    	if (cc->flags & CLIENT_FREEZE)
    		return;
    
    	if (cc->flags & CLIENT_VMAXIMIZED) {
    		cc->geom.y = cc->savegeom.y;
    		cc->geom.h = cc->savegeom.h;
    		cc->flags &= ~CLIENT_VMAXIMIZED;
    		goto resize;
    	}
    
    	cc->savegeom.y = cc->geom.y;
    	cc->savegeom.h = cc->geom.h;
    
    	area = screen_area(sc,
    	    cc->geom.x + cc->geom.w / 2,
    	    cc->geom.y + cc->geom.h / 2, 1);
    
    	cc->geom.y = area.y;
    	cc->geom.h = area.h - (cc->bwidth * 2);
    	cc->flags |= CLIENT_VMAXIMIZED;
    
    resize:
    	client_resize(cc, 0);
    	xu_ewmh_set_net_wm_state(cc);
    	client_ptr_inbound(cc, 1);
    }
    
    void
    client_toggle_hmaximize(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	struct geom		 area;
    
    	if (cc->flags & CLIENT_FREEZE)
    		return;
    
    	if (cc->flags & CLIENT_HMAXIMIZED) {
    		cc->geom.x = cc->savegeom.x;
    		cc->geom.w = cc->savegeom.w;
    		cc->flags &= ~CLIENT_HMAXIMIZED;
    		goto resize;
    	}
    
    	cc->savegeom.x = cc->geom.x;
    	cc->savegeom.w = cc->geom.w;
    
    	area = screen_area(sc,
    	    cc->geom.x + cc->geom.w / 2,
    	    cc->geom.y + cc->geom.h / 2, 1);
    
    	cc->geom.x = area.x;
    	cc->geom.w = area.w - (cc->bwidth * 2);
    	cc->flags |= CLIENT_HMAXIMIZED;
    
    resize:
    	client_resize(cc, 0);
    	xu_ewmh_set_net_wm_state(cc);
    	client_ptr_inbound(cc, 1);
    }
    
    void
    client_resize(struct client_ctx *cc, int reset)
    {
    	if (reset) {
    		cc->flags &= ~CLIENT_MAXIMIZED;
    		xu_ewmh_set_net_wm_state(cc);
    	}
    
    	client_draw_border(cc);
    
    	XMoveResizeWindow(X_Dpy, cc->win, cc->geom.x,
    	    cc->geom.y, cc->geom.w, cc->geom.h);
    	cc->dim.w = (cc->geom.w - cc->hint.basew) / cc->hint.incw;
    	cc->dim.h = (cc->geom.h - cc->hint.baseh) / cc->hint.inch;
    	client_config(cc);
    }
    
    void
    client_move(struct client_ctx *cc)
    {
    	XMoveWindow(X_Dpy, cc->win, cc->geom.x, cc->geom.y);
    	client_config(cc);
    }
    
    void
    client_lower(struct client_ctx *cc)
    {
    	XLowerWindow(X_Dpy, cc->win);
    }
    
    void
    client_raise(struct client_ctx *cc)
    {
    	XRaiseWindow(X_Dpy, cc->win);
    }
    
    void
    client_config(struct client_ctx *cc)
    {
    	XConfigureEvent	 cn;
    
    	(void)memset(&cn, 0, sizeof(cn));
    	cn.type = ConfigureNotify;
    	cn.event = cc->win;
    	cn.window = cc->win;
    	cn.x = cc->geom.x;
    	cn.y = cc->geom.y;
    	cn.width = cc->geom.w;
    	cn.height = cc->geom.h;
    	cn.border_width = cc->bwidth;
    	cn.above = None;
    	cn.override_redirect = 0;
    
    	XSendEvent(X_Dpy, cc->win, False, StructureNotifyMask, (XEvent *)&cn);
    }
    
    void
    client_ptr_inbound(struct client_ctx *cc, int getpos)
    {
    	if (getpos)
    		xu_ptr_get(cc->win, &cc->ptr.x, &cc->ptr.y);
    
    	if (cc->ptr.x < 0)
    		cc->ptr.x = 0;
    	else if (cc->ptr.x > cc->geom.w - 1)
    		cc->ptr.x = cc->geom.w - 1;
    	if (cc->ptr.y < 0)
    		cc->ptr.y = 0;
    	else if (cc->ptr.y > cc->geom.h - 1)
    		cc->ptr.y = cc->geom.h - 1;
    
    	client_ptr_warp(cc);
    }
    
    void
    client_ptr_warp(struct client_ctx *cc)
    {
    	xu_ptr_set(cc->win, cc->ptr.x, cc->ptr.y);
    }
    
    void
    client_ptr_save(struct client_ctx *cc)
    {
    	int	 x, y;
    
    	xu_ptr_get(cc->win, &x, &y);
    	if (client_inbound(cc, x, y)) {
    		cc->ptr.x = x;
    		cc->ptr.y = y;
    	} else {
    		cc->ptr.x = cc->geom.w / 2;
    		cc->ptr.y = cc->geom.h / 2;
    	}
    }
    
    void
    client_hide(struct client_ctx *cc)
    {
    	XUnmapWindow(X_Dpy, cc->win);
    
    	if (cc->flags & CLIENT_ACTIVE) {
    		cc->flags &= ~CLIENT_ACTIVE;
    		xu_ewmh_net_active_window(cc->sc, None);
    	}
    	cc->flags |= CLIENT_HIDDEN;
    	xu_set_wm_state(cc->win, IconicState);
    }
    
    void
    client_show(struct client_ctx *cc)
    {
    	XMapRaised(X_Dpy, cc->win);
    
    	cc->flags &= ~CLIENT_HIDDEN;
    	xu_set_wm_state(cc->win, NormalState);
    	client_draw_border(cc);
    }
    
    void
    client_urgency(struct client_ctx *cc)
    {
    	if (!(cc->flags & CLIENT_ACTIVE))
    		cc->flags |= CLIENT_URGENCY;
    }
    
    void
    client_draw_border(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    	unsigned long		 pixel;
    
    	if (cc->flags & CLIENT_ACTIVE)
    		switch (cc->flags & CLIENT_HIGHLIGHT) {
    		case CLIENT_GROUP:
    			pixel = sc->xftcolor[CWM_COLOR_BORDER_GROUP].pixel;
    			break;
    		case CLIENT_UNGROUP:
    			pixel = sc->xftcolor[CWM_COLOR_BORDER_UNGROUP].pixel;
    			break;
    		default:
    			pixel = sc->xftcolor[CWM_COLOR_BORDER_ACTIVE].pixel;
    			break;
    		}
    	else
    		pixel = sc->xftcolor[CWM_COLOR_BORDER_INACTIVE].pixel;
    
    	if (cc->flags & CLIENT_URGENCY)
    		pixel = sc->xftcolor[CWM_COLOR_BORDER_URGENCY].pixel;
    
    	XSetWindowBorderWidth(X_Dpy, cc->win, (unsigned int)cc->bwidth);
    	XSetWindowBorder(X_Dpy, cc->win, pixel | (0xffu << 24));
    }
    
    static void
    client_class_hint(struct client_ctx *cc)
    {
    	XClassHint	ch;
    
    	if (XGetClassHint(X_Dpy, cc->win, &ch)) {
    		if (ch.res_class) {
    			cc->res_class = xstrdup(ch.res_class);
    			XFree(ch.res_class);
    		}
    		if (ch.res_name) {
    			cc->res_name = xstrdup(ch.res_name);
    			XFree(ch.res_name);
    		}
    	}
    }
    
    static void
    client_wm_protocols(struct client_ctx *cc)
    {
    	Atom	*p;
    	int	 i, j;
    
    	if (XGetWMProtocols(X_Dpy, cc->win, &p, &j)) {
    		for (i = 0; i < j; i++) {
    			if (p[i] == cwmh[WM_DELETE_WINDOW])
    				cc->flags |= CLIENT_WM_DELETE_WINDOW;
    			else if (p[i] == cwmh[WM_TAKE_FOCUS])
    				cc->flags |= CLIENT_WM_TAKE_FOCUS;
    		}
    		XFree(p);
    	}
    }
    
    void
    client_wm_hints(struct client_ctx *cc)
    {
    	XWMHints	*wmh;
    
    	if ((wmh = XGetWMHints(X_Dpy, cc->win)) != NULL) {
    		if ((wmh->flags & InputHint) && (wmh->input))
    			cc->flags |= CLIENT_INPUT;
    		if ((wmh->flags & XUrgencyHint))
    			client_urgency(cc);
    		if ((wmh->flags & StateHint))
    			cc->initial_state = wmh->initial_state;
    		XFree(wmh);
    	}
    }
    
    void
    client_close(struct client_ctx *cc)
    {
    	if (cc->flags & CLIENT_WM_DELETE_WINDOW)
    		xu_send_clientmsg(cc->win, cwmh[WM_DELETE_WINDOW], CurrentTime);
    	else
    		XKillClient(X_Dpy, cc->win);
    }
    
    void
    client_set_name(struct client_ctx *cc)
    {
    	struct winname	*wn, *wnnxt;
    	int		 i = 0;
    
    	free(cc->name);
    	if (!xu_get_strprop(cc->win, ewmh[_NET_WM_NAME], &cc->name))
    		if (!xu_get_strprop(cc->win, XA_WM_NAME, &cc->name))
    			cc->name = xstrdup("");
    
    	TAILQ_FOREACH_SAFE(wn, &cc->nameq, entry, wnnxt) {
    		if (strcmp(wn->name, cc->name) == 0) {
    			TAILQ_REMOVE(&cc->nameq, wn, entry);
    			free(wn->name);
    			free(wn);
    		}
    		i++;
    	}
    	wn = xmalloc(sizeof(*wn));
    	wn->name = xstrdup(cc->name);
    	TAILQ_INSERT_TAIL(&cc->nameq, wn, entry);
    
    	/* Garbage collection. */
    	if ((i + 1) > Conf.nameqlen) {
    		wn = TAILQ_FIRST(&cc->nameq);
    		TAILQ_REMOVE(&cc->nameq, wn, entry);
    		free(wn->name);
    		free(wn);
    	}
    }
    
    static void
    client_placement(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    
    	if (cc->hint.flags & (USPosition | PPosition)) {
    		if (cc->geom.x >= sc->view.w)
    			cc->geom.x = sc->view.w - cc->bwidth - 1;
    		if (cc->geom.x + cc->geom.w + cc->bwidth <= 0)
    			cc->geom.x = -(cc->geom.w + cc->bwidth - 1);
    		if (cc->geom.y >= sc->view.h)
    			cc->geom.x = sc->view.h - cc->bwidth - 1;
    		if (cc->geom.y + cc->geom.h + cc->bwidth <= 0)
    			cc->geom.y = -(cc->geom.h + cc->bwidth - 1);
    		if (cc->flags & CLIENT_IGNORE) {
    			if (((cc->obwidth * 2) + cc->geom.x + cc->geom.w) == sc->view.w)
    				cc->geom.x += cc->obwidth * 2;
    			if (((cc->obwidth * 2) + cc->geom.y + cc->geom.h) == sc->view.h)
    				cc->geom.y += cc->obwidth * 2;
    		}
    	} else {
    		struct geom	 area;
    		int		 xmouse, ymouse, xslack, yslack;
    
    		xu_ptr_get(sc->rootwin, &xmouse, &ymouse);
    		area = screen_area(sc, xmouse, ymouse, 1);
    
    		xmouse = MAX(MAX(xmouse, area.x) - cc->geom.w / 2, area.x);
    		ymouse = MAX(MAX(ymouse, area.y) - cc->geom.h / 2, area.y);
    
    		xslack = area.x + area.w - cc->geom.w - cc->bwidth * 2;
    		yslack = area.y + area.h - cc->geom.h - cc->bwidth * 2;
    
    		if (xslack >= area.x) {
    			cc->geom.x = MAX(MIN(xmouse, xslack), area.x);
    		} else {
    			cc->geom.x = area.x;
    			cc->geom.w = area.x + area.w;
    		}
    		if (yslack >= area.y) {
    			cc->geom.y = MAX(MIN(ymouse, yslack), area.y);
    		} else {
    			cc->geom.y = area.y;
    			cc->geom.h = area.y + area.h;
    		}
    	}
    }
    
    void
    client_mtf(struct client_ctx *cc)
    {
    	struct screen_ctx	*sc = cc->sc;
    
    	TAILQ_REMOVE(&sc->clientq, cc, entry);
    	TAILQ_INSERT_HEAD(&sc->clientq, cc, entry);
    }
    
    void
    client_get_sizehints(struct client_ctx *cc)
    {
    	long		 tmp;
    	XSizeHints	 size;
    
    	if (!XGetWMNormalHints(X_Dpy, cc->win, &size, &tmp))
    		size.flags = 0;
    
    	cc->hint.flags = size.flags;
    
    	if (size.flags & PBaseSize) {
    		cc->hint.basew = size.base_width;
    		cc->hint.baseh = size.base_height;
    	} else if (size.flags & PMinSize) {
    		cc->hint.basew = size.min_width;
    		cc->hint.baseh = size.min_height;
    	}
    	if (size.flags & PMinSize) {
    		cc->hint.minw = size.min_width;
    		cc->hint.minh = size.min_height;
    	} else if (size.flags & PBaseSize) {
    		cc->hint.minw = size.base_width;
    		cc->hint.minh = size.base_height;
    	}
    	if (size.flags & PMaxSize) {
    		cc->hint.maxw = size.max_width;
    		cc->hint.maxh = size.max_height;
    	}
    	if (size.flags & PResizeInc) {
    		cc->hint.incw = size.width_inc;
    		cc->hint.inch = size.height_inc;
    	}
    	cc->hint.incw = MAX(1, cc->hint.incw);
    	cc->hint.inch = MAX(1, cc->hint.inch);
    	cc->hint.minw = MAX(1, cc->hint.minw);
    	cc->hint.minh = MAX(1, cc->hint.minh);
    
    	if (size.flags & PAspect) {
    		if (size.min_aspect.x > 0)
    			cc->hint.mina = (float)size.min_aspect.y /
    			    size.min_aspect.x;
    		if (size.max_aspect.y > 0)
    			cc->hint.maxa = (float)size.max_aspect.x /
    			    size.max_aspect.y;
    	}
    }
    
    void
    client_apply_sizehints(struct client_ctx *cc)
    {
    	Bool		 baseismin;
    
    	baseismin = (cc->hint.basew == cc->hint.minw) &&
    	    (cc->hint.baseh == cc->hint.minh);
    
    	/* temporarily remove base dimensions, ICCCM 4.1.2.3 */
    	if (!baseismin) {
    		cc->geom.w -= cc->hint.basew;
    		cc->geom.h -= cc->hint.baseh;
    	}
    
    	/* adjust for aspect limits */
    	if (cc->hint.mina && cc->hint.maxa) {
    		if (cc->hint.maxa < (float)cc->geom.w / cc->geom.h)
    			cc->geom.w = cc->geom.h * cc->hint.maxa;
    		else if (cc->hint.mina < (float)cc->geom.h / cc->geom.w)
    			cc->geom.h = cc->geom.w * cc->hint.mina;
    	}
    
    	/* remove base dimensions for increment */
    	if (baseismin) {
    		cc->geom.w -= cc->hint.basew;
    		cc->geom.h -= cc->hint.baseh;
    	}
    
    	/* adjust for increment value */
    	cc->geom.w -= cc->geom.w % cc->hint.incw;
    	cc->geom.h -= cc->geom.h % cc->hint.inch;
    
    	/* restore base dimensions */
    	cc->geom.w += cc->hint.basew;
    	cc->geom.h += cc->hint.baseh;
    
    	/* adjust for min width/height */
    	cc->geom.w = MAX(cc->geom.w, cc->hint.minw);
    	cc->geom.h = MAX(cc->geom.h, cc->hint.minh);
    
    	/* adjust for max width/height */
    	if (cc->hint.maxw)
    		cc->geom.w = MIN(cc->geom.w, cc->hint.maxw);
    	if (cc->hint.maxh)
    		cc->geom.h = MIN(cc->geom.h, cc->hint.maxh);
    }
    
    static void
    client_mwm_hints(struct client_ctx *cc)
    {
    	struct mwm_hints	*mwmh;
    
    	if (xu_get_prop(cc->win, cwmh[_MOTIF_WM_HINTS],
    	    cwmh[_MOTIF_WM_HINTS], MWM_HINTS_ELEMENTS,
    	    (unsigned char **)&mwmh) <= 0)
    		return;
    
    	if ((mwmh->flags & MWM_HINTS_DECORATIONS) &&
    	    !(mwmh->decorations & MWM_DECOR_ALL)) {
    		if (!(mwmh->decorations & MWM_DECOR_BORDER))
    			cc->bwidth = 0;
    	}
    	XFree(mwmh);
    }
    
    void
    client_transient(struct client_ctx *cc)
    {
    	struct client_ctx	*tc;
    	Window			 trans;
    
    	if (XGetTransientForHint(X_Dpy, cc->win, &trans)) {
    		if ((tc = client_find(trans)) != NULL) {
    			if (tc->flags & CLIENT_IGNORE) {
    				cc->flags |= CLIENT_IGNORE;
    				cc->bwidth = tc->bwidth;
    			}
    		}
    	}
    }
    
    int
    client_inbound(struct client_ctx *cc, int x, int y)
    {
    	return(x < cc->geom.w && x >= 0 &&
    	    y < cc->geom.h && y >= 0);
    }
    
    int
    client_snapcalc(int n0, int n1, int e0, int e1, int snapdist)
    {
    	int	 s0, s1;
    
    	s0 = s1 = 0;
    
    	if (abs(e0 - n0) <= snapdist)
    		s0 = e0 - n0;
    
    	if (abs(e1 - n1) <= snapdist)
    		s1 = e1 - n1;
    
    	/* possible to snap in both directions */
    	if (s0 != 0 && s1 != 0)
    		if (abs(s0) < abs(s1))
    			return s0;
    		else
    			return s1;
    	else if (s0 != 0)
    		return s0;
    	else if (s1 != 0)
    		return s1;
    	else
    		return 0;
    }
    
    void
    client_htile(struct client_ctx *cc)
    {
    	struct client_ctx	*ci;
    	struct screen_ctx 	*sc = cc->sc;
    	struct geom 		 area;
    	int 			 i, n, mh, x, w, h;
    
    	i = n = 0;
    	area = screen_area(sc,
    	    cc->geom.x + cc->geom.w / 2,
    	    cc->geom.y + cc->geom.h / 2, 1);
    
    	TAILQ_FOREACH(ci, &sc->clientq, entry) {
    		if (ci->gc != cc->gc)
    			continue;
    		if (ci->flags & CLIENT_HIDDEN ||
    		    ci->flags & CLIENT_IGNORE || (ci == cc) ||
    		    ci->geom.x < area.x ||
    		    ci->geom.x > (area.x + area.w) ||
    		    ci->geom.y < area.y ||
    		    ci->geom.y > (area.y + area.h))
    			continue;
    		n++;
    	}
    	if (n == 0)
    		return;
    
    	if (cc->flags & CLIENT_VMAXIMIZED ||
    	    cc->geom.h + (cc->bwidth * 2) >= area.h)
    		return;
    
    	cc->flags &= ~CLIENT_HMAXIMIZED;
    	cc->geom.x = area.x;
    	cc->geom.y = area.y;
    	cc->geom.w = area.w - (cc->bwidth * 2);
    	if (Conf.htile > 0)
    		cc->geom.h = ((area.h - (cc->bwidth * 2)) * Conf.htile) / 100;
    	client_resize(cc, 1);
    	client_ptr_warp(cc);
    
    	mh = cc->geom.h + (cc->bwidth * 2);
    	x = area.x;
    	w = area.w / n;
    	h = area.h - mh;
    	TAILQ_FOREACH(ci, &sc->clientq, entry) {
    		if (ci->gc != cc->gc)
    			continue;
    		if (ci->flags & CLIENT_HIDDEN ||
    		    ci->flags & CLIENT_IGNORE || (ci == cc) ||
    		    ci->geom.x < area.x ||
    		    ci->geom.x > (area.x + area.w) ||
    		    ci->geom.y < area.y ||
    		    ci->geom.y > (area.y + area.h))
    			continue;
    		ci->bwidth = Conf.bwidth;
    		ci->geom.x = x;
    		ci->geom.y = area.y + mh;
    		ci->geom.w = w - (ci->bwidth * 2);
    		ci->geom.h = h - (ci->bwidth * 2);
    		if (i + 1 == n)
    			ci->geom.w = area.x + area.w -
    			    ci->geom.x - (ci->bwidth * 2);
    		x += w;
    		i++;
    		client_resize(ci, 1);
    	}
    }
    
    void
    client_vtile(struct client_ctx *cc)
    {
    	struct client_ctx	*ci;
    	struct screen_ctx 	*sc = cc->sc;
    	struct geom 		 area;
    	int 			 i, n, mw, y, w, h;
    
    	i = n = 0;
    	area = screen_area(sc,
    	    cc->geom.x + cc->geom.w / 2,
    	    cc->geom.y + cc->geom.h / 2, 1);
    
    	TAILQ_FOREACH(ci, &sc->clientq, entry) {
    		if (ci->gc != cc->gc)
    			continue;
    		if (ci->flags & CLIENT_HIDDEN ||
    		    ci->flags & CLIENT_IGNORE || (ci == cc) ||
    		    ci->geom.x < area.x ||
    		    ci->geom.x > (area.x + area.w) ||
    		    ci->geom.y < area.y ||
    		    ci->geom.y > (area.y + area.h))
    			continue;
    		n++;
    	}
    	if (n == 0)
    		return;
    
    	if (cc->flags & CLIENT_HMAXIMIZED ||
    	    cc->geom.w + (cc->bwidth * 2) >= area.w)
    		return;
    
    	cc->flags &= ~CLIENT_VMAXIMIZED;
    	cc->geom.x = area.x;
    	cc->geom.y = area.y;
    	if (Conf.vtile > 0)
    		cc->geom.w = ((area.w - (cc->bwidth * 2)) * Conf.vtile) / 100;
    	cc->geom.h = area.h - (cc->bwidth * 2);
    	client_resize(cc, 1);
    	client_ptr_warp(cc);
    
    	mw = cc->geom.w + (cc->bwidth * 2);
    	y = area.y;
    	h = area.h / n;
    	w = area.w - mw;
    	TAILQ_FOREACH(ci, &sc->clientq, entry) {
    		if (ci->gc != cc->gc)
    			continue;
    		if (ci->flags & CLIENT_HIDDEN ||
    		    ci->flags & CLIENT_IGNORE || (ci == cc) ||
    		    ci->geom.x < area.x ||
    		    ci->geom.x > (area.x + area.w) ||
    		    ci->geom.y < area.y ||
    		    ci->geom.y > (area.y + area.h))
    			continue;
    		ci->bwidth = Conf.bwidth;
    		ci->geom.x = area.x + mw;
    		ci->geom.y = y;
    		ci->geom.w = w - (ci->bwidth * 2);
    		ci->geom.h = h - (ci->bwidth * 2);
    		if (i + 1 == n)
    			ci->geom.h = area.y + area.h -
    			    ci->geom.y - (ci->bwidth * 2);
    		y += h;
    		i++;
    		client_resize(ci, 1);
    	}
    }