/*
 * src/client.c - client management
 *
 * Copyright (C) 1999, 2000, 2001 Martin Grabmueller <mgrabmue@cs.tu-berlin.de>
 *
 * This 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 software 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 package; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.  
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif 

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

#include "alloc.h"
#include "client.h"
#include "events.h"
#include "display.h"
#include "workset.h"

/*
 * CLIENT_ROOT is the root of the client structure chain.
 */
client_t client_root = NULL;

/*
 * CURRENT_CLIENT points to the client which has the input focus, or
 * is NULL if no client hash currently the focus.
 */
client_t current_client = NULL;

int send_xmessage (Window w, Atom a, long x);

static int penalty (int x1, int y1, int width1, int height1,
		    int x2, int y2, int width2, int height2);
/*
 * Find the client structure which holds information about window W. W
 * is viewed as being the clients own window if FROM_PARENT is zero,
 * otherwise, W is considered being the parent window.
 *
 * Window w		Window to search for.
 * int from_parent	If zero, search for client window W, otherwise
 *			search for the parent window W.
 */
client_t
find_client (Window w, int from_parent)
{
  client_t c;

  if (w && w != root_window)
    for (c = client_root; c; c = c->next)
      {
	if (w == (from_parent ? c->parent : c->window))
	  return c;
      }
  return NULL;
}

/* Find the client which belongs to the tab window @var{w}.  */
client_t
find_client_from_tab (Window w)
{
  client_t c;

  if (w)
    for (c = client_root; c; c = c->next)
      {
	if (w == c->tab)
	  return c;
      }
  return NULL;
}

/*
 * Set the WM state of client C to STATE.
 *
 * client_t c	Client to change state.
 * int state	New state for the client.
 */
