/* GDK - The GIMP Drawing Kit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "../config.h"

#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H_ */

#define XLIB_ILLEGAL_ACCESS
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xos.h>
#include <X11/Xutil.h>
#include "gdk.h"
#include "gdkprivate.h"


#ifndef X_GETTIMEOFDAY
#define X_GETTIMEOFDAY(tv)  gettimeofday (tv, NULL)
#endif /* X_GETTIMEOFDAY */


#define DOUBLE_CLICK_TIME      250
#define TRIPLE_CLICK_TIME      500
#define DOUBLE_CLICK_DIST      5
#define TRIPLE_CLICK_DIST      5


#ifndef NO_FD_SET
#  define SELECT_MASK fd_set
#else
#  ifndef _AIX
     typedef long fd_mask;
#  endif
#  if defined(_IBMR2)
#    define SELECT_MASK void
#  else
#    define SELECT_MASK int
#  endif
#endif


typedef struct _GdkInput      GdkInput;
typedef struct _GdkPredicate  GdkPredicate;

struct _GdkInput
{
  gint tag;
  gint source;
  GdkInputCondition condition;
  GdkInputFunction function;
  gpointer data;
};

struct _GdkPredicate
{
  GdkEventFunc func;
  gpointer data;
};


/* Private function declarations
 */
static gint gdk_event_wait (void);
static gint gdk_event_translate (GdkEvent *event, XEvent *xevent);
static Bool gdk_event_get_type (Display *display, XEvent *xevent, XPointer arg);
static void gdk_synthesize_click (GdkEvent *event, gint nclicks);

static void gdk_exit_func (void);
static int  gdk_x_error (Display *display, XErrorEvent *error);
static int  gdk_x_io_error (Display *display);
static RETSIGTYPE gdk_signal (int signum);


/* Private variable declarations
 */
static int initialized = 0;                         /* 1 if the library is initialized,
						     * 0 otherwise.
						     */
static int connection_number = 0;                   /* The file descriptor number of our
						     *  connection to the X server. This
						     *  is used so that we may determine
						     *  when events are pending by using
						     *  the "select" system call.
						     */

static gint received_destroy_notify = FALSE;        /* Did we just receive a destroy notify
						     *  event? If so, we need to actually
						     *  destroy the window which received
						     *  it now.
						     */
static GdkWindow *window_to_destroy = NULL;         /* If we previously received a destroy
						     *  notify event then this is the window
						     *  which received that event.
						     */

static struct timeval start;                        /* The time at which the library was
						     *  last initialized.
						     */
static struct timeval timer;                        /* Timeout interval to use in the call
						     *  to "select". This is used in
						     *  conjunction with "timerp" to create
						     *  a maximum time to wait for an event
						     *  to arrive.
						     */
static struct timeval *timerp;                      /* The actual timer passed to "select"
						     *  This may be NULL, in which case
						     *  "select" will block until an event
						     *  arrives.
						     */
static guint32 timer_val;                           /* The timeout length as specified by
						     *  the user in milliseconds.
						     */
static GList *inputs;                               /* A list of the input file descriptors
						     *  that we care about. Each list node
						     *  contains a GdkInput struct that describes
						     *  when we are interested in the specified
						     *  file descriptor. That is, when it is
						     *  available for read, write or has an
						     *  exception pending.
						     */
static guint32 button_click_time[2];                /* The last 2 button click times. Used
						     *  to determine if the latest button click
						     *  is part of a double or triple click.
						     */
static GdkWindow *button_window[2];                 /* The last 2 windows to receive button presses.
						     *  Also used to determine if the latest button
						     *  click is part of a double or triple click.
						     */
static gint button_number[2];                       /* The last 2 buttons to be pressed.
						     */

#define OTHER_XEVENT_BUFSIZE 4
static XEvent other_xevent[OTHER_XEVENT_BUFSIZE];   /* XEvents passed along to user  */
static int other_xevent_i = 0;
static GList *putback_events = NULL;

static gulong base_id;
static gint autorepeat;


/*
 *--------------------------------------------------------------
 * gdk_init
 *
 *   Initialize the library for use.
 *
 * Arguments:
 *   "argc" is the number of arguments.
 *   "argv" is an array of strings.
 *
 * Results:
 *   "argc" and "argv" are modified to reflect any arguments
 *   which were not handled. (Such arguments should either
 *   be handled by the application or dismissed).
 *
 * Side effects:
 *   The library is initialized.
 *
 *--------------------------------------------------------------
 */

