/* FvwmTaskBar Module for Fvwm.
 *
 * (Much reworked version of FvwmWinList)
 *  Copyright 1994,  Mike Finger (mfinger@mermaid.micro.umn.edu or
 *                               Mike_Finger@atk.com)
 *
 * Two little hacks by DYB: 1) make size of a strip in hidden state tunable
 * (with *FvwmTaskBar: AutoHide <pixels>); 2) make process of TaskBar
 * hiding/unhiding more smooth.
 *
 * Minor hack by TKP to enable autohide to work with pages (leaves 2 pixels
 * visible so that the 1 pixel panframes don't get in the way)
 *
 * Merciless hack by RBW for the Great Style Flag Rewrite - 04/20/1999.
 * Now the GSFR is used and there are no other flags (olicha may 19, 2000).
 *
 * The author makes not guarantees or warantees, either express or
 * implied.  Feel free to use any code contained here for any purpose, as long
 * and this and any other applicable copyrights are kept intact.

 * The functions in this source file that are based on part of the FvwmIdent
 * module for Fvwm are noted by a small copyright atop that function, all others
 * are copyrighted by Mike Finger.  For those functions modified/used, here is
 *  the full, original copyright:
 *
 * Copyright 1994, Robert Nation and Nobutaka Suzuki.
 * No guarantees or warantees or anything
 * are provided or implied in any way whatsoever. Use this program at your
 * own risk. Permission to use this program for any purpose is given,
 * as long as the copyright is kept intact. */

/* 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 of the License, 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 <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <stdarg.h>

#include <unistd.h>
#include <ctype.h>

#ifdef HAVE_SYS_BSDTYPES_H
#include <sys/bsdtypes.h> /* Saul */
#endif /* Saul */

#include <stdlib.h>

#ifdef I18N_MB
#include <X11/Xlocale.h>
#endif

#include "libs/Module.h"
#include "libs/fvwmlib.h"  /* for pixmaps routines */
#include "libs/FScreen.h"
#include "libs/FShape.h"
#include "libs/safemalloc.h"
#include "libs/fvwmsignal.h"
#include "libs/Colorset.h"

#include "FvwmTaskBar.h"
#include "ButtonArray.h"
#include "List.h"
#include "Colors.h"
#include "Mallocs.h"
#include "Goodies.h"
#include "Start.h"

#define MAX_NO_ICON_ACTION_LENGTH (MAX_MODULE_INPUT_TEXT_LEN - 100)

#define GRAB_EVENTS (ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|EnterWindowMask|LeaveWindowMask)
#define SomeButtonDown(a) ((a) & DEFAULT_ALL_BUTTONS_MASK)

#define DEFAULT_CLICK1 "Iconify -1, Raise, Focus"
#define DEFAULT_CLICK2 "Iconify +1, Lower"
#define DEFAULT_CLICK3 "Nop"
#define DEFAULT_CLICK_N "Nop"

#define gray_width  8
#define gray_height 8
unsigned char gray_bits[] = { 0xaa, 0x55, 0xaa, 0x55,
                              0xaa, 0x55, 0xaa, 0x55 };

#define DEFAULT_VISIBLE_PIXELS 3

/* File type information */
int   Fvwm_fd[2];
int   x_fd;

/* X related things */
Display *dpy;
Window  Root, win;
int     screen;
Pixel back;
Pixel fore;
static Pixel iconfore;
static Pixel iconback;
static Pixel focusfore;
static Pixel focusback;
GC      icongraph = None;
GC      iconbackgraph = None;
GC      focusgraph = None;
GC      focusbackgraph = None;
GC      graph = None;
GC      shadow = None;
GC      hilite = None;
GC      iconshadow = None;
GC      iconhilite = None;
GC      focusshadow = None;
GC      focushilite = None;
GC      blackgc = None;
GC      whitegc = None;
GC      checkered = None;
int     colorset = -1;
int     iconcolorset = -1;
int     focuscolorset = -1;
XFontStruct *ButtonFont, *SelButtonFont;
#ifdef I18N_MB
XFontSet ButtonFontset, SelButtonFontset;
#endif
int fontheight;
static Atom wm_del_win;
Atom MwmAtom = None;
static Bool is_dead_pipe = False;

/* Module related information */
char *Module;
int  win_width    = 5,
     win_height   = 5,
     win_grav,
     win_x,
     win_y,
     win_border = 4,
     win_has_title = 0,
     win_has_bottom_title = 0,
     win_title_height = 0,
     win_is_shaded = 0,
     button_width = DEFAULT_BTN_WIDTH,
     Clength,
     ButPressed   = -1,
     ButReleased  = -1,
     Checked      = 0,
     WindowState  = -2, /* -2 unmaped, 1 not hidden, -1 hidden,
			 *  0 hidden -> not hidden (for the events loop) */
     FocusInWin = 0;    /* 1 if the Taskbar has the focus */

static int auto_stick_y = 0;

static volatile sig_atomic_t AlarmSet = NOT_SET;
static volatile sig_atomic_t tip_window_alarm = False;
static volatile sig_atomic_t hide_taskbar_alarm = False;
static volatile sig_atomic_t stick_taskbar_alarm = False;


int UpdateInterval = 30;

ButtonArray buttons;
List windows;

char *ClickAction[NUMBER_OF_MOUSE_BUTTONS] =
{
  DEFAULT_CLICK1,
  DEFAULT_CLICK2,
  DEFAULT_CLICK3
},
     *EnterAction,
     *BackColor      = "white",
     *ForeColor      = "black",
     *IconBackColor  = "white",
     *IconForeColor  = "black",
     *FocusBackColor = NULL,
     *FocusForeColor = NULL,
     *geometry       = NULL,
     *font_string    = "fixed",
     *selfont_string = NULL;

static char *AnimCommand = NULL;

int UseSkipList    = False,
    UseIconNames   = False,
    ShowTransients = False,
    adjust_flag    = False,
    AutoStick      = False,
    AutoHide       = False,
    AutoFocus      = False,
    HighlightFocus = False,
    DeskOnly       = False,
    PageOnly       = False,
    ScreenOnly     = False,
    NoBrightFocus  = False,
    ThreeDfvwm     = False,
    RowsNumber     = 1;

int VisiblePixels  = DEFAULT_VISIBLE_PIXELS;
/* This macro should be used when calculating geometry */
#define VISIBLE_PIXELS() (VisiblePixels < win_height? VisiblePixels:win_height)

rectangle screen_g;
rectangle global_scr_g;

int NRows, RowHeight, Midline;

#define COUNT_LIMIT    10000
long DeskNumber;               /* Added by Balaji R */
int  First = 1;
int  Count = 0;

/* Imported from Goodies */
extern int stwin_width, goodies_width;
extern TipStruct Tip;

/* Imported from Start */
extern int StartButtonWidth, StartButtonHeight;
extern char *StartPopup;

char *ImagePath = NULL;
char *XineramaConfig = NULL;
static int fscreen = 0;

static void ParseConfig( void );
static void ParseConfigLine(char *tline);
static void ShutMeDown(void);
static RETSIGTYPE TerminateHandler(int sig);
static RETSIGTYPE Alarm(int sig);
static void SetAlarm(tb_alarm_t event);
static void ClearAlarm(tb_alarm_t event);
static void DoAlarmAction(void);
static int ErrorHandler(Display*, XErrorEvent*);
static Bool change_colorset(int cset, Bool force);
static void WarpTaskBar(int y);
int IsItemIndexIconSuppressed(List *list, int i);

