 /* 
  * Oroborus Window Manager
  *
  * Copyright (C) 2001 Ken Lynch
  * Copyright (C) 2002 Stefan Pfetzing
  *
  * OroboROX Window Manager
  * 
  * Copyright (C) 2004 Guido Schimmels
  *
  * 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
  */

#include "config.h"
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>


#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xmd.h>
#include <X11/extensions/shape.h>

#include <ft2build.h>
#include <X11/Xft/Xft.h>

#include "xerror.h"
#include "keyboard.h"
#include "pixmap.h"
#include "main.h"
#include "hints.h"
#include "client.h"
#include "i18n.h"
#include "xinerama.h"
#include "events.h"
#include "settings.h"
#include "mouse.h"
#include "workspaces.h"
#include "stacking.h"
#include "frame.h"
#include "startup_notification.h"
#include "focus.h"

#define DBUG_VERBOSE(x)			//DBUG(x)
#define DBUG_CYCLING(x)			//fprintf(stderr, "[OROBOROX]: %s\n", x)


/***************************/
/* BEGIN private functions */
/***************************/


inline gboolean client_is_transient(Client * c)
{
	g_return_val_if_fail(c != NULL, FALSE);

	DBUG_VERBOSE("entering clientIsTransient");

	return (((c->transientFor != root) && (c->transientFor != None)) ||
			((c->transientFor == root) && (c->group_leader != None)));
}

inline gboolean client_is_modal(Client * c)
{
	g_return_val_if_fail(c != NULL, FALSE);

	DBUG_VERBOSE("entering client_is_modal");

	return ((c->state & STATE_MODAL) &&
			(((c->transientFor != root) && (c->transientFor != None)) ||
			 (c->group_leader != None)));
}

static gboolean client_is_transient_or_modal(Client * c)
{
	g_return_val_if_fail(c != NULL, FALSE);
	DBUG("entering client_is_transient_or_modal");
	return (client_is_transient(c) || client_is_modal(c));
}

inline static void clientAddToList(Client * c)
{
	dbg("Adding %#lx to client list\n", c->window);
	client_list = g_array_append_val(client_list, c->window);
	// FIXME: this is too simple
	// we need to consider window layer
	client_list_stacking = g_array_append_val(client_list_stacking, c->window);
	client_sync_client_list();
	clients = g_list_append(clients, c);
}


static void clientRemoveFromList(Client * c)
{
	DBUG("entering clientRemoveFromList");

	int i = client_list->len;

	while (--i >= 0)
		if (g_array_index(client_list, Window, i) == c->window)
		{
			client_list = g_array_remove_index(client_list, i);
			break;
		}

	i = client_list_stacking->len;
	while (--i >= 0)
		if (g_array_index(client_list_stacking, Window, i) == c->window)
		{
			client_list_stacking = g_array_remove_index(client_list_stacking, i);
			break;
		}

	client_sync_client_list();
	clients = g_list_remove(clients, c);
}

static gboolean clientInList(Client * c, char *list)
{
	DBUG_VERBOSE("entering clientInList\n");

	gboolean flag = FALSE;

	if (c->class.res_name)
	{
		char *buf = g_strdup(list);
		char *tok = strtok(buf, ";");

		while (tok)
		{
			if (strcasecmp(tok, c->class.res_name) == 0)
			{
				flag = TRUE;
				break;
			}
			tok = strtok(NULL, ";");
		}
		g_free(buf);
	}

	return flag;
}


static Window client_create_simple_window(Client * c)
{
	return XCreateSimpleWindow(dpy, c->frame, 0, 0, 1, 1, 0, 0, 0);
}

inline static void match_client(Client * c)
{
	if (clientInList(c, iconic_windows))
		c->state |= STATE_HIDDEN;

	if (clientInList(c, ontop_windows))
		c->state |= STATE_ABOVE;

	if (clientInList(c, sticky_windows) || c->win_workspace == -1)
		c->state |= STATE_STICKY;

	if (clientInList(c, borderless_windows))
		c->has_decor = 0;
}

static void get_allowed_actions(Client * c)
{
	AllowedActions actions =
		(ACTION_RESIZE | ACTION_SHADE | ACTION_FULLSCREEN | ACTION_CHANGE_DESKTOP |
		 ACTION_CLOSE | ACTION_MINIMIZE | ACTION_MAXIMIZE_HORZ | ACTION_MAXIMIZE_VERT |
		 ACTION_MOVE);

	MWMHints mwm_hints = c->mwm_hints;

	if (mwm_hints & MWM_HAS_HINTS)
	{
		if (!(mwm_hints & MWM_HAS_CLOSE_FUNC))
			actions &= ~ACTION_CLOSE;
		if (!(mwm_hints & MWM_HAS_MINIMIZE_FUNC))
			actions &= ~ACTION_MINIMIZE;
		if (!(mwm_hints & MWM_HAS_MAXIMIZE_FUNC))
			actions &= ~(ACTION_MAXIMIZE_HORZ | ACTION_MAXIMIZE_VERT);
		if (!(mwm_hints & MWM_HAS_MOVE_FUNC))
			actions &= ~ACTION_MOVE;
	}

	/* If min_size == max_size, then don't allow resize */
	gboolean hasminmax = ((c->size->flags & (PMinSize | PMaxSize)) == (PMinSize | PMaxSize));

	if (hasminmax &&
		c->size->min_width == c->size->max_width && c->size->min_height == c->size->max_height)
		actions &= ~ACTION_RESIZE;

	if (c->type != WINDOW_NORMAL)
	{
		if (c->type == WINDOW_TOOLBAR)
			c->has_decor = 0;
		else if (c->type & (WINDOW_DESKTOP | WINDOW_DOCK | WINDOW_SPLASHSCREEN))
		{
			c->has_decor = 0;
			actions &= ~(ACTION_CLOSE | ACTION_SHADE | ACTION_MOVE | ACTION_RESIZE);

			/* FIXME this (ACTION_MOVE|ACTION_RESIZE) keeps panels and things from using
			 * NET_WM_MOVERESIZE; the problem is that some
			 * panels (edge panels) have fixed possible locations,
			 * and others ("floating panels") do not.
			 *
			 * Perhaps we should require edge panels to explicitly
			 * disable movement?
			 */
		}
		else if ((c->type & WINDOW_DOCK))
		{
			c->has_decor = 0;
			actions &= ~(ACTION_CLOSE | ACTION_SHADE);
		}
	}

	if (!(actions & ACTION_RESIZE))
	{
		actions &= ~(ACTION_MAXIMIZE_HORZ | ACTION_MAXIMIZE_VERT);

		/* don't allow fullscreen if we can't resize, unless the size
		 * is entire screen size (kind of broken, because we
		 * actually fullscreen to xinerama head size not screen size)
		 */
		if (hasminmax &&
			c->size->min_width == display_width &&
			c->size->min_height == display_height && c->has_decor == 0)
			;					/* leave fullscreen available */
		else
			actions &= ~(ACTION_FULLSCREEN);
	}

	/* no shading if not decorated */
	if (!c->has_decor || (c->mwm_hints & MWM_BORDER_ONLY))
		actions &= ~(ACTION_SHADE);

	/*if (actions & ACTION_MOVE)
	   fprintf(stderr, "ACTION_MOVE ");
	   if (actions & ACTION_RESIZE)
	   fprintf(stderr, "ACTION_RESIZE ");
	   if (actions & ACTION_MINIMIZE)
	   fprintf(stderr, "ACTION_MINIMIZE ");
	   if (actions & ACTION_SHADE)
	   fprintf(stderr, "ACTION_SHADE ");
	   if (actions & ACTION_STICK)
	   fprintf(stderr, "ACTION_STICK ");
	   if (actions & ACTION_MAXIMIZE_HORZ)
	   fprintf(stderr, "ACTION_MAXIMIZE_HORZ ");
	   if (actions & ACTION_MAXIMIZE_VERT)
	   fprintf(stderr, "ACTION_MAXIMIZE_VERT ");
	   if (actions & ACTION_FULLSCREEN)
	   fprintf(stderr, "ACTION_FULLSCREEN ");
	   if (actions & ACTION_CHANGE_DESKTOP)
	   fprintf(stderr, "ACTION_CHANGE_DESKTOP ");
	   if (actions & ACTION_CLOSE)
	   fprintf(stderr, "ACTION_CLOSE ");
	   fprintf(stderr, "\n"); */

  out:
	c->actions = actions;
}

static void _client_toggle_sticky(Client * c)
{
#ifdef DEBUG
	DBUG("entering clientToggleSticky");
	fprintf(stderr, "sticking/unsticking client (%#lx)\n", c->window);
#endif

	if (c->always_sticky && c->state & STATE_STICKY)
		return;

	c->state ^= STATE_STICKY;
	set_net_wm_state(c);

	int ws = workspace;

	if (c->state & STATE_STICKY)
		ws = -1;
	client_set_workspace(c, ws);
}