void
gdk_init (int    *argc,
	  char ***argv)
{
  XKeyboardState keyboard_state;
  int synchronize;
  int i, j, k;

  X_GETTIMEOFDAY (&start);

  signal (SIGHUP, gdk_signal);
  signal (SIGINT, gdk_signal);
  signal (SIGQUIT, gdk_signal);
  signal (SIGBUS, gdk_signal);
  signal (SIGSEGV, gdk_signal);
  signal (SIGPIPE, gdk_signal);
  signal (SIGTERM, gdk_signal);

  gdk_display_name = NULL;

  XSetErrorHandler (gdk_x_error);
  XSetIOErrorHandler (gdk_x_io_error);

  synchronize = FALSE;

  if (argc && argv)
    {
      if (*argc > 0)
	gdk_progname = (*argv)[0];

      for (i = 1; i < *argc;)
	{
	  if (strcmp ("--display", (*argv)[i]) == 0)
	    {
	      (*argv)[i] = NULL;

	      if ((i + 1) < *argc)
		{
		  gdk_display_name = g_strdup ((*argv)[i + 1]);
		  (*argv)[i + 1] = NULL;
		  i += 1;
		}
	    }
	  else if (strcmp ("--sync", (*argv)[i]) == 0)
	    {
	      (*argv)[i] = NULL;
	      synchronize = TRUE;
	    }
	  else if (strcmp ("--show-events", (*argv)[i]) == 0)
	    {
	      (*argv)[i] = NULL;
	      gdk_show_events = TRUE;
	    }
	  else if (strcmp ("--no-show-events", (*argv)[i]) == 0)
	    {
	      (*argv)[i] = NULL;
	      gdk_show_events = FALSE;
	    }
	  else if (strcmp ("--no-xshm", (*argv)[i]) == 0)
	    {
	      (*argv)[i] = NULL;
	      gdk_use_xshm = FALSE;
	    }
	  else if (strcmp ("--debug-level", (*argv)[i]) == 0)
	    {
	      if ((i + 1) < *argc)
		{
		  (*argv)[i++] = NULL;
		  gdk_debug_level = atoi ((*argv)[i]);
		  (*argv)[i] = NULL;
		}
	    }

	  i += 1;
	}

      for (i = 1; i < *argc; i++)
	{
	  for (k = i; k < *argc; k++)
	    if ((*argv)[k] != NULL)
	      break;

	  if (k > i)
	    {
	      k -= i;
	      for (j = i + k; j < *argc; j++)
		(*argv)[j-k] = (*argv)[j];
	      *argc -= k;
	    }
	}
    }
  else
    {
      gdk_progname = "<unknown>";
    }

  gdk_display = XOpenDisplay (gdk_display_name);
  if (!gdk_display)
    g_error ("cannot open display: %s", XDisplayName (gdk_display_name));

  /* This is really crappy. We have to look into the display structure
   *  to find the base resource id. This is only needed for recording
   *  and playback of events.
   */
  /* base_id = RESOURCE_BASE; */
  base_id = 0;
  if (gdk_show_events)
    g_print ("base id: %lu\n", base_id);

  connection_number = ConnectionNumber (gdk_display);
  if (gdk_debug_level >= 1)
    g_print ("connection number: %d\n", connection_number);

  if (synchronize)
    XSynchronize (gdk_display, True);

  gdk_screen = DefaultScreen (gdk_display);
  gdk_root_window = RootWindow (gdk_display, gdk_screen);

  gdk_wm_delete_window = XInternAtom (gdk_display, "WM_DELETE_WINDOW", True);
  gdk_wm_take_focus = XInternAtom (gdk_display, "WM_TAKE_FOCUS", True);
  gdk_wm_protocols = XInternAtom (gdk_display, "WM_PROTOCOLS", True);
  gdk_wm_window_protocols[0] = gdk_wm_delete_window;
  gdk_wm_window_protocols[1] = gdk_wm_take_focus;
  gdk_selection_property = XInternAtom (gdk_display, "GDK_SELECTION", False);

  XGetKeyboardControl (gdk_display, &keyboard_state);
  autorepeat = keyboard_state.global_auto_repeat;

  timer.tv_sec = 0;
  timer.tv_usec = 0;
  timerp = NULL;

  button_click_time[0] = 0;
  button_click_time[1] = 0;
  button_window[0] = NULL;
  button_window[1] = NULL;
  button_number[0] = -1;
  button_number[1] = -1;

  if (ATEXIT (gdk_exit_func))
    g_warning ("unable to register exit function");

  gdk_visual_init ();
  gdk_window_init ();

  initialized = 1;
}

/*
 *--------------------------------------------------------------
 * gdk_exit
 *
 *   Restores the library to an un-itialized state and exits
 *   the program using the "exit" system call.
 *
 * Arguments:
 *   "errorcode" is the error value to pass to "exit".
 *
 * Results:
 *   Allocated structures are freed and the program exits
 *   cleanly.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void
gdk_exit (int errorcode)
{
  if (initialized)
    {
      gdk_image_exit ();
      gdk_key_repeat_restore ();

      XCloseDisplay (gdk_display);
      initialized = 0;
    }

  exit (errorcode);
}

/*
 *--------------------------------------------------------------
 * gdk_events_pending
 *
 *   Returns the number of events pending on the queue.
 *   These events have already been read from the server
 *   connection.
 *
 * Arguments:
 *
 * Results:
 *   Returns the number of events on XLib's event queue.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_events_pending ()
{
  return XPending (gdk_display);
}

/*
 *--------------------------------------------------------------
 * gdk_event_get
 *
 *   Gets the next event.
 *
 * Arguments:
 *   "event" is used to hold the received event.
 *   If "event" is NULL an event is received as normal
 *   however it is not placed in "event" (and thus no
 *   error occurs).
 *
 * Results:
 *   Returns TRUE if an event was received that we care about
 *   and FALSE otherwise. This function will also return
 *   before an event is received if the timeout interval
 *   runs out.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_event_get (GdkEvent     *event,
	       GdkEventFunc  pred,
	       gpointer      data)
{
  GdkEvent *temp_event;
  GdkPredicate event_pred;
  GList *temp_list;
  XEvent xevent;

  /* If the last event we received was a destroy notify
   *  event then we will actually destroy the "gdk" data
   *  structures now. We don't want to destroy them at the
   *  time of receiving the event since the main program
   *  may try to access them and may need to destroy user
   *  data that has been attached to the window
   */
  if (received_destroy_notify)
    {
      if (gdk_show_events)
	g_print ("destroying window:\twindow: %ld\n",
		 ((GdkWindowPrivate*) window_to_destroy)->xwindow - base_id);

      gdk_window_real_destroy (window_to_destroy);
      received_destroy_notify = FALSE;
      window_to_destroy = NULL;
    }

  /* Initially we haven't received an event and want to
   *  return FALSE. If "event" is non-NULL, then initialize
   *  it to the nothing event.
   */
  if (event)
    {
      event->any.type = GDK_NOTHING;
      event->any.window = NULL;
      event->any.send_event = FALSE;
    }

  if (pred)
    {
      temp_list = putback_events;
      while (temp_list)
	{
	  temp_event = temp_list->data;

	  if ((* pred) (temp_event, data))
	    {
	      if (event)
		*event = *temp_event;
	      putback_events = g_list_remove_link (putback_events, temp_list);
	      g_list_free (temp_list);
	      return TRUE;
	    }

	  temp_list = temp_list->next;
	}

      event_pred.func = pred;
      event_pred.data = data;

      if (XCheckIfEvent (gdk_display, &xevent, gdk_event_get_type, (XPointer) &event_pred))
	if (event)
	  return gdk_event_translate (event, &xevent);
    }
  else
    {
      if (putback_events)
	{
	  temp_event = putback_events->data;
	  *event = *temp_event;

	  temp_list = putback_events;
	  putback_events = putback_events->next;
	  if (putback_events)
	    putback_events->prev = NULL;

	  temp_list->next = NULL;
	  temp_list->prev = NULL;
	  g_list_free (temp_list);
	  g_free (temp_event);

	  return TRUE;
	}

      /* Wait for an event to occur or the timeout to elapse.
       * If an event occurs "gdk_event_wait" will return TRUE.
       *  If the timeout elapses "gdk_event_wait" will return
       *  FALSE.
       */
      if (gdk_event_wait ())
	{
	  /* If we get here we can rest assurred that an event
	   *  has occurred. Read it.
	   */
	  XNextEvent (gdk_display, &xevent);

	  event->any.send_event = xevent.xany.send_event;

	  /* If "event" non-NULL.
	   */
	  if (event)
	    return gdk_event_translate (event, &xevent);
	}
    }

  return FALSE;
}