/******************************************************************************
  Main - Setup the XConnection,request the window list and loop forever
    Based on main() from FvwmIdent:
      Copyright 1994, Robert Nation and Nobutaka Suzuki.
******************************************************************************/
int main(int argc, char **argv)
{
  const char *temp;
  char *s;
  int i;

  for (i = 3; i < NUMBER_OF_MOUSE_BUTTONS; i++)
  {
    ClickAction[i] = DEFAULT_CLICK_N;
  }

  /* Save the program name for error messages and config parsing */
  temp = argv[0];
  s=strrchr(argv[0], '/');
  if (s != NULL)
    temp = s + 1;

#ifdef I18N_MB
  setlocale(LC_CTYPE, "");
#endif

  if((argc != 6)&&(argc != 7)) {
    fprintf(stderr,"%s Version %s should only be executed by fvwm!\n",temp,
	    VERSION);
    exit(1);
  }

  /* alise support */
  if (argc == 7 && argv[6] != NULL)
  {
    Module = safemalloc(strlen(argv[6])+2);
    strcpy(Module,"*");
    strcat(Module, argv[6]);
    Clength = strlen(Module);
  }
  else
  {
    Module = safemalloc(strlen(temp)+2);
    strcpy(Module,"*");
    strcat(Module, temp);
    Clength = strlen(Module);
  }

  /* setup fvwm pipes */
  Fvwm_fd[0] = atoi(argv[1]);
  Fvwm_fd[1] = atoi(argv[2]);

#ifdef HAVE_SIGACTION
  {
    struct sigaction  sigact;

    sigemptyset(&sigact.sa_mask);
#ifdef SA_RESTART
    sigact.sa_flags = SA_RESTART;
#else
    sigact.sa_flags = 0;
#endif
    sigact.sa_handler = Alarm;
    sigaction(SIGALRM, &sigact, NULL);

#ifdef SA_INTERRUPT
    sigact.sa_flags = SA_INTERRUPT;
#else
    sigact.sa_flags = 0;
#endif
    sigaddset(&sigact.sa_mask, SIGPIPE);
    sigaddset(&sigact.sa_mask, SIGTERM);
    sigaddset(&sigact.sa_mask, SIGINT);
    sigact.sa_handler = TerminateHandler;
    sigaction(SIGPIPE, &sigact, NULL);
    sigaction(SIGTERM, &sigact, NULL);
    sigaction(SIGINT,  &sigact, NULL);
  }
#else
#ifdef USE_BSD_SIGNALS
  fvwmSetSignalMask( sigmask(SIGTERM) | sigmask(SIGPIPE) | sigmask(SIGINT) );
#endif
  signal (SIGPIPE, TerminateHandler);
  signal (SIGTERM, TerminateHandler);
  signal (SIGINT,  TerminateHandler);
  signal (SIGALRM, Alarm);
#ifdef HAVE_SIGINTERRUPT
  siginterrupt(SIGPIPE, 1);
  siginterrupt(SIGTERM, 1);
  siginterrupt(SIGINT, 1);
#endif
#endif

  SetMessageMask(Fvwm_fd,M_ADD_WINDOW | M_CONFIGURE_WINDOW | M_DESTROY_WINDOW |
		 M_WINDOW_NAME | M_ICON_NAME | M_RES_NAME | M_DEICONIFY |
		 M_ICONIFY | M_END_WINDOWLIST | M_FOCUS_CHANGE |
		 M_CONFIG_INFO | M_END_CONFIG_INFO | M_NEW_DESK | M_SENDCONFIG
#ifdef MINI_ICONS
		 | M_MINI_ICON
#endif
		);

  /* Parse the config file */
  ParseConfig();

  /* Setup the XConnection */
  StartMeUp();
  XSetErrorHandler(ErrorHandler);

  StartButtonInit(RowHeight);

  /* init the array of buttons */
  InitArray(&buttons, StartButtonWidth + 4, 0,
                      win_width - stwin_width - 8 - StartButtonWidth -10,
                      RowHeight, button_width);
  InitList(&windows);

  /* Request a list of all windows,
   * wait for ConfigureWindow packets */
  SendFvwmPipe(Fvwm_fd, "Send_WindowList",0);

  /* tell fvwm we're running */
  SendFinishedStartupNotification(Fvwm_fd);

  /* Lock on send only for iconify and deiconify (for NoIconAction) */
  if (AnimCommand && (AnimCommand[0] != 0)) {
    SetSyncMask(Fvwm_fd, M_DEICONIFY | M_ICONIFY);
    SetNoGrabMask(Fvwm_fd, M_DEICONIFY | M_ICONIFY);
  }
  /* Receive all messages from Fvwm */
  EndLessLoop();
#ifdef FVWM_DEBUG_MSGS
  if ( isTerminated )
  {
    fprintf(stderr, "%s: Received signal: exiting...\n", Module);
  }
#endif
  return 0;
}


/******************************************************************************
  EndLessLoop -  Read and redraw until we get killed, blocking when can't read
******************************************************************************/
void EndLessLoop(void)
{
  fd_set readset;
  fd_set_size_t  fd_width;
  struct timeval tv;

  fd_width = x_fd;
  if (Fvwm_fd[1] > fd_width)
    fd_width = Fvwm_fd[1];
  ++fd_width;

  while ( !isTerminated )
  {
    FD_ZERO(&readset);
    FD_SET(Fvwm_fd[1], &readset);
    FD_SET(x_fd, &readset);

    tv.tv_sec  = UpdateInterval;
    tv.tv_usec = 0;

    XFlush(dpy);
    if ( fvwmSelect(fd_width, &readset, NULL, NULL, &tv) > 0 )
    {

      if (FD_ISSET(x_fd, &readset) || XPending(dpy))
        LoopOnEvents();

      if (FD_ISSET(Fvwm_fd[1], &readset))
      {
	ReadFvwmPipe();
      }
    }
    DoAlarmAction();

    DrawGoodies();
  } /* while */
}


/******************************************************************************
  ReadFvwmPipe - Read a single message from the pipe from Fvwm
    Originally Loop() from FvwmIdent:
      Copyright 1994, Robert Nation and Nobutaka Suzuki.
******************************************************************************/
void ReadFvwmPipe(void)
{
    FvwmPacket* packet = ReadFvwmPacket(Fvwm_fd[1]);
    if ( packet == NULL )
	DeadPipe(0);
    else
	ProcessMessage( packet->type, packet->body );
}

