/*
 * Nickleby Copyright (c) 2007, James Bailie.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * The name of James Bailie may not be used to endorse or promote
 * products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

Display *dpy;
Screen screen;
Window root, position;
GC gc1, gc2;
XFontStruct *font;

int tw, cw, sw, ew, workspace = 1, bw;

Atom delete, protocols, state, focus;

void update_titlebars( XEvent *, int );

char *font_names[ 5 ] =
{
   "-*-helvetica-medium-r-normal-*-*-80-*-*-*-iso8859-1",
   "-*-lucida-medium-r-normal-*-*-80-*-*-*-iso8859-1",
   "-*-clean-medium-r-normal-*-*-80-*-*-*-iso8859-1",
   "-*-fixed-medium-r-normal-*-*-80-*-*-*-iso8859-1",
   NULL
};

int font_height, position_onscreen = 0;
unsigned long white, black, gray;

XButtonEvent start;
XWindowAttributes move_resize_attr, root_attr;
XSizeHints hints;

struct client
{
   Window win, client, title, footer, button,
      t_close, t_shade, t_expand,
      f_close, f_shade, f_expand;

   XSizeHints hints;

   XWindowAttributes attr;

   int workspace, hidden, max, focus, delete, focussed, ox, oy, ow, oh,
      reparenting, shaded, recursive;

   struct client *next;
} *clients = NULL;

struct client *client = NULL;

void *memory( unsigned int size )
{
   void *ptr;

   if ( size == 0 )
      return NULL;

   if (( ptr = malloc( size )) == NULL )
   {
      perror( "malloc" );
      exit( 1 );
   }

   return ptr;
}

struct client *find_client( Window win )
{
   struct client *ptr;

   for( ptr = clients; ptr != NULL; ptr = ptr->next )
      if ( ptr->win == win )
         break;

   return ptr;
}

struct client *find_client_window( Window win )
{
   struct client *ptr;

   for( ptr = clients; ptr != NULL; ptr = ptr->next )
      if ( ptr->client == win )
         break;

   return ptr;
}

void send_message_to_client( Window win, Atom msg )
{
   XEvent ev;

   bzero( &ev, sizeof( ev ));

   ev.type = ClientMessage;
   ev.xclient.type = ClientMessage;
   ev.xclient.window = win;
   ev.xclient.message_type = protocols;

   ev.xclient.data.l[ 0 ] = msg;
   ev.xclient.data.l[ 1 ] = CurrentTime;
   ev.xclient.format = 32;

   XSendEvent( dpy, win, False, 0, &ev  );
   XFlush( dpy );
}

void draw_name( struct client *ptr )
{
   char *name;
   int in_focus, need_free = 1, len, width, height;

   if ( ptr == NULL )
      return;

   XFetchName( dpy, ptr->client, &name );
   in_focus = ptr->focussed;

   if ( name == NULL )
   {
      need_free = 0;
      name = "(untitled)";
   }

   len = strlen( name );

   if ( need_free )
      XFree( name );

   XClearWindow( dpy, ptr->title );

   XDrawString( dpy, ptr->t_shade, ( in_focus ? gc1 : gc2 ),
                0, font->ascent, "_", 1 );

   XDrawString( dpy, ptr->t_expand, ( in_focus ? gc1 : gc2 ),
                0, font->ascent, "[]", 2 );

   XDrawString( dpy, ptr->t_close, ( in_focus ? gc1 : gc2 ),
                0, font->ascent, "||", 2 );

   XDrawString( dpy, ptr->title, ( in_focus ? gc1 : gc2 ),
                8, font->ascent + 5, name, len );

   height = font_height + 10;
   width = ( ptr->attr.width + ptr->attr.border_width * 2 );

   if ( ptr->hidden )
      XDrawRectangle( dpy, ptr->title, gc2, 1, 1, width - 3, height - 3 );
   else if ( client == NULL )
      XDrawRectangle( dpy, ptr->title, ( in_focus ? gc1 : gc2 ),
                      1, 1, width - 3, height - 2 );
   else 
      XDrawRectangle( dpy, ptr->title, ( in_focus ? gc1 : gc2 ), 
                      1, 1, width - 3, height - ( in_focus ? 3 : 2 ));

   XClearWindow( dpy, ptr->footer );

   XDrawString( dpy, ptr->f_shade, ( in_focus ? gc1 : gc2 ),
                0, font->ascent - 1, "_", 1 );

   XDrawString( dpy, ptr->f_expand, ( in_focus ? gc1 : gc2 ),
                0, font->ascent - 1, "[]", 2 );

   XDrawString( dpy, ptr->f_close, ( in_focus ? gc1 : gc2 ),
                0, font->ascent - 1, "||", 2 );

   XDrawString( dpy, ptr->footer, ( in_focus ? gc1 : gc2 ),
                8, font->ascent + 4, name, len );

   if ( ptr->hidden )
      XDrawRectangle( dpy, ptr->footer, gc2, 1, 1, width - 3, height - 3 );
   else if ( client == NULL )
      XDrawRectangle( dpy, ptr->footer, ( in_focus ? gc1 : gc2 ),
                      1, 0, width - 3, height - 2 );
   else
      XDrawRectangle( dpy, ptr->footer, ( in_focus ? gc1 : gc2 ),
                      1, 1, width - 3, height - ( in_focus ? 3 : 2 ));
}

void focus_raise( struct client *ptr, int raise )
{
   struct client *ptr2;

   if ( ptr->hidden )
      return;

   if ( raise )
   {
      XRaiseWindow( dpy, ptr->win );
      XRaiseWindow( dpy, ptr->title );
      XRaiseWindow( dpy, ptr->footer );
   }

   XSetInputFocus( dpy, ptr->client, RevertToNone, CurrentTime );
   ptr->focussed = 1;
   draw_name( ptr );

   if ( ptr->attr.colormap != None )
      XInstallColormap( dpy, ptr->attr.colormap );

   for( ptr2 = clients; ptr2 != NULL; ptr2 = ptr2->next )
      if ( ptr2 != ptr )
         if ( ptr2->focussed )
         {
            ptr2->focussed = 0;
            draw_name( ptr2 );
         }

   if ( ptr->focus )
      send_message_to_client( ptr->client, focus );
}

void constrain_aspect_ratio( int *w, int *h )
{
   int t, min_width, max_width, min_height, max_height, width_inc, height_inc;

   width_inc = height_inc = 1;

   if ( hints.flags & PResizeInc )
   {
      width_inc = hints.width_inc;
      height_inc = hints.height_inc;
   }

   min_width = min_height = 1;

   max_width = root_attr.width - 
      ( move_resize_attr.border_width * 2 + move_resize_attr.x );

   max_height = root_attr.height -
      (( font_height + 10 ) * 1 + move_resize_attr.border_width * 2 +
        move_resize_attr.y );

   if ( hints.flags & PMinSize )
   {
      if ( hints.min_width > 0 && hints.min_width <= max_width )
         min_width = hints.min_width;

      if ( hints.min_height > 0 && hints.min_height <= max_height )
         min_height = hints.min_height;
   }

   if ( hints.flags & PMaxSize )
   {
      if ( hints.max_width <= max_width && hints.max_width > 0 )
         max_width = hints.max_width;

      if ( hints.max_height <= max_height && hints.max_height > 0 )
         max_height = hints.max_height;
   }

   if ( hints.min_aspect.x * *h > hints.min_aspect.y * *w )
   {
      t = hints.min_aspect.x * *h / hints.min_aspect.y - *w;
      t = t / width_inc * width_inc;

      if ( *w + t <= max_width )
         *w += t;
      else
      {
         t = *h - *w * hints.min_aspect.y / hints.min_aspect.x;
         t = t / height_inc * height_inc;

         if ( *h - t >= min_height )
            *h -= t;
      }
   }

   if ( hints.max_aspect.x * *h < hints.max_aspect.y * *w )
   {
      t = *w * hints.max_aspect.y / hints.max_aspect. x - *h;
      t = t / height_inc * height_inc;

      if ( *h + t <= max_height )
         *h += t;
      else
      {
         t = *w - hints.max_aspect.x * *h / hints.max_aspect.y;
         t = t / width_inc * width_inc;

         if ( *w - t >= min_width )
            *w -= t;
      }
   }
}

void constrain_by_hints( int *w, int *h )
{
   if ( hints.flags & PMinSize )
   {
      if ( *w < hints.min_width && hints.min_width <= root_attr.width )
         *w = hints.min_width;

      if ( *h < hints.min_height && hints.min_height <= root_attr.height )
         *h = hints.min_height;
   }

   if ( hints.flags & PMaxSize )
   {
      if ( *w > hints.max_width )
         *w = hints.max_width;

      if ( *h > hints.max_height )
         *h = hints.max_height;
   }

   if ( hints.flags & PBaseSize && hints.flags & PResizeInc )
   {
      *w = ( *w - hints.base_width ) /
           hints.width_inc * hints.width_inc + hints.base_width;

      *h = ( *h - hints.base_height ) /
           hints.height_inc * hints.height_inc + hints.base_height;
   }

   if ( hints.flags & PAspect )
      constrain_aspect_ratio( w, h );
}

struct client *find_client_by_title_or_footer( Window win )
{
   struct client *ptr;

   for( ptr = clients; ptr != NULL; ptr = ptr->next )
      if ( ptr->title == win || ptr->footer == win )
         break;

   return ptr;
}

struct client *find_client_by_button( Window win )
{
   struct client *ptr;

   for( ptr = clients; ptr != NULL; ptr = ptr->next )
      if ( ptr->f_shade == win  ||
           ptr->f_expand == win ||
           ptr->f_close == win  ||
           ptr->t_shade == win  ||
           ptr->t_expand == win ||
           ptr->t_close == win )
      {
         ptr->button = win;
         break;
      }

   return ptr;
}

void show_geometry( int x, int y, int w, int h, int bw )
{
   char buffer[ 64 ];
   int len, t;

   w -= bw * 2;
   h -= bw * 2;
   x += bw;
   y += bw;

   snprintf( buffer, sizeof( buffer ), "+%d+%d, %dx%d", x, y,
             ( hints.flags & PResizeInc ? w / hints.width_inc : w ),
             ( hints.flags & PResizeInc ? h / hints.height_inc : h ));

   len = strlen( buffer );
   x += ( w - ( t = 10 + XTextWidth( font, buffer, len ))) / 2;
   y += ( h - ( font_height + 10 )) / 2;
   h = font_height + 10;
   w = t;

   if ( ! position_onscreen )
   {
      XMapRaised( dpy, position );
      position_onscreen = 1;
   }

   XMoveResizeWindow( dpy, position, x, y, w, h );
   XClearWindow( dpy, position );
   XDrawRectangle( dpy, position, gc1, 1, 1, w - 3, h - 3 );
   XDrawString( dpy, position, gc1, 5, font->ascent + 5, buffer, len );
}

int confine_to_root( int *x, int *y, int *w, int *h, int bw, int resizing )
{
   int tb, ret;

   ret = 0;
   tb = font_height + 10;

   if (( *x + *w + bw ) > root_attr.width )
   {
      ++ret;

      if ( resizing )
         *w = root_attr.width - ( *x + bw );
      else
         *x = root_attr.width - ( *w + bw );
   }

   if ( *y < tb )
   {
      *y = tb;
      ++ret;
   }

   if (( *y + *h + bw + tb ) > root_attr.height )
   {
      ++ret;

      if ( resizing )
         *h = root_attr.height - ( *y + bw + tb );
      else
         *y = root_attr.height - ( *h + bw + tb );
   }

   return ret;
}

void move_resize_window( XEvent *ev )
{
   int xdiff, ydiff, x, y, w, h;

   /*
    * Occasionally we receive a MotionNotify event after we have
    * released the pointer grab, in which case we must avoid
    * dereferencing a NULL client pointer.  The ButtonRelease event
    * which caused us to relinquish the grab, will have reset client
    * to NULL.
    */

   if ( client == NULL )
      return;

   xdiff = ev->xmotion.x_root - start.x_root;
   ydiff = ev->xmotion.y_root - start.y_root;

   x = move_resize_attr.x + ( start.button < 3 ? xdiff : 0 );
   y = move_resize_attr.y + ( start.button < 3 ? ydiff : 0 );
   w = move_resize_attr.width + ( start.button == 3 ? xdiff : 0 );
   h = move_resize_attr.height + ( start.button == 3 ? ydiff : 0 );

   confine_to_root( &x, &y, &w, &h, move_resize_attr.border_width * 2,
                    ( start.button == 3 ));

   if ( start.button == 3 )
   {
      if (( w -= bw * 2 ) <= 0 )
         w = 1;

      if (( h -= bw * 2 ) <= 0 )
         h = 1;

      hints = client->hints;
      constrain_by_hints( &w, &h );

      w = client->attr.width = w + bw * 2;
      h = client->attr.height = h + bw * 2;
   }
   else
   {
   
      if ( x < 0 )
         x = 0;
      
      if ( y < 0 )
         y = 0;
   
      client->attr.x = x;
      client->attr.y = y;
   }

   update_titlebars( ev, 0 );
   show_geometry( x, y, w, h, move_resize_attr.border_width );
}