void
gdk_event_put (GdkEvent *event)
{
  GdkEvent *new_event;

  g_return_if_fail (event != NULL);

  new_event = g_new (GdkEvent, 1);
  *new_event = *event;

  putback_events = g_list_prepend (putback_events, new_event);
}

/*
 *--------------------------------------------------------------
 * gdk_set_debug_level
 *
 *   Sets the debugging level.
 *
 * Arguments:
 *   "level" is the new debugging level.
 *
 * Results:
 *
 * Side effects:
 *   Other function calls to "gdk" use the debugging
 *   level to determine what kind of debugging information
 *   to print out.
 *
 *--------------------------------------------------------------
 */

void
gdk_set_debug_level (int level)
{
  gdk_debug_level = level;
}

/*
 *--------------------------------------------------------------
 * gdk_set_show_events
 *
 *   Turns on/off the showing of events.
 *
 * Arguments:
 *   "show_events" is a boolean describing whether or
 *   not to show the events gdk receives.
 *
 * Results:
 *
 * Side effects:
 *   When "show_events" is TRUE, calls to "gdk_event_get"
 *   will output debugging informatin regarding the event
 *   received to stdout.
 *
 *--------------------------------------------------------------
 */

void
gdk_set_show_events (int show_events)
{
  gdk_show_events = show_events;
}

void
gdk_set_use_xshm (gint use_xshm)
{
  gdk_use_xshm = use_xshm;
}

gint
gdk_get_debug_level ()
{
  return gdk_debug_level;
}

gint
gdk_get_show_events ()
{
  return gdk_show_events;
}

gint
gdk_get_use_xshm ()
{
  return gdk_use_xshm;
}

/*
 *--------------------------------------------------------------
 * gdk_time_get
 *
 *   Get the number of milliseconds since the library was
 *   initialized.
 *
 * Arguments:
 *
 * Results:
 *   The time since the library was initialized is returned.
 *   This time value is accurate to milliseconds even though
 *   a more accurate time down to the microsecond could be
 *   returned.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

guint32
gdk_time_get ()
{
  struct timeval end;
  struct timeval elapsed;
  guint32 milliseconds;

  X_GETTIMEOFDAY (&end);

  if (start.tv_usec > end.tv_usec)
    {
      end.tv_usec += 1000000;
      end.tv_sec--;
    }
  elapsed.tv_sec = end.tv_sec - start.tv_sec;
  elapsed.tv_usec = end.tv_usec - start.tv_usec;

  milliseconds = (elapsed.tv_sec * 1000) + (elapsed.tv_usec / 1000);

  return milliseconds;
}

/*
 *--------------------------------------------------------------
 * gdk_timer_get
 *
 *   Returns the current timer.
 *
 * Arguments:
 *
 * Results:
 *   Returns the current timer interval. This interval is
 *   in units of milliseconds.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

guint32
gdk_timer_get ()
{
  return timer_val;
}

/*
 *--------------------------------------------------------------
 * gdk_timer_set
 *
 *   Sets the timer interval.
 *
 * Arguments:
 *   "milliseconds" is the new value for the timer.
 *
 * Results:
 *
 * Side effects:
 *   Calls to "gdk_event_get" will last for a maximum
 *   of time of "milliseconds". However, a value of 0
 *   milliseconds will cause "gdk_event_get" to block
 *   indefinately until an event is received.
 *
 *--------------------------------------------------------------
 */

void
gdk_timer_set (guint32 milliseconds)
{
  timer_val = milliseconds;
  timer.tv_sec = milliseconds / 1000;
  timer.tv_usec = (milliseconds % 1000) * 1000;

}

void
gdk_timer_enable ()
{
  timerp = &timer;
}

void
gdk_timer_disable ()
{
  timerp = NULL;
}

gint
gdk_input_add (gint              source,
	       GdkInputCondition condition,
	       GdkInputFunction  function,
	       gpointer          data)
{
  static gint next_tag = 1;
  GList *list;
  GdkInput *input;
  gint tag;

  tag = 0;
  list = inputs;

  while (list)
    {
      input = list->data;
      list = list->next;

      if ((input->source == source) && (input->condition == condition))
	{
	  input->function = function;
	  input->data = data;
	  tag = input->tag;
	}
    }

  if (!tag)
    {
      input = g_new (GdkInput, 1);
      input->tag = next_tag++;
      input->source = source;
      input->condition = condition;
      input->function = function;
      input->data = data;
      tag = input->tag;

      inputs = g_list_prepend (inputs, input);
    }

  return tag;
}