/******************************************************************************
  ProcessMessage - Process the message coming from Fvwm
    Skeleton based on processmessage() from FvwmIdent:
      Copyright 1994, Robert Nation and Nobutaka Suzuki.
******************************************************************************/
void ProcessMessage(unsigned long type,unsigned long *body)
{
  int redraw=-1;
  int i = -1;
  char *string;
  long Desk;
  Picture p;
  struct ConfigWinPacket  *cfgpacket;
  int iconified;
  rectangle new_g;

/*    memset(&p, 0, sizeof(Picture)); */

  switch(type)
  {
  case M_FOCUS_CHANGE:
    i = FindItem(&windows, body[0]);
    if (win != body[0])
    {
      /* This case is handled in LoopOnEvents() */
      if (IsItemIconified(&windows, body[0]))
	i = -1;
      RadioButton(&buttons, i, BUTTON_BRIGHT);
      ButPressed = i;
      ButReleased = -1;
      redraw = 0;
      FocusInWin = 0;
    } else
      FocusInWin = 1;
    break;

  case M_ADD_WINDOW:
  case M_CONFIGURE_WINDOW:
    cfgpacket = (ConfigWinPacket *) body;
    new_g.x = cfgpacket->frame_x;
    new_g.y = cfgpacket->frame_y;
    new_g.width = cfgpacket->frame_width;
    new_g.height = cfgpacket->frame_height;
    if (!ShowTransients && (IS_TRANSIENT(cfgpacket)))
      break;
    /* Notes: users cannot modify the win_width of the taskbar (see
       the hints in StartMeUp; it is a good idea). So "we have to" modify
       the win_width here by modifying the WMNormalHints. Moreover, 1. we want
       that the TaskBar width with its border is execately the screen width
       2. be carful with dynamic style change */
    if (cfgpacket->w == win)
    {
      if (win_border != (short)cfgpacket->border_width ||
	  win_title_height != (int)cfgpacket->title_height ||
	  win_has_title != HAS_TITLE(cfgpacket) ||
	  win_has_bottom_title != HAS_BOTTOM_TITLE(cfgpacket))
      {
	XSizeHints hints;
	long dumy;

	win_border = (short)cfgpacket->border_width;
	win_width = screen_g.width-(win_border<<1);
	win_title_height = (int)cfgpacket->title_height;
	win_has_title = HAS_TITLE(cfgpacket);
	win_has_bottom_title = HAS_BOTTOM_TITLE(cfgpacket);

	if (AutoStick)
	{
	  win_x = screen_g.x + win_border;
	  if (!win_is_shaded)
	  {
	    if (win_y > Midline)
	    {
	      win_y = screen_g.height -
		(AutoHide ?
		 VISIBLE_PIXELS() - win_title_height*win_has_bottom_title :
		 win_height + win_border);
	    }
	    else
	    {
	      win_y = AutoHide ?
		VISIBLE_PIXELS() + win_title_height*win_has_bottom_title
		- win_height : win_border + win_title_height;
	    }
	  }
	  else
	  {
	    if (win_y > Midline)
	    {
	      win_y = screen_g.height - win_border -
		(win_has_bottom_title)*win_height;
	    }
	    else
	    {
	      win_y = win_title_height + win_border -
		(win_has_bottom_title)*win_height;
	    }
	  }
	  win_y += screen_g.y;
	}

	XGetWMNormalHints(dpy,win,&hints,&dumy);
	hints.min_width   = win_width;
	hints.base_width  = win_width;
	hints.max_width   = win_width;

	XSetWMNormalHints(dpy,win,&hints);

	if (AutoStick)
	  XMoveResizeWindow(dpy, win, win_x, win_y, win_width, win_height);
	else
	  XResizeWindow(dpy, win, win_width, win_height);

	UpdateArray(&buttons, -1, -1,
		    win_width - stwin_width - 8 - StartButtonWidth -10,-1, -1);
	ArrangeButtonArray (&buttons);
      }
      if (AutoStick && win_is_shaded != IS_SHADED(cfgpacket))
      {
	win_is_shaded = IS_SHADED(cfgpacket);
	if (win_is_shaded)
	{
	  if (win_y > Midline)
	  {
	    win_y = screen_g.height - win_border -
	      (win_has_bottom_title)*win_height;
	  }
	  else
	  {
	    win_y = win_title_height + win_border -
	      (win_has_bottom_title)*win_height;
	  }
	}
	else
	{
	  if (win_y > Midline)
	  {
	    win_y = screen_g.height - win_border - win_height;
	  }
	  else
	  {
	    win_y = win_title_height + win_border;
	  }
	  if (AutoHide)
	  {
	    WindowState = 1;
	    SetAlarm(HIDE_TASK_BAR);
	  }
	}
	win_y += screen_g.y;
	XMoveWindow(dpy, win, win_x, win_y);
      }
      break;
    }

    if ((i=FindItem(&windows, body[0])) != -1)
    {
      int remove=0;
      int add = 0;
      int skiplist = 0;
      rectangle *pold_g;
      Bool is_on_desk = True;
      Bool is_changing_desk = False;
      Bool is_on_page = True;
      Bool is_changing_page = False;
      Bool is_on_screen = True;
      Bool is_changing_screen = False;
      Bool is_managed;

      if (!GetItemGeometry(&windows, i, &pold_g))
      {
	pold_g = &new_g;
      }

      if (DeskOnly && GetDeskNumber(&windows,i,&Desk))
      {
	if (IsItemIndexSticky(&windows,i))
	{
	  is_on_desk = True;
	  is_changing_desk = False;
	}
	else if (!IsItemIndexSticky(&windows,i))
	{
	  is_on_desk = (DeskNumber == cfgpacket->desk);
	  is_changing_desk = (Desk != cfgpacket->desk);
	}
	UpdateItemIndexDesk(&windows, i, cfgpacket->desk);
      }
      if (PageOnly)
      {
	is_on_page = fvwmrect_do_rectangles_intersect(&new_g, &global_scr_g);
	is_changing_page =
	  (fvwmrect_do_rectangles_intersect(pold_g, &global_scr_g) !=
	   is_on_page);
      }
      if (ScreenOnly)
      {
	is_on_screen = fvwmrect_do_rectangles_intersect(&new_g, &screen_g);
	is_changing_screen =
	  (fvwmrect_do_rectangles_intersect(pold_g, &screen_g) !=
	   is_on_screen);
      }
      UpdateItemIndexGeometry(&windows, i, &new_g);

      is_managed = (is_on_desk && is_on_page && is_on_screen);
      if (is_changing_desk || is_changing_page || is_changing_screen)
      {
	if (is_managed)
	{
	  /* window entering managed area of the virtual desktop */
	  add = 1;
	}
	else
	{
	  /* window leaving managed area of the virtual desktop */
	  remove = 1;
	}
      }

      if (UseSkipList && is_managed)
      {
	skiplist = IsItemIndexSkipWindowList(&windows,i);
	if (DO_SKIP_WINDOW_LIST(cfgpacket) && !skiplist)
	{
	  add = 0;
	  remove = 1;
	}
	if (!DO_SKIP_WINDOW_LIST(cfgpacket) && skiplist) {
	  add = 1;
	  remove = 0;
	}
      }

      UpdateItemGSFRFlags(&windows, cfgpacket);
      if (remove)
      {
	RemoveButton(&buttons, i);
	redraw = 1;
      }
      else if (add)
      {
	AddButton(&buttons, ItemName(&windows,i),
		  GetItemPicture(&windows,i), BUTTON_UP, i,
		  IsItemIndexIconified(&windows, i));
	redraw = 1;
      }
    }
    else if (!UseSkipList || !DO_SKIP_WINDOW_LIST(cfgpacket))
    {
      AddItem(&windows, cfgpacket->w,
	      cfgpacket, cfgpacket->desk, Count++);
      if (Count > COUNT_LIMIT)
	Count = 0;
      i = FindItem(&windows, cfgpacket->w);
      UpdateItemIndexGeometry(&windows, i, &new_g);
    }
    if (i != -1)
    {
      Button *temp = find_n(&buttons, i);
      if (temp && IS_ICONIFIED(cfgpacket))
	temp->iconified = 1;
    }
    break;

  case M_DESTROY_WINDOW:
    if ((i = FindItem(&windows, body[0])) == -1)
      break;
      DeleteItem(&windows, body[0]);
      RemoveButton(&buttons, i);
      redraw = 1;
    break;

#ifdef MINI_ICONS
  case M_MINI_ICON:
    if ((i = FindItem(&windows, body[0])) == -1) break;
    p.picture = body[6];
    p.mask    = body[7];
    p.width   = body[3];
    p.height  = body[4];
    p.depth   = body[5];
    UpdateItemPicture(&windows, i, &p);
    if (UpdateButton(&buttons, i, NULL, DONT_CARE) != -1) {
      UpdateButtonPicture(&buttons, i, &p);
      redraw = 0;
    }
    break;
#endif

  case M_WINDOW_NAME:
  case M_ICON_NAME:
    if ((type == M_ICON_NAME   && !UseIconNames) ||
	(type == M_WINDOW_NAME &&  UseIconNames)) break;
    string = (char *) &body[3];
    if ((i = UpdateItemName(&windows, body[0], (char *)&body[3])) == -1)
      break;
    if (UpdateButton(&buttons, i, string, DONT_CARE) == -1)
    {
      rectangle *pold_g;

      if (GetDeskNumber(&windows, i, &Desk) == 0)
	break;
      if (DeskOnly && Desk != DeskNumber)
	break;
      if (!GetItemGeometry(&windows, i, &pold_g))
	break;
      if (PageOnly && !fvwmrect_do_rectangles_intersect(pold_g, &global_scr_g))
	break;
      if (ScreenOnly && !fvwmrect_do_rectangles_intersect(pold_g, &screen_g))
	break;
      if (UseSkipList && IsItemIndexSkipWindowList(&windows, i))
	break;
      AddButton(&buttons, string, NULL, BUTTON_UP, i,
		IsItemIndexIconified(&windows, i));
      redraw = 1;
    }
    else
      redraw = 0;
    break;

  case M_DEICONIFY:
  case M_ICONIFY:
    /* fwvm will wait for an Unlock message before continuing
     * be careful when changing this construct, make sure unlock happens
     * if AnimCommand && AnimCommand[0] != 0 */
    iconified = IsItemIconified(&windows, body[0]);
    if (((i = FindItem(&windows, body[0])) == -1)
	|| (type == M_DEICONIFY && !iconified)
	|| (type == M_ICONIFY   && iconified))
    {
      if (AnimCommand && AnimCommand[0] != 0)
	SendUnlockNotification(Fvwm_fd);
      break;
    }

    iconified = !iconified;
    UpdateItemIconifiedFlag(&windows, body[0], iconified);
    {
      Button *temp = find_n(&buttons, i);
      if (temp)
      {
	if (AnimCommand && (AnimCommand[0] != 0)
	    && IsItemIndexIconSuppressed(&windows,i))
	{
	  char buff[MAX_MODULE_INPUT_TEXT_LEN];
	  Window child;
	  int x, y;
	  int abs_x, abs_y;

	  ButtonCoordinates(&buttons, i, &x, &y);
	  XTranslateCoordinates(dpy, win, Root, x, y,
				&abs_x, &abs_y, &child);
	  if (type == M_DEICONIFY) {
	    sprintf(buff, "%s %d %d %d %d %d %d %d %d", AnimCommand,
		    abs_x, abs_y, buttons.tw-2, RowHeight,
		    (int)body[7], (int)body[8], (int)body[9],
		    (int)body[10]);
	  } else {
	    sprintf(buff, "%s %d %d %d %d %d %d %d %d", AnimCommand,
		    (int)body[7], (int)body[8], (int)body[9], (int)body[10],
		    abs_x, abs_y, buttons.tw-2, RowHeight);
	  }
	  SendText(Fvwm_fd, buff, 0);
	}
	temp->needsupdate = 1;
	temp->iconified = iconified;
	DrawButtonArray(&buttons, 0);
      }
      if (AnimCommand && AnimCommand[0] != 0)
	SendUnlockNotification(Fvwm_fd);
    }
    if (type == M_ICONIFY && i == ButReleased) {
      RadioButton(&buttons, -1, BUTTON_UP);
      ButReleased = ButPressed = -1;
      redraw = 0;
    }
    break;

  case M_END_WINDOWLIST:
    AdjustWindow(win_width, win_height);
    XMapRaised(dpy, win);
    if (AutoHide)
      WindowState = -1;
    else
      WindowState = 1;
    break;

  case M_NEW_DESK:
    DeskNumber = body[0];
    if (!First && DeskOnly)
      redraw_buttons();
    else
      First = 0;
    break;

  case M_NEW_PAGE:
    break;

  case M_CONFIG_INFO:
    {
      char *tline;
      int cset;

      tline = (char*)&(body[3]);
      if (strncasecmp(tline, "Colorset", 8) == 0)
      {
	cset = LoadColorset(tline + 8);
	if (change_colorset(cset, False))
	{
	  redraw = 1;
	}
      }
      else if (strncasecmp(tline, XINERAMA_CONFIG_STRING,
			   sizeof(XINERAMA_CONFIG_STRING) - 1) == 0)
      {
	FScreenConfigureModule(
	  tline + sizeof(XINERAMA_CONFIG_STRING) - 1);
      }
    }
    break;
  } /* switch */

  if (redraw >= 0)
    RedrawWindow(redraw);
}