void
set_wm_state (client_t c, int state)
{
  long data[2];

  data[0] = (long) state;
  data[1] = None; /* icon window */

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

/*
 * Return the current WM state of client c.
 *
 * client_t c	Client to request state for.
 */
int
get_wm_state (client_t c)
{
  Atom real_type;
  int real_format, state;
  unsigned long n, extra;
  unsigned char *data;

  if ((XGetWindowProperty (display, c->window, _XA_WM_STATE, 0L, 2L, False,
			   AnyPropertyType, &real_type, &real_format, &n,
			   &extra, &data) == Success) && n)
    {
      state = *(int *)data;
      c->wm_state = state;
      XFree(data);
      return state;
    }
  else
    return WithdrawnState;
}

/*
 * Send the current window configuration of the client C to its window.
 *
 * client_t c	Client whose configuration has been changed.
 */
void
send_config (client_t c)
{
  XConfigureEvent ce;

  ce.type = ConfigureNotify;
  ce.event = c->window;
  ce.window = c->window;
  ce.x = c->x;
  ce.y = c->y;
  ce.width = c->width;
  ce.height = c->height;
  ce.border_width = 0;
  ce.above = None;
  ce.override_redirect = 0;
  
  XSendEvent(display, c->window, False, StructureNotifyMask, (XEvent *)&ce);
}

/*
 * Remove the client C from the client list and free the resources it
 * allocated.
 *
 * client_t c	Client to remove.
 */
void
remove_client (client_t c)
{
  client_t p;

  XGrabServer (display);

  set_wm_state (c, WithdrawnState);

  XReparentWindow (display, c->window, root_window, c->x, c->y);
  XSetWindowBorderWidth (display, c->window, 1);
  XDestroyWindow (display, c->tab);
  XDestroyWindow (display, c->parent);

  /*
   * Give the focus to the next window if it is the focused window we
   * are destroying. Set CURRENT_CLIENT to NULL if no windows are
   * left.
   */
  if (c == current_client)
    {
      current_client = c->next;
      if (current_client != NULL)
	transfer_focus (current_client, c);
    }

  if (client_root == c)
    client_root = c->next;
  else
    for (p = client_root; p && p->next; p = p->next)
      {
	if (p->next == c)
	  p->next = c->next;
      }

  if (c->name)
    XFree (c->name);
  if (c->size)
    XFree (c->size);
  xfree (c, sizeof (struct client));

  XSync (display, False);
  XUngrabServer (display);
}

/*
 * Redraw the parent window of client C.
 *
 * client_t c	Client whose frame is to be redrawn.
 */
void
redraw (client_t c)
{
  XPoint points[5], points0[3];
  GC gc, gc_box;

  if (c == current_client)
    {
      gc = active_gc;
      gc_box = box_gc;
    }
  else
    {
      gc = inactive_gc;
      gc_box = inactive_box_gc;
    }
  XClearWindow (display, c->tab);
  if (c == current_client)
    {
      points[0].x = c->tab_width - c->title_height + 2;
      points[0].y = 2;
      points[1].x = c->tab_width - 4;
      points[1].y = 2;
      points[2].x = c->tab_width - 4;
      points[2].y = c->title_height - 5;
      points[3].x = c->tab_width - c->title_height + 2;
      points[3].y = c->title_height - 5;
      points[4].x = c->tab_width - c->title_height + 2;
      points[4].y = 2;
      XDrawLines(display, c->tab, shadow_gc, points, 5, CoordModeOrigin);
      points[0].x = c->tab_width - c->title_height + 3;
      points[0].y = 3;
      points[1].x = c->tab_width - 3;
      points[1].y = 3;
      points[2].x = c->tab_width - 3;
      points[2].y = c->title_height - 4;
      points[3].x = c->tab_width - c->title_height + 3;
      points[3].y = c->title_height - 4;
      points[4].x = c->tab_width - c->title_height + 3;
      points[4].y = 3;
	  
      XDrawLines(display, c->tab, highlight_gc, points, 5, CoordModeOrigin);
    }
  if (c->name)
    {
      XDrawString(display, c->tab, gc, SPACE*3,
		  SPACE + font->ascent - 1, c->name, strlen(c->name));
    }

  points0[0].x = 0;
  points0[0].y = c->title_height - 1;
  points0[1].x = 0;
  points0[1].y = 0;
  points0[2].x = c->tab_width - 1;
  points0[2].y = 0;
  XDrawLines(display, c->tab, highlight_gc, points0, 3, CoordModeOrigin);
  points0[0].x = c->tab_width - 1;
  points0[0].y = 0;
  points0[1].x = c->tab_width - 1;
  points0[1].y = c->title_height - 2;
  points0[2].x = 0;
  points0[2].y = c->title_height - 2;
  XDrawLines(display, c->tab, shadow_gc, points0, 3, CoordModeOrigin);

}

#ifdef SHAPE
/*
 * FIXME: Add description here.
 */
void
set_shape (client_t c)
{
  int n, order;
  XRectangle *rect;

  rect = XShapeGetRectangles (display, c->window, ShapeBounding, &n, &order);
  if (n > 1)
    XShapeCombineShape(display,
		       c->parent, ShapeBounding, 0, c->title_height,
		       c->window, ShapeBounding, ShapeSet);
  XFree(rect);
}
#endif

/*
 * Delete the window of client C.  Uses the DELETE client message if
 * the client window supports that message, kills the client otherwise.
 *
 * client_t c	The client window to remove.
 */
void
send_wm_delete (client_t c)
{
  int i, count, found = 0;
  Atom * protocols;

  assert (c != NULL);

  if (XGetWMProtocols(display, c->window, &protocols, &count))
    {
      for (i = 0; i < count; i++)
	{
	  if (protocols[i] == _XA_WM_DELETE)
	    found++;
	}
      XFree (protocols);
    }

  if (found > 0)
    send_xmessage (c->window, _XA_WM_PROTOS, _XA_WM_DELETE);
  else
    XKillClient(display, c->window);
}

/*
 * Send an X client message with a single long parameter to a window.
 *
 * Window w	Window to send the message to.
 * Atom a	Message type.
 * long x	Message parameter.
 *
 * Returns the status code of the Xlib call used to send the message.
 */
int
send_xmessage (Window w, Atom a, long x)
{
  XEvent ev;

  ev.type = ClientMessage;
  ev.xclient.window = w;
  ev.xclient.message_type = a;
  ev.xclient.format = 32;
  ev.xclient.data.l[0] = x;
  ev.xclient.data.l[1] = CurrentTime;
  
  return XSendEvent(display, w, False, NoEventMask, &ev);
}

/*
 * Track mouse movement until the user clicks and releases the mouse
 * button.  Return the client the user has clicked or NULL, if the
 * root window was clicked.
 */
client_t
manually_select_window (void)
{
  client_t c;
  XEvent event;
  Window root, win;
  int root_x, root_y, win_x, win_y;
  unsigned button, mask;

  XGrabPointer (display, root_window, True,
		ButtonPressMask | ButtonReleaseMask,
		GrabModeAsync, GrabModeAsync,
		None, resize_curs, CurrentTime);

  XMaskEvent (display, ButtonPressMask, &event);
  button = event.xbutton.button;
  XQueryPointer (display, root_window, &root, &win,
		 &root_x, &root_y, &win_x, &win_y, &mask);
  do
    {
      XMaskEvent (display, ButtonReleaseMask, &event);
    }
  while (button != event.xbutton.button);

  XUngrabPointer (display, CurrentTime);
  XFlush (display);

  if (win != None)
    {
      c = find_client(win, 1);
      if (!c)
	c = find_client_from_tab (win);
      return c;
    }
  else
    {
      XBell(display, 100);
      return NULL;
    }
}

static void
calculate_smart_geometry (client_t c)
{
  int x, y;
  int best_x = 0, best_y = 0;
  int min_penalty = 100000000;

  for (y = 0; y < DisplayHeight (display, default_screen) -
	 (c->height + c->title_height + border_width); y += 16)
    for (x = 0; x < DisplayWidth (display, default_screen) -
	   (c->width + border_width); x += 16)
      {
	client_t cc;
	int act_penalty = 0;
	for (cc = client_root; cc; cc = cc->next)
	  {
	    if (cc == c)
	      continue;
	    if (cc->workset != -1 && cc->workset != c->workset)
	      continue;
	    act_penalty += penalty (x, y,
				    c->width + border_width,
				    c->height + border_width,
				    cc->x, cc->y,
				    cc->width + border_width,
				    cc->height + border_width);
	  }
	if (act_penalty < min_penalty)
	  {
	    best_x = x;
	    best_y = y;
	    min_penalty = act_penalty;
	  }
      }
  c->x = best_x;
  c->y = best_y + c->title_height;
}

/*
 * Initialize the geometry fields of the client structure pointed to
 * by CLIENT.  Take all client-provided information about its geometry
 * into account.
 */
static void
initialize_geometry (client_t client)
{
#if 0
  static int next_x = 0;
  static int next_y = 0;
#endif
  assert (client != NULL);

  if (client->size->flags & (PPosition | USPosition))
    {
      client->x = client->size->x;
      if (client->size->y > client->title_height)
	client->y = client->size->y;
      else
	client->y = client->title_height;
    }
  else
    {
#if 0
      client->x = next_x;
      client->y = next_y;
      next_x += 20;
      next_y += 20;
      if (next_x > DisplayWidth(display, default_screen) - 20)
	next_x = 0;
      if (next_y > DisplayHeight(display, default_screen) - 20)
	next_y = 0;
#else
      calculate_smart_geometry (client);
#endif
    }
}

void
client_adjust_tab_width (client_t c)
{
  int text_width;
  int width;
  
  text_width = c->name ? XTextWidth (font, c->name, strlen (c->name)) : 0;
  width = text_width + SPACE * 6 +  c->title_height;

  if (width < 20)
    width = 20;
  c->tab_width = width;
  if (c->workset == -1 || c->workset == current_workset)
    XMoveResizeWindow (display, c->tab, 
		       c->x + c->tab_offset, c->y - c->title_height,
		       c->tab_width, c->title_height - 1);
}

static int
sticky_candidate (client_t client)
{
  zzz_scm_t result;

  if (procedure_p (sticky_candidate_p))
    {
      result_t err;

      if ((err = zzz_apply (zzz_current_environment,
			     sticky_candidate_p,
			     zzz_make_n_list
			     (1, zzz_make_ro_string (client->name, -1)),
			     &result)) != RESULT_SUCCESS)
	return 0;
      if (zzz_eq (result, zzz_false_value))
	return 0;
      else
	return 1;
    }
  else
    return 0;
}

/*
 * Initialize the client structure pointed to by CLIENT to handle the
 * window WIN.  If the window is adopted ADOPTED_ATTR contains the window
 * attributes.  If it's a new window ADOPTED_ATTR is NULL.
 */
static void
initialize (client_t client, Window win, XWindowAttributes *adopted_attr)
{
  XWindowAttributes attr_store;
  XWindowAttributes *attr;
  XWMHints * hints;
  long supplied;
  
  assert (client != NULL);

  /*
   * Initialize basic structure fields.
   */
  client->window = win;
  client->ignore_unmap = 0;
  /*
   * Get some information about the window.
   */
  XGetTransientForHint (display, client->window, &client->transient_for);
/*    if (client->transient_for) */
/*      client->title_height = transient_titlebar_height + border_width; */
/*    else */
  client->title_height = titlebar_height;
  client->tab_width = 0;
  client->tab_offset = 0;
  client->tab_offset_goal = 0;
  client->tab_dir = 1;

  XFetchName (display, client->window, &client->name);

  client_adjust_tab_width (client);

  if (adopted_attr)
    attr = adopted_attr;
  else
    {
      attr = &attr_store;
      XGetWindowAttributes (display, client->window, attr);
    }

  client->x = attr->x - border_width;
  client->y = attr->y - border_width;
  client->width = attr->width;
  client->height = attr->height;
  client->cmap = attr->colormap;

  client->size = XAllocSizeHints ();
  XGetWMNormalHints (display, client->window, client->size, &supplied);

  if (attr->map_state == IsViewable)
    client->ignore_unmap++;
  else
    {
      if (adopted_attr == NULL)
	initialize_geometry (client);
      hints = XGetWMHints (display, win);
      if (hints)
	{
	  if (hints->flags & StateHint)
	    set_wm_state (client, hints->initial_state);
	  XFree(hints);
	}
    }

  client->wm_state = get_wm_state (client);
}

/*
 * Create a window for the window of client CLIENT and make the new
 * window the parent of the client window.
 */
static result_t
reparent (client_t client)
{
  XSetWindowAttributes p_attr;

  XSelectInput (display, client->window,
		ColormapChangeMask | EnterWindowMask | PropertyChangeMask);

  p_attr.override_redirect = True;
  p_attr.background_pixel = background_color.pixel;
  p_attr.border_pixel = border_color.pixel;
  p_attr.event_mask = ChildMask | ButtonPressMask | ExposureMask;
  client->parent = XCreateWindow (display, root_window,
				  client->x,
				  client->y,
				  client->width,
				  client->height,
				  border_width,
				  DefaultDepth (display, default_screen),
				  CopyFromParent,
				  DefaultVisual (display, default_screen),
				  CWOverrideRedirect | CWBackPixel
				  | CWBorderPixel | CWEventMask, &p_attr);

  p_attr.event_mask = (ChildMask | ButtonPressMask | ButtonReleaseMask
		       | ExposureMask | EnterWindowMask | Button3MotionMask);
  client->tab = XCreateWindow (display, root_window,
			       client->x + client->tab_offset,
			       client->y - client->title_height,
			       client->tab_width,
			       client->title_height - 1,
			       1,
			       DefaultDepth (display, default_screen),
			       CopyFromParent,
			       DefaultVisual (display, default_screen),
			       CWOverrideRedirect | CWBackPixel
			       | CWBorderPixel | CWEventMask, &p_attr);

#ifdef SHAPE
  if (shape)
    {
      XShapeSelectInput (display, client->window, ShapeNotifyMask);
      set_shape (client);
    }
#endif /* SHAPE */

  XAddToSaveSet (display, client->window);

  XSetWindowBorderWidth (display, client->window, 0);
  XResizeWindow (display, client->window, client->width, client->height);
  XReparentWindow (display, client->window, client->parent,
		   0, 0);
  XMapWindow (display, client->tab);
  XDefineCursor (display, client->tab, get_font_cursor (XC_top_left_arrow));

  send_config (client);

  return RESULT_SUCCESS;
}

/*
 * Allocate a new client structure for window WIN and insert it into
 * the global client list.
 */
void
create_client_window (Window win, XWindowAttributes *adopted_attr)
{
  client_t client;
  client_t old_client;

  XGrabServer (display);

  client = (client_t) xmalloc (sizeof(struct client));
  client->next = client_root;
  client_root = client;

  client->is_maximized = 0;
  client->workset = current_workset;

  initialize (client, win, adopted_attr);
  reparent (client);

  if (sticky_candidate (client))
    client->workset = -1;

  if (get_wm_state(client) == IconicState)
    {
      client->ignore_unmap++;
      XUnmapWindow (display, client->parent);
      XUnmapWindow (display, client->window);
    }
  else
    {
      XMapWindow (display, client->window);
      XMapRaised (display, client->parent);
      set_wm_state (client, NormalState);
    }

  XSync (display, False);
  XUngrabServer (display);

  /*
   * Make sure that new windows get the input focus.
   */
  old_client = current_client;
  current_client = client;
  transfer_focus (client, old_client);
}

/* Blow up `c' to fill the whole screen, make not even the title
   bar visible.  */
void
full_screen_client (client_t c)
{
  if (c == NULL)
    return;

  if (c->is_maximized)
    {
      c->x = c->old_x;
      c->y = c->old_y;
      c->width = c->old_width;
      c->height = c->old_height;
      c->is_maximized = 0;
    }
  else
    {
      c->old_x = c->x;
      c->old_y = c->y;
      c->old_width = c->width;
      c->old_height = c->height;
      c->x = -border_width;
      c->y = 0;
      c->width = DisplayWidth (display, default_screen);
      c->height = DisplayHeight (display, default_screen);
      c->is_maximized = 1;
    }
  client_realize_size (c);
}

#if 0
static int
intersect (int x11, int y11, int x12, int y12,
	   int x21, int y21, int x22, int y22)
{
  return !(x12 < x21 ||
	   x22 < x11 ||
	   y12 < y21 ||
	   y22 < y11);
}
#endif

static int
intersect_x (int x11, int y11, int x12, int y12,
	     int x21, int y21, int x22, int y22)
{
  return !(x12 < x21 || x22 < x11);
}

static int
intersect_y (int x11, int y11, int x12, int y12,
	     int x21, int y21, int x22, int y22)
{
  return !(y12 < y21 || y22 < y11);
}

/* Return true if the rectangles defined with the passed coordinates
   do intersect.  */
static int
intersect (int x11, int y11, int x12, int y12,
	   int x21, int y21, int x22, int y22)
{
  return intersect_x (x11, y11, x12, y12, x21, y21, x22, y22) &&
    intersect_y (x11, y11, x12, y12, x21, y21, x22, y22);
}

/* Return the penalty for positioning the first rectangle over the
   second.  */
static int
penalty (int x1, int y1, int width1, int height1,
	 int x2, int y2, int width2, int height2)
{

  if (intersect (x1, y1, x1 + width1, y1 + height1,
		 x2, y2, x2 + width2, y2 + height2))
    {
      int int_x = 0, int_y;

      if (x1 <= x2 && x1 + width1 <= x2 + width2)
	int_x = (x1 + width1) - x2;
      else if (x1 > x2 && x1 + width1 > x2 + width2)
	int_x = (x2 + width2) - x1;
      else if (x1 <= x2 && x1 + width1 > x2 + width2)
	int_x = width2;
      else if (x1 > x2 && x1 + width1 <= x2 + width2)
	int_x = width1;

      if (y1 <= y2 && y1 + height1 <= y2 + height2)
	int_y = (y1 + height1) - y2;
      else if (y1 > y2 && y1 + height1 > y2 + height2)
	int_y = (y2 + height2) - y1;
      else if (y1 <= y2 && y1 + height1 > y2 + height2)
	int_y = height2;
      else if (y1 > y2 && y1 + height1 <= y2 + height2)
	int_y = height1;
/*        fprintf (stderr, "intersection: x=%d, y=%d\n", int_x, int_y); */
	       
      return int_x * int_y;
    }
  else
    return 0;
}

/* Recalculate the tab positions for all windows.  */
static void
client_recalculate_tabs (void)
{
  client_t c, cc;		/* Current clients in outer & inner loops.  */
  int x;			/* Counter for current X position.  */
  int limit;			/* Limit for X.  */
  int w;			/* Width of current tab window.  */
  int h;			/* Heigt of current tab window.  */
  int min_penalty;		/* Minimal penalty for leftmost position.  */
  int min_right_penalty;	/* Minimal penalty for rightmos position.  */
  int cur_penalty;		/* Current penalty for current X position. */
  int best_x, best_right_x;	/* Best left- and rightmost X positions. */

  /* For every client @var{c}... */
  for (c = client_root; c; c = c->next)
    {
      /* Reset variables for current @var{c}.  */
      best_x = 0;
      best_right_x = 0;
      min_penalty = 1000000;
      min_right_penalty = min_penalty;
      x = 0;
      /* Precalculate some often needed values.  */
      limit = c->width - c->tab_width + 1;
      w = c->tab_width;
      h = c->title_height;

      /* For all possible X positions... */
      while (x < limit)
	{
	  /* Reset penalty for current X position.  */
	  cur_penalty = 0;

	  /* For all clients different from @var{c}...  */
	  for (cc = client_root; cc; cc = cc->next)
	    {
	      /* Is it another client?  Is it on the same working set
                 as @var{c}?  Is it visible? */
	      if (cc != c && (c->workset == cc->workset ||
			      cc->workset == -1) &&
		  (cc->wm_state != IconicState))
		{
		  /* Calculate the penalty for overlapping with
                     @var{cc}'s tab or client window.  */
		  cur_penalty += penalty (c->x + x, c->y - h, 
					  w + 1, h - 1,
					  cc->x, cc->y,
					  cc->width + border_width,
					  cc->height);
		  cur_penalty += penalty (c->x + x, c->y - h,
					  w + 1, h - 1,
					  cc->x + cc->tab_offset_goal,
					  cc->y - cc->title_height,
					  cc->tab_width + 1,
					  cc->title_height - 1);
		}
	    }

	  /* Now check if the penalty has decreased.  */
	  if (cur_penalty < min_penalty)
	    {
	      best_x = x;
	      min_penalty = cur_penalty;
	    }
	  else if (cur_penalty == min_penalty)
	    {
	      /* If it is the same minimal penalty, we have to update
                 the penalty and location for the rightmost X
                 position.  */
	      best_right_x = x;
	      min_penalty = cur_penalty;
	      min_right_penalty = cur_penalty;
	    }
	  /* Advance to next X position.  */
	  x++;
	}
      /* Let's check whether the left- or the rightmost penalty was
         less, and chose the goal position accordingly.  */
      if (min_penalty < min_right_penalty)
	c->tab_offset_goal = best_x;
      else if (min_penalty > min_right_penalty)
	c->tab_offset_goal = best_right_x;
      else
	{
	  /* In the case that both penalties are the same, we chose
             the goal position which is closest to the current goal
             position.  That reduces movement on the screen.  */
#if 0
	  if (abs (c->tab_offset_goal - best_x) <=
	      abs (c->tab_offset_goal - best_right_x))
	    c->tab_offset_goal = best_x;
	  else
	    c->tab_offset_goal = best_right_x;
#else
	  c->tab_offset_goal = best_x;
#endif
	}
    }
}

/* Realize the change in size and/or position of client @var{c} and
   send the new configuration to the client.  */
void
client_realize_size (client_t c)
{
  XMoveResizeWindow (display, c->parent, c->x, c->y, c->width, c->height);
  XMoveResizeWindow (display, c->window, 0, 0, c->width, c->height);
  XMoveResizeWindow (display, c->tab, 
		     c->x + c->tab_offset, c->y - c->title_height,
		     c->tab_width, c->title_height - 1);
  send_config(c);
}

/* Realize the movement of client @var{c} and send the new
   configuration to the client.  */
void
client_realize_move (client_t c)
{
  XMoveWindow (display, c->parent, c->x, c->y);
  XMoveResizeWindow (display, c->tab, 
		     c->x + c->tab_offset, c->y - c->title_height,
		     c->tab_width, c->title_height - 1);
  send_config(c);
}

/* Realize any changes to the tab window position, if the client
   @var{c} is currently visible on the screen.  */
void
client_realize_tab_move (client_t c)
{
  if (c->workset == -1 || c->workset == current_workset)
    XMoveWindow (display, c->tab,
		 c->x + c->tab_offset, c->y - c->title_height);
}

/* Raise the client @var{c} in the window stacking order.  */
void
client_raise (client_t c)
{
  XRaiseWindow (display, c->parent);
  XRaiseWindow (display, c->tab);
}

/* Lower the client @var{c} to the bottom in the window stacking order.  */
void
client_lower (client_t c)
{
  XLowerWindow (display, c->parent);
  XLowerWindow (display, c->tab);
}

/* This is the periodic task function for the client module.  It is
   responsible for the tab movement animation and recalculates the
   goal positions for the tab windows once every 10 calls.  */
void
client_tick (void)
{
  static int counter = 0;

  if (tab_scroll_speed)
    {
      if (current_client != NULL &&
	  (current_client->workset == current_workset ||
	   current_client->workset == -1) &&
	  current_client->tab_width < current_client->width)
	{
	  client_t c = current_client;
	  if (c->tab_dir < 0)
	    {
	      c->tab_offset -= tab_scroll_speed;
	      if (c->tab_offset < 0)
		{
		  c->tab_offset = 0;
		  c->tab_dir = 1;
		}
	    }
	  else
	    {
	      c->tab_offset += tab_scroll_speed;
	      if (c->tab_offset + c->tab_width > c->width)
		{
		  c->tab_offset = c->width - c->tab_width;
		  c->tab_dir = -1;
		}
	    }
	  client_realize_tab_move (current_client);
	}
    }
  else
    {
      client_t c;

      /* Check whether recalculation is needed.  */
      if (counter++ > 20)
	{
	  client_recalculate_tabs ();
	  counter = 0;
	}
      /* Loop through all clients.  */
      for (c = client_root; c; c = c->next)
	{
	  /* Animation required?  */
	  if (c->tab_offset != c->tab_offset_goal)
	    {
	      int speed = 50;
	      int a = abs (c->tab_offset - c->tab_offset_goal);

	      /* Simulate slowdown effect by reducing speed for short
                 distances.  */
#if 0
	      if (abs (c->tab_offset - c->tab_offset_goal) < 45)
		speed = 20;
	      if (abs (c->tab_offset - c->tab_offset_goal) < 30)
		speed = 8;
	      if (abs (c->tab_offset - c->tab_offset_goal) < 20)
		speed = 5;
	      if (abs (c->tab_offset - c->tab_offset_goal) < 13)
		speed = 3;
	      if (abs (c->tab_offset - c->tab_offset_goal) < 7)
		speed = 2;
	      if (abs (c->tab_offset - c->tab_offset_goal) < 3)
		speed = 1;
#endif
	      if (a < 100)
		{
		  speed = (a + 1) / 2;
		  
		}

	      /* Update tab position and realize the new positions.  */
	      if (c->tab_offset < c->tab_offset_goal)
		{
		  c->tab_offset += speed;
		  if (c->tab_offset > c->tab_offset_goal)
		    c->tab_offset = c->tab_offset_goal;
		  client_realize_tab_move (c);
		}
	      else if (c->tab_offset > c->tab_offset_goal)
		{
		  c->tab_offset -= speed;
		  if (c->tab_offset < c->tab_offset_goal)
		    c->tab_offset = c->tab_offset_goal;
		  client_realize_tab_move (c);
		}
	    }
	}
    }
}