void
gdk_input_remove (gint tag)
{
  GList *list;
  GList *temp_list;
  GdkInput *input;

  list = inputs;
  while (list)
    {
      input = list->data;

      if (input->tag == tag)
	{
	  temp_list = list;

	  if (list->next)
	    list->next->prev = list->prev;
	  if (list->prev)
	    list->prev->next = list->next;
	  if (inputs == list)
	    inputs = list->next;

	  temp_list->next = NULL;
	  temp_list->prev = NULL;

	  g_free (temp_list->data);
	  g_list_free (temp_list);
	  break;
	}

      list = list->next;
    }
}

/*
 *--------------------------------------------------------------
 * gdk_pointer_grab
 *
 *   Grabs the pointer to a specific window
 *
 * Arguments:
 *   "window" is the window which will receive the grab
 *   "owner_events" specifies whether events will be reported as is,
 *     or relative to "window"
 *   "event_mask" masks only interesting events
 *   "confine_to" limits the cursor movement to the specified window
 *   "cursor" changes the cursor for the duration of the grab
 *   "time" specifies the time
 *
 * Results:
 *
 * Side effects:
 *   requires a corresponding call to gdk_pointer_ungrab
 *
 *--------------------------------------------------------------
 */

gint
gdk_pointer_grab (GdkWindow *     window,
		  gint            owner_events,
		  GdkEventMask    event_mask,
		  GdkWindow *     confine_to,
		  GdkCursor *     cursor,
		  guint32         time)
{
  /*  From gdkwindow.c  */
  extern int nevent_masks;
  extern int event_mask_table[];

  gint return_val;
  GdkWindowPrivate *window_private;
  GdkWindowPrivate *confine_to_private;
  GdkCursorPrivate *cursor_private;
  guint xevent_mask;
  Window xwindow;
  Window xconfine_to;
  Cursor xcursor;
  int i;

  g_return_val_if_fail (window != NULL, 0);

  window_private = (GdkWindowPrivate*) window;
  confine_to_private = (GdkWindowPrivate*) confine_to;
  cursor_private = (GdkCursorPrivate*) cursor;

  xwindow = window_private->xwindow;

  if (!confine_to)
    xconfine_to = None;
  else
    xconfine_to = confine_to_private->xwindow;

  if (!cursor)
    xcursor = None;
  else
    xcursor = cursor_private->xcursor;


  xevent_mask = 0;
  for (i = 0; i < nevent_masks; i++)
    {
      if (event_mask & (1 << (i + 1)))
	xevent_mask |= event_mask_table[i];
    }

  return_val = XGrabPointer (window_private->xdisplay,
			     xwindow,
			     owner_events,
			     xevent_mask,
			     GrabModeAsync, GrabModeAsync,
			     xconfine_to,
			     xcursor,
			     time);

  return return_val;
}

/*
 *--------------------------------------------------------------
 * gdk_pointer_ungrab
 *
 *   Releases any pointer grab
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void
gdk_pointer_ungrab (guint32 time)
{
  XUngrabPointer (gdk_display, time);
}

/*
 *--------------------------------------------------------------
 * gdk_keyboard_grab
 *
 *   Grabs the keyboard to a specific window
 *
 * Arguments:
 *   "window" is the window which will receive the grab
 *   "owner_events" specifies whether events will be reported as is,
 *     or relative to "window"
 *   "time" specifies the time
 *
 * Results:
 *
 * Side effects:
 *   requires a corresponding call to gdk_keyboard_ungrab
 *
 *--------------------------------------------------------------
 */

gint
gdk_keyboard_grab (GdkWindow *     window,
                   gint            owner_events,
                   guint32         time)
{
  GdkWindowPrivate *window_private;
  Window xwindow;

  g_return_val_if_fail (window != NULL, 0);

  window_private = (GdkWindowPrivate*) window;
  xwindow = window_private->xwindow;

  return XGrabKeyboard (window_private->xdisplay,
			xwindow,
			owner_events,
			GrabModeAsync, GrabModeAsync,
			time);
}