inline static void client_apply_initial_state(Client * c)
{

	dbg("entering client_apply_initial_state\n");

	/* We check that afterwards to make sure all states are now known */
	if ((c->state & (STATE_MAXIMIZED_HORZ | STATE_MAXIMIZED_VERT)))
	{
		if (c->actions & (ACTION_MAXIMIZE_VERT | ACTION_MAXIMIZE_HORZ))
		{
			int mode;
			mode = c->state & (STATE_MAXIMIZED_HORZ | STATE_MAXIMIZED_VERT);
			/* Unset fullscreen mode so that clientToggleMaximized() really change the state */
			c->state &= -1-STATE_FULLSCREEN-STATE_MAXIMIZED_HORZ-STATE_MAXIMIZED_VERT;
			clientToggleMaximized(c, mode, 0);
		}
	}
	if ((c->state & STATE_FULLSCREEN) && (c->actions & ACTION_FULLSCREEN))
	{
		//if (!clientIsValidTransientOrModal (c))
		//{
		DBUG("Applying client's initial state:fullscreen");
		//clientUpdateFullscreenState (c);
		c->state &= -1-STATE_FULLSCREEN;
		clientToggleFullscreen(c, 0);
		//}
	}
	/*if (FLAG_TEST_AND_NOT (c->flags, CLIENT_FLAG_ABOVE, CLIENT_FLAG_BELOW))
	   {
	   if (!clientIsValidTransientOrModal (c))
	   {
	   DBUG ("Applying client's initial state: above");
	   clientUpdateAboveState (c);
	   }
	   } */
	/*if (FLAG_TEST_AND_NOT (c->flags, CLIENT_FLAG_BELOW, CLIENT_FLAG_ABOVE))
	   {
	   if (!clientIsValidTransientOrModal (c))
	   {
	   TRACE ("Applying client's initial state: below");
	   clientUpdateBelowState (c);
	   }
	   } */
	if ((c->state & STATE_STICKY) && (c->actions & ACTION_STICK))
	{
		//if (!clientIsValidTransientOrModal (c))
		//{
		DBUG("Applying client's initial state: sticky");
		c->state &= -1-STATE_STICKY;
		_client_toggle_sticky(c);
		//}
	}
	if ((c->state & STATE_SHADED) && (c->actions & ACTION_SHADE))
	{
		DBUG("Applying client's initial state: shaded");
		c->state &= -1-STATE_SHADED;
		clientToggleShaded(c);
	}
	else if ((c->state & STATE_HIDDEN))
		set_mapping_state(c, IconicState);
	
	set_net_wm_state(c);
}

gint grab_server(Display * dpy, gboolean grab)
{
	static guint sgrabs = 0;

	if (grab)
	{
		if (sgrabs++ == 0)
		{
			XGrabServer(dpy);
			XSync(dpy, FALSE);
		}
	}
	else if (sgrabs > 0)
	{
		if (--sgrabs == 0)
		{
			XUngrabServer(dpy);
			XFlush(dpy);
		}
	}
	return sgrabs;
}



void clientInitPosition(Client * c)
{
	int mx, my, min_x, min_y, max_x, max_y;

	//Client *c2;

#ifdef DEBUG
	DBUG("entering clientInitPosition");
#endif

	if (!get_mouse_xy(dpy, root, &mx, &my))
		mx = my = 0; /* FIXME: what really to do? */
	
	if (c->type == WINDOW_DESKTOP)
	{
		min_x = min_y = 0;
		max_x = display_width;
		max_y = display_height;
	}
	else
	{
		int m = xineramaGetMonitorUnderPoint(mx, my);

		min_x = xinerama_screen_info[m].x_org;
		min_y = xinerama_screen_info[m].y_org;
		max_x = min_x + xinerama_screen_info[m].width;
		max_y = min_y + xinerama_screen_info[m].height;
	}
	client_gravitate(c);
	
	if (!c->non_focusing && !(c->state & STATE_ABOVE))
	{
		if (min_x == 0)
			min_x = getMargin(MARGIN_LEFT, min_y, max_y);
		if (max_x == display_width)
			max_x -= getMargin(MARGIN_RIGHT, min_y, max_y);
		if (min_y == 0)
			min_y = getMargin(MARGIN_TOP, min_x, max_x);
		if (max_y == display_height)
			max_y -= getMargin(MARGIN_BOTTOM, min_x, max_x);
	}


	if (c->size->flags & (PPosition | USPosition))
	{
/*        if (CONSTRAINED_WINDOW (c))*/
/*          {*/
/*            clientKeepVisible (c);*/
/*          }*/
/*        return;*/
	}
	else
	{
		if (window_placement == PLACEMENT_MOUSE)
		{
			c->geometry.x = mx - (frameWidth(c) / 2);
			c->geometry.y = my - (frameHeight(c) / 2);
		}
		else if (window_placement == PLACEMENT_ROOT)
		{
			c->geometry.x = (max_x - min_x) / 2 - (frameWidth(c) / 2);
			c->geometry.y = (max_y - min_y) / 2 - (frameHeight(c) / 2);
		}
		//else if smartplacement... (TODO)
	}

/*  if (clientIsTransient (c) && (c2 = clientGetTransient (c)))
    {
        // Center transient relative to their parent window
        c->geometry.x = c2->x + (c2->width - c->geometry.width) / 2;
        c->geometry.y = c2->y + (c2->height - c->geometry.height) / 2;
        if (CONSTRAINED_WINDOW (c))
        {
            clientKeepVisible (c);
        }
        return;
    }
*/
	if (c->geometry.x > max_x - frameWidth(c) + frameLeft(c))
		c->geometry.x = max_x - frameWidth(c) + frameLeft(c);
	if (c->geometry.y > max_y - frameHeight(c) + frameTop(c))
		c->geometry.y = max_y - frameHeight(c) + frameTop(c);
	if (c->geometry.x < min_x + frameLeft(c))
		c->geometry.x = min_x + frameLeft(c);
	if (c->geometry.y < min_y + frameTop(c))
		c->geometry.y = min_y + frameTop(c);
}

inline static void client_create_frame(Client *c, Window w)
{
	c->decor_state = INACTIVE;
	c->frame =
		XCreateSimpleWindow(dpy, root, frameX(c), frameY(c), frameWidth(c), frameHeight(c), 0, 0,
							0);
	XSelectInput(dpy, c->frame,
				 SubstructureNotifyMask | SubstructureRedirectMask | EnterWindowMask);
	XSelectInput(dpy, c->window, FocusChangeMask | PropertyChangeMask);
	if (shape)
		XShapeSelectInput(dpy, c->window, ShapeNotifyMask);
	XSetWindowBorderWidth(dpy, w, 0);
	XReparentWindow(dpy, w, c->frame, frameLeft(c), frameTop(c));
	XGrabButton(dpy, AnyButton, AnyModifier, c->frame, False,
				ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);

	c->sides[SIDE_LEFT] = client_create_simple_window(c);
	c->sides[SIDE_RIGHT] = client_create_simple_window(c);
	c->sides[SIDE_BOTTOM] = client_create_simple_window(c);
	c->corners[CORNER_BOTTOM_LEFT] = client_create_simple_window(c);
	c->corners[CORNER_BOTTOM_RIGHT] = client_create_simple_window(c);
	c->corners[CORNER_TOP_LEFT] = client_create_simple_window(c);
	c->corners[CORNER_TOP_RIGHT] = client_create_simple_window(c);
	c->title = client_create_simple_window(c);
	
	int i = BUTTON_COUNT; do
	{
		c->buttons[BUTTON_COUNT - i] = client_create_simple_window(c);
		if (button_cursors)
			XDefineCursor(dpy, c->buttons[BUTTON_COUNT - i], button_cursor[BUTTON_COUNT - i]);
	} while (--i > 0);

	for (i = 0; i < 4; i++)
		XDefineCursor(dpy, c->corners[i], resize_cursor[i]);
	for (i = 0; i < 3; i++)
		XDefineCursor(dpy, c->sides[i], border_drag == DRAG_MOVE ? None : resize_cursor[i + 4]);

	c->xftdraw = XftDrawCreate(dpy, (Drawable) c->frame, visual, cmap);
}