void redraw_buttons()
{
  Item *item;

  FreeAllButtons(&buttons);

  for (item=windows.head; item; item=item->next)
  {
    if ((DeskNumber != item->Desk && !IS_STICKY(item) &&
	 (!IS_ICONIFIED(item) || !IS_ICON_STICKY(item))))
    {
      continue;
    }
    if (DO_SKIP_WINDOW_LIST(item) && UseSkipList)
    {
      continue;
    }
    if (PageOnly &&
	!fvwmrect_do_rectangles_intersect(&item->win_g, &global_scr_g))
    {
      continue;
    }
    AddButton(
	    &buttons, item->name, &(item->p), BUTTON_UP, item->count,
	    IS_ICONIFIED(item));
  }

  RedrawWindow(1);
}


/***********************************************************************
  Detected a broken pipe - time to exit
    Based on DeadPipe() from FvwmIdent:
      Copyright 1994, Robert Nation and Nobutaka Suzuki.
 **********************************************************************/
void DeadPipe(int nonsense)
{
  is_dead_pipe = True;
  exit(1);
}

/**********************************************************************
  Reentrant signal handler that will tell the application to quit.
    It is not safe to just call "exit()" here; instead we will
    just set a flag that will break the event-loop when the
    handler returns.
 **********************************************************************/
static RETSIGTYPE
TerminateHandler(int sig)
{
  fvwmSetTerminate(sig);
}

/******************************************************************************
  WaitForExpose - Used to wait for expose event so we don't draw too early
******************************************************************************/
void WaitForExpose(void)
{
  XEvent Event;

  while(1) {
    XNextEvent(dpy, &Event);
    if (Event.type == Expose) {
      if (Event.xexpose.count == 0) break;
    }
  }
}

/******************************************************************************
  RedrawWindow - Update the needed lines and erase any old ones
******************************************************************************/
void RedrawWindow(int force)
{
  if (Tip.open)
  {
    RedrawTipWindow();

    /* 98-11-21 13:45, Mohsin_Ahmed, mailto:mosh@sasi.com. */
    if( Tip.type >= 0 && AutoFocus )
    {
      SendFvwmPipe(Fvwm_fd, "Iconify off, Raise, Focus",
		    ItemID( &windows, Tip.type ) );
    }
  }

  if (force)
  {
    XClearArea (dpy, win, 0, 0, win_width, win_height, False);
    DrawGoodies();
  }
  DrawButtonArray(&buttons, force);
  StartButtonDraw(force);
  if (XQLength(dpy) && !force)
    LoopOnEvents();
}

static char *configopts[] =
{
  "imagepath",
  "colorset",
  XINERAMA_CONFIG_STRING,
  NULL
};

static char *moduleopts[] =
{
  "Font",
  "SelFont",
  "Geometry",
  "Fore",
  "Back",
  "Colorset",
  "IconFore",
  "IconBack",
  "IconColorset",
  "Action",
  "UseSkipList",
  "AutoFocus",
  "AutoStick",
  "AutoHide",
  "UseIconNames",
  "ShowTransients",
  "DeskOnly",
  "UpdateInterval",
  "HighlightFocus",
  "ButtonWidth",
  "NoIconAction",
  "NoBrightFocus",
  "FocusFore",
  "FocusBack",
  "FocusColorset",
  "3DFvwm",
  "Rows",
  "PageOnly",
  "ScreenOnly",
  NULL
};

void ParseConfig(void)
{
  char *buf;

  InitGetConfigLine(Fvwm_fd,Module);
  while (GetConfigLine(Fvwm_fd,&buf), buf != NULL)
  {
    ParseConfigLine(buf);
  } /* end config lines */
} /* end function */

static void ParseConfigLine(char *tline)
{
  char *rest;
  int index;

  while (isspace((unsigned char)*tline))
    tline++;

  if (strncasecmp(tline, Module, Clength) != 0)
  {
    /* Non module spcific option */
    index = GetTokenIndex(tline, configopts, -1, &rest);
    while (*rest && *rest != '\n' && isspace(*rest))
      rest++;
    switch(index)
    {
    case 0: /* imagepath */
      if (ImagePath)
      {
	free(ImagePath);
      }
      CopyString(&ImagePath, rest);
      break;
    case 1: /* colorset */
      LoadColorset(rest);
      break;
    case 2: /* XINERAMA_CONFIG_STRING */
      if (XineramaConfig)
      {
	free(XineramaConfig);
      }
      CopyString(&XineramaConfig, rest);
      break;
    default:
      /* unknown option */
      break;
    }
  } /* if options */
  else
  {
    /* option beginning with '*ModuleName' */
    rest = tline + Clength;
    index = GetTokenIndex(rest, moduleopts, -1, &rest);
    while (*rest && *rest != '\n' && isspace(*rest))
      rest++;
    switch(index)
    {
    case 0: /* Font */
      CopyStringWithQuotes(&font_string, rest);
      break;
    case 1: /* SelFont */
      CopyStringWithQuotes(&selfont_string, rest);
      break;
    case 2: /* Geometry */
      while (isspace((unsigned char)*rest) && *rest != '\n' && *rest != 0)
	rest++;
      UpdateString(&geometry, rest);
      break;
    case 3: /* Fore */
      CopyString(&ForeColor, rest);
      colorset = -1;
      break;
    case 4: /* Back */
      CopyString(&BackColor, rest);
      colorset = -1;
      break;
    case 5: /* Colorset */
      colorset = -1;
      colorset = atoi(rest);
      AllocColorset(colorset);
      break;
    case 6: /* IconFore */
      CopyString(&IconForeColor, rest);
      iconcolorset = -1;
      break;
    case 7: /* IconBack */
      CopyString(&IconBackColor, rest);
      iconcolorset = -1;
      break;
    case 8: /* IconColorset */
      iconcolorset = -1;
      iconcolorset = atoi(rest);
      AllocColorset(iconcolorset);
      break;
    case 9: /* Action */
      LinkAction(rest);
      break;
    case 10: /* UseSkipList */
      UseSkipList=True;
      break;
    case 11: /* AutoFocus */
      AutoFocus=True;
      break;
    case 12: /* AutoStick */
      AutoStick=True;
      break;
    case 13: /* AutoHide */
      AutoHide=True;
      AutoStick=True;
      VisiblePixels=atoi(rest);
      if (VisiblePixels < 1) VisiblePixels = DEFAULT_VISIBLE_PIXELS;
      break;
    case 14: /* UseIconNames */
      UseIconNames=True;
      break;
    case 15: /* ShowTransients */
      ShowTransients=True;
      break;
    case 16: /* DeskOnly */
      DeskOnly=True;
      break;
    case 17: /* UpdateInterval */
      UpdateInterval = atoi(rest);
      break;
    case 18: /* HighlightFocus */
      HighlightFocus=True;
      break;
    case 19: /* ButtonWidth */
      button_width = atoi(rest);
      break;
    case 20: /* NoIconAction */
      CopyString(&AnimCommand, rest);
      if (strlen(AnimCommand) > MAX_NO_ICON_ACTION_LENGTH)
	AnimCommand[MAX_NO_ICON_ACTION_LENGTH] = 0;
      break;
    case 21: /* NoBrightFocus */
      NoBrightFocus = True;
      break;
    case 22: /* FocusFore */
      CopyString(&FocusForeColor, rest);
      focuscolorset = -1;
      break;
    case 23: /* FocusBack */
      CopyString(&FocusBackColor, rest);
      focuscolorset = -1;
      break;
    case 24: /* FocusColorset */
      focuscolorset = -1;
      focuscolorset = atoi(rest);
      AllocColorset(focuscolorset);
      break;
    case 25: /* 3DFvwm */
      ThreeDfvwm = True;
      break;
    case 26: /* Rows */
      RowsNumber = atoi(rest);
      if (!(1 <= RowsNumber && RowsNumber <= 8)) {
	RowsNumber = 1;
      }
      break;
    case 27: /* PageOnly */
      PageOnly=True;
      break;
    case 28: /* ScreenOnly */
      ScreenOnly=True;
      break;
    default:
      if (!GoodiesParseConfig(tline) &&
	  !StartButtonParseConfig(tline))
      {
	fprintf(stderr,"%s: unknown configuration option %s", Module, tline);
      }
      break;
    } /* switch */
  } /* else options */
}