/*
 *--------------------------------------------------------------
 * gdk_keyboard_ungrab
 *
 *   Releases any keyboard grab
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void
gdk_keyboard_ungrab (guint32 time)
{
  XUngrabKeyboard (gdk_display, time);
}

/*
 *--------------------------------------------------------------
 * gdk_screen_width
 *
 *   Return the width of the screen.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_screen_width ()
{
  gint return_val;

  return_val = DisplayWidth (gdk_display, gdk_screen);

  return return_val;
}

/*
 *--------------------------------------------------------------
 * gdk_screen_height
 *
 *   Return the height of the screen.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

gint
gdk_screen_height ()
{
  gint return_val;

  return_val = DisplayHeight (gdk_display, gdk_screen);

  return return_val;
}

void
gdk_key_repeat_disable ()
{
  XAutoRepeatOff (gdk_display);
}

void
gdk_key_repeat_restore ()
{
  if (autorepeat)
    XAutoRepeatOn (gdk_display);
  else
    XAutoRepeatOff (gdk_display);
}


/*
 *--------------------------------------------------------------
 * gdk_flush
 *
 *   Flushes the Xlib output buffer and then waits
 *   until all requests have been received and processed
 *   by the X server. The only real use for this function
 *   is in dealing with XShm.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

void gdk_flush ()
{
  XSync (gdk_display, False);
}


void
gdk_beep ()
{
  XBell(gdk_display, 100);
}


/*
 *--------------------------------------------------------------
 * gdk_event_wait
 *
 *   Waits until an event occurs or the timer runs out.
 *
 * Arguments:
 *
 * Results:
 *   Returns TRUE if an event is ready to be read and FALSE
 *   if the timer ran out.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

static gint
gdk_event_wait ()
{
  GList *list;
  GdkInput *input;
  GdkInputCondition condition;
  SELECT_MASK readfds;
  SELECT_MASK writefds;
  SELECT_MASK exceptfds;
  int max_input;
  int nfd;

  /* If there are no events pending we will wait for an event.
   * The time we wait is dependant on the "timer". If no timer
   *  has been specified then we'll block until an event arrives.
   *  If a timer has been specified we'll block until an event
   *  arrives or the timer expires. (This is all done using the
   *  "select" system call).
   */

  if (XPending (gdk_display) == 0)
    {
      FD_ZERO (&readfds);
      FD_ZERO (&writefds);
      FD_ZERO (&exceptfds);

      FD_SET (connection_number, &readfds);
      max_input = connection_number;

      list = inputs;
      while (list)
	{
	  input = list->data;
	  list = list->next;

	  if (input->condition & GDK_INPUT_READ)
	    FD_SET (input->source, &readfds);
	  if (input->condition & GDK_INPUT_WRITE)
	    FD_SET (input->source, &writefds);
	  if (input->condition & GDK_INPUT_EXCEPTION)
	    FD_SET (input->source, &exceptfds);

	  max_input = MAX (max_input, input->source);
	}

      nfd = select (max_input+1, &readfds, &writefds, &exceptfds, timerp);

      timerp = NULL;
      timer_val = 0;

      if (nfd > 0)
	{
	  if (FD_ISSET (connection_number, &readfds))
	    {
	      if (XPending (gdk_display) == 0)
		{
		  if (nfd == 1)
		    {
		      XNoOp (gdk_display);
		      XFlush (gdk_display);
		    }
		  return FALSE;
		}
	      else
		return TRUE;
	    }

	  list = inputs;
	  while (list)
	    {
	      input = list->data;
	      list = list->next;

	      condition = 0;
	      if (FD_ISSET (input->source, &readfds))
		condition |= GDK_INPUT_READ;
	      if (FD_ISSET (input->source, &writefds))
		condition |= GDK_INPUT_WRITE;
	      if (FD_ISSET (input->source, &exceptfds))
		condition |= GDK_INPUT_EXCEPTION;

	      if (condition && input->function)
		(* input->function) (input->data, input->source, condition);
	    }
	}
    }
  else
    return TRUE;

  return FALSE;
}