Client *clientFrame(Window w, XWindowAttributes * attr)
{
	Client *c;
	long dummy;
	int i;

	DBUG("entering clientFrame");

	c = g_new0(Client, 1);

	g_return_val_if_fail(c, NULL);

	grab_server(dpy, TRUE);

	c->window = w;

	c->size = XAllocSizeHints();
	get_size_hints(c);

	if (!XGetClassHint(dpy, w, &c->class))
	{		
		static char *unknown = "UNKNOWN";
		c->class.res_name = unknown;
		c->class.res_class = unknown;
		c->classless = True;
	}


	get_net_wm_name(c);
	get_wm_name(c);
	get_window_title(c);

#ifdef USE_ICON_NAME
	get_net_wm_icon_name(c);
	get_wm_icon_name(c);
#endif
	
	get_net_wm_type(c);
	get_net_wm_state(c);
	get_wm_protocols(c);
	get_mwm_hints(c);

	c->has_decor = ((c->mwm_hints == 0) || (c->mwm_hints & MWM_DECORATED)) ? 1 : 0;

	c->geometry.x = c->unmax_geometry.x = attr->x;
	c->geometry.y = c->unmax_geometry.y = attr->y;
	c->geometry.width = c->unmax_geometry.width = attr->width;
	c->geometry.height = c->unmax_geometry.width = attr->height;
	c->border_width = attr->border_width;

	getNetWMStrut(w, c->margins);

	XGetWMColormapWindows(dpy, c->window, &c->cmap_windows, &c->ncmap);

	get_transientFor(c, dpy, screen);
	
	c->wmhints = XGetWMHints(dpy, c->window);
	c->group_leader = None;
	if (c->wmhints)
	{
		if (c->wmhints->flags & WindowGroupHint)
		{
			c->group_leader = c->wmhints->window_group;
		}
	}

	if (c->group_leader == None && c->transientFor && c->transientFor != root)
	{
		c->group_leader = c->transientFor;		
	}
	
	c->client_leader = get_client_leader(dpy, c->window);

#ifdef HAVE_LIBSTARTUP_NOTIFICATION
	sn_get_startup_id(c);
	sn_client_startup_properties(c);
#endif

	if ((c->wmhints) && (c->wmhints->initial_state == IconicState) &&
		!client_is_transient_or_modal(c))
		c->state |= STATE_HIDDEN;

	if (!get_property_card32(w, intern_atoms[NET_WM_DESKTOP], &c->win_workspace))
		set_property_card32(w, intern_atoms[NET_WM_DESKTOP], c->win_workspace = workspace);

	if (c->win_workspace > workspace_count - 1)
		set_property_card32(w, intern_atoms[NET_WM_DESKTOP], c->win_workspace = workspace_count - 1);


	if (c->type & (WINDOW_DESKTOP | WINDOW_DOCK))
	{
	  dock:
		c->state |= STATE_STICKY;
		c->always_sticky = TRUE;
		c->non_focusing = TRUE;
		c->has_decor = FALSE;
	}

	if (c->type & (WINDOW_DESKTOP | WINDOW_DOCK | WINDOW_TOOLBAR | WINDOW_MENU
				   | WINDOW_UTILITY | WINDOW_SPLASHSCREEN))
		c->state |= (STATE_SKIP_TASKBAR | STATE_SKIP_PAGER);
	else if ((c->type & (WINDOW_DIALOG)) && (c->transientFor != None && c->transientFor != root))
		c->state |= STATE_SKIP_TASKBAR;

	if (!wants_focus(c))
		c->non_focusing = TRUE;

	match_client(c);

	if (c->state & STATE_STICKY)
		set_property_card32(w, intern_atoms[NET_WM_DESKTOP], c->win_workspace = -1);

	get_allowed_actions(c);
	set_allowed_actions_hint(c);


//fprintf(stderr,"%#lx: attr->map_state: %d\n",c->window,attr.map_state);
	//c->ignore_unmap = attr->map_state == IsViewable ? 1 : 0;

	/* Fullscreen for older legacy apps */
	if ((c->has_decor == 0) && (c->geometry.x == 0) && (c->geometry.y == 0) &&
		(c->geometry.width == display_width) && (c->geometry.height == display_height) && 
		(c->type == WINDOW_NORMAL))
	{
		c->legacy_fullscreen = TRUE;
	}

	c->had_decor = c->has_decor;

	/* Once we know the type of window, we can initialize window position */
	//if (!FLAG_TEST (c->geometry.xfwm_flags, XFWM_FLAG_SESSION_MANAGED))
	{
		if ((attr->map_state != IsUnmapped))
		{
			client_gravitate(c);
		}
		else
		{
			clientInitPosition(c);
		}
	}

	client_create_frame(c, w);
	
	/*
	   We must call client_apply_initial_state() after having placed the
	   window so that the inital position values are correctly set if the
	   inital state is maximize or fullscreen
	 */

	client_apply_initial_state(c);

	clientAddToList(c);
	clientGrabKeys(c);
		
	XWindowChanges wc;
	wc.x = c->geometry.x;
	wc.y = c->geometry.y;
	wc.height = c->geometry.height;
	wc.width = c->geometry.width;
	wc.stack_mode = Above;
	clientConfigure(c, &wc, CWX | CWY | CWHeight | CWWidth | CWStackMode);

	if(!(c->state & STATE_HIDDEN))
		clientShow(c, TRUE);

    dbg("window %#lx has state: %d\n",c->window,get_wm_state(c->window));

	XAddToSaveSet(dpy, c->window);
	grab_server(dpy, FALSE);

	//not sure, if this is correct, needs some testing first
	/*XEvent event_return;
	while (XCheckWindowEvent(dpy, c->window, 0L-1L, &event_return));*/

	return c;
}

static void client_free(Client * c)
{
	dbg("freeing client: %s\n", c->class.res_name);

	delNetWMStrut(c);
	client_ungravitate(c);
	clientUngrabKeys(c);

	/* Put back anything we messed up */
	if (c->border_width)
		XSetWindowBorderWidth(dpy, c->window, c->border_width);

	XReparentWindow(dpy, c->window, root, c->geometry.x, c->geometry.y);
	XDestroyWindow(dpy, c->frame);
	/* No save set */
	XRemoveFromSaveSet(dpy, c->window);

	/* Don't get events on not-managed windows */
	XSelectInput(dpy, c->window, NoEventMask);

	ping_timeout_remove(c);
	clientRemoveFromList(c);

	XftDrawDestroy(c->xftdraw);
	if (shape)
		XShapeSelectInput(dpy, c->window, NoEventMask);

	x_free(c->size);

	if(c->classless == False)
	{
		x_free(c->class.res_name);
		x_free(c->class.res_class);
	}

	x_free(c->wmhints);
	g_free(c->wm_name);
	g_free(c->net_wm_name);
	g_free(c->wm_icon_name);
	g_free(c->net_wm_icon_name);
#ifdef HAVE_STARTUP_NOTIFICATION
	g_free(c->startup_id);
#endif
	g_free(c);
}

/***************************/
/* END private functions   */
/***************************/

void update_allowed_actions(Client * c)
{
	AllowedActions actions = c->actions;

	get_allowed_actions(c);
	if (c->actions != actions)
		set_allowed_actions_hint(c);
}





void client_toggle_sticky(Client * c)
{
	_client_toggle_sticky(c);
	if (buttons_show_state)
		frameDraw(c);
}

static void change_root_window_property(Atom property, _Xconst unsigned char *data, int n)
{
	XChangeProperty(dpy, root, property, XA_WINDOW, 32, PropModeReplace, data, n);
}

void client_sync_client_list(void)
{
	change_root_window_property(intern_atoms[NET_CLIENT_LIST], (unsigned char *)client_list->data, client_list->len);
	change_root_window_property(intern_atoms[NET_CLIENT_LIST_STACKING], (unsigned char *)client_list_stacking->data, client_list_stacking->len);
}

void client_configure_xy(Client * c)
{
	XWindowChanges wc;

	wc.x = c->geometry.x;
	wc.y = c->geometry.y;
	clientConfigure(c, &wc, CWX | CWY);
}


void client_configure_wh(Client * c)
{
	XWindowChanges wc;

	wc.width = c->geometry.width;
	wc.height = c->geometry.height;
	clientConfigure(c, &wc, CWWidth | CWHeight);
}


void client_configure_xywh(Client * c)
{
	XWindowChanges wc;

	wc.x = c->geometry.x;
	wc.y = c->geometry.y;
	wc.width = c->geometry.width;
	wc.height = c->geometry.height;
	clientConfigure(c, &wc, CWX | CWY | CWWidth | CWHeight);
}

static void follow_group_leader_hide(Window window)
{
	GList *l;

	for (l = clients; l != NULL; l = l->next)
	{
		Client *c = (Client *) l->data;

		if (c->group_leader == window && c->window != window)
		{
			XUnmapWindow(dpy, c->window);
			XUnmapWindow(dpy, c->frame);
			set_mapping_state(c, IconicState);
			shuffle_below_normal_layer(c);
		}
	}
}

static void follow_group_leader_show(Window window)
{
	GList *l;

	for (l = clients; l != NULL; l = l->next)
	{
		Client *c = (Client *) l->data;

		if (c->group_leader == window && c->window != window)
		{
			XMapWindow(dpy, c->window);
			XMapWindow(dpy, c->frame);
			set_mapping_state(c, NormalState);
			//shuffle_below_normal_layer(c);
		}
	}
}

Client *client_new(Window window)
{			
	XWindowAttributes attr;

	if (!XGetWindowAttributes(dpy, window, &attr))
	{
		dbg("Cannot get window attributes");
		return NULL;
	}
	if (attr.override_redirect)
	{
		dbg("Not managing override_redirect windows");
		return NULL;
	}

	/*if (attr.map_state != IsViewable)
	{
		gulong state = get_wm_state(window);
		if (state == WithdrawnState)
			return NULL;
	}	*/
	if (attr.map_state == IsViewable)
		return (clientFrame(window, &attr));

	return NULL;
}

void client_destroy(Client *c)
{
	dbg("%s w=%lx\n", __func__, c->window);
		
	XDeleteProperty(dpy, c->window, intern_atoms[NET_WM_DESKTOP]);
	XDeleteProperty(dpy, c->window, intern_atoms[NET_WM_STATE]);
	set_mapping_state(c, WithdrawnState);
	
	client_free(c);
	
	if (get_input_focus() == None)
		focus_workspace();
}

inline static Bool client_on_this_host(Client *c, const char *host)
{
	char buf[257];
	dbg("%s() got host machine for ewmh : %s\n", __func__, host);
    return (gethostname(buf, sizeof(buf)-1) == 0) && (!strcmp(buf, host));
}

void clientKill(Client * c)
{
	dbg("%s\n", __func__);

	// backup window, as client_destroy() frees c
	Window window = c->window;
	
	client_destroy(c);
	
	// take advantage _NET_WM_PID, to make sure
	// the bastard is truely gone for good, instead
	// of chugging along in the background
	gulong pid;
	if(!get_property_card32(window, intern_atoms[NET_WM_PID], &pid))
		goto xkill;
		  
	XTextProperty  text_prop;
	if (!XGetWMClientMachine(dpy, window, &text_prop))
		goto xkill;
	
	Bool thishost = client_on_this_host(c, (char *)text_prop.value);
			
	XFree((char *) text_prop.value);

	if (!thishost)
		goto xkill;
		
	if (kill (pid, SIGKILL) < 0)
	{
		fprintf(stderr, "OroboROX: kill %i on %s failed.\n", SIGKILL, c->title_string);
		goto xkill;
	}

	return;

xkill: XKillClient(dpy, window);
}


void clientHide(Client * c, int change_state)
{
#ifdef DEBUG
	fprintf(stderr, "entering clientHide\n");
	fprintf(stderr, "hiding client (%#lx)\n", c->window);
#endif

	if (!(c->actions & ACTION_MINIMIZE))
		return;

	XUnmapWindow(dpy, c->window);
	XUnmapWindow(dpy, c->frame);
	if (change_state)
	{
		set_mapping_state(c, IconicState);
		shuffle_below_normal_layer(c);
		//follow_group_leader_hide(c->window);
		if (c->decor_state == ACTIVE)
		{
			c->decor_state = INACTIVE;
			focus_workspace();
		}
	}
	c->ignore_unmap++;

	
}