void make_buttons( struct client *ptr, int width )
{
   Window close, shade, expand;

   if (( shade = XCreateSimpleWindow( dpy, ptr->title,
                                      width - ( sw + ew + cw + 5 ), 5,
                                      sw, font_height,
                                      0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create shade button window", stderr );
      exit( 1 );
   }

   if (( expand = XCreateSimpleWindow( dpy, ptr->title,
                                       width - ( ew + cw + 5 ), 5,
                                       ew, font_height,
                                       0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create expand button window", stderr );
      exit( 1 );
   }

   if (( close = XCreateSimpleWindow( dpy, ptr->title,
                                      width - ( cw + 5 ),
                                      5, cw, font_height,
                                      0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create close button window", stderr );
      exit( 1 );
   }

   ptr->t_shade = shade;
   ptr->t_expand = expand;
   ptr->t_close = close;

   if (( shade = XCreateSimpleWindow( dpy, ptr->footer,
                                      width - ( sw + ew + cw + 5 ), 5,
                                      sw, font_height,
                                      0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create shade button window", stderr );
      exit( 1 );
   }

   if (( expand = XCreateSimpleWindow( dpy, ptr->footer,
                                       width - ( ew + cw + 5 ), 5,
                                       ew, font_height,
                                       0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create expand button window", stderr );
      exit( 1 );
   }

   if (( close = XCreateSimpleWindow( dpy, ptr->footer,
                                      width - ( cw + 5 ),
                                      5, cw, font_height,
                                      0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create close button window", stderr );
      exit( 1 );
   }

   ptr->f_shade = shade;
   ptr->f_expand = expand;
   ptr->f_close = close;

   XSelectInput( dpy, ptr->t_shade, ButtonPressMask );
   XSelectInput( dpy, ptr->t_expand, ButtonPressMask );
   XSelectInput( dpy, ptr->t_close, ButtonPressMask );

   XSelectInput( dpy, ptr->f_shade, ButtonPressMask );
   XSelectInput( dpy, ptr->f_expand, ButtonPressMask );
   XSelectInput( dpy, ptr->f_close, ButtonPressMask );

   XMapWindow( dpy, ptr->t_shade );
   XMapWindow( dpy, ptr->t_expand );
   XMapWindow( dpy, ptr->t_close );

   XMapWindow( dpy, ptr->f_shade );
   XMapWindow( dpy, ptr->f_expand );
   XMapWindow( dpy, ptr->f_close );
}

void make_title_footer( struct client *ptr )
{
   Window title, footer;
   XSetWindowAttributes wa;
   XWindowChanges wc;

   if (( title = XCreateSimpleWindow( dpy, root, 0, 0, 50,
                                      font_height,
                                      0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create title window", stderr );
      exit( 1 );
   }

   wa.override_redirect = True;
   XChangeWindowAttributes( dpy, title, CWOverrideRedirect, &wa );
   XSelectInput( dpy, title, ExposureMask | ButtonPressMask |
                             EnterWindowMask );

   wc.x = ptr->attr.x;
   wc.y = ptr->attr.y - ( font_height + 10 );
   wc.width = ptr->attr.width + ptr->attr.border_width * 2;
   wc.height = font_height + 10;

   ptr->title = title;
   XConfigureWindow( dpy, title, CWX | CWY | CWWidth | CWHeight, &wc );

   if (( footer = XCreateSimpleWindow( dpy, root, 0, 0, 50,
                                       font_height, 0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create footer window", stderr );
      exit( 1 );
   }

   wa.override_redirect = True;
   XChangeWindowAttributes( dpy, footer, CWOverrideRedirect, &wa );
   XSelectInput( dpy, footer, ExposureMask | ButtonPressMask |
                              EnterWindowMask );

   wc.x = ptr->attr.x;
   wc.y = ptr->attr.y + ptr->attr.height + ptr->attr.border_width * 2;

   ptr->footer = footer;
   XConfigureWindow( dpy, footer, CWX | CWY | CWWidth | CWHeight, &wc );

   XMapRaised( dpy, title );
   XMapRaised( dpy, footer );

   make_buttons( ptr, wc.width );
   draw_name( ptr );
}

void shade_unshade_window( struct client *ptr, int title )
{
   if ( ! ptr->hidden )
   {
      XAddToSaveSet( dpy, ptr->win );
      XUnmapWindow( dpy, ptr->win );
      XUnmapWindow( dpy, ( title ? ptr->footer : ptr->title ));

      ptr->shaded = ( title ? 1 : 2 );
      ptr->hidden = 1;
      ptr->focussed = 0;
      draw_name( ptr );
   }
   else
   {
      XRemoveFromSaveSet( dpy, ptr->win );
      XMapWindow( dpy, ptr->win );
      XMapWindow( dpy, ( title ? ptr->footer : ptr->title ));

      ptr->hidden = 0;
      ptr->shaded = 0;
      focus_raise( ptr, 1 );
   }
}

void add_client( XEvent *ev )
{
   Window win;
   XWindowAttributes attr;
   XWMHints *wmhints;
   struct client *ptr1, *ptr2, *ptr3;
   long int supplied;
   Atom *protos;
   int np, i, w, h;

   for( ptr1 = clients, ptr2 = NULL;
        ptr1 != NULL;
        ptr1 = ptr1->next )
   {
      if ( ptr1->client == ev->xmaprequest.window )
         return;

      ptr2 = ptr1;
   }

   XGetWindowAttributes( dpy, ev->xmaprequest.window, &attr );

   if ( attr.override_redirect == True )
   {
      XMapWindow( dpy, ev->xmaprequest.window );
      return;
   }

   if ( XGetTransientForHint( dpy, ev->xmaprequest.window, &win ))
   {
      for ( ptr3 = clients; ptr3 != NULL; ptr3 = ptr3->next )
         if ( ptr3->client == win )
            break;

      /* 
       * Don't manage transients of unmanaged windows.
       */

      if ( ptr3 == NULL )
      {
         XMapWindow( dpy, ev->xmaprequest.window );
         XSelectInput( dpy, ev->xmaprequest.window, EnterWindowMask );
         return;
      }
   }

   if ( ptr2 == NULL )
      ptr1 = clients = memory( sizeof( struct client ));
   else
      ptr1 = ptr2->next = memory( sizeof( struct client ));

   ptr1->attr = attr;
   XGetWMNormalHints( dpy, ev->xmaprequest.window, &ptr1->hints, &supplied );

   np = 0;
   XGetWMProtocols( dpy, ev->xmaprequest.window, &protos, &np );

   ptr1->focussed = 0;
   ptr1->shaded = 0;
   ptr1->focus = 0;
   ptr1->recursive = 0;

   ptr1->workspace = workspace;
   ptr1->delete = 0;
   ptr1->button = None;

   i = np;
   while( i-- )
   {
      if ( protos[ i ] == focus )
         ptr1->focus = 1;
      else if ( protos[ i ] == delete )
         ptr1->delete = 1;
   }

   if ( np )
      XFree( protos );

   ptr1->client = ev->xmaprequest.window;
   ptr1->next = NULL;

   ptr1->max = ptr1->hidden = 0;
   ptr1->ox = ptr1->oy = ptr1->oh = ptr1->ow = -1;
   ptr1->attr.x -= 1; ptr1->attr.y -= 1;

   if ( ptr1->attr.x < 0 )
      ptr1->attr.x = 0;

   if ( ptr1->attr.y < 0 )
      ptr1->attr.y = 0;
   
   ptr1->attr.width += ptr1->attr.border_width * 2;
   ptr1->attr.height += ptr1->attr.border_width * 2;

   if (( ptr1->win = XCreateSimpleWindow( dpy, root,
                                          ptr1->attr.x,
                                          ptr1->attr.y,
                                          ptr1->attr.width,
                                          ptr1->attr.height,
                                          1, black, black ))
       == None )
   {
      fputs( "nickleby: could not create window frame", stderr );
      exit( 1 );
   }

   /*
    * We need ButtonPress events even though we don't use them.  They
    * prevent clicks on ptr->win border from going through to the root
    * window.
    */

   XSelectInput( dpy, ptr1->win, SubstructureNotifyMask | EnterWindowMask |
                                 ButtonPressMask );
   XSelectInput( dpy, ptr1->client, ColormapChangeMask | PropertyChangeMask |
                                    EnterWindowMask );

   XReparentWindow( dpy, ptr1->client, ptr1->win, 0, 0 );
   XGetWindowAttributes( dpy, root, &root_attr );

   if ( confine_to_root( &ptr1->attr.x, &ptr1->attr.y,
                         &ptr1->attr.width, &ptr1->attr.height,
                         2, 1 ))
   {
      h = ptr1->attr.height - ptr1->attr.border_width * 2;
      w = ptr1->attr.width - ptr1->attr.border_width * 2;
      hints = ptr1->hints;
      constrain_by_hints( &w, &h );

      ptr1->attr.width = w + ptr1->attr.border_width * 2;
      ptr1->attr.height = h + ptr1->attr.border_width * 2;

      XMoveResizeWindow( dpy, ptr1->win, ptr1->attr.x, ptr1->attr.y,
                         ptr1->attr.width, ptr1->attr.height );
      XMoveResizeWindow( dpy, ptr1->client, 0, 0, w, h );
   }

   ptr1->attr.border_width = 1;
   ptr1->reparenting = 1;

   XMapWindow( dpy, ptr1->client );
   XMapWindow( dpy, ptr1->win );
   make_title_footer( ptr1 );

   wmhints = XGetWMHints( dpy, ptr1->win );

   if ( wmhints != NULL && wmhints->initial_state == IconicState )
      shade_unshade_window( ptr1, 1 );
   else
      focus_raise( ptr1, 1 );

   if ( wmhints != NULL )
      XFree( wmhints );
}

void remove_client( Window win, int reparent )
{
   struct client *ptr1, *ptr2;

   for( ptr1 = clients, ptr2 = NULL;
        ptr1 != NULL;
        ptr1 = ptr1->next )
   {
      if ( ptr1->client == win )
         break;

      ptr2 = ptr1;
   }

   if ( ptr1 == NULL )
      return;

   if ( ptr2 == NULL )
      clients = ptr1->next;
   else
      ptr2->next = ptr1->next;

   if ( reparent )
   {
      XReparentWindow( dpy, ptr1->client, root, ptr1->attr.x, ptr1->attr.y );

      if ( reparent == 2 && XGetTransientForHint( dpy, ptr1->client, &win ))
         XSelectInput( dpy, ptr1->client, EnterWindowMask );
   }
   
   if ( position_onscreen )
   {
      position_onscreen = 0;
      XUnmapWindow( dpy, position );
   }

   XUnmapWindow( dpy, ptr1->win );
   XUnmapWindow( dpy, ptr1->title );
   XUnmapWindow( dpy, ptr1->footer );

   XDestroySubwindows( dpy, ptr1->title );
   XDestroySubwindows( dpy, ptr1->footer );

   XDestroyWindow( dpy, ptr1->win );
   XDestroyWindow( dpy, ptr1->title );
   XDestroyWindow( dpy, ptr1->footer );

   free( ptr1 );
}

void install_colormap( XEvent *ev )
{
   struct client *ptr;

   if (( ptr = find_client_window( ev->xcolormap.window )) == NULL )
      return;

   if ( ev->xcolormap.new == True )
   {
      ptr->attr.colormap = ev->xcolormap.colormap;
      XInstallColormap( dpy, ev->xcolormap.colormap );
   }
}

void update_config( struct client *ptr )
{
   int w, h;
   XWindowAttributes wa;

   XGetWindowAttributes( dpy, ptr->client, &wa );

   ptr->attr.width = wa.width + wa.border_width * 2;
   ptr->attr.height = wa.height + wa.border_width * 2;

   confine_to_root( &ptr->attr.x, &ptr->attr.y,
                    &ptr->attr.width, &ptr->attr.height,
                    ptr->attr.border_width * 2, 0 );

   XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
                      ptr->attr.width, ptr->attr.height );

   w = ptr->attr.width - wa.border_width * 2;
   h = ptr->attr.height - wa.border_width * 2;

   XMoveResizeWindow( dpy, ptr->client, 0, 0, w, h );
}

void update_titlebars( XEvent *ev, int event )
{
   int title_width;
   struct client *ptr;

   if ( event )
   {
      if (( ptr = find_client_window( ev->xconfigure.window )) == NULL )
         return;

      update_config( ptr );
   }
   else
      ptr = client;

   title_width = ptr->attr.width + ptr->attr.border_width * 2;

   XMoveResizeWindow( dpy, ptr->title,
                      ptr->attr.x,
                      ptr->attr.y - ( font_height + 10 ),
                      title_width,
                      font_height + 10 );

   XMoveWindow( dpy, ptr->t_shade, title_width - tw, 5 );
   XMoveWindow( dpy, ptr->t_expand, title_width - ( ew + cw + 5 ), 5 );
   XMoveWindow( dpy, ptr->t_close, title_width - ( cw + 5 ), 5 );

   XMoveResizeWindow( dpy, ptr->footer,
                      ptr->attr.x,
                      ptr->attr.y + ptr->attr.height +
                        ptr->attr.border_width * 2,
                      ptr->attr.width + ptr->attr.border_width * 2,
                      font_height + 10 );

   XMoveWindow( dpy, ptr->f_shade, title_width - tw, 5 );
   XMoveWindow( dpy, ptr->f_expand, title_width - ( ew + cw + 5 ), 5 );
   XMoveWindow( dpy, ptr->f_close, title_width - ( cw + 5 ), 5 );

   draw_name( ptr );
}

void start_move_resize( struct client *ptr, XEvent *ev )
{
   if ( XGrabPointer( dpy, ptr->win, True,
                           PointerMotionMask | ButtonReleaseMask,
                           GrabModeAsync,
                           GrabModeAsync, None, None, CurrentTime )
        == GrabSuccess )
   {
      client = ptr;
      hints = ptr->hints;
      start = ev->xbutton;

      XGetWindowAttributes( dpy, client->client, &move_resize_attr );
      bw = move_resize_attr.border_width;
      move_resize_attr = ptr->attr;

      XGetWindowAttributes( dpy, root, &root_attr );

      if ( ev->xbutton.button == 3 )
      {
         start.x_root = ptr->attr.x + ptr->attr.width;
         start.y_root = ptr->attr.y + ptr->attr.height;

         /*
          * Refuse to resize windows which are partially offscreen
          * because we can't warp the pointer offscreen.  This should
          * never happen.
          */

         if ( start.x_root > root_attr.width ||
              start.y_root > root_attr.height )
         {
            XUngrabPointer( dpy, CurrentTime );
            return;
         }

         XWarpPointer( dpy, None, ptr->win, 0, 0, 0, 0,
                       ptr->attr.width,
                       ptr->attr.height );

         focus_raise( ptr, 1 );
      }

      show_geometry( ptr->attr.x, ptr->attr.y,
                     ptr->attr.width, ptr->attr.height,
                     ptr->attr.border_width );
   }
}

void maximize( struct client *ptr )
{
   int h, w;
   XWindowAttributes wa;

   focus_raise( ptr, 1 );

   if ( ptr->max )
   {
      ptr->attr.x = ptr->ox;
      ptr->attr.y = ptr->oy;
      ptr->attr.width = ptr->ow;
      ptr->attr.height = ptr->oh;

      XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
                         ptr->attr.width, ptr->attr.height );

      XGetWindowAttributes( dpy, ptr->client, &wa );
      XResizeWindow( dpy, ptr->client, ptr->attr.width - wa.border_width * 2,
                     ptr->attr.height - wa.border_width * 2 );

      ptr->max = 0;
      return;
   }

   XGetWindowAttributes( dpy, root, &root_attr );

   ptr->ox = ptr->attr.x;
   ptr->oy = ptr->attr.y;
   ptr->ow = ptr->attr.width;
   ptr->oh = ptr->attr.height;
   ptr->max = 1;

   ptr->attr.x = 0;
   ptr->attr.y = font_height + 10;
   ptr->attr.width = root_attr.width - ptr->attr.border_width * 2;
   ptr->attr.height = root_attr.height -
      ( ptr->attr.border_width * 2  + ( font_height + 10 ) * 2 );

   XGetWindowAttributes( dpy, ptr->client, &wa );
   w = ptr->attr.width - wa.border_width * 2;
   h = ptr->attr.height - wa.border_width * 2;

   hints = ptr->hints;
   constrain_by_hints( &w, &h );

   ptr->attr.width = w + wa.border_width * 2;
   ptr->attr.height = h + wa.border_width * 2;

   XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
                      ptr->attr.width, ptr->attr.height );
   XResizeWindow( dpy, ptr->client, w, h );
}

void process_button( XEvent *ev, struct client *ptr )
{
   focus_raise( ptr, 1 );

   switch( ev->xbutton.state )
   {
      case ControlMask:
      case ShiftMask:
         break;

      case None:
         switch( ev->xbutton.button )
         {
            case 1:
               if ( ptr->button == ptr->t_shade )
               {
                  shade_unshade_window( ptr, 1 );
                  return;
               }
               else if ( ptr->button == ptr->f_shade )
               {
                  shade_unshade_window( ptr, 0 );
                  return;
               }

               if ( ! ptr->hidden )
               {
                  if ( ptr->button == ptr->t_shade )
                     shade_unshade_window( ptr, 1 );
                  else if ( ptr->button == ptr->f_shade )
                     shade_unshade_window( ptr, 0 );
                  else if ( ptr->button == ptr->t_expand ||
                            ptr->button == ptr->f_expand )
                     maximize( ptr );
                  else if ( ptr->button == ptr->t_close ||
                            ptr->button == ptr->f_close )
                  {
                     if ( ptr->delete )
                        send_message_to_client( ptr->client, delete );
                     else
                        XKillClient( dpy, ptr->client );
                  }
               }
               break;

            case 2:
               break;

            case 3:
               if ( ! ptr->hidden )
               {
                  if ( ptr->button == ptr->t_shade ||
                       ptr->button == ptr->f_shade )
                     remove_client( ptr->client, 1 );
                  else if ( ptr->button == ptr->t_expand ||
                            ptr->button == ptr->f_expand )
                     ptr->workspace = ( ptr->workspace ? 0 : workspace );
                  else if ( ptr->button == ptr->t_close ||
                            ptr->button == ptr->f_close )
                     XKillClient( dpy, ptr->client );
               }
               break;
         }
   }
}

void process_title_footer( XEvent *ev )
{
   struct client *ptr;

   if (( ptr = find_client_by_title_or_footer( ev->xbutton.window )) == NULL )
   {
      if (( ptr = find_client_by_button( ev->xbutton.window )) != NULL )
         process_button( ev, ptr );

      return;
   }

   focus_raise( ptr, 1 );

   switch ( ev->xbutton.state )
   {
      case ShiftMask:
      case ControlMask:
         break;

      case None:
         switch( ev->xbutton.button )
         {
            case 2:
               XLowerWindow( dpy, ptr->win );
               XLowerWindow( dpy, ptr->title );
               XLowerWindow( dpy, ptr->footer );
               break;

            case 1:
            case 3:
               start_move_resize( ptr, ev );
               break;
         }
   }
}

void circulate_clients()
{
   struct client *ptr, *ptr2;

   if ( clients == NULL || clients->next == NULL )
      return;

   for( ptr = clients; ptr != NULL; ptr = ptr->next )
      if ( ptr->focussed )
         break;

   if ( ptr == NULL )
   {
      for ( ptr = clients; ptr != NULL; ptr = ptr->next )
         if ( ! ptr->hidden )
            break;

      if ( ptr != NULL )
         focus_raise( ptr, 1 );
   }
   else
   {
      ptr2 = ptr;

      for( ptr = ptr->next; ptr2 != ptr; ptr = ptr->next )
      {
         if ( ptr == NULL )
            ptr = clients;

         if ( ! ptr->hidden )
            break;
      }

      if ( ptr != ptr2 )
         focus_raise( ptr, 1 );
   }
}

void change_workspace()
{
   struct client *ptr;

   workspace = ( workspace == 1 ? 2 : 1 );

   for( ptr = clients; ptr != NULL; ptr = ptr->next )
      if ( ptr->workspace )
      {
         if ( ptr->workspace != workspace )
         {
            XUnmapWindow( dpy, ptr->win );
            XUnmapWindow( dpy, ptr->title );
            XUnmapWindow( dpy, ptr->footer );

            XAddToSaveSet( dpy, ptr->win );
            ptr->hidden = 1;
         }
         else
         {
            if ( ptr->focussed )
            {
               ptr->focussed = 0;
               draw_name( ptr );
            }

            switch( ptr->shaded )
            {
               case 0:
                  XMapWindow( dpy, ptr->win );
                  XMapWindow( dpy, ptr->title );
                  XMapWindow( dpy, ptr->footer );

                  ptr->hidden = 0;
                  XRemoveFromSaveSet( dpy, ptr->win );
                  break;

               case 1:
                  XMapWindow( dpy, ptr->title );
                  break;

               case 2:
                  XMapWindow( dpy, ptr->footer );
                  break;
            }
         }
      }
}

void process_root( XEvent *ev )
{
   switch( ev->xbutton.button )
   {
      case 1:
         if ( ev->xbutton.state == ControlMask )
            exit( 0 );
         else if ( ev->xbutton.state == None )
            circulate_clients();
         break;

      case 2:
         if ( ev->xbutton.state == None )
            change_workspace();
         break;

      case 3:
         if ( ev->xbutton.state == None );
            switch( fork() )
            {
               case -1:
                  perror( "fork" );
                  break;

               case 0:
                  close( ConnectionNumber( dpy ));
                  execlp( "xterm", "xterm", 0 );
                  perror( "execlp" );
                  exit( 1 );
                  break;
            }
      }
}

void process_property( XEvent *ev )
{
   struct client *ptr;
   long int supplied;

   if (( ptr = find_client_window( ev->xproperty.window )) == NULL )
      return;

   switch( ev->xproperty.atom )
   {
      case XA_WM_NAME:
         draw_name( ptr );
         break;

      case XA_WM_NORMAL_HINTS:
         XGetWMNormalHints( dpy, ptr->client, &ptr->hints, &supplied );
         break;
   }
}

void process_message( XEvent *ev )
{
   struct client *ptr;

   if (( ptr = find_client( ev->xclient.window )) == NULL )
      return;

   if ( ev->xclient.message_type == state &&
        ev->xclient.format == 32 )
   {
      if (( ev->xclient.data.l[ 0 ] == IconicState && ! ptr->hidden ) ||
          ( ev->xclient.data.l[ 0 ] == NormalState && ptr->hidden ))
         shade_unshade_window( ptr, 1 );

      if ( ev->xclient.data.l[ 0 ] == WithdrawnState )
         remove_client( ptr->client, 1 );
   }
}

void configure_window( XEvent *ev )
{
   XWindowChanges wc;
   unsigned int value_mask = 0;

   if ( find_client( ev->xconfigurerequest.window ) != NULL )
      return;

   if ( CWX & ev->xconfigurerequest.value_mask )
   {
      wc.x = ev->xconfigurerequest.x;
      value_mask |= CWX;
   }

   if ( CWY & ev->xconfigurerequest.value_mask )
   {
      wc.y = ev->xconfigurerequest.y;
      value_mask |= CWY;
   }

   if ( CWWidth & ev->xconfigurerequest.value_mask )
   {
      wc.width = ev->xconfigurerequest.width;
      value_mask |= CWWidth;
   }

   if ( CWHeight & ev->xconfigurerequest.value_mask )
   {
      wc.height = ev->xconfigurerequest.height;
      value_mask |= CWHeight;
   }

   if ( CWBorderWidth & ev->xconfigurerequest.value_mask )
   {
      wc.border_width = ev->xconfigurerequest.border_width;
      value_mask |= CWBorderWidth;
   }

   if ( CWSibling & ev->xconfigurerequest.value_mask )
   {
      wc.sibling = ev->xconfigurerequest.above;
      value_mask |= CWSibling;
   }

   if ( CWStackMode & ev->xconfigurerequest.value_mask )
   {
      wc.stack_mode = ev->xconfigurerequest.detail;
      value_mask |= CWStackMode;
   }

   XConfigureWindow( dpy, ev->xconfigurerequest.window, value_mask, &wc );
}

void process_events()
{
   XEvent ev;
   struct client *ptr;

   for( ; ; )
   {
      XNextEvent( dpy, &ev );

      switch( ev.type )
      {
         case ClientMessage:
            process_message( &ev );
            break;

         case MappingNotify:
            XRefreshKeyboardMapping( &ev.xmapping );
            break;

         case EnterNotify:
            while( XCheckTypedEvent( dpy, EnterNotify, &ev ))
               ;

            if ( client != NULL || ev.xcrossing.mode != NotifyNormal )
               continue;

            if (( ptr = find_client_by_title_or_footer( ev.xcrossing.window ))
                != NULL ||
                ( ptr = find_client( ev.xcrossing.window )) != NULL ||
                ( ptr = find_client_window( ev.xcrossing.window )) != NULL )
               focus_raise( ptr, 0 );
            else
            {
               for ( ptr = clients; ptr != NULL; ptr = ptr->next )
               {
                  if ( ptr->focussed )
                  {
                     ptr->focussed = 0;
                     draw_name( ptr );
                  }
               }

               XSetInputFocus( dpy, ev.xcrossing.window, RevertToNone, 
                               CurrentTime );
               send_message_to_client( ev.xcrossing.window, focus );
            }
            break;

         case Expose:
            if ( ev.xexpose.count )
               continue;

            draw_name( find_client_by_title_or_footer( ev.xexpose.window ));
            break;

         case ColormapNotify:
            install_colormap( &ev );
            break;

         case MapRequest:
            add_client( &ev );
            break;

         case ConfigureRequest:
            configure_window( &ev );
            break;

         case UnmapNotify:
            if (( ptr = find_client_window( ev.xunmap.window )) != NULL )
            {
               if ( ptr->reparenting )
                  ptr->reparenting = 0;
               else
                  remove_client( ev.xunmap.window, 1 );
            }
            break;

         case DestroyNotify:
            remove_client( ev.xdestroywindow.window, 0 );
            break;

         case ConfigureNotify:
            update_titlebars( &ev, 1 );
            break;

         case PropertyNotify:
            process_property( &ev );
            break;

         case ButtonPress:
            if ( ev.xbutton.window == root )
               process_root( &ev );
            else
               process_title_footer( &ev );
            break;

         case MotionNotify:
            while( XCheckTypedEvent( dpy, MotionNotify, &ev ))
               ;
            move_resize_window( &ev );
            break;

         case ButtonRelease:
            XUngrabPointer( dpy, CurrentTime );
            XUnmapWindow( dpy, position );

            if ( ev.xbutton.button == 3 )
            {
               XWindowAttributes wa;

               XGetWindowAttributes( dpy, client->client, &wa );
               XResizeWindow( dpy, client->win, client->attr.width,
                              client->attr.height );
               XResizeWindow( dpy, client->client, 
                              client->attr.width - wa.border_width * 2,
                              client->attr.height - wa.border_width * 2 );
            }
            else
               XMoveWindow( dpy, client->win, client->attr.x, client->attr.y );

            ptr = client;
            client = NULL;
            draw_name( ptr );
            position_onscreen = 0;
      }
   }
}

void initialize_gc_font()
{
   XGCValues gcvalues;
   XColor color;
   unsigned long gcmask;
   char **ptr;

   black = BlackPixel( dpy, DefaultScreen( dpy ));
   white = WhitePixel( dpy, DefaultScreen( dpy ));

   for( ptr = font_names; *ptr; ++ptr )
      if (( font = XLoadQueryFont( dpy, *ptr ) ) != NULL )
         break;

   if ( font == NULL )
   {
      fputs( "nickleby: could not create font", stderr );
      exit( 1 );
   }

   font_height = font->ascent + font->descent;

   if ( ! XAllocNamedColor( dpy, DefaultColormap( dpy, DefaultScreen( dpy )),
                            "gray85", &color, &color ))
      gcvalues.foreground = white;
   else
      gcvalues.foreground = color.pixel;

   gcvalues.function = GXcopy;
   gcvalues.line_width = 1;
   gcvalues.line_style = LineSolid;
   gcvalues.cap_style = CapButt;
   gcvalues.fill_style = FillSolid;
   gcvalues.fill_rule = EvenOddRule;
   gcvalues.background = black;

   gcmask = GCFunction | GCBackground | GCForeground | GCLineWidth |
            GCLineStyle | GCFillStyle | GCFillRule;

   if ( ! ( gc1 = XCreateGC( dpy, root, gcmask, &gcvalues )))
   {
      fputs( "nickleby: could not create GC 1", stderr );
      exit( 1 );
   }

   if ( ! XAllocNamedColor( dpy, DefaultColormap( dpy, DefaultScreen( dpy )),
                            "gray60", &color, &color ))
      gc2 = gc1;
   else
   {
      gcvalues.foreground = color.pixel;

      if( ( ! ( gc2 = XCreateGC( dpy, root, gcmask, &gcvalues ))))
      {
         fputs( "nickleby: could not create GC 2", stderr );
         exit( 1 );
      }

      XSetFont( dpy, gc2, font->fid );
   }

   XSetFont( dpy, gc1, font->fid );

   sw = XTextWidth( font, "_   ", 4 ) + 2;
   ew = XTextWidth( font, "[]   ", 5 );
   cw = XTextWidth( font, "|| ", 3 );

   tw = cw + sw + ew + 5;
}

void manage_existing()
{
   Window r, p, *c;
   Atom *protos;
   int np, i, w, h;
   struct client *ptr;
   long int supplied;
   unsigned int nc;

   nc = 0;
   if ( ! XQueryTree( dpy, root, &r, &p, &c, &nc ))
      return;

   XGetWindowAttributes( dpy, root, &root_attr );

   ptr = NULL;

   while( nc-- )
   {
      XGetWindowAttributes( dpy, c[ nc ], &move_resize_attr );

      if ( move_resize_attr.override_redirect == True ||
           move_resize_attr.map_state == IsUnmapped )
         continue;
   
      if ( ptr == NULL )
         ptr = clients = memory( sizeof( struct client ));
      else
         ptr = ptr->next = memory( sizeof( struct client ));

      ptr->attr = move_resize_attr;
      XGetWMNormalHints( dpy, c[ nc ], &ptr->hints, &supplied );

      np = 0;
      XGetWMProtocols( dpy, c[ nc ], &protos, &np );
      ptr->focussed = 0;
      ptr->focus = 0;
      ptr->delete = 0;
      ptr->recursive = 0;
      ptr->workspace = workspace;

      i = np;
      while( i-- )
      {
         if ( protos[ i ] == focus )
            ptr->focus = 1;
         else if ( protos[ i ] == delete )
            ptr->delete = 1;
      }

      if ( np )
         XFree( protos );

      ptr->client = c[ nc ];
      ptr->next = NULL;
      ptr->hidden = 0;
      ptr->shaded = 0;
      ptr->max = 0;
      ptr->ox = ptr->oy = ptr->oh = ptr->ow = -1;

      if (( ptr->attr.y -= 1 ) < 0 )
         ptr->attr.y = 0;
      if (( ptr->attr.x -= 1 ) < 0 )
         ptr->attr.x = 0;

      ptr->attr.width += ptr->attr.border_width * 2;
      ptr->attr.height += ptr->attr.border_width * 2;

      if (( ptr->win = XCreateSimpleWindow( dpy, root,
                                             ptr->attr.x,
                                             ptr->attr.y,
                                             ptr->attr.width,
                                             ptr->attr.height,
                                             1, black, black ))
          == None )
      {
         fputs( "nickleby: could not create window frame", stderr );
         exit( 1 );
      }

      /*
       * We need to receive ButtonPress events even though we don't use
       * them to prevent them from going to the root window.
       */

      XSelectInput( dpy, ptr->win, SubstructureNotifyMask | EnterWindowMask |
                                   ButtonPressMask );
      XSelectInput( dpy, c[ nc ], ColormapChangeMask | EnterWindowMask |
                                  PropertyChangeMask );

      XReparentWindow( dpy, ptr->client, ptr->win, 0, 0 );
      XMapWindow( dpy, ptr->client );
      XMapWindow( dpy, ptr->win );

      XGetWindowAttributes( dpy, root, &root_attr );

      if ( confine_to_root( &ptr->attr.x, &ptr->attr.y,
                            &ptr->attr.width, &ptr->attr.height,
                            2, 1 ))
      {
         hints = ptr->hints;
         w = ptr->attr.width - ptr->attr.border_width * 2;
         h = ptr->attr.height - ptr->attr.border_width * 2;
         constrain_by_hints( &w, &h );

         ptr->attr.width = w + ptr->attr.border_width * 2;
         ptr->attr.height = h + ptr->attr.border_width * 2;

         XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
                            ptr->attr.width, ptr->attr.height );
         XMoveResizeWindow( dpy, ptr->client, 0, 0, w, h );
      }

      ptr->reparenting = 1;
      ptr->attr.border_width = 1;

      make_title_footer( ptr );
      focus_raise( ptr, 1 );
   }

   XFree( c );
}

void create_position()
{
   XSetWindowAttributes wa;

   if (( position = XCreateSimpleWindow( dpy, root, 0, 0, 50,
                                         font_height,
                                         0, white, black ))
       == None )
   {
      fputs( "nickleby: could not create position window", stderr );
      exit( 1 );
   }

   wa.override_redirect = True;
   XChangeWindowAttributes( dpy, position, CWOverrideRedirect, &wa );
}

int error_handler( Display *dpy, XErrorEvent *ev )
{
#ifdef DEBUG
   char buffer[ 128 ];
   static char *codes[ 18 ] =
      { "Success", "BadRequest", "BadValue", "BadWindow", "BadPixmap",
        "BadAtom", "BadCursor", "BadFont", "BadMatch", "BadDrawable",
        "BadAccess", "BadAlloc", "BadColor", "BadGC", "BadIDChoice",
        "BadName", "BadLength", "BadImplementation" };

   fprintf( stderr, "nickleby: X error: %s:\n          Resource ID: %lu\n",
            XDisplayString( dpy ), ev->resourceid );

   XGetErrorText( dpy, ev->error_code, buffer, 128 );

   if ( ev->error_code > 0 && ev->error_code < 18 )
      fprintf( stderr, "          %s: %s\n", buffer, codes[ ev->error_code ] );
   else
      fprintf( stderr, "          %s\n", buffer );
#endif

   return 0;
}

int main( int argc, char **argv )
{
   int i;
   char *disp = NULL;

   while(( i = getopt( argc, argv, "d:" )) != -1 )
      if ( i == 'd' )
      {
         if (( disp = optarg ) != NULL && ! *disp )
            disp = NULL;
      }

   if ( !( dpy = XOpenDisplay( disp )))
   {
      fputs( "nickleby: could not open display\n", stderr );
      return 1;
   }

   XSetCloseDownMode( dpy, DestroyAll );
   root = DefaultRootWindow( dpy );

   protocols = XInternAtom( dpy, "WM_PROTOCOLS", False );
   delete = XInternAtom( dpy, "WM_DELETE_WINDOW", False );
   state = XInternAtom( dpy, "WM_STATE", False );
   focus = XInternAtom( dpy, "WM_TAKE_FOCUS", False );

   initialize_gc_font();

   XSetInputFocus( dpy, None, RevertToNone, CurrentTime );
   XSetErrorHandler( error_handler );
   signal( SIGCHLD, SIG_IGN );

   create_position();
   manage_existing();

   if ( ! XSelectInput( dpy, root, ButtonPressMask |
                                   SubstructureNotifyMask |
                                   SubstructureRedirectMask ))
   {
      fputs( "nickleby: could not get events on root", stderr );
      exit( 1 );
   }

   XFlush( dpy );
   process_events();

   return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1