static gint
gdk_event_translate (GdkEvent *event,
		     XEvent   *xevent)
{
  GdkWindow *window;
  XComposeStatus compose;
  int charcount;
  char buf[16];
  gint return_val;

  return_val = FALSE;

  /* Find the GdkWindow that this event occurred in.
   * All events occur in some GdkWindow (otherwise, why
   *  would we be receiving them). It really is an error
   *  to receive an event for which we cannot find the
   *  corresponding GdkWindow.
   */
  window = gdk_window_lookup (xevent->xany.window);

  /* We do a "manual" conversion of the XEvent to a
   *  GdkEvent. The structures are mostly the same so
   *  the conversion is fairly straightforward. We also
   *  optionally print debugging info regarding events
   *  received.
   */
  switch (xevent->type)
    {
    case KeyPress:
      /* Lookup the string corresponding to the given keysym.
       */
      charcount = XLookupString (&xevent->xkey, buf, 16,
				 (KeySym*) &event->key.keyval,
				 &compose);

      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("key press:\t\twindow: %ld  key: %12s  %d\n",
		 xevent->xkey.window - base_id,
		 XKeysymToString (event->key.keyval),
		 event->key.keyval);

      event->key.type = GDK_KEY_PRESS;
      event->key.window = window;
      event->key.time = xevent->xkey.time;
      event->key.state = (GdkModifierType) xevent->xkey.state;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case KeyRelease:
      /* Lookup the string corresponding to the given keysym.
       */
      charcount = XLookupString (&xevent->xkey, buf, 16,
				 (KeySym*) &event->key.keyval,
				 &compose);

      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("key release:\t\twindow: %ld  key: %12s  %d\n",
		 xevent->xkey.window - base_id,
		 XKeysymToString (event->key.keyval),
		 event->key.keyval);

      event->key.type = GDK_KEY_RELEASE;
      event->key.window = window;
      event->key.time = xevent->xkey.time;
      event->key.state = (GdkModifierType) xevent->xkey.state;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case ButtonPress:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("button press:\t\twindow: %ld  x,y: %d %d  button: %d\n",
		 xevent->xbutton.window - base_id,
		 xevent->xbutton.x, xevent->xbutton.y,
		 xevent->xbutton.button);

      event->button.type = GDK_BUTTON_PRESS;
      event->button.window = window;
      event->button.time = xevent->xbutton.time;
      event->button.x = xevent->xbutton.x;
      event->button.y = xevent->xbutton.y;
      event->button.state = (GdkModifierType) xevent->xbutton.state;
      event->button.button = xevent->xbutton.button;

      if ((event->button.time < (button_click_time[1] + TRIPLE_CLICK_TIME)) &&
	  (event->button.window == button_window[1]) &&
	  (event->button.button == button_number[1]))
	{
	  gdk_synthesize_click (event, 3);

	  button_click_time[1] = 0;
	  button_click_time[0] = 0;
	  button_window[1] = NULL;
	  button_window[0] = 0;
	  button_number[1] = -1;
	  button_number[0] = -1;
	}
      else if ((event->button.time < (button_click_time[0] + DOUBLE_CLICK_TIME)) &&
	       (event->button.window == button_window[0]) &&
	       (event->button.button == button_number[0]))
	{
	  gdk_synthesize_click (event, 2);

	  button_click_time[1] = button_click_time[0];
	  button_click_time[0] = event->button.time;
	  button_window[1] = button_window[0];
	  button_window[0] = event->button.window;
	  button_number[1] = button_number[0];
	  button_number[0] = event->button.button;
	}
      else
	{
	  button_click_time[1] = 0;
	  button_click_time[0] = event->button.time;
	  button_window[1] = NULL;
	  button_window[0] = event->button.window;
	  button_number[1] = -1;
	  button_number[0] = event->button.button;
	}

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case ButtonRelease:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("button release:\twindow: %ld  x,y: %d %d  button: %d\n",
		 xevent->xbutton.window - base_id,
		 xevent->xbutton.x, xevent->xbutton.y,
		 xevent->xbutton.button);

      event->button.type = GDK_BUTTON_RELEASE;
      event->button.window = window;
      event->button.time = xevent->xbutton.time;
      event->button.x = xevent->xbutton.x;
      event->button.y = xevent->xbutton.y;
      event->button.state = (GdkModifierType) xevent->xbutton.state;
      event->button.button = xevent->xbutton.button;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case MotionNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("motion notify:\t\twindow: %ld  x,y: %d %d  hint: %s\n",
		 xevent->xmotion.window - base_id,
		 xevent->xmotion.x, xevent->xmotion.y,
		 (xevent->xmotion.is_hint) ? "true" : "false");

      event->motion.type = GDK_MOTION_NOTIFY;
      event->motion.window = window;
      event->motion.time = xevent->xmotion.time;
      event->motion.x = xevent->xmotion.x;
      event->motion.y = xevent->xmotion.y;
      event->motion.state = (GdkModifierType) xevent->xmotion.state;
      event->motion.is_hint = xevent->xmotion.is_hint;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case EnterNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("enter notify:\t\twindow: %ld  detail: %d\n",
		 xevent->xcrossing.window - base_id,
		 xevent->xcrossing.detail);

      event->crossing.type = GDK_ENTER_NOTIFY;
      event->crossing.window = window;

      /* If the subwindow field of the XEvent is non-NULL, then
       *  lookup the corresponding GdkWindow.
       */
      if (xevent->xcrossing.subwindow != None)
	event->crossing.subwindow = gdk_window_lookup (xevent->xcrossing.subwindow);
      else
	event->crossing.subwindow = NULL;

      /* Translate the crossing detail into Gdk terms.
       */
      switch (xevent->xcrossing.detail)
	{
	case NotifyInferior:
	  event->crossing.detail = GDK_NOTIFY_INFERIOR;
	  break;
	case NotifyAncestor:
	  event->crossing.detail = GDK_NOTIFY_ANCESTOR;
	  break;
	case NotifyVirtual:
	  event->crossing.detail = GDK_NOTIFY_VIRTUAL;
	  break;
	case NotifyNonlinear:
	  event->crossing.detail = GDK_NOTIFY_NONLINEAR;
	  break;
	case NotifyNonlinearVirtual:
	  event->crossing.detail = GDK_NOTIFY_NONLINEAR_VIRTUAL;
	  break;
	default:
	  event->crossing.detail = GDK_NOTIFY_UNKNOWN;
	  break;
	}

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case LeaveNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("leave notify:\t\twindow: %ld  detail: %d\n",
		 xevent->xcrossing.window - base_id,
		 xevent->xcrossing.detail);

      event->crossing.type = GDK_LEAVE_NOTIFY;
      event->crossing.window = window;

      /* Translate the crossing detail into Gdk terms.
       */
      switch (xevent->xcrossing.detail)
	{
	case NotifyInferior:
	  event->crossing.detail = GDK_NOTIFY_INFERIOR;
	  break;
	case NotifyAncestor:
	  event->crossing.detail = GDK_NOTIFY_ANCESTOR;
	  break;
	case NotifyVirtual:
	  event->crossing.detail = GDK_NOTIFY_VIRTUAL;
	  break;
	case NotifyNonlinear:
	  event->crossing.detail = GDK_NOTIFY_NONLINEAR;
	  break;
	case NotifyNonlinearVirtual:
	  event->crossing.detail = GDK_NOTIFY_NONLINEAR_VIRTUAL;
	  break;
	default:
	  event->crossing.detail = GDK_NOTIFY_UNKNOWN;
	  break;
	}

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case FocusIn:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("focus in:\t\twindow: %ld\n",
		 xevent->xfocus.window - base_id);

      event->focus_change.type = GDK_FOCUS_CHANGE;
      event->focus_change.window = window;
      event->focus_change.in = TRUE;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case FocusOut:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("focus out:\t\twindow: %ld\n",
		 xevent->xfocus.window - base_id);

      event->focus_change.type = GDK_FOCUS_CHANGE;
      event->focus_change.window = window;
      event->focus_change.in = FALSE;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case KeymapNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("keymap notify\n");

      /* Not currently handled */
      break;

    case Expose:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("expose:\t\twindow: %ld  %d  x,y: %d %d  w,h: %d %d\n",
		 xevent->xexpose.window - base_id, xevent->xexpose.count,
		 xevent->xexpose.x, xevent->xexpose.y,
		 xevent->xexpose.width, xevent->xexpose.height);

      event->expose.type = GDK_EXPOSE;
      event->expose.window = window;
      event->expose.area.x = xevent->xexpose.x;
      event->expose.area.y = xevent->xexpose.y;
      event->expose.area.width = xevent->xexpose.width;
      event->expose.area.height = xevent->xexpose.height;
      event->expose.count = xevent->xexpose.count;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case GraphicsExpose:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("graphics expose:\tdrawable: %ld\n",
		 xevent->xgraphicsexpose.drawable - base_id);

      event->expose.type = GDK_EXPOSE;
      event->expose.window = window;
      event->expose.area.x = xevent->xgraphicsexpose.x;
      event->expose.area.y = xevent->xgraphicsexpose.y;
      event->expose.area.width = xevent->xgraphicsexpose.width;
      event->expose.area.height = xevent->xgraphicsexpose.height;
      event->expose.count = xevent->xexpose.count;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case NoExpose:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("no expose:\t\tdrawable: %ld\n",
		 xevent->xnoexpose.drawable - base_id);

      /* Not currently handled */
      break;

    case VisibilityNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	switch (xevent->xvisibility.state)
	  {
	  case VisibilityFullyObscured:
	    g_print ("visibility notify:\twindow: %ld  none\n",
		     xevent->xvisibility.window - base_id);
	    break;
	  case VisibilityPartiallyObscured:
	    g_print ("visibility notify:\twindow: %ld  partial\n",
		     xevent->xvisibility.window - base_id);
	    break;
	  case VisibilityUnobscured:
	    g_print ("visibility notify:\twindow: %ld  full\n",
		     xevent->xvisibility.window - base_id);
	    break;
	  }

      /* Not currently handled */
      break;

    case CreateNotify:
      /* Not currently handled */
      break;

    case DestroyNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("destroy notify:\twindow: %ld\n",
		 xevent->xdestroywindow.window - base_id);

      event->any.type = GDK_DESTROY;
      event->any.window = window;

      /* Remeber which window received the destroy notify
       *  event so that we can destroy our associated
       *  data structures the next time the user asks
       *  us for an event.
       */
      received_destroy_notify = TRUE;
      window_to_destroy = window;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case UnmapNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("unmap notify:\t\twindow: %ld\n",
		 xevent->xmap.window - base_id);

      event->any.type = GDK_UNMAP;
      event->any.window = window;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case MapNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("map notify:\t\twindow: %ld\n",
		 xevent->xmap.window - base_id);

      event->any.type = GDK_MAP;
      event->any.window = window;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case ReparentNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("reparent notify:\twindow: %ld\n",
		 xevent->xreparent.window - base_id);

      /* Not currently handled */
      break;

    case ConfigureNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("configure notify:\twindow: %ld  x,y: %d %d  w,h: %d %d\n",
		 xevent->xconfigure.window - base_id,
		 xevent->xconfigure.x, xevent->xconfigure.y,
		 xevent->xconfigure.width, xevent->xconfigure.height);

      if ((((GdkWindowPrivate*) window)->window_type != GDK_WINDOW_CHILD) &&
	  ((((GdkWindowPrivate*) window)->width != xevent->xconfigure.width) ||
	   (((GdkWindowPrivate*) window)->height != xevent->xconfigure.height)))
	{
	  event->configure.type = GDK_CONFIGURE;
	  event->configure.window = window;
	  event->configure.x = xevent->xconfigure.x;
	  event->configure.y = xevent->xconfigure.y;
	  event->configure.width = xevent->xconfigure.width;
	  event->configure.height = xevent->xconfigure.height;

	  ((GdkWindowPrivate*) window)->x = xevent->xconfigure.x;
	  ((GdkWindowPrivate*) window)->y = xevent->xconfigure.y;
	  ((GdkWindowPrivate*) window)->width = xevent->xconfigure.width;
	  ((GdkWindowPrivate*) window)->height = xevent->xconfigure.height;
	  if (((GdkWindowPrivate*) window)->resize_count > 1)
	    ((GdkWindowPrivate*) window)->resize_count -= 1;

	  return_val = !((GdkWindowPrivate*) window)->destroyed;
	}
      break;

    case PropertyNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("property notify:\twindow: %ld\n",
		 xevent->xproperty.window - base_id);

      event->property.type = GDK_PROPERTY_NOTIFY;
      event->property.window = window;
      event->property.atom = xevent->xproperty.atom;
      event->property.time = xevent->xproperty.time;
      event->property.state = xevent->xproperty.state;

      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;

    case SelectionClear:
      if (gdk_show_events)
	g_print ("selection clear:\twindow: %ld\n",
		 xevent->xproperty.window - base_id);

      event->selection.type = GDK_SELECTION_CLEAR;
      event->selection.window = window;
      event->selection.selection = xevent->xselectionclear.selection;
      event->selection.time = xevent->xselectionclear.time;

      switch (event->selection.selection)
	{
	case GDK_SELECTION_PRIMARY:
	  if (selection_owner[0])
	    {
	      selection_owner[0] = NULL;
	      return_val = !((GdkWindowPrivate*) window)->destroyed;
	    }
	  break;
	case GDK_SELECTION_SECONDARY:
	  if (selection_owner[1])
	    {
	      selection_owner[1] = NULL;
	      return_val = !((GdkWindowPrivate*) window)->destroyed;
	    }
	  break;
	}
      break;

    case SelectionRequest:
      if (gdk_show_events)
	g_print ("selection request:\twindow: %ld\n",
		 xevent->xproperty.window - base_id);

      if (xevent->xselectionrequest.target == XA_STRING)
	{
	  event->selection.type = GDK_SELECTION_REQUEST;
	  event->selection.window = window;
	  event->selection.selection = xevent->xselectionrequest.selection;
	  event->selection.property = xevent->xselectionrequest.property;
	  event->selection.requestor = xevent->xselectionrequest.requestor;
	  event->selection.time = xevent->xselectionrequest.time;

	  return_val = !((GdkWindowPrivate*) window)->destroyed;
	}
      break;

    case SelectionNotify:
      if (gdk_show_events)
	g_print ("selection notify:\twindow: %ld\n",
		 xevent->xproperty.window - base_id);

      if ((xevent->xselection.target == XA_STRING) &&
	  (xevent->xselection.property != None))
	{
	  event->selection.type = GDK_SELECTION_NOTIFY;
	  event->selection.window = window;
	  event->selection.selection = xevent->xselection.selection;
	  event->selection.property = xevent->xselection.property;
	  event->selection.time = xevent->xselection.time;

	  return_val = !((GdkWindowPrivate*) window)->destroyed;
	}
      break;

    case ColormapNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("colormap notify:\twindow: %ld\n",
		 xevent->xcolormap.window - base_id);

      /* Not currently handled */
      break;

    case ClientMessage:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("client message:\twindow: %ld\n",
		 xevent->xclient.window - base_id);

      /* Client messages are the means of the window manager
       *  communicating with a program. We'll first check to
       *  see if this is really the window manager talking
       *  to us.
       */
      if (xevent->xclient.message_type == gdk_wm_protocols)
	{
	  if (xevent->xclient.data.l[0] == gdk_wm_delete_window)
	    {
	      /* The delete window request specifies a window
	       *  to delete. We don't actually destroy the
	       *  window because "it is only a request". (The
	       *  window might contain vital data that the
	       *  program does not want destroyed). Instead
	       *  the event is passed along to the program,
	       *  which should then destroy the window.
	       */

	      /* Print debugging info.
	       */
	      if (gdk_show_events)
		g_print ("delete window:\t\twindow: %ld\n",
			 xevent->xclient.window - base_id);

	      event->any.type = GDK_DELETE;
	      event->any.window = window;

	      return_val = !((GdkWindowPrivate*) window)->destroyed;
	    }
	  else if (xevent->xclient.data.l[0] == gdk_wm_take_focus)
	    {
	      /* Print debugging info.
	       */
	      if (gdk_show_events)
		g_print ("take focus:\t\twindow: %ld\n",
			 xevent->xclient.window - base_id);

	      /* Not currently handled */
	    }
	}
      break;

    case MappingNotify:
      /* Print debugging info.
       */
      if (gdk_show_events)
	g_print ("mapping notify\n");

      /* Let XLib know that there is a new keyboard mapping.
       */
      XRefreshKeyboardMapping (&xevent->xmapping);
      break;

    default:
      /* something else - (e.g., a Xinput event) */
      event->other.type = GDK_OTHER_EVENT;
      event->other.window = window;
      event->other.xevent = &other_xevent[other_xevent_i];
      memcpy(&other_xevent[other_xevent_i],xevent,sizeof(XEvent));
      other_xevent_i = (other_xevent_i+1) % OTHER_XEVENT_BUFSIZE;
      return_val = !((GdkWindowPrivate*) window)->destroyed;
      break;
    }

  return return_val;
}