void clientShow(Client * c, gboolean change_state)
{
#ifdef DEBUG
	fprintf(stderr, "entering clientShow\n");
	fprintf(stderr, "showing client (%#lx)\n", c->window);
#endif

	if (!workspaces_match(c, workspace))
	{
		//if(change_state) switch_to_workspace(workspace); //does not work, why?
		//fprintf(stderr, "clientShow() %s\n", c->class.res_name);
		switch_to_workspace(c->win_workspace);
	}

	XMapWindow(dpy, c->window);
	XMapWindow(dpy, c->frame);

	if (change_state)
	{
		set_mapping_state(c, NormalState);
		//follow_group_leader_show(c->window);
	}
	if (showing_desktop)
	{
		unshow_desktop();
		client_raise(c);
		update_focus();
	}
}


void iconify_client(Client * c)
{
	dbg("iconify: %s\n", c->title_string);

	XUnmapWindow(dpy, c->window);
	XUnmapWindow(dpy, c->frame);
	c->ignore_unmap++;
	set_mapping_state(c, IconicState);
}

void hide_client(Client * c)
{
	iconify_client(c);
	shuffle_below_normal_layer(c);
	//follow_group_leader_hide(c->window);

	if (get_input_focus() == None)
		focus_workspace();
}

void map_client(Client * c)
{
	dbg("map: %s\n", c->title_string);

	if (!workspaces_match(c, workspace))
		switch_to_workspace(c->win_workspace);

	XMapWindow(dpy, c->window);
	XMapWindow(dpy, c->frame);
}

void show_client(Client * c)
{
	map_client(c);
	set_mapping_state(c, NormalState);
	//follow_group_leader_show(c->window);
}

void clientButtonPress(Client * c, Window w, XButtonEvent * bev)
{
	int pressed = TRUE, b;

	dbg("entering clientButtonPress");

	for (b = 0; b < BUTTON_COUNT; b++)
		if (c->buttons[b] == w)
			break;

	if (!grab_keyboard_and_pointer(dpy, c->window, w,
		ButtonReleaseMask | EnterWindowMask | LeaveWindowMask, None))
	{
		dbg("grab failed in clientButtonPress");
		return;
	}

	DBUG("entering button press loop");
	
	//fprintf(stderr, "[OROBOROX]: %s\n", __func__);
	

	c->button_pressed[b] = TRUE;
	frameDraw(c);

	do
	{
		XEvent ev;

		XNextEvent(dpy, &ev);

		if (ev.type == EnterNotify || ev.type == LeaveNotify)
		{
			c->button_pressed[b] = !c->button_pressed[b];
			frameDraw(c);
		}
		else if (ev.type == ButtonRelease)
			pressed = FALSE;
		else if (ev.type == UnmapNotify && ev.xunmap.window == c->window)
		{
			pressed = FALSE;
			c->button_pressed[b] = FALSE;
		}
		else if (ev.type == KeyPress || ev.type == KeyRelease)
		{
		}
		else
			handleEvent(&ev);
	} while (pressed && client_exists(c));

#ifdef DEBUG
	fprintf(stderr, "leaving button press loop\n");
#endif

	XUngrabPointer(dpy, CurrentTime);
	XUngrabKeyboard(dpy, CurrentTime);

	if (client_exists(c) && (c->button_pressed[b] == TRUE))
	{
		c->button_pressed[b] = FALSE;
		switch (b)
		{
			case HIDE_BUTTON:
				if (bev->button == Button3)
					clientToggleShaded(c);
				else
					clientHide(c, TRUE);
				break;
			case CLOSE_BUTTON:
				/*FIXME: Is this really a good idea? */
				if (bev->button == Button3)
					clientKill(c);
				else
				{
					c->not_warp_to_next = TRUE;
					clientClose(c, bev->time);
				}
				break;
			case MAXIMIZE_BUTTON:
				if (bev->button == Button1)
					clientToggleMaximized(c, (STATE_MAXIMIZED_VERT | STATE_MAXIMIZED_HORZ), 0);
				else if (bev->button == Button2)
					clientToggleMaximized(c, STATE_MAXIMIZED_VERT, 0);
				else if (bev->button == Button3)
					clientToggleMaximized(c, STATE_MAXIMIZED_HORZ, 0);
				break;
			case SHADE_BUTTON:
				clientToggleShaded(c);
				break;
			case STICK_BUTTON:
				if (bev->button == Button3)
				{
					c->state ^= STATE_ABOVE;
					set_net_wm_state(c);
				}
				else
					_client_toggle_sticky(c);
				break;
		}						// switch
		frameDraw(c);
	}							// if
}

void clientGrabKeys(Client * c)
{
	int i;

#ifdef DEBUG
	printf("entering clientGrabKeys\n");
	printf("grabbing keys for client (%#lx)\n", c->window);
#endif

	// do not grab the keys for panels.
	if (c->non_focusing)
	{
		// bind only these keys to "special" windows
		int keynums[] = {
			KEY_CYCLE_WINDOWS,
			KEY_CYCLE_APP,
			KEY_NEXT_WORKSPACE,
			KEY_PREV_WORKSPACE,
			KEY_ADD_WORKSPACE,
			KEY_DEL_WORKSPACE,
			KEY_WORKSPACE_1,
			KEY_WORKSPACE_2,
			KEY_WORKSPACE_3,
			KEY_WORKSPACE_4,
			KEY_WORKSPACE_5,
			KEY_WORKSPACE_6,
			KEY_WORKSPACE_7,
			KEY_WORKSPACE_8,
			KEY_WORKSPACE_9,
			KEY_QUIT
		};
		for (i = 0; i < 15; i++)
			grabKey(dpy, &keys[keynums[i]], c->window);
	}
	else
		for (i = 0; i < KEY_COUNT; i++)
			grabKey(dpy, &keys[i], c->window);
}

void clientUngrabKeys(Client * c)
{
#ifdef DEBUG
	printf("entering clientUngrabKeys\n");
	printf("ungrabing keys for client (%#lx)\n", c->window);
#endif

	ungrabKeys(dpy, c->window);
}

void gravitation(Client * c, Point * p)
{
	DBUG_VERBOSE("entering gravitation()");

	int dx, dy;

	dx = dy = (c->border_width * 2);

	switch (c->gravity)
	{
		case CenterGravity:
			dx = dx - ((frameLeft(c) + frameRight(c)) / 2);
			dy = dy - ((frameTop(c) + frameBottom(c)) / 2);
			break;
		case NorthGravity:
			dx = dx - ((frameLeft(c) + frameRight(c)) / 2);
			dy = frameTop(c);
			break;
		case SouthGravity:
			dx = dx - ((frameLeft(c) + frameRight(c)) / 2);
			dy = dy - frameBottom(c);
			break;
		case EastGravity:
			dx = dx - frameRight(c);
			dy = dy - ((frameTop(c) + frameBottom(c)) / 2);
			break;
		case WestGravity:
			dx = frameLeft(c);
			dy = dy - ((frameTop(c) + frameBottom(c)) / 2);
			break;
		case NorthWestGravity:
			dx = frameLeft(c);
			dy = frameTop(c);
			break;
		case NorthEastGravity:
			dx = dx - frameRight(c);
			dy = frameTop(c);
			break;
		case SouthWestGravity:
			dx = frameLeft(c);
			dy = dy - frameBottom(c);
			break;
		case SouthEastGravity:
			dx = dx - frameRight(c);
			dy = dy - frameBottom(c);
			break;
		default:
			dx = dy = 0;
			break;
	}

	p->x = dx;
	p->y = dy;

}

void client_gravitate(Client * c)
{
	DBUG_VERBOSE("entering client_gravitate()");

	Point p;
	gravitation(c, &p);
	c->geometry.x += p.x;
	c->geometry.y += p.y;

}

void client_ungravitate(Client * c)
{
	DBUG_VERBOSE("entering client_ungravitate ()");

	Point p;
	gravitation(c, &p);
	c->geometry.x -= p.x;
	c->geometry.y -= p.y;

}

static int clientGetWidthInc(Client * c)
{
	if (c->size->flags & PResizeInc)
		return c->size->width_inc;
	return 1;
}

static int clientGetHeightInc(Client * c)
{
	if (c->size->flags & PResizeInc)
		return c->size->height_inc;
	return 1;
}


/* The following algorithm should be used by the window manager to calculate the
 * displayed size of the top-level window. i and j are nonnegative integer loop 
 * variables within the window manager that would be incremented until a size that 
 * matches the window manager's window management policy is reached.
 *
 * width = base_width + (i * width_inc)
 * height = base_height + (j * height_inc)
 *
*/

static void clientSetWidth(Client * c, int w1)
{
	int w2;

	if (c->size->flags & PResizeInc)
	{
		if (c->size->width_inc == 0)
			c->size->width_inc = 1;
		w2 = (w1 - c->size->min_width) / c->size->width_inc;
		w1 = c->size->min_width + (w2 * c->size->width_inc);
	}
	if (c->size->flags & PMinSize)
	{
		if (w1 < c->size->min_width)
			w1 = c->size->min_width;
	}
	if ((c->size->flags & PMaxSize) && c->size->max_width > c->size->min_width)
	{
		if (w1 > c->size->max_width)
			w1 = c->size->max_width;
	}
	if (w1 < 1)
		w1 = 1;
	c->geometry.width = w1;
}

static void clientSetHeight(Client * c, int h1)
{
	int h2;

	if (c->size->flags & PResizeInc)
	{
		if (c->size->height_inc == 0)
			c->size->height_inc = 1;
		h2 = (h1 - c->size->min_height) / c->size->height_inc;
		h1 = c->size->min_height + (h2 * c->size->height_inc);
	}
	if (c->size->flags & PMinSize)
	{
		if (h1 < c->size->min_height)
			h1 = c->size->min_height;
	}
	if ((c->size->flags & PMaxSize) && c->size->max_height > c->size->min_height)
	{
		if (h1 > c->size->max_height)
			h1 = c->size->max_height;
	}
	if (h1 < 1)
		h1 = 1;
	c->geometry.height = h1;
}