/******************************************************************************
  Alarm - Handle a SIGALRM - used to implement timeout events
******************************************************************************/
static RETSIGTYPE
Alarm(int nonsense)
{
  if (AlarmSet & SHOW_TIP)
  {
    tip_window_alarm = True;
  }
  if (AlarmSet & HIDE_TASK_BAR)
  {
    hide_taskbar_alarm = True;
  }
  if (AlarmSet & STICK_TASK_BAR)
  {
    stick_taskbar_alarm = True;
  }

  AlarmSet = NOT_SET;
#if !defined(HAVE_SIGACTION) && !defined(USE_BSD_SIGNALS)
  signal (SIGALRM, Alarm);
#endif
}

/******************************************************************************
  DoAlarmAction -
******************************************************************************/
void
DoAlarmAction(void)
{

  if (hide_taskbar_alarm)
  {
    hide_taskbar_alarm = False;
    HideTaskBar();
  }
  if (stick_taskbar_alarm)
  {
    stick_taskbar_alarm = False;
    WarpTaskBar(auto_stick_y);
  }
  if (tip_window_alarm)
  {
    tip_window_alarm = False;
    if (AutoHide && WindowState == 0)
    {
      Window dummy_rt, dummy_c;
      int abs_x, abs_y, pos_x, pos_y;
      unsigned int dummy;
      XEvent sevent;

      /* We are now "sure" that the TaskBar is not hidden for the
	 Event loop. We send a motion notify for activating tips */
      WindowState = 1;
      XQueryPointer(dpy, win, &dummy_rt,&dummy_c, &abs_x, &abs_y,
		    &pos_x, &pos_y, &dummy);
      sevent.xmotion.x = pos_x;
      sevent.xmotion.y = pos_y;
      sevent.xany.type = MotionNotify;
      sevent.xmotion.state = 0;
      XSendEvent(dpy, win, False, EnterWindowMask, &sevent);
      Tip.type = NO_TIP;
    }
    else
      ShowTipWindow(1);
  }
}

/******************************************************************************
  CheckForTip - determine when to popup the tip window
******************************************************************************/
void CheckForTip(int x, int y)
{
  int  num, bx, by, trunc;
  char *name;

  if (MouseInStartButton(x, y)) {
    if (Tip.type != START_TIP) PopupTipWindow(3, 0, "Click here to start");
    Tip.type = START_TIP;
  }
  else if (MouseInMail(x, y)) {
    if (Tip.type != MAIL_TIP) CreateMailTipWindow();
    Tip.type = MAIL_TIP;
  }
  else if (MouseInClock(x, y)) {
    if (Tip.type != DATE_TIP) CreateDateWindow();
    Tip.type = DATE_TIP;
  }
  else {
    num = LocateButton(&buttons, x, y, &bx, &by, &name, &trunc);
    if (num != -1) {
      if ((Tip.type != num)  ||
	  (Tip.text == NULL) || (!trunc && (strcmp(name, Tip.text) != 0)))
	PopupTipWindow(bx+3, by, name);
      Tip.type = num;
    } else {
      Tip.type = NO_TIP;
    }
  }

  if (Tip.type != NO_TIP) {
    if (AlarmSet != SHOW_TIP && !Tip.open)
      SetAlarm(SHOW_TIP);
  }
  else {
    ClearAlarm(SHOW_TIP);
    if (Tip.open) ShowTipWindow(0);
  }
}


/******************************************************************************
  LoopOnEvents - Process all the X events we get
******************************************************************************/
void LoopOnEvents(void)
{
  int  num = 0;
  char tmp[100];
  XEvent Event;
  XEvent Event2;
  int x, y, redraw;
  static unsigned long lasttime = 0L;
  Time NewTimestamp = lasttime;

  while(XPending(dpy))
  {
    redraw = -1;
    XNextEvent(dpy, &Event);
    if (Event.xany.type == ConfigureNotify)
    {
      /* Purge all but the last configure events */
      while (XCheckTypedEvent(dpy, ConfigureNotify, &Event))
	;
    }

    NewTimestamp = lasttime;
    switch(Event.type)
    {
    case ButtonRelease:
      NewTimestamp = Event.xbutton.time;
      num = WhichButton(&buttons, Event.xbutton.x, Event.xbutton.y);
      if (num == -1) {
	if (MouseInStartButton(Event.xbutton.x, Event.xbutton.y))
	  StartButtonUpdate(NULL, BUTTON_UP);
      } else {
	ButReleased = ButPressed; /* Avoid race fvwm pipe */
	if (Event.xbutton.button >= 1 &&
	    Event.xbutton.button <= NUMBER_OF_MOUSE_BUTTONS)
	{
	  SendFvwmPipe(Fvwm_fd, ClickAction[Event.xbutton.button-1],
		       ItemID(&windows, num));
	}
      }

      if (MouseInStartButton(Event.xbutton.x, Event.xbutton.y)) {
	StartButtonUpdate(NULL, BUTTON_UP);
	redraw = 0;
	usleep(50000);
      }
      if (HighlightFocus) {
	if (num == ButPressed)
	  RadioButton(&buttons, num, BUTTON_DOWN);
	if (num != -1)
	  SendFvwmPipe(Fvwm_fd, "Focus 0", ItemID(&windows, num));
      }
      ButPressed = -1;
      redraw = 0;
      break;

    case ButtonPress:
      NewTimestamp = Event.xbutton.time;
      RadioButton(&buttons, -1, BUTTON_UP); /* no windows focused anymore */
      if (MouseInStartButton(Event.xbutton.x, Event.xbutton.y)) {
	StartButtonUpdate(NULL, BUTTON_DOWN);
	x = win_x;
	if (win_y < Midline) {
	  /* bar in top half of the screen */
	  y = win_y + RowHeight;
	} else {
	  /* bar in bottom of the screen */
	  y = win_y - screen_g.height;
	}
	sprintf(tmp,"Popup %s %d %d", StartPopup, x, y);
	SendFvwmPipe(Fvwm_fd, tmp, 0);
      } else {
	StartButtonUpdate(NULL, BUTTON_UP);
	if (MouseInMail(Event.xbutton.x, Event.xbutton.y)) {
	  HandleMailClick(Event);
	} else {
	  num = WhichButton(&buttons, Event.xbutton.x, Event.xbutton.y);
	  UpdateButton(&buttons, num, NULL, (ButPressed == num) ?
		       BUTTON_BRIGHT : BUTTON_DOWN);

	  ButPressed = num;
	}
      }
      redraw = 0;
      break;

    case Expose:
      memcpy(&Event2, &Event, sizeof(Event));
      /* eat up excess Expose events. */
      while (XCheckTypedWindowEvent(dpy, win, Expose, &Event2))
      {
	memcpy(&Event, &Event2, sizeof(Event));
      }
      if (Event.xexpose.count == 0)
      {
	if (Event.xexpose.window == Tip.win)
	  redraw = 0;
	else
	  redraw = 1;
      }
      break;

    case ClientMessage:
      if ((Event.xclient.format==32) && (Event.xclient.data.l[0]==wm_del_win))
	exit(0);
      break;

    case EnterNotify:
      NewTimestamp = Event.xcrossing.time;
      if (AutoHide)
	RevealTaskBar();

      if (Event.xcrossing.mode != NotifyNormal)
	break;
      num = WhichButton(&buttons, Event.xcrossing.x, Event.xcrossing.y);
      if (!HighlightFocus) {
	if (SomeButtonDown(Event.xcrossing.state)) {
	  if (num != -1) {
	    RadioButton(&buttons, num, BUTTON_DOWN);
	    ButPressed = num;
	    redraw = 0;
	  } else {
	    ButPressed = -1;
	  }
	}
      } else {
	if (num != -1 && num != ButPressed)
	  SendFvwmPipe(Fvwm_fd, "Focus 0", ItemID(&windows, num));
      }

      CheckForTip(Event.xmotion.x, Event.xmotion.y);
      break;

    case LeaveNotify:
      NewTimestamp = Event.xcrossing.time;
      ClearAlarm(SHOW_TIP);
      if (Tip.open)
	ShowTipWindow(0);

      if (AutoHide)
	SetAlarm(HIDE_TASK_BAR);

      if (Event.xcrossing.mode != NotifyNormal)
	break;

      if (!HighlightFocus) {
	if (SomeButtonDown(Event.xcrossing.state)) {
	  if (ButPressed != -1) {
	    RadioButton(&buttons, -1, BUTTON_UP);
	    ButPressed = -1;
	    redraw = 0;
	  }
	} else {
	  if (ButReleased != -1) {
	    RadioButton(&buttons, -1, BUTTON_UP);
	    ButReleased = -1;
	    redraw = 0;
	  }
	}
      }
      break;

    case MotionNotify:
      NewTimestamp = Event.xmotion.time;
      if (MouseInStartButton(Event.xmotion.x, Event.xbutton.y)) {
	if (SomeButtonDown(Event.xmotion.state))
	  redraw = StartButtonUpdate(NULL, BUTTON_DOWN) ? 0 : -1;
	CheckForTip(Event.xmotion.x, Event.xmotion.y);
	break;
      }
      redraw = StartButtonUpdate(NULL, BUTTON_UP) ? 0 : -1;
      num = WhichButton(&buttons, Event.xmotion.x, Event.xmotion.y);
      if (!HighlightFocus) {
	if (SomeButtonDown(Event.xmotion.state) && num != ButPressed) {
	  if (num != -1) {
	    RadioButton(&buttons, num, BUTTON_DOWN);
	    ButPressed = num;
	  } else {
	    RadioButton(&buttons, num, BUTTON_UP);
	    ButPressed = -1;
	  }
	  redraw = 0;
	}
      } else if (num != -1 && num != ButPressed)
	SendFvwmPipe(Fvwm_fd, "Focus 0", ItemID(&windows, num));

      CheckForTip(Event.xmotion.x, Event.xmotion.y);
      break;

    case ConfigureNotify:
      memcpy(&Event2, &Event, sizeof(Event));
      /* eat up excess ConfigureNotify events. */
      while (XCheckTypedWindowEvent(dpy, win, ConfigureNotify, &Event2))
      {
	memcpy(&Event, &Event2, sizeof(Event));
      }
      if (Event.xconfigure.height != win_height) {
	AdjustWindow(Event.xconfigure.width, Event.xconfigure.height);
	redraw = 1;
      }
      /* useful because of dynamic style change */
      if (!Event.xconfigure.send_event)
      {
	break;
      }
      else if (Event.xconfigure.x != win_x || Event.xconfigure.y != win_y)
      {
	if (AutoStick)
	{
	  SetAlarm(STICK_TASK_BAR);
	  auto_stick_y = Event.xconfigure.y;
	}
	else
	{
	  win_x = Event.xconfigure.x;
	  win_y = Event.xconfigure.y;
	}
      }
      break;
    } /* switch (Event.type) */

    if (redraw >= 0)
      RedrawWindow(redraw);
    DoAlarmAction();
    if (NewTimestamp - lasttime > UpdateInterval*1000L)
    {
      DrawGoodies();
      lasttime = NewTimestamp;
    }
  }

  return;
}