static Bool
gdk_event_get_type (Display  *display,
		    XEvent   *xevent,
		    XPointer  arg)
{
  GdkEvent event;
  GdkPredicate *pred;

  if (gdk_event_translate (&event, xevent))
    {
      pred = (GdkPredicate*) arg;
      return (* pred->func) (&event, pred->data);
    }

  return FALSE;
}

static void
gdk_synthesize_click (GdkEvent *event,
		      gint      nclicks)
{
  GdkEvent temp_event;

  g_return_if_fail (event != NULL);

  temp_event = *event;
  temp_event.type = (nclicks == 2) ? GDK_2BUTTON_PRESS : GDK_3BUTTON_PRESS;

  gdk_event_put (&temp_event);
}

/*
 *--------------------------------------------------------------
 * gdk_exit_func
 *
 *   This is the "atexit" function that makes sure the
 *   library gets a chance to cleanup.
 *
 * Arguments:
 *
 * Results:
 *
 * Side effects:
 *   The library is un-initialized and the program exits.
 *
 *--------------------------------------------------------------
 */

static void
gdk_exit_func ()
{
  if (initialized)
    gdk_exit (0);
}

/*
 *--------------------------------------------------------------
 * gdk_x_error
 *
 *   The X error handling routine.
 *
 * Arguments:
 *   "display" is the X display the error orignated from.
 *   "error" is the XErrorEvent that we are handling.
 *
 * Results:
 *   Either we were expecting some sort of error to occur,
 *   in which case we set the "gdk_error_code" flag, or this
 *   error was unexpected, in which case we will print an
 *   error message and exit. (Since trying to continue will
 *   most likely simply lead to more errors).
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

static int
gdk_x_error (Display     *display,
	     XErrorEvent *error)
{
  char buf[64];

  if (gdk_error_warnings)
    {
      XGetErrorText (display, error->error_code, buf, 63);
      g_error ("%s", buf);
    }

  gdk_error_code = -1;
  return 0;
}

/*
 *--------------------------------------------------------------
 * gdk_x_io_error
 *
 *   The X I/O error handling routine.
 *
 * Arguments:
 *   "display" is the X display the error orignated from.
 *
 * Results:
 *   An X I/O error basically means we lost our connection
 *   to the X server. There is not much we can do to
 *   continue, so simply print an error message and exit.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

static int
gdk_x_io_error (Display *display)
{
  g_error ("an x io error occurred");
  return 0;
}

/*
 *--------------------------------------------------------------
 * gdk_signal
 *
 *   The signal handler.
 *
 * Arguments:
 *   "sig_num" is the number of the signal we received.
 *
 * Results:
 *   The signals we catch are all fatal. So we simply build
 *   up a nice little error message and print it and exit.
 *   If in the process of doing so another signal is received
 *   we notice that we are already exiting and simply kill
 *   our process.
 *
 * Side effects:
 *
 *--------------------------------------------------------------
 */

static RETSIGTYPE
gdk_signal (int sig_num)
{
  static int caught_fatal_sig = 0;
  char *sig;

  if (caught_fatal_sig)
    kill (getpid (), sig_num);
  caught_fatal_sig = 1;

  switch (sig_num)
    {
    case SIGHUP:
      sig = "sighup";
      break;
    case SIGINT:
      sig = "sigint";
      break;
    case SIGQUIT:
      sig = "sigquit";
      break;
    case SIGBUS:
      sig = "sigbus";
      break;
    case SIGSEGV:
      sig = "sigsegv";
      break;
    case SIGPIPE:
      sig = "sigpipe";
      break;
    case SIGTERM:
      sig = "sigterm";
      break;
    default:
      sig = "unknown signal";
      break;
    }

  g_print ("\n** ERROR **: %s caught\n", sig);
  gdk_exit (1);
}