void clientConfigure(Client * c, XWindowChanges * wc, int mask)
{
	XConfigureEvent ce;
	Client *sibling = NULL;

	dbg("configuring client (%s)\n", c->class.res_name);

	if (mask & CWX)
		c->geometry.x = wc->x;
	if (mask & CWY)
		c->geometry.y = wc->y;
	if (mask & CWWidth)
		clientSetWidth(c, wc->width);
	if (mask & CWHeight)
		clientSetHeight(c, wc->height);
	if (mask & CWBorderWidth)
		c->border_width = wc->border_width;
	if (mask & CWStackMode)
	{
		switch (wc->stack_mode)
		{
			case Above:
				sibling = clientGetTopMost(client_layer(c));
				if (!sibling)
					wc->stack_mode = Below;
				break;
			case Below:
				sibling = clientGetBottomMost(client_layer(c));
				break;
		}
		if (sibling)
		{
			if (sibling != c)
			{
				wc->sibling = sibling->frame;
				mask = mask | CWSibling;
			}
			else
				mask = mask & ~(CWSibling | CWStackMode);
		}
		else
			mask = mask & ~CWSibling;
	}

	wc->x = frameX(c);
	wc->y = frameY(c);
	wc->width = frameWidth(c);
	wc->height = frameHeight(c);
	wc->border_width = 0;
	XConfigureWindow(dpy, c->frame, mask, wc);
	wc->x = frameLeft(c);
	wc->y = frameTop(c);
	wc->width = c->geometry.width;
	wc->height = c->geometry.height;
	mask = mask & ~CWStackMode;
	mask = mask & ~CWSibling;
	XConfigureWindow(dpy, c->window, mask, wc);

	if (mask)
	{
		if (mask & (CWWidth | CWHeight))
			frameDraw(c);
		ce.type = ConfigureNotify;
		ce.event = c->window;
		ce.window = c->window;
		ce.x = c->geometry.x;
		ce.y = c->geometry.y;
		ce.width = c->geometry.width;
		ce.height = c->geometry.height;
		ce.border_width = 0;
		ce.above = None;
		ce.override_redirect = False;
		XSendEvent(dpy, c->window, False, StructureNotifyMask, (XEvent *) & ce);
	}
}

void clientFrameAll(void)
{
	dbg("entering clientFrameAll\n");

	unsigned int count;
	Window *wins = NULL;

	{
		Window w1, w2;
		XQueryTree(dpy, root, &w1, &w2, &wins, &count);
	}

	unsigned int i;

	for (i = 0; i < count; i++)
	{
		Client *c = client_new(wins[i]);
		if (c) 
		{		
#ifdef HAVE_COMPOSITE
			c->ignore_unmap += 2;
#else
			c->ignore_unmap += 1;
#endif
		}
	}

	x_free(wins);
}

void clientUnframeAll(void)
{
	Client *c;
	unsigned int count, i;
	Window w1, w2, *wins = NULL;

	DBUG("entering clientUnframeAll");

	XQueryTree(dpy, root, &w1, &w2, &wins, &count);
	for (i = 0; i < count; i++)
	{
		c = client_of_decor(wins[i]);
		if (c)
		{
			set_wm_state(c->window, NormalState);
			XMapWindow(dpy, c->window);		
			client_free(c);
		}
	}
	x_free(wins);
}

Client *client_of_window(Window w)
{
	if (w)
	{
		GList *l;

		for (l = clients; l != NULL; l = l->next)
		{
			Client *c = (Client *) l->data;
			if (c->window == w)
			{

				dbg("%sfound \"%s\" ", __func__, c->class.res_name);
				return (c);
			}
		}
	}

	return NULL;
}

gboolean client_exists(Client * c)
/* use this to test if a client hasn't been removed meanwhile */
{
	if (c && g_list_find(clients, c))
		return TRUE;
		
	DBUG("Client doesn't exist");
	return FALSE;
}


//frame_find_client(Window w);
//window_or_frame_find_client(Window w);


Client *client_of_decor(Window w)
{
	if (!w)
		return NULL;

	dbg("%s looking for (0x%lx)\n", __func__, w);

	GList *l;

	for (l = clients; l != NULL; l = l->next)
	{
		Client *c = (Client *) l->data;
		if (c->frame == w)
		{
			dbg("found \"%s\" (mode FRAME)", c->title_string);
			return (c);
		}
	}

	dbg("no client found\n");
	return NULL;
}





void send_icccm_message(Window window, Atom atom, Time timestamp)
{
	/* This comment and code are from twm, copyright
	 * Open Group, Evans & Sutherland, etc.
	 */

	/*
	 * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all
	 * client messages will have the following form:
	 *
	 *     event type ClientMessage
	 *     message type   _XA_WM_PROTOCOLS
	 *     window     tmp->w
	 *     format     32
	 *     data[0]        message atom
	 *     data[1]        time stamp
	 */

	XClientMessageEvent ev;

	ev.type = ClientMessage;
	ev.window = window;
	ev.message_type = xa_wm_protocols;
	ev.format = 32;
	ev.data.l[0] = atom;
	ev.data.l[1] = timestamp;

	XSendEvent(dpy, window, False, 0, (XEvent *) & ev);
}


void clientClose(Client * c, Time timestamp)
{
	if (!client_exists(c))
		return;

	Bool moribund = c->ping_moribund;
	ping_timeout_remove(c);			
	
	dbg("%s\n", __func__);

	if (!moribund && (c->wm_protocols & PROTOCOL_WM_DELETE_WINDOW))
	{
		send_icccm_message(c->window, xa_wm_delete_window, timestamp);
		if ((c->wm_protocols & PROTOCOL_WM_PING))
			net_wm_ping(c, timestamp);
	}
	else
		clientKill(c);
}


void clientToggleShaded(Client * c)
{
#ifdef DEBUG
	printf("entering clientToggleShaded\n");
	printf("shading/unshading client (%#lx)\n", c->window);
#endif

	if (c->state & STATE_SHADED)
	{
		c->actions = c->fs_actions;
	}
	else if (c->actions & ACTION_SHADE)
	{
		c->fs_actions = c->actions;
		c->actions &=
			~(ACTION_RESIZE | ACTION_MAXIMIZE_HORZ | ACTION_MAXIMIZE_VERT | ACTION_MINIMIZE);
	}
	else
		return;

	c->state ^= STATE_SHADED;
	set_allowed_actions_hint(c);
	set_net_wm_state(c);

	XWindowChanges wc;
	wc.height = c->geometry.height;
	clientConfigure(c, &wc, CWHeight);
}




void client_configure_fullscreen(XWindowChanges * wc, int all_monitors)
{
#ifdef HAVE_XINERAMA
	if (XINERAMA_IS_ACTIVE() && !all_monitors)
	{
		int mx, my;
		int mon;

		if (!get_mouse_xy(dpy, root, &mx, &my))
			mx = my = 0; /*FIXME: what really to do? */
				
		mon = xineramaGetMonitorUnderPoint(mx, my);
		wc->x = xinerama_screen_info[mon].x_org;
		wc->y = xinerama_screen_info[mon].y_org;
		wc->width = xinerama_screen_info[mon].width;
		wc->height = xinerama_screen_info[mon].height;
	}
	else
#endif
	{
#ifdef HAVE_XF68VMLIB_H
		XF86VidModeGetViewPort(dpy, screen, &wc->x, &wc->y);
#else
		wc->x = 0;
		wc->y = 0;
#endif
		wc->width = display_width;
		wc->height = display_height;
	}
}


/*
void Alternative_clientToggleFullscreen(Client * c, int all_monitors)
{
// This one just removes the borders and then maximizes, so that margins
// are avoided... It would be better to just add the code to avoid margins
// in clientToggleFullscreen(), IF that is what we want?
// We could also pass WIN_STATE_FULLSCREEN to mode argument to
// clientToggleMaximized. The states should be individual: unfullscreening
// a previously maximized client should leave it maximized.
*/

void clientToggleFullscreen(Client * c, int all_monitors)
{
	XWindowChanges wc;

#ifdef DEBUG
	DBUG("entering clientToggleFullscreen");
	if (c->class.res_class)
		fprintf(stderr, "fullscreening/unfullscreening client (%s)\n", c->class.res_class);
#endif

	if (!(c->actions & ACTION_FULLSCREEN))
		return;

	if (c->state & STATE_FULLSCREEN)
	{
		c->state &= ~STATE_FULLSCREEN;
		wc.width = c->fs_geometry.width;
		wc.height = c->fs_geometry.height;
		wc.x = c->fs_geometry.x;
		wc.y = c->fs_geometry.y;
		c->has_decor = c->had_decor;
		c->actions = c->fs_actions;
		c->state = c->fs_state;
		frameDraw(c);
	}
	else
	{
		c->fs_geometry.x = c->geometry.x;
		c->fs_geometry.y = c->geometry.y;
		c->fs_geometry.width = c->geometry.width;
		c->fs_geometry.height = c->geometry.height;
		c->fs_state = c->state;
		c->state |= STATE_FULLSCREEN;
		c->had_decor = c->has_decor;
		c->has_decor = FALSE;
		c->fs_actions = c->actions;
		c->actions &=
			~(ACTION_SHADE | ACTION_MOVE | ACTION_RESIZE | ACTION_MAXIMIZE_HORZ |
			  ACTION_MAXIMIZE_VERT);

		client_configure_fullscreen(&wc, all_monitors);
	}

	set_net_wm_state(c);

	clientConfigure(c, &wc, CWX | CWY | CWWidth | CWHeight);
	client_raise(c);
}