/***********************************
  AdjustWindow - Resize the window
  **********************************/
void AdjustWindow(int width, int height)
{
  NRows = (height+2)/RowHeight;
  win_height = height;
  win_width = width;
  ArrangeButtonArray(&buttons);
  change_colorset(0, False);
}

/******************************************************************************
  LinkAction - Link an response to a users action
******************************************************************************/
void LinkAction(const char *string)
{
  const char *temp=string;
  while(isspace((unsigned char)*temp)) temp++;

  if(strncasecmp(temp, "Click", 5)==0)
  {
    int n;
    int b;
    int i;

    i = sscanf(temp + 5, "%d%n", &b, &n);
    if (i > 0 && b >=1 && b <= NUMBER_OF_MOUSE_BUTTONS)
    {
      CopyString(&ClickAction[b - 1], temp + 5 + n);
    }
  }
  else if(strncasecmp(temp, "Enter", 5)==0)
    CopyString(&EnterAction,&temp[5]);
}

static void CreateOrUpdateGCs(void)
{
   XGCValues gcval;
   unsigned long gcmask;
   Pixel pfore;
   Pixel pback;
   Pixel piconfore;
   Pixel piconback;
   Pixel piconhilite;
   Pixel piconshadow;
   Pixel pfocusfore;
   Pixel pfocusback;
   Pixel pfocushilite;
   Pixel pfocusshadow;
   Pixel philite;
   Pixel pshadow;

   if (colorset >= 0)
   {
     pfore = Colorset[colorset].fg;
     pback = Colorset[colorset].bg;
     philite = Colorset[colorset].hilite;
     pshadow = Colorset[colorset].shadow;
   }
   else
   {
     pfore = fore;
     pback = back;
     philite = GetHilite(back);
     if(Pdepth < 2)
       pshadow = GetShadow(fore);
     else
       pshadow = GetShadow(back);
   }
   if (iconcolorset >= 0)
   {
     piconfore = Colorset[iconcolorset].fg;
     piconback = Colorset[iconcolorset].bg;
     piconhilite = Colorset[iconcolorset].hilite;
     piconshadow = Colorset[iconcolorset].shadow;
   }
   else
   {
     piconfore = iconfore;
     piconback = iconback;
     piconhilite = GetHilite(iconback);
     if(Pdepth < 2)
       piconshadow = GetShadow(iconfore);
     else
       piconshadow = GetShadow(iconback);
   }
   if (focuscolorset >= 0)
   {
     pfocusfore = Colorset[focuscolorset].fg;
     pfocusback = Colorset[focuscolorset].bg;
     pfocushilite = Colorset[focuscolorset].hilite;
     pfocusshadow = Colorset[focuscolorset].shadow;
   }
   else
   {
     pfocusshadow = pshadow;
     if (FocusForeColor != NULL)
     {
       pfocusfore = focusfore;
       if(Pdepth < 2)
	 pfocusshadow = GetShadow(focusfore);
     }
     else
     {
       pfocusfore = fore;
     }
     if (FocusBackColor != NULL)
     {
       pfocusback = focusback;
       pfocushilite = GetHilite(focusback);
       if(!(Pdepth < 2))
	 pfocusshadow = GetShadow(focusback);
     }
     else
     {
       pfocusback = back;
       pfocushilite = philite;
     }
   }

   /* only the foreground changes for all GCs */
   gcval.background = pback;
   gcval.font = SelButtonFont->fid;
   gcval.graphics_exposures = False;

   gcmask = GCForeground | GCBackground | GCFont | GCGraphicsExposures;
   /* Normal */
   gcval.foreground = pfore;
   if (graph)
     XChangeGC(dpy,graph,gcmask,&gcval);
   else
     graph = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   /* iconified */
   gcval.foreground = piconfore;
   if (iconbackgraph)
     XChangeGC(dpy,icongraph,gcmask,&gcval);
   else
     icongraph = fvwmlib_XCreateGC(dpy,Root,gcmask,&gcval);

   gcval.foreground = piconback;
   if (iconbackgraph)
     XChangeGC(dpy,iconbackgraph,gcmask,&gcval);
   else
     iconbackgraph = fvwmlib_XCreateGC(dpy,Root,gcmask,&gcval);

   gcval.foreground = piconshadow;
   if (iconshadow)
     XChangeGC(dpy,iconshadow,gcmask,&gcval);
   else
     iconshadow = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   gcval.foreground = piconhilite;
   if (iconhilite)
     XChangeGC(dpy,iconhilite,gcmask,&gcval);
   else
     iconhilite = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   /* focused */
   gcval.foreground = pfocusfore;
   if (focusbackgraph)
     XChangeGC(dpy,focusgraph,gcmask,&gcval);
   else
     focusgraph = fvwmlib_XCreateGC(dpy,Root,gcmask,&gcval);

   gcval.foreground = pfocusback;
      if (focusbackgraph)
     XChangeGC(dpy,focusbackgraph,gcmask,&gcval);
   else
     focusbackgraph = fvwmlib_XCreateGC(dpy,Root,gcmask,&gcval);

   gcval.foreground = pfocusshadow;
   if (focusshadow)
     XChangeGC(dpy,focusshadow,gcmask,&gcval);
   else
     focusshadow = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   gcval.foreground = pfocushilite;
   if (focushilite)
     XChangeGC(dpy,focushilite,gcmask,&gcval);
   else
     focushilite = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   /* "normal" continued */
   gcmask = GCForeground | GCBackground | GCGraphicsExposures;
   gcval.foreground = pshadow;
   if (shadow)
     XChangeGC(dpy,shadow,gcmask,&gcval);
   else
     shadow = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   gcval.foreground = philite;
   if (hilite)
     XChangeGC(dpy,hilite,gcmask,&gcval);
   else
     hilite = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   gcval.foreground = WhitePixel(dpy, screen);;
   if (whitegc)
     XChangeGC(dpy,whitegc,gcmask,&gcval);
   else
     whitegc = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   gcval.foreground = BlackPixel(dpy, screen);
   if (blackgc)
     XChangeGC(dpy,blackgc,gcmask,&gcval);
   else
     blackgc = fvwmlib_XCreateGC(dpy,win,gcmask,&gcval);

   /* brighting */
   gcmask = GCForeground | GCBackground | GCTile |
	    GCFillStyle  | GCGraphicsExposures;
   if (focuscolorset >= 0 || FocusBackColor != NULL)
     gcval.foreground = pfocusback;
   else
     gcval.foreground = philite;
   gcval.fill_style = FillTiled;
   gcval.tile       = XCreatePixmapFromBitmapData(dpy, win, (char *)gray_bits,
						  gray_width, gray_height,
						  gcval.foreground,
						  gcval.background,Pdepth);
   if (checkered)
     XChangeGC(dpy, checkered, gcmask, &gcval);
   else
     checkered = fvwmlib_XCreateGC(dpy, win, gcmask, &gcval);
}

static Bool change_colorset(int cset, Bool force)
{
  Bool do_redraw = False;

  if (cset < 0)
    return False;
  if (force || cset == colorset || cset == iconcolorset ||
      cset == focuscolorset)
  {
    CreateOrUpdateGCs();
    if (force || cset == colorset)
    {
      SetWindowBackground(
	dpy, win, win_width, win_height, &Colorset[colorset],
	Pdepth, graph, True);
    }
    do_redraw = True;
  }
  do_redraw |= change_goody_colorset(cset, force);

  return do_redraw;
}


/******************************************************************************
  StartMeUp - Do X initialization things
******************************************************************************/
void StartMeUp(void)
{
   XSizeHints hints;
   XClassHint classhints;
   int ret;
   int i;
   XSetWindowAttributes attr;
#ifdef I18N_MB
  char **ml;
  XFontStruct **fs_list;
#endif

   if (!(dpy = XOpenDisplay(""))) {
     fprintf(stderr,"%s: can't open display %s", Module,
	     XDisplayName(""));
     exit (1);
   }
   InitPictureCMap(dpy);
   FScreenInit(dpy);
   if (XineramaConfig)
   {
     FScreenConfigureModule(XineramaConfig);
     free(XineramaConfig);
   }
   AllocColorset(0);
   FShapeInit(dpy);
   x_fd = XConnectionNumber(dpy);
   screen= DefaultScreen(dpy);
   Root = RootWindow(dpy, screen);

   if (geometry == NULL)
     UpdateString(&geometry, "+0-0");
   /* evaluate further down */
   ret = FScreenParseGeometryWithScreen(
     geometry, &hints.x, &hints.y, (unsigned int *)&hints.width,
     (unsigned int *)&hints.height, &fscreen);
   FScreenGetScrRect(
     NULL, fscreen,
     &screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height);
   FScreenGetScrRect(
     NULL, FSCREEN_GLOBAL,
     &global_scr_g.x, &global_scr_g.y, &global_scr_g.width,
     &global_scr_g.height);
   Midline = (screen_g.height >> 1) + screen_g.y;

   if (selfont_string == NULL)
     selfont_string = font_string;

#ifdef I18N_MB
   if ((ButtonFontset=GetFontSetOrFixed(dpy,font_string)) == NULL)
   {
	   fprintf(stderr,
		   "%s: Couldn't load font. Exiting!\n",Module);
	   exit(1);
   }
   XFontsOfFontSet(ButtonFontset,&fs_list,&ml);
   ButtonFont = fs_list[0];
   if ((SelButtonFontset=GetFontSetOrFixed(dpy,selfont_string)) == NULL)
   {
	   fprintf(stderr,
		   "%s: Couldn't load selfont. Exiting!\n",Module);
	   exit(1);
   }
   XFontsOfFontSet(SelButtonFontset,&fs_list,&ml);
   SelButtonFont = fs_list[0];
#else
   if ((ButtonFont = XLoadQueryFont(dpy, font_string)) == NULL) {
     if ((ButtonFont = XLoadQueryFont(dpy, "fixed")) == NULL) {
       fprintf(stderr, "%s: Couldn't load fixed font. Exiting!\n", Module);
       exit(1);
     }
   }
   if ((SelButtonFont = XLoadQueryFont(dpy, selfont_string)) == NULL) {
     if ((SelButtonFont = XLoadQueryFont(dpy, "fixed")) == NULL) {
       fprintf(stderr, "%s: Couldn't load fixed font. Exiting!\n", Module);
       exit(1);
     }
   }
#endif

   fontheight = SelButtonFont->ascent + SelButtonFont->descent;

   NRows = 1;
   RowHeight = fontheight + 8;

   win_border = 4; /* default border width */
   win_height = RowHeight+(RowsNumber-1)*(RowHeight+2);
   win_width = screen_g.width - (win_border * 2);

   /* now evaluate the geometry parsed above */
   if (ret & YNegative)
   {
     if (AutoStick)
     {
       if (-hints.y < Midline)
	 hints.y = screen_g.height - (AutoHide ? VISIBLE_PIXELS() : win_height);
       else
	 hints.y = AutoHide ? VISIBLE_PIXELS() - win_height : 0;
       hints.y += screen_g.y;
     }
     else
     {
       hints.y += screen_g.height - win_height;
     }
   }
   else if (AutoStick)
   {
     if (hints.y < Midline)
       hints.y = AutoHide ? VISIBLE_PIXELS() - win_height : 0;
     else
       hints.y = screen_g.height - (AutoHide ? VISIBLE_PIXELS() : win_height);
     hints.y += screen_g.y;
   }

   if (ret & XNegative)
   {
     if (ret & YNegative)
       hints.win_gravity=SouthEastGravity;
     else
       hints.win_gravity=NorthEastGravity;
   }
   else
   {
     if (ret & YNegative)
       hints.win_gravity=SouthWestGravity;
     else
       hints.win_gravity=NorthWestGravity;
   }

   hints.flags=USPosition|PPosition|USSize|PSize|PResizeInc|
     PWinGravity|PMinSize|PMaxSize|PBaseSize;
   hints.x           = screen_g.x;
   hints.width       = win_width;
   hints.height      = win_height;
   hints.width_inc   = 1;
   hints.height_inc  = RowHeight+2;
   hints.min_width   = win_width;
   hints.min_height  = RowHeight;
   hints.max_width   = win_width;
   hints.max_height  = RowHeight+7*(RowHeight+2);
   hints.base_width  = win_width;
   hints.base_height = RowHeight;

   win_x = hints.x + win_border;
   win_y = hints.y;

   if(Pdepth < 2) {
     back = WhitePixel(dpy, screen);
     fore = BlackPixel(dpy, screen);
     iconback = WhitePixel(dpy, screen);
     iconfore = BlackPixel(dpy, screen);
     focusback = WhitePixel(dpy, screen);
     focusfore = BlackPixel(dpy, screen);
   } else {
     back = GetColor(BackColor);
     fore = GetColor(ForeColor);
     iconback = GetColor(IconBackColor);
     iconfore = GetColor(IconForeColor);
     if (FocusBackColor != NULL)
       focusback = GetColor(FocusBackColor);
     else
       focusback = back;
     if (FocusForeColor != NULL)
       focusfore = GetColor(FocusForeColor);
     else
       focusfore = fore;
   }

   attr.background_pixel =
     (colorset >= 0) ? Colorset[colorset].bg : back;
   attr.border_pixel = 0;
   attr.colormap = Pcmap;
   win=XCreateWindow(dpy,Root,hints.x,hints.y,hints.width,hints.height,0,Pdepth,
		     InputOutput,Pvisual,CWBackPixel|CWBorderPixel|CWColormap,
		     &attr);

   wm_del_win=XInternAtom(dpy,"WM_DELETE_WINDOW",False);
   XSetWMProtocols(dpy,win,&wm_del_win,1);

  {
    XTextProperty nametext;
    char *list[]={NULL,NULL};
    list[0] = Module+1;

    classhints.res_name=safestrdup(Module+1);
    classhints.res_class=safestrdup("FvwmTaskBar");

    if(!XStringListToTextProperty(list,1,&nametext))
    {
      fprintf(stderr,"%s: Failed to convert name to XText\n",Module);
      exit(1);
    }
    XSetWMProperties(dpy,win,&nametext,&nametext,
		     NULL,0,&hints,NULL,&classhints);
    XFree(nametext.value);
  }

   for (i = 1; i <= NUMBER_OF_MOUSE_BUTTONS; i++)
   {
     XGrabButton(dpy,i,AnyModifier,win,True,GRAB_EVENTS,GrabModeAsync,
		 GrabModeAsync,None,None);
   }

   /*   SetMwmHints(MWM_DECOR_ALL|MWM_DECOR_MAXIMIZE|MWM_DECOR_MINIMIZE,
	       MWM_FUNC_ALL|MWM_FUNC_MAXIMIZE|MWM_FUNC_MINIMIZE,
	       MWM_INPUT_MODELESS);
	       */

   CreateOrUpdateGCs();
   if (colorset >= 0)
   {
     SetWindowBackground(
       dpy, win, win_width, win_height, &Colorset[colorset],
       Pdepth, graph, False);
   }

   XSelectInput(dpy,win,(ExposureMask | KeyPressMask | PointerMotionMask |
			 EnterWindowMask | LeaveWindowMask |
			 StructureNotifyMask));

   InitGoodies();
   atexit(ShutMeDown);
}