void clientToggleMaximized(Client * c, int mode, int all_monitors)
{
	XWindowChanges wc;

#ifdef DEBUG
	fprintf(stderr, "entering clientToggleMaximized\n");
	fprintf(stderr, "maximzing/unmaximizing client (%#lx)\n", c->window);
#endif

	if (c->state & STATE_FULLSCREEN)
	{
		clientToggleFullscreen(c, 0);
		DBUG("Ignoring maximize due to fullscreen");
		return;
	}

	if (!(c->actions & (ACTION_MAXIMIZE_VERT | ACTION_MAXIMIZE_HORZ)))
		return;

	if (c->state & (STATE_MAXIMIZED_VERT | STATE_MAXIMIZED_HORZ))
	{
		wc.width = c->unmax_geometry.width;
		wc.height = c->unmax_geometry.height;
		wc.x = c->unmax_geometry.x;
		wc.y = c->unmax_geometry.y;
		c->state &= ~(STATE_MAXIMIZED_VERT | STATE_MAXIMIZED_HORZ);
	}
	else
	{
		int min_x, min_y, max_x, max_y;

		c->unmax_geometry.x = c->geometry.x;
		c->unmax_geometry.y = c->geometry.y;
		c->unmax_geometry.width = c->geometry.width;
		c->unmax_geometry.height = c->geometry.height;

#ifdef HAVE_XINERAMA
		if (XINERAMA_IS_ACTIVE() && !all_monitors)
		{
			/* Look for monitor under mouse */
			int m, mx, my;

			if(!get_mouse_xy(dpy, root, &mx, &my))
				mx = my = 0; /* FIXME: what really to do? */
			m = xineramaGetMonitorUnderPoint(mx, my);

			min_x = xinerama_screen_info[m].x_org;
			min_y = xinerama_screen_info[m].y_org;
			max_x = min_x + xinerama_screen_info[m].width;
			max_y = min_y + xinerama_screen_info[m].height;
		}
		else
#endif
		{
			min_x = 0;
			min_y = 0;
			max_x = display_width;
			max_y = display_height;
		}
#ifdef DEBUG
		printf("Maximising window to monitor limits (%d, %d) - (%d, %d)\n"
			   "Display size %d x %d", min_x, min_y, max_x, max_y, display_width, display_height);
#endif
		if (mode != STATE_MAXIMIZED_VERT)
		{
			wc.x = min_x + frameLeft(c);
			wc.width = max_x - min_x - frameLeft(c) - frameRight(c);
#ifdef DEBUG
			printf("Allowing for frame only, x and width are %d + %d\n", wc.x, wc.width);
#endif
			if (min_x == 0)
			{
				int margin = getMarginWithFrame(c, MARGIN_LEFT, wc.y, wc.y + wc.height);

#ifdef DEBUG
				printf("Adjusting for left margin %d\n", margin);
#endif
				wc.x += margin;
				wc.width -= margin;
			}
			if (max_x == display_width)
			{
				int margin = getMarginWithFrame(c, MARGIN_RIGHT,
												wc.y, wc.y + wc.height);

#ifdef DEBUG
				printf("Adjusting width for right margin %d\n", margin);
#endif
				wc.width -= margin;
			}
			if (maximize_state != REMAX_SIZE)	// terry blunt 7/3/5
				c->state |= STATE_MAXIMIZED_HORZ;
		}
		else
		{
			wc.x = c->geometry.x;
			wc.width = c->geometry.width;
		}
		if (mode != STATE_MAXIMIZED_HORZ)
		{
			wc.y = min_y + frameTop(c);
			wc.height = max_y - min_y - frameTop(c) - frameBottom(c);
#ifdef DEBUG
			fprintf(stderr, "Allowing for frame only, y and height are %d + %d\n", wc.y, wc.height);
#endif
			if (min_y == 0)
			{
				int margin = getMarginWithFrame(c, MARGIN_TOP, wc.x, wc.x + wc.width);

#ifdef DEBUG
				fprintf(stderr, "Adjusting for top margin %d\n", margin);
#endif
				wc.y += margin;
				wc.height -= margin;
			}
			if (max_y == display_height)
			{
				int margin = getMarginWithFrame(c, MARGIN_BOTTOM,
												wc.x, wc.x + wc.width);

#ifdef DEBUG
				fprintf(stderr, "Adjusting height for bottom margin %d\n", margin);
#endif
				wc.height -= margin;
			}
			if (maximize_state != REMAX_SIZE)	// terry blunt 7/3/5
				c->state |= STATE_MAXIMIZED_VERT;
		}
		else
		{
			wc.y = c->geometry.y;
			wc.height = c->geometry.height;
		}

	}
#ifdef DEBUG
	fprintf(stderr, "Final corner (%d, %d) size %d x %d\n\n", wc.x, wc.y, wc.width, wc.height);
	fflush(stdout);
#endif
	set_net_wm_state(c);
	clientConfigure(c, &wc, CWX | CWY | CWWidth | CWHeight);

//  if(buttons_show_state)
//    c->button_pressed[MAXIMIZE_BUTTON]=c->state & (STATE_MAXIMIZED_VERT|STATE_MAXIMIZED_HORZ);

//    fflush(stdout);
}

int map_state(Window window)
{
	XWindowAttributes attr;
	attr.map_state = IsUnmapped;
	XGetWindowAttributes(dpy, window, &attr);
	return attr.map_state;
}

gboolean client_is_viewable(Client * c)
{
	return (map_state(c->window) == IsViewable);
}


gboolean client_is_viewable_or_obscured(Client * c)
{
 return (get_wm_state(c->window) == NormalState) && workspaces_match(c, workspace);	
}
	
void clientDrawOutline(Client * c)
{
	DBUG("entering clientDrawOutline");

	XDrawRectangle(dpy, root, box_gc, frameX(c), frameY(c), frameWidth(c) - 1, frameHeight(c) - 1);
}

inline static gboolean edgesOverlap(int s1, int l1, int s2, int l2)
{
	return (s1 > s2 && s1 < s2 + l2) ||
		(s1 + l1 > s2 && s1 + l1 < s2 + l2) || (s1 <= s2 && s1 + l1 >= s2 + l2);
}

void clientMove(Client * c, XEvent * e)
{
	if (!(c->actions & ACTION_MOVE))
		return;

	int mx, my, moving = TRUE, grab = FALSE, use_keys = FALSE;
	Client *closestx, *closesty;
	int ox, oy;
	int cx, cy, dx, dy;
	int allow_vert = (!(c->state & STATE_MAXIMIZED_VERT)) || maximize_state == UNLOCK_SIZE;
	int allow_horz = (!(c->state & STATE_MAXIMIZED_HORZ)) || maximize_state == UNLOCK_SIZE;

#ifdef DEBUG
	fprintf(stderr, "entering clientDoMove\n");
	if (c->class.res_name)
		fprintf(stderr, "moving client (%#lx)\n", c->class.res_name);
#endif

//  if ((c->state & (STATE_MAXIMIZED_HORZ|STATE_MAXIMIZED_VERT)) || c->non_focusing)
	if (c->non_focusing)
		return;

	if (!grab_keyboard_and_pointer(dpy, c->window, c->frame,
								   PointerMotionMask | ButtonReleaseMask, move_cursor))
	{
		DBUG("grab failed in clientMove");
		return;
	}

	if (e->type == KeyPress)
	{
		use_keys = TRUE;
		XPutBackEvent(dpy, e);
	}

	if (!get_mouse_xy(dpy, root, &mx, &my))
		mx = my = 0; /* FIXME: what really to do? */

	ox = c->geometry.x;
	oy = c->geometry.y;

	DBUG("entering move loop");

	if (geometry_infobox)
		infoWinCreate();

	while (moving)
	{
		XEvent ev;

		XNextEvent(dpy, &ev);

		if (geometry_infobox)
		{
			char size_string[256];

			sprintf(size_string, "%d , %d", c->geometry.x, c->geometry.y);
			infoWinSet(size_string);
		}

		if (ev.type == KeyPress && !use_keys)
		{
			int i = 9;

			while (i--)
				if (ev.xkey.keycode == keys[KEY_WORKSPACE_1 + i].keycode)
				{
					move_client_to_workspace(c, i);
					break;
				}
		}
		if (ev.type == KeyPress && use_keys)
		{
			if (!grab && box_move)
			{
				XGrabServer(dpy);
				grab = TRUE;
				clientDrawOutline(c);
			}
			if (box_move)
				clientDrawOutline(c);
			if (allow_horz)
			{
				if (ev.xkey.keycode == keys[KEY_MOVE_LEFT].keycode)
					c->geometry.x = c->geometry.x - 16;
				if (ev.xkey.keycode == keys[KEY_MOVE_RIGHT].keycode)
					c->geometry.x = c->geometry.x + 16;
			}
			if (allow_vert)
			{
				if (ev.xkey.keycode == keys[KEY_MOVE_UP].keycode)
					c->geometry.y = c->geometry.y - 16;
				if (ev.xkey.keycode == keys[KEY_MOVE_DOWN].keycode)
					c->geometry.y = c->geometry.y + 16;
			}
			if (box_move)
				clientDrawOutline(c);
			else
				client_configure_xy(c);
		}
		else if (use_keys && ev.type == KeyRelease)
		{
			if (IsModifierKey(XKeycodeToKeysym(dpy, ev.xkey.keycode, 0)))
				moving = FALSE;
		}
		else if (ev.type == MotionNotify)
		{
			int new_ws = 0;
			int switch_ws = 0;

			while (XCheckTypedEvent(dpy, MotionNotify, &ev));

			if (!grab && box_move)
			{
				XGrabServer(dpy);
				grab = TRUE;
				clientDrawOutline(c);
			}
			if (box_move)
				clientDrawOutline(c);

			if (workspace_count > 1 && cycle_workspaces)
			{
				int msx, msy;

				if(get_mouse_xy(dpy, root, &msx, &msy))
				{
					if (msx == 0 && !(workspace == 0 && !wrap_workspaces))
					{
						new_ws = workspace - 1;
						switch_ws = 1;
						XWarpPointer(dpy, None, root, 0, 0, 0, 0, display_width - 11, msy);
						ev.xmotion.x_root = display_width - 11;
					}
					else if (msx == display_width - 1 && !(workspace == workspace_count - 1
														   && !wrap_workspaces))
					{
						new_ws = workspace + 1;
						switch_ws = 1;
						XWarpPointer(dpy, None, root, 0, 0, 0, 0, 10, msy);
						ev.xmotion.x_root = 10;
					}
				}
			}

			/* Need to work out preliminary positions in both dimensions before
			 * snapping, because margins in each dimension depend on position
			 * and size in the other */
			int prevx = c->geometry.x;
			int prevy = c->geometry.y;
			int snap_left = INT_MIN, snap_right = INT_MIN;
			gboolean no_snap_left = FALSE, no_snap_right = FALSE;
			int snap_top = INT_MIN, snap_bottom = INT_MIN;
			gboolean no_snap_top = FALSE, no_snap_bottom = FALSE;
			int snap;

			if (allow_horz)
				c->geometry.x = ox + (ev.xmotion.x_root - mx);
			if (allow_vert)
				c->geometry.y = oy + (ev.xmotion.y_root - my);

			if (allow_horz)
			{
				if (snap_to_border)
				{
					int margin;
					int m;

					for (m = 0; m < xinerama_nscreens; ++m)
					{
						snap = frameLeft(c) + xinerama_screen_info[m].x_org;
						if (abs(frameX(c) - xinerama_screen_info[m].x_org) < snap_width)
						{
							if (prevx == snap)
								snap_left = snap;
							else
								no_snap_left = TRUE;
						}
						snap = xinerama_screen_info[m].x_org +
							xinerama_screen_info[m].width - frameRight(c) - c->geometry.width;
						if (abs(frameX(c) - xinerama_screen_info[m].x_org -
								xinerama_screen_info[m].width + frameWidth(c)) < snap_width)
						{
							if (prevx == snap)
								snap_right = snap;
							else
								no_snap_right = TRUE;
						}
					}
					margin = getMarginWithFrame(c, MARGIN_LEFT, c->geometry.y, c->geometry.y + c->geometry.height);
					snap = frameLeft(c) + margin;
					if (abs(frameX(c) - margin) < snap_width)
					{
						if (prevx == snap)
							snap_left = snap;
						else
							no_snap_left = TRUE;
					}
					margin = getMarginWithFrame(c, MARGIN_RIGHT, c->geometry.y, c->geometry.y + c->geometry.height);
					snap = display_width - frameRight(c) - c->geometry.width - margin;
					if (abs(frameX(c) - display_width + frameWidth(c) + margin) < snap_width)
					{
						if (prevx == snap)
							snap_right = snap;
						else
							no_snap_right = TRUE;
					}
				}
			}

			if (allow_vert)
			{
				if (snap_to_border)
				{
					int margin;
					int m;

					for (m = 0; m < xinerama_nscreens; ++m)
					{
						snap = frameTop(c) + xinerama_screen_info[m].y_org;
						if (abs(frameY(c) - xinerama_screen_info[m].y_org) < snap_width)
						{
							if (prevy == snap)
								snap_top = snap;
							else
								no_snap_top = TRUE;
						}
						snap = xinerama_screen_info[m].y_org +
							xinerama_screen_info[m].height - frameHeight(c) + frameTop(c);
						if (abs(frameY(c) - xinerama_screen_info[m].y_org -
								xinerama_screen_info[m].height + frameHeight(c)) < snap_width)
						{
							if (prevy == snap)
								snap_bottom = snap;
							else
								no_snap_bottom = TRUE;
						}
					}
					margin = getMarginWithFrame(c, MARGIN_TOP, c->geometry.x, c->geometry.x + c->geometry.width);
					snap = frameTop(c) + margin;
					if (abs(frameY(c) - margin) < snap_width)
					{
						if (prevy == snap)
							snap_top = snap;
						else
							no_snap_top = TRUE;
					}
					margin = getMarginWithFrame(c, MARGIN_BOTTOM, c->geometry.x, c->geometry.x + c->geometry.width);
					snap = display_height - margin - frameHeight(c) + frameTop(c);
					if (abs(frameY(c) - display_height + frameHeight(c) + margin) < snap_width)
					{
						if (prevy == snap)
							snap_bottom = snap;
						else
							no_snap_bottom = TRUE;
					}
				}
			}

			if (snap_to_windows && (!(c->state & STATE_MAXIMIZED_VERT)
									|| !(c->state & STATE_MAXIMIZED_HORZ)))
			{
				cx = cy = snap_width + 1;
				closestx = closesty = NULL;

				GList *l;

				for (l = clients; l != NULL; l = l->next)
				{
					Client *c2 = (Client *) l->data;

					if (c2->win_workspace != c->win_workspace)
						continue;

					if (c2->non_focusing)
						continue;

					if (allow_vert &&
						edgesOverlap(frameX(c), frameWidth(c), frameX(c2), frameWidth(c2)))
					{
						/* Are we touching the bottom of a window? */
						dy = abs(frameY(c) - (frameY(c2) + frameHeight(c2)));
						if (dy < snap_width && dy < cy)
						{
							cy = dy;
							closesty = c2;
						}

						/* Top? */
						dy = abs(frameY(c2) - (frameY(c) + frameHeight(c)));
						if (dy < snap_width && dy < cy)
						{
							cy = dy;
							closesty = c2;
						}
					}

					if (allow_horz &&
						edgesOverlap(frameY(c), frameHeight(c), frameY(c2), frameHeight(c2)))
					{
						/* Left? */
						dx = abs(frameX(c2) - (frameX(c) + frameWidth(c)));
						if (dx < snap_width && dx < cx)
						{
							cx = dx;
							closestx = c2;
						}

						/* Right */
						dx = abs(frameX(c) - (frameX(c2) + frameWidth(c2)));
						if (dx < snap_width && dx < cx)
						{
							cx = dx;
							closestx = c2;
						}
					}
				}

				if (closesty)
				{
					if (cy == abs(frameY(c) - (frameY(closesty) + frameHeight(closesty))))
					{
						snap = frameY(closesty) + frameHeight(closesty) + frameTop(c);
						if (prevy == snap)
							snap_top = snap;
						else
							no_snap_top = TRUE;
					}
					else
					{
						snap = frameY(closesty) - (frameHeight(c) - frameTop(c));
						if (prevy == snap)
							snap_bottom = snap;
						else
							no_snap_bottom = TRUE;
					}
				}

				if (closestx)
				{
					if (cx == abs(frameX(c) - (frameX(closestx) + frameWidth(closestx))))
					{
						snap = frameX(closestx) + frameWidth(closestx) + frameLeft(c);
						if (prevx == snap)
							snap_left = snap;
						else
							no_snap_left = TRUE;
					}
					else
					{
						snap = (frameX(closestx) - frameWidth(c)) + frameRight(c);
						if (prevx == snap)
							snap_right = snap;
						else
							no_snap_right = TRUE;
					}
				}
			}
			if (snap_left != INT_MIN && !no_snap_right)
				c->geometry.x = snap_left;
			if (snap_right != INT_MIN && !no_snap_left)
				c->geometry.x = snap_right;
			if (snap_top != INT_MIN && !no_snap_bottom)
				c->geometry.y = snap_top;
			if (snap_bottom != INT_MIN && !no_snap_top)
				c->geometry.y = snap_bottom;

			if (box_move)
				clientDrawOutline(c);
			else
				client_configure_xy(c);

			if (switch_ws)
				move_client_to_workspace(c, new_ws);
		}
		else if (!use_keys && ev.type == ButtonRelease)
			moving = FALSE;
		else if (ev.type == UnmapNotify && ev.xunmap.window == c->window)
			moving = FALSE;
		else
			handleEvent(&ev);
	}

	DBUG("leaving move loop");

	if (geometry_infobox)
		infoWinDelete();

	XUngrabKeyboard(dpy, CurrentTime);
	XUngrabPointer(dpy, CurrentTime);

	if (client_exists(c))
	{
		if (grab && box_move)
			clientDrawOutline(c);
		client_configure_xy(c);
	}

	XUngrabServer(dpy);
}