/******************************************************************************
  ShutMeDown - Do X client cleanup
******************************************************************************/
static void
ShutMeDown(void)
{
  if (!is_dead_pipe)
  {
    FreeList(&windows);
    FreeAllButtons(&buttons);
    XFreeGC(dpy,graph);
    XFreeGC(dpy,icongraph);
    XFreeGC(dpy,focusgraph);
    XDestroyWindow(dpy, win);
    XCloseDisplay(dpy);
  }
}


/******************************************************************************
  ChangeWindowName - Self explanitory
    Original work from FvwmIdent:
      Copyright 1994, Robert Nation and Nobutaka Suzuki.
******************************************************************************/
void ChangeWindowName(char *str)
{
  XTextProperty name;
  if (XStringListToTextProperty(&str,1,&name) == 0) {
    fprintf(stderr,"%s: cannot allocate window name.\n",Module);
    return;
  }
  XSetWMName(dpy,win,&name);
  XSetWMIconName(dpy,win,&name);
  XFree(name.value);
}

/**************************************************************************
 *
 * Sets mwm hints
 *
 *************************************************************************/
/*
 *  Now, if we (hopefully) have MWW - compatible window manager ,
 *  say, mwm, ncdwm, or else, we will set useful decoration style.
 *  Never check for MWM_RUNNING property.May be considered bad.
 */

void SetMwmHints(unsigned int value, unsigned int funcs, unsigned int input)
{
PropMwmHints prop;

  if (MwmAtom==None)
    {
      MwmAtom=XInternAtom(dpy,"_MOTIF_WM_HINTS",False);
    }
  if (MwmAtom!=None)
    {
      /* sh->mwm.decorations contains OR of the MWM_DECOR_XXXXX */
      prop.decorations= value;
      prop.functions = funcs;
      prop.inputMode = input;
      prop.flags =
	MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS | MWM_HINTS_INPUT_MODE;

      /* HOP - LA! */
      XChangeProperty (dpy,win,
		       MwmAtom, MwmAtom,
		       32, PropModeReplace,
		       (unsigned char *)&prop,
		       PROP_MWM_HINTS_ELEMENTS);
    }
}

/***********************************************************************
 WarpTaskBar -- Enforce AutoStick feature
 ***********************************************************************/
static void WarpTaskBar(int y)
{
  /* The tests on y are really useful ! */
  if (!AutoHide &&
      ((y != screen_g.y + screen_g.height - win_height - win_border &&
	y !=  screen_g.y + win_border))) {
    if (y > Midline)
    {
      win_y = screen_g.height - win_height - win_border;
    }
    else
    {
      win_y = win_border + win_title_height;
    }
    win_y += screen_g.y;
    XSync(dpy, 0);
    XMoveWindow(dpy, win, win_x, win_y);
    XSync(dpy, 0);
  }

  if (AutoHide)
     SetAlarm(HIDE_TASK_BAR);
  ClearAlarm(STICK_TASK_BAR);
}

/***********************************************************************
 SleepALittle -- do a small delay in a fairly portable way
 Replace "#if 1" with "#if 0" to turn delay off
 ***********************************************************************/
static void SleepALittle(void)
{
  struct timeval tv;

    tv.tv_sec  = 0;
    tv.tv_usec = 10000;
#if 1
    XSync(dpy, 0);
    select(0, NULL, NULL, NULL, &tv);
    XSync(dpy, 0);
#endif
}

/***********************************************************************
 RevealTaskBar -- Make taskbar fully visible
 ***********************************************************************/
void RevealTaskBar()
{
  int new_win_y;
  int inc_y = 2;

  ClearAlarm(HIDE_TASK_BAR);
   /* do not reveal if the taskbar is already revealed */
  if (WindowState >= 0)
    return;

  /* Go faster with the number of rows */
  inc_y += (NRows >= 3 ? 3 : 0) + (NRows >= 5 ? 3 : 0);

  if (win_y < Midline)
  {
    new_win_y = screen_g.y + win_border + win_title_height;
    for (; win_y<=new_win_y; win_y +=inc_y)
    {
      XMoveWindow(dpy, win, win_x, win_y);
      SleepALittle();
    }
  }
  else
  {
    new_win_y = screen_g.y + screen_g.height - win_height - win_border;
    for (; win_y>=new_win_y; win_y -=inc_y)
    {
      XMoveWindow(dpy, win, win_x, win_y);
      SleepALittle();
    }
  }
  win_y = new_win_y;
  XMoveWindow(dpy, win, win_x, win_y);
  WindowState = 0;
}

/***********************************************************************
 HideTaskbar -- Make taskbar partially visible
 ***********************************************************************/
void HideTaskBar()
{
  int new_win_y;
  int inc_y = 1;
  Window d_rt, d_ch;
  int d_x, d_y, wx, wy;
  unsigned int mask;

  ClearAlarm(HIDE_TASK_BAR);
  /* do not hide if the taskbar is already hiden */
  if (WindowState == -1 || win_is_shaded)
    return;

  if (FocusInWin)
  {
    XQueryPointer(dpy, win, &d_rt,&d_ch, &d_x, &d_y,
		  &wx, &wy, &mask);
    if (wy >= -(win_border + win_title_height*(1-win_has_bottom_title)) &&
	wy < win_height + win_border +
	win_title_height*win_has_bottom_title)
    {
      if (wy < 0 || wy >= win_height || wx < 0 || wx >= win_width)
	SetAlarm(HIDE_TASK_BAR);
      return;
    }
  }

  /* Go faster with the number of rows */
  inc_y += (NRows >= 3 ? 2 : 0) + (NRows >= 5 ? 2 : 0);

  if (win_y < Midline)
  {
    new_win_y = screen_g.y + VISIBLE_PIXELS() +
      win_title_height*win_has_bottom_title - win_height;
    for (; win_y>=new_win_y; win_y -=inc_y)
    {
      XMoveWindow(dpy, win, win_x, win_y);
      SleepALittle();
    }
  }
  else
  {
    new_win_y = screen_g.y + screen_g.height - VISIBLE_PIXELS() +
      win_title_height*win_has_bottom_title;
    for (; win_y<=new_win_y; win_y +=inc_y)
    {
      XMoveWindow(dpy, win, win_x, win_y);
      SleepALittle();
    }
  }
  win_y = new_win_y;
  XMoveWindow(dpy, win, win_x, win_y);
  WindowState = -1;
}

/***********************************************************************
 SetAlarm -- Schedule a timeout event
 ************************************************************************/
static void
SetAlarm(tb_alarm_t event)
{
  alarm(0);  /* remove a race-condition */
  AlarmSet |= event;
  alarm(1);
}

/***********************************************************************
 ClearAlarm -- Disable timeout events
 ************************************************************************/
static void
ClearAlarm(tb_alarm_t event)
{
  AlarmSet &= ~event;
  if (AlarmSet == 0)
  {
    alarm(0);
  }
}

/************************************************************************
  X Error Handler
************************************************************************/
static int
ErrorHandler(Display *d, XErrorEvent *event)
{
  /* some errors are OK=ish */
  if (event->error_code == BadPixmap)
    return 0;
  if (event->error_code == BadDrawable)
    return 0;

  PrintXErrorAndCoredump(d, event, Module);
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1