void clientResize(Client * c, XEvent * e)
{
	if (!(c->actions & ACTION_RESIZE))
		return;

	int mx, my, oldw, oldh, resizing = TRUE, grab = FALSE, corner =
		CORNER_BOTTOM_RIGHT, use_keys = FALSE;
	int cursor = corner;
	int allow_vert = (!(c->state & STATE_MAXIMIZED_VERT)) || maximize_state == UNLOCK_SIZE;
	int allow_horz = (!(c->state & STATE_MAXIMIZED_HORZ)) || maximize_state == UNLOCK_SIZE;
	XEvent ev;

#ifdef DEBUG
	DBUG("entering clientResize");
	fprintf(stderr, "resizing client (%#lx)\n", c->window);
#endif


	if (c->state & STATE_SHADED)
		return;

	if(!get_mouse_xy(dpy, c->frame, &mx, &my))
		mx = my = 0; /*FIXME: what really to do? */
	
	if (e->type == KeyPress)
	{
		corner = CORNER_BOTTOM_RIGHT;
		use_keys = TRUE;
	}
	else
	{
		int win = get_mouse_window(dpy, c->frame);

		if (win)
		{
			if (mx < frameWidth(c) / 2 && my < frameHeight(c) / 2)
				corner = CORNER_TOP_LEFT;
			else if (mx < frameWidth(c) / 2)
				corner = CORNER_BOTTOM_LEFT;
			else if (my < frameHeight(c) / 2)
				corner = CORNER_TOP_RIGHT;
		
			if (win == c->sides[SIDE_BOTTOM])
			{
				allow_horz = 0;
				cursor = CORNER_BOTTOM;
			}
			else if (win == c->sides[SIDE_LEFT])
			{
				allow_vert = 0;
				cursor = CORNER_LEFT;
			}
			else if (win == c->sides[SIDE_RIGHT])
			{
				allow_vert = 0;
				cursor = CORNER_RIGHT;
			}
			else
				cursor = corner;
		}

	}

	if (!grab_keyboard_and_pointer(dpy, c->window, c->frame,
								   PointerMotionMask | ButtonReleaseMask, resize_cursor[cursor]))
	{
		DBUG("grab failed in clientResize");
		return;
	}

	if (use_keys)
		XPutBackEvent(dpy, e);

	if (corner == CORNER_TOP_RIGHT || corner == CORNER_BOTTOM_RIGHT)
		mx = frameWidth(c) - mx;
	if (corner == CORNER_BOTTOM_LEFT || corner == CORNER_BOTTOM_RIGHT)
		my = frameHeight(c) - my;

	DBUG("entering resize loop");

	if (geometry_infobox)
		infoWinCreate();

	while (resizing)
	{

		XNextEvent(dpy, &ev);

		if (geometry_infobox)
		{
			char size_string[256];
			int w = c->geometry.width, h = c->geometry.height;

			if (c->size->flags & PResizeInc)
			{
				if (c->size->width_inc)
					w = (w - c->size->base_width) / c->size->width_inc;
				if (c->size->height_inc)
					h = (h - c->size->base_height) / c->size->height_inc;
			}
			sprintf(size_string, "%d x %d", w, h);
			infoWinSet(size_string);
		}

		if (ev.type == KeyPress)
		{
			if (!grab && box_resize)
			{
				XGrabServer(dpy);
				grab = TRUE;
				clientDrawOutline(c);
			}
			if (box_resize)
				clientDrawOutline(c);
			if (allow_vert)
			{
				if (ev.xkey.keycode == keys[KEY_MOVE_UP].keycode)
					c->geometry.height =
						c->geometry.height - (clientGetHeightInc(c) < 10 ? 10 : clientGetHeightInc(c));
				if (ev.xkey.keycode == keys[KEY_MOVE_DOWN].keycode)
					c->geometry.height =
						c->geometry.height + (clientGetHeightInc(c) < 10 ? 10 : clientGetHeightInc(c));
			}
			if (allow_horz)
			{
				if (ev.xkey.keycode == keys[KEY_MOVE_LEFT].keycode)
					c->geometry.width = c->geometry.width - (clientGetWidthInc(c) < 10 ? 10 : clientGetWidthInc(c));
				if (ev.xkey.keycode == keys[KEY_MOVE_RIGHT].keycode)
					c->geometry.width = c->geometry.width + (clientGetWidthInc(c) < 10 ? 10 : clientGetWidthInc(c));
			}
			if (box_resize)
				clientDrawOutline(c);
			else
			{
				client_configure_xywh(c);

			}
		}
		else if (use_keys && ev.type == KeyRelease)
		{
			if (IsModifierKey(XKeycodeToKeysym(dpy, ev.xkey.keycode, 0)))
				resizing = FALSE;
		}
		else if (ev.type == MotionNotify)
		{
			while (XCheckTypedEvent(dpy, MotionNotify, &ev));

			if (!grab && box_resize)
			{
				XGrabServer(dpy);
				grab = TRUE;
				clientDrawOutline(c);
			}
			if (box_resize)
				clientDrawOutline(c);
			oldw = c->geometry.width;
			oldh = c->geometry.height;
			if (allow_horz)
			{
				if (corner == CORNER_TOP_LEFT || corner == CORNER_BOTTOM_LEFT)
					c->geometry.width = (c->geometry.x + c->geometry.width) - ev.xmotion.x_root + mx - frameLeft(c);
				if (corner == CORNER_BOTTOM_RIGHT || corner == CORNER_TOP_RIGHT)
					c->geometry.width = (ev.xmotion.x_root - c->geometry.x) + mx - frameRight(c);
			}
			if (allow_vert)
			{
				if (corner == CORNER_TOP_LEFT || corner == CORNER_TOP_RIGHT)
					c->geometry.height = (c->geometry.y + c->geometry.height) - ev.xmotion.y_root + my - frameTop(c);
				if (corner == CORNER_BOTTOM_RIGHT || corner == CORNER_BOTTOM_LEFT)
					c->geometry.height = (ev.xmotion.y_root - c->geometry.y) + my - frameBottom(c);
			}
			clientSetWidth(c, c->geometry.width);
			clientSetHeight(c, c->geometry.height);
			if (allow_horz && (corner == CORNER_TOP_LEFT || corner == CORNER_BOTTOM_LEFT))
				c->geometry.x = c->geometry.x - (c->geometry.width - oldw);
			if (allow_vert && (corner == CORNER_TOP_LEFT || corner == CORNER_TOP_RIGHT))
				c->geometry.y = c->geometry.y - (c->geometry.height - oldh);
			if (box_resize)
				clientDrawOutline(c);
			else
			{
				client_configure_xywh(c);
			}
		}
		else if (ev.type == ButtonRelease)
			resizing = FALSE;
		else if (ev.type == UnmapNotify && ev.xunmap.window == c->window)
			resizing = FALSE;
		else
			handleEvent(&ev);
	}
	DBUG("leaving resize loop");

	if (geometry_infobox)
		infoWinDelete();

	XUngrabKeyboard(dpy, CurrentTime);
	XUngrabPointer(dpy, CurrentTime);

	if (client_exists(c))
	{
		if (grab && box_resize)
			clientDrawOutline(c);
		client_configure_xywh(c);
	}

	XUngrabServer(dpy);
}


static gboolean client_of_group(Window app, Client *c)
{
	if (c->group_leader == app)
		return TRUE;
	
	Window transientfor = c->transientFor;
	if (transientfor && (transientfor != root))
	{
		if (transientfor == app)
			return TRUE;
		c = client_of_window(transientfor);	
		if (client_exists(c) && (c->group_leader == app))
			return TRUE;
	}
		
	return FALSE;
}



static Client *client_cycle_next(Client * c, GList *list)
{
	GList *l = g_list_find(list, c);
	return (Client *) ((l = g_list_next(l)) ? l->data : g_list_first(list)->data);
}

enum { NONE, SHADE, ICONIFY };

static int cycle_show(Client *c)
{		
	int action = NONE;

	if (cycle_infobox)
		infoWinSet(c->title_string);
	else 
	{	
		if ((c->state & STATE_SHADED))
		{
			clientToggleShaded(c);
			action = SHADE;
		}
		else 
		{
			if ((get_wm_state(c->window) == IconicState))
			{
				action = ICONIFY;
			}
		}
				
		clientShow(c, TRUE);
		
		XWindowChanges wc;
		wc.stack_mode = Above;
		clientConfigure(c, &wc, CWStackMode);
		
		set_input_focus(c->window);
		update_focus();
	}

	return action;
}

static void cycle_unshow(Client *c, int action)
{
	if (action != NONE)
	{							
		if (action == SHADE)
			clientToggleShaded(c);
		else if (action == ICONIFY)
			iconify_client(c);
	}
}

void clientCycle(int mode)
{
	if(!keys[mode].modifier)
		return;

	Client *c, *c2;

	c = client_of_window(get_input_focus());
	if (!c && (mode == KEY_CYCLE_APP))
		return; // no app focused in cycle app mode

	GList *cyclelist = NULL;

	GList *l;
	for (l = clients; l != NULL; l = l->next)
	{
		Client *c2 = (Client *) l->data;
		if(!wants_focus(c2) || !workspaces_match(c2, workspace))
			continue;
		if ((mode == KEY_CYCLE_APP) && strcmp(c->class.res_class, c2->class.res_class))
			continue;	
		cyclelist = g_list_append(cyclelist, c2);
		dbg("CYCLELIST: %s\n", c2->title_string);
	}
		
	if (!cyclelist)
	{
		DBUG_CYCLING("clientCycle: nothing there to cycle through!");
		return;
	}
			
	if (!grab_keyboard_and_pointer(dpy, root, root, NoEventMask, None))
	{
		DBUG_CYCLING("grab failed in clientCycle");
		return;
	}

	if (cycle_infobox)
		infoWinCreate();
	
	if(c)
		c2 = client_cycle_next(c, cyclelist);
	else
		c2 = (Client *)cyclelist->data;

	int action = cycle_show(c2);

	do
	{
		XEvent ev; XNextEvent(dpy, &ev);

		if (ev.type == KeyPress)
		{
			if (ev.xkey.keycode == keys[mode].keycode)
			{
				cycle_unshow(c2, action);
				action = NONE;
									
				c2 = client_cycle_next(c2, cyclelist);
				
				if (!client_exists(c2))
					break;

				action = cycle_show(c2);
			}
			else
			{
				XPutBackEvent(dpy, &ev);
				break;
			}
		}
		else if (ev.type == KeyRelease)
		{
			dbg("CYCLING: KeyRelease\n");
			if (IsModifierKey(XKeycodeToKeysym(dpy, ev.xkey.keycode, 0)))
				break;
		}
		else
			handleEvent(&ev);
	} while (client_exists(c2));

	DBUG_CYCLING("leaving cycle loop");

	XUngrabKeyboard(dpy, CurrentTime);
	XUngrabPointer(dpy, CurrentTime);

	if (cycle_infobox)
		infoWinDelete();

	if (client_exists(c2))
	{
		if (cycle_infobox)
		{	
			clientShow(c2, TRUE);
			client_raise(c2);
		}
		if (warp_after_cycle)	//Jonatan Liljedahl 
			XWarpPointer(dpy, None, c2->window, 0, 0, 0, 0, frameWidth(c2) / 2,
						 (frameHeight(c2) / 2) * ((c2->state & STATE_SHADED) ? -1 : 1));
		else
			warp_mouse_pointer(c2);
		set_input_focus(c2->window);
		update_focus();
		DBUG_CYCLING("cycle end:");
		DBUG_CYCLING(c2->title_string);
	}
	else
	{
		Window focus = root;
		if (client_exists(c))
		{			
			DBUG_CYCLING("Cycling ends with old client");
			focus = c->window;	
		}
		set_input_focus(root);
		update_focus();
	}

	g_list_free(cyclelist);
}

