/*
 * xcb: Copyright (C) 1992,1993,1994 by Farrell McKay.
 * XView modifications provided by Danny Vanderryn.
 * mb/utf8 support by Marc Lehmann <pcg@goof.com>.
 *
 * Simple X interface to the cut buffers in an X server.
 * The program creates a window subdivided into a number of subwindows,
 * one per cut buffer.  The user may copy cut buffer contents around
 * using mouse buttons 1 and 2, or rotate the buffers using mouse
 * button 3.  Buffers may be cleared by using Shift-button 2.
 *
 * Note that this program assumes the cut buffers contain textual
 * information, and displays buffer contents using the XDrawString
 * function.  It is not suitable for use in any other environment.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  This software is provided "as is" without express or
 * implied warranty.
 */

/*
 * The Xutf8*-API seems to just add complexity for each and every code author
 * without providing any benefits (such as better font handling). To the
 * contrary, xfree86 seems to go into the (wrong) direction of "unicode fonts",
 * thereby forcing the user to accept their glyph style. Why. Why. Why???
 */

#if !defined(ATHENA) && !defined(MOTIF)
Bong ! Start again.
You must define either the 'ATHENA' or 'MOTIF' symbol when compiling.
#endif

#include <ctype.h>
#include <stdio.h>
#include <string.h>                /* for strcmp() */
#include <stdarg.h>
#include <stdlib.h>                /* for exit()... */
#include <unistd.h>                /* for read(), write() */
#include <X11/Xatom.h>                /* for pre-defined atom names */
#include <X11/StringDefs.h>        /* for XtNforeground et.al. */
#include <X11/Intrinsic.h>
#include <locale.h>

#ifdef ATHENA
#ifndef VMS
#include <X11/Xaw/Form.h>
#else
#include <Xaw/Form.h>
#endif
#endif

#ifdef MOTIF
#include <Xm/Frame.h>
#include <Xm/RowColumn.h>
#endif

#include "cb.h"
#include "patchlevel.h"

#define        eq(a,b)                (strcmp((a),(b)) == 0)
#define        max(a,b)        ((a) > (b)? (a): (b))

#define        XtNbufferCount        "bufferCount"        /* Application resources */
#define        XtCBufferCount        "BufferCount"
#define        XtNlayout        "layout"
#define        XtCLayout        "Layout"

#define        PGM_NAME        "xcb"
#define        PGM_CLASS        "Xcb"
#define        BUFINC                2048

static Display *dpy;
static Window root;
static XtAppContext app;
static Widget top, box, *wdg;
static Atom xa_compound_text, xa_utf8_string, xa_text;
static Atom delwin;
static Atom *atom;
static int natoms, nbuffs;
static Window seln;
static int use_utf8 = 0;

static Atom convert_to;                /* convert selection to this cut buffer */

#ifndef X_HAVE_UTF8_STRING
#define Xutf8TextPropertyToTextList XmbTextPropertyToTextList
#define Xutf8TextListToTextProperty XmbTextListToTextProperty
#endif

#define FONT_ELEMENT_SIZE 50

static const char *
i_strstr (const char *str, const char *ptn)
{
  const char *s2, *p2;
  for (; *str; str++)
    {
      for (s2 = str, p2 = ptn;; s2++, p2++)
	{
	  if (!*p2)
	    return str;
	  if (toupper (*s2) != toupper (*p2))
	    break;
	}
    }
  return NULL;
}

static const char *
Font_GetElement (const char *pattern, char *buf, int bufsiz, ...)
{
  const char *p, *v;
  char *p2;
  va_list va;

  va_start (va, bufsiz);
  buf[bufsiz - 1] = 0;
  buf[bufsiz - 2] = '*';
  while ((v = va_arg (va, char *)) != NULL)
    {
      p = i_strstr (pattern, v);
      if (p)
	{
	  strncpy (buf, p + 1, bufsiz - 2);
	  p2 = strchr (buf, '-');
	  if (p2)
	    *p2 = 0;
	  va_end (va);
	  return p;
	}
    }
  va_end (va);
  strncpy (buf, "*", bufsiz);
  return NULL;
}

static const char *
Font_GetSize (const char *pattern, int *size)
{
  const char *p;
  const char *p2 = NULL;
  int n = 0;

  for (p = pattern; 1; p++)
    {
      if (!*p)
	{
	  if (p2 != NULL && n > 1 && n < 72)
	    {
	      *size = n;
	      return p2 + 1;
	    }
	  else
	    {
	      *size = 16;
	      return NULL;
	    }
	}
      else if (*p == '-')
	{
	  if (n > 1 && n < 72 && p2 != NULL)
	    {
	      *size = n;
	      return p2 + 1;
	    }
	  p2 = p;
	  n = 0;
	}
      else if (*p >= '0' && *p <= '9' && p2 != NULL)
	{
	  n *= 10;
	  n += *p - '0';
	}
      else
	{
	  p2 = NULL;
	  n = 0;
	}
    }
}

/* from http://www.debian.org/doc/manuals/intro-i18n/ */
static XFontSet
XCreateFontSetWithGuess (Display * d, const char *pattern, char ***miss, int *n_miss, char **def)
{
  XFontSet fs;
  char *pattern2;
  int pixel_size, bufsiz;
  char weight[FONT_ELEMENT_SIZE], slant[FONT_ELEMENT_SIZE];

  /* No problem?  or 'fs' for pattern analysis */
  fs = XCreateFontSet (d, pattern, miss, n_miss, def);
  if (fs && !*n_miss)
    return fs;                        /* no need for font guessing */

  /* for non-iso8859-1 language and iso8859-1 specification */
  /* This 'fs' is only for pattern analysis. */
  if (!fs)
    {
      if (*n_miss)
        XFreeStringList (*miss);

      setlocale (LC_CTYPE, "C");
      fs = XCreateFontSet (d, pattern, miss, n_miss, def);
      setlocale (LC_CTYPE, "");
    }

  /* make XLFD font name for pattern analysis */
  if (fs)
    {
      XFontStruct **fontstructs;
      char **fontnames;
      XFontsOfFontSet (fs, &fontstructs, &fontnames);
      pattern = fontnames[0];
    }

  /* read elements of font name */
  Font_GetElement (pattern, weight, FONT_ELEMENT_SIZE,
                   "-medium-", "-bold-", "-demibold-", "-regular-", NULL);
  Font_GetElement (pattern, slant, FONT_ELEMENT_SIZE,
                   "-r-", "-i-", "-o-", "-ri-", "-ro-", NULL);
  Font_GetSize (pattern, &pixel_size);

  /* modify elements of font name to fit usual font names */
  if (!strcmp (weight, "*"))
    strncpy (weight, "medium", FONT_ELEMENT_SIZE);
  if (!strcmp (slant, "*"))
    strncpy (slant, "r", FONT_ELEMENT_SIZE);
  if (pixel_size < 3)
    pixel_size = 3;
  else if (pixel_size > 97)
    pixel_size = 97;

  /* build font pattern for better matching for various charsets */
  bufsiz = strlen (pattern) + FONT_ELEMENT_SIZE * 2 + 2 * 2 + 58;
  pattern2 = (char *) malloc (bufsiz);
  if (pattern2)
    {
      snprintf (pattern2, bufsiz - 1, "%s,"
                "-*-*-%s-%s-*-*-%d-*-*-*-*-*-*-*,"
                "-*-*-*-*-*-*-%d-*-*-*-*-*-*-*,*",
                pattern,
                weight, slant, pixel_size,
                pixel_size);
      pattern = pattern2;
    }
  if (*n_miss)
    XFreeStringList (*miss);
  if (fs)
    XFreeFontSet (d, fs);

  /* create fontset */
  fs = XCreateFontSet (d, pattern, miss, n_miss, def);
  if (pattern2)
    free (pattern2);

  return fs;
}

static
Boolean 
CvtStringToFontSet (dpy, args, num_args, fromVal, toVal, closure_ret)
     Display *dpy;
     XrmValuePtr args;
     Cardinal *num_args;
     XrmValuePtr fromVal;
     XrmValuePtr toVal;
     XtPointer *closure_ret;
{
  XFontSet f;
  char **missing_charset_list;
  int missing_charset_count;
  char *def_string;

  f = XCreateFontSetWithGuess (dpy, (char *) fromVal->addr,
		&missing_charset_list, &missing_charset_count, &def_string);
  /* Free any returned missing charset list */
  if (missing_charset_count)
    {
      fprintf (stderr, "Missing charsets in String to FontSet conversion (%s)\n", missing_charset_list[0]);
      XFreeStringList (missing_charset_list);
    }

  if (!f)
    {
      f = XCreateFontSetWithGuess (dpy, "-*-*-*-R-*-*-*-120-*-*-*-*",
                    &missing_charset_list, &missing_charset_count, &def_string);
    }

  *(XFontSet *) (toVal->addr) = f;

  return f ? True : False;
}

/*
 * Fetch the contents of cut buffer n from the root window.
 */
static char *
fetch_buffer (a, nb, force_mb)
     Atom a;
     int *nb;
     int force_mb;
{
  unsigned long after;
  char **list;
  char *data;
  int count;
  XTextProperty pt;

  *nb = 0;
  if (XGetWindowProperty (dpy, root, a,
                          0L, 10000000L, False, AnyPropertyType,
                          &pt.encoding, &pt.format, &pt.nitems,
                          &after, &pt.value) != Success || !pt.nitems)
    return strdup ("");

  if (pt.nitems)
    {
      (force_mb ? XmbTextPropertyToTextList : Xutf8TextPropertyToTextList)
        (dpy, &pt, &list, &count);

      data = strdup (list[0]);
      *nb = strlen (data);

      XFreeStringList (list);
      XFree (pt.value);
    }
  else
    data = strdup ("");

  return data;
}

/*
 * Store the string p into cut buffer n on the root window.
 */
static void
store_buffer (p, nb, atom, force_mb)
     char *p;
     int nb;
     Atom atom;
     int force_mb;
{
  XTextProperty pt;

  (force_mb ? XmbTextListToTextProperty : Xutf8TextListToTextProperty)
    (dpy, &p, 1, XStdICCTextStyle, &pt);

  XChangeProperty (dpy, root, atom, pt.encoding,
                   8, PropModeReplace, pt.value, pt.nitems);

  XFree (pt.value);
}

/*
 * Add an atom to the program's atom cache.
 */
static Atom
get_atom (n, ifexists)
     int n, ifexists;
{
  char tmp[32];

  if (n >= natoms)
    {
      atom = (Atom *) XtRealloc ((char *) atom, (n + 1) * sizeof (Atom));
      while (natoms < n + 1)
        atom[natoms++] = 0;
    }

  if (!atom[n])
    {
      sprintf (tmp, "CUT_BUFFER%d", n);
      atom[n] = XInternAtom (dpy, tmp, (Bool) ifexists);
    }

  return atom[n];
}

static void
initialize_properties (void)
{
  int i;

  for (i = nbuffs - 1; i >= 0; i--)
    {
      Atom a;
      int nbytes;
      char *data;

      a = get_atom (i, False);
      data = fetch_buffer (a, &nbytes, 0);
      store_buffer (data, nbytes, a, 0);

      free (data);
    }
}

/*
 * Draw a string in the window with top-left corner justification.
 */
static void
place_text (cb, str, len, y)
     CbWidget cb;
     char *str;
     int len, y;
{
  int cols;
  GC gc;

  gc = (cb->core.window == seln) ? cb->gc_inv : cb->gc;
  if (y <= (int) cb->core.height)
    {
      /* we rely on clipping... */
      /*cols = ((int) cb->core.width + cb->font_width - 1)
         / cb->font_width; */
      /*len = min(len, cols); */
      if (len > 0)
        {
          y -= cb->font_ascent;
#if X_HAVE_UTF8_STRING
          Xutf8DrawImageString
#else
          XmbDrawImageString
#endif
            (dpy, cb->core.window, cb->fontset, gc, 0, y, str, len);
        }
    }
}

/*
 * ============================================================================
 * The following collection of functions and data structures define
 * the cb widget.  Each cb widget displays a single cut buffer value
 * in a window, and provides cut and paste access to that buffer.
 */

static void
cb_initialize (req, wdg, args, nargs)        /*ARGSUSED */
     Widget req, wdg;
     ArgList args;
     Cardinal *nargs;
{
  CbWidget cb = (CbWidget) wdg;
  XFontSetExtents *xfe = XExtentsOfFontSet (cb->fontset);

  cb->font_width = xfe->max_logical_extent.width;
  cb->font_height = xfe->max_logical_extent.height;
  cb->font_ascent = xfe->max_logical_extent.y;
  cb->gc = 0;
  cb->gc_inv = 0;
}

static void
cb_realize (wdg, mask, attrs)
     Widget wdg;
     XtValueMask *mask;
     XSetWindowAttributes *attrs;
{
  CbWidget cb = (CbWidget) wdg;
  XtGCMask v_mask = 0L;
  XGCValues values;

  XtCreateWindow (wdg, InputOutput, CopyFromParent, *mask, attrs);
  XStoreName (dpy, cb->core.window, cb->core.name);

  values.foreground = cb->fgnd;
  values.background = cb->core.background_pixel;
  values.plane_mask = AllPlanes;
  v_mask = GCForeground | GCBackground | GCPlaneMask;
  cb->gc = XtGetGC (wdg, v_mask, &values);

  values.foreground = cb->core.background_pixel;
  values.background = cb->fgnd;
  cb->gc_inv = XtGetGC (wdg, v_mask, &values);
}

/*
 * Redraw the contents of one of the subwindows.
 * The function assumes the cut buffer contains text data, and parses
 * it accordingly.  The data is split into lines at each '\n' character.
 * Lines which extend beyond the subwindow's borders are clipped; no
 * wrap-around processing is done.
 * Keep it simple.
 */
static void
cb_redisplay (wdg, event, region)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     Region region;
{
  CbWidget cb = (CbWidget) wdg;
  char *p, *pp, *base;
  int y, nbytes;

  y = 0;
  p = pp = base = fetch_buffer (cb->atom, &nbytes, 0);
  while (pp < base + nbytes)
    {
      if (*pp == '\n')
        {
          place_text (cb, p, pp - p, y);
          p = pp + 1;
          y += cb->font_height;
        }
      pp++;
    }
  place_text (cb, p, pp - p, y);
  XFree (base);
}

static void
cb_destroy (wdg)
     Widget wdg;
{
  CbWidget cb = (CbWidget) wdg;

  XtReleaseGC (wdg, cb->gc);
  XtReleaseGC (wdg, cb->gc_inv);
}

/*
 * Make this widget the owner of the PRIMARY selection.
 * The window contents are then redrawn with highlighting.
 *
 * It seems that if a XSetSelectionOwner is performed on a client's
 * window, no SelectionClear event is generated if another window
 * within the same client is already the selection owner.
 * This function originally attempted to avoid that problem and force
 * a SelectionClear event by firstly setting the selection ownership
 * to None and then setting it to this widget's window.  Apparently
 * that didn't work for all X servers.
 *
 * Therefore the function must do the SelectionClear logic itself,
 * which means it must know when another xcb widget has selection
 * ownership, which brings about the need for the 'seln' global variable.
 * This breaks all the rules for object oriented widgets.  Disgusting, no?
 */
static void
cb_cut (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  CbWidget cb = (CbWidget) wdg;
  Window win = cb->core.window;
  Window w, tmp;

  if (win == seln)
    return;

  XSetSelectionOwner (dpy, XA_PRIMARY, win, event->xbutton.time);
  w = XGetSelectionOwner (dpy, XA_PRIMARY);
  if (seln && w != seln)
    {
      tmp = seln;
      seln = 0;
      XClearArea (dpy, tmp, 0, 0, 0, 0, False);
      cb_redisplay (XtWindowToWidget (dpy, tmp), (XEvent *) 0, (Region) 0);
    }

  if (w == win)
    {
      seln = win;
      XClearArea (dpy, win, 0, 0, 0, 0, False);
      cb_redisplay (wdg, (XEvent *) 0, (Region) 0);
    }
}

static void
cb_paste (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  CbWidget cb = (CbWidget) wdg;
  Window w;
  char *ptr;
  int n;

  w = XGetSelectionOwner (dpy, XA_PRIMARY);
  if (w == None)
    {
      ptr = fetch_buffer (atom[0], &n, 0);        /* copy from cb0 */
      store_buffer (ptr, n, cb->atom, 0);
      XFree (ptr);
    }
  else if (w != cb->core.window)
    XConvertSelection (dpy, XA_PRIMARY, xa_utf8_string,
                       convert_to = cb->atom, XtWindow (wdg), event->xbutton.time);
}

static void
cb_clear (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  CbWidget cb = (CbWidget) wdg;
  Window win = cb->core.window;

  store_buffer ("", 0, cb->atom, 0);
  if (win == seln)
    {
      seln = 0;
      XSetSelectionOwner (dpy, XA_PRIMARY, None, event->xbutton.time);
    }
}

static void
cb_rotate (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  int n = 0;

  if (*nparms > 0)
    n = atoi (parms[0]);

  if (n != 0)
    XRotateWindowProperties (dpy, root, atom, nbuffs, n);
}

static void
cb_quit (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  exit (0);
}

/*
 * Clear and redraw the widget's window.
 */
static void
cb_refresh (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  XClearArea (dpy, wdg->core.window, 0, 0, 0, 0, False);
  cb_redisplay (wdg, (XEvent *) 0, (Region) 0);
}

/*
 * Someone or something wants a copy of the current PRIMARY selection.
 * Such a request is only satisfied if the target type is STRING.
 * (No conversion facilities are provided by this program).
 * The selection request is met by copying the current contents
 * of the cut buffer to the target window+atom.
 */
static void
cb_selreq (wdg, event, parms, nparms)        /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  int nbytes;
  char *ptr;
  XSelectionEvent notify;
  XSelectionRequestEvent *rq;
  CbWidget cb = (CbWidget) wdg;
  Window win = cb->core.window;

  rq = (XSelectionRequestEvent *) event;

  notify.type = SelectionNotify;
  notify.display = rq->display;
  notify.requestor = rq->requestor;
  notify.selection = rq->selection;
  notify.target = rq->target;
  notify.property = None;
  notify.time = rq->time;

  if (win == seln)
    {
      XICCEncodingStyle style;
      Atom target = None;

      if (rq->target == XA_STRING)
        {
          target = XA_STRING;
          style = XStringStyle;
        }
      else if (rq->target == xa_text)
        {
          target = xa_compound_text;
          style = XStdICCTextStyle;
        }
      else if (rq->target == xa_compound_text)
        {
          target = xa_compound_text;
          style = XCompoundTextStyle;
        }
#ifdef X_HAVE_UTF8_STRING
      else if (rq->target == xa_utf8_string)
        {
          target = xa_utf8_string;
          style = XUTF8StringStyle;
        }
#endif

      if (target != None)
        {
          XTextProperty pt;
          char *fl;

          fl = fetch_buffer (cb->atom, &nbytes, 0);
          Xutf8TextListToTextProperty (dpy, &fl, 1, style, &pt);
          XFree (fl);

          XChangeProperty (dpy, rq->requestor, rq->property,
                           pt.encoding, 8, PropModeReplace,
                           pt.value, pt.nitems);

          notify.property = rq->property;
          XFree (pt.value);
        }
    }

  XSendEvent (dpy, rq->requestor, False, 0, (XEvent *) & notify);
}

/*
 * Boo hiss, someone has taken the PRIMARY selection ownership
 * away from this widget.  The current window contents must
 * be redrawn without highlighting.
 */
static void
cb_selclear (wdg, event, parms, nparms)                /*ARGSUSED */
     Widget wdg;
     XEvent *event;
     String *parms;
     Cardinal *nparms;
{
  CbWidget cb = (CbWidget) wdg;

  if (event->xproperty.atom != XA_PRIMARY)
    return;

  seln = 0;
  XClearArea (dpy, cb->core.window, 0, 0, 0, 0, False);
  cb_redisplay (wdg, (XEvent *) 0, (Region) 0);
}

static XtResource resources[] =
{
#define offset(field)                XtOffset(CbWidget, field)
        /* {name, class, type, size, offset, default_type, default_addr}, */
  {XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
   offset (fgnd), XtRString, (XtPointer) "XtDefaultForeground"},
  {XtNfontSet, XtCFontSet, XtRFontSet, sizeof (XFontSet),
   offset (fontset), XtRString, (XtPointer) "XtDefaultFontSet"},
  {XtNatom, XtCAtom, XtRAtom, sizeof (Atom),        /* internal use */
   offset (atom), XtRImmediate, (XtPointer) 0},
#undef offset
};

static XtActionsRec actions[] =
{
  {"cut", cb_cut},
  {"paste", cb_paste},
  {"clear", cb_clear},
  {"rotate", cb_rotate},
  {"quit", cb_quit},
  {"refresh", cb_refresh},
  {"selreq", cb_selreq},
  {"selclear", cb_selclear},
};

static char cb_transl[] = "\
        <Btn1Down>:                cut() \n\
        Shift <Btn2Down>:        clear() \n\
        <Btn2Down>:                paste() \n\
        Shift <Btn3Down>:        rotate(-1) \n\
        <Btn3Down>:                rotate(1) \n\
        <Key>Left:                rotate(-1) \n\
        <Key>Right:                rotate(1) \n\
        <Key>Up:                rotate(-1) \n\
        <Key>Down:                rotate(1) \n\
        <Key>q:                        quit() \n\
        <SelReq>:                selreq() \n\
        <SelClr>:                selclear() \n\
        ";

CbClassRec cbClassRec =
{
  {
    (WidgetClass) & widgetClassRec,        /* superclass */
    "Buffer",                        /* class_name */
    sizeof (CbRec),                /* widget_size */
    NULL,                        /* class_initialize */
    NULL,                        /* class_part_initialize */
    FALSE,                        /* class_inited */
    cb_initialize,                /* initialize */
    NULL,                        /* initialize_hook */
    cb_realize,                        /* realize */
    actions,                        /* actions */
    XtNumber (actions),                /* num_actions */
    resources,                        /* resources */
    XtNumber (resources),        /* num_resources */
    NULLQUARK,                        /* xrm_class */
    TRUE,                        /* compress_motion */
    TRUE,                        /* compress_exposure */
    TRUE,                        /* compress_enterleave */
    FALSE,                        /* visible_interest */
    cb_destroy,                        /* destroy */
    NULL,                        /* resize */
    cb_redisplay,                /* expose */
    NULL,                        /* set_values */
    NULL,                        /* set_values_hook */
    XtInheritSetValuesAlmost,        /* set_values_almost */
    NULL,                        /* get_values_hook */
    NULL,                        /* accept_focus */
    XtVersion,                        /* version */
    NULL,                        /* callback_private */
    cb_transl,                        /* tm_table */
    XtInheritQueryGeometry,        /* query_geometry */
    XtInheritDisplayAccelerator,        /* display_accelerator */
    NULL                        /* extension */
  },
};

WidgetClass cbWidgetClass = (WidgetClass) & cbClassRec;

/*
 * Here endeth the section concerned with the cb widget.
 * Normal viewing shall now be resumed.
 * ============================================================================
 */


static void
usage ()
{
  fprintf (stderr,
           "Usage: %s [Xt option] [-l layout] [-n count] [-p|-s|-S list] [-r count]\n",
           PGM_NAME);
  exit (1);
}

/*
 * Gracefully exit after an XIO error, or after a "delete window"
 * directive from the window manager.
 * xioerror() avoids messy error messages sometimes seen from xterm
 * or in the xdm-errors file when forcibly destroying the client program.
 */
static int
xioerror (d)                        /*ARGSUSED */
     Display *d;
{
  exit (1);                        /*NOTREACHED */
}

static void
wmdel (wdg, ptr, ep, cont)        /*ARGSUSED */
     Widget wdg;
     XtPointer ptr;
     XEvent *ep;
     Boolean *cont;
{
  if (ep->type == ClientMessage && ep->xclient.data.l[0] == delwin)
    exit (0);
}

/*
 * Print the contents of a cut buffer on stdout.
 */
static void
doprint (n, ptr, nb)
     int n;
     char *ptr;
     int nb;
{
  Atom a;

  a = get_atom (n, True);
  if (a)
    {
      ptr = fetch_buffer (a, &nb, !use_utf8);

      if (write (1, ptr, nb) != nb)
        {
          fprintf (stderr, "Write error\n");
          exit (1);
        }

      XFree (ptr);
    }
}

/*
 * Load a new value into one of the cut buffers.
 */
static void
doset (n, ptr, nb)
     int n;
     char *ptr;
     int nb;
{
  char *str = malloc (nb + 1);

  memcpy (str, ptr, nb);
  str[nb] = 0;

  store_buffer (str, nb, get_atom (n, False), !use_utf8);
  free (str);
}

static void
timeout (arg, id)
     char *arg;
     XtIntervalId *id;
{
  exit (2);
}

/*
 * Copy the PRIMARY selection into a cut buffer.
 */
static void
dogetseln (n, ptr, nb)
     int n;
     char *ptr;
     int nb;
{
  char *data;
  int nbytes;
  Atom atm;
  XEvent event;

  atm = get_atom (n, False);
  if (XGetSelectionOwner (dpy, XA_PRIMARY) == None)
    {
      if (n != 0)
        {
          data = fetch_buffer (atom[0], &nbytes, 0);
          store_buffer (data, nbytes, atm, 0);
          XFree (data);
        }
      return;
    }

  XSelectInput (dpy, root, PropertyChangeMask);
  XConvertSelection (dpy, XA_PRIMARY, xa_utf8_string, atm, root, CurrentTime);
  XtAppAddTimeOut (app, 3000, timeout, (char *) 0);

  for (;;)
    {
      XtAppNextEvent (app, &event);
      if (event.type == PropertyNotify
          && event.xproperty.window == root
          && event.xproperty.atom == atm
          && event.xproperty.state == PropertyNewValue)
        {
          return;
        }

      XtDispatchEvent (&event);        /* This cannot happen. !!?? */
    }
}

/*
 * Process an ASCII list of cut buffer numbers.
 * Lists must obey the form "buffno[,buffno...]"
 * where buffno is a non-negative integer or a range
 * of the form M-N.  A processing function is called
 * for each buffer number in the list.  Duplicates and
 * list ordering is significant.
 */
static void
dolist (list, fn, data, nbytes)
     char *list;
     void (*fn) ();
     char *data;
     int nbytes;
{
  int m, n, x;

  while (*list)
    {
      if (!isdigit (*list))
        usage ();
      for (m = 0; isdigit (*list); list++)
        m = m * 10 + *list - '0';

      (*fn) (m, data, nbytes);

      if (*list == '-')
        {
          list++;
          if (!isdigit (*list))
            usage ();
          for (n = 0; isdigit (*list); list++)
            n = n * 10 + *list - '0';

          x = (m > n) ? -1 : 1;
          while (m != n)
            {
              m += x;
              (*fn) (m, data, nbytes);
            }
        }

      if (*list == ',')
        list++;
      else if (*list)
        usage ();
    }
}

/*
 * Perform a task mode command, i.e.
 * do something to the cut buffers immediately,
 * without the need to create any X windows first.
 */
static void
dotask (cmd, arg)
     int cmd;
     char *arg;
{
  char *ptr;
  int i, n, nb;

  ptr = (char *) 0;
  n = nb = 0;

  switch (cmd)
    {
    case 'p':                        /* print one or more buffers */
      dolist (arg, doprint, (char *) 0, 0);
      break;
    case 'r':                        /* rotate the buffer contents */
      n = atoi (arg);

      if (n == 0)
        break;

      initialize_properties ();

      XRotateWindowProperties (dpy, root, atom, nbuffs, n);
      break;
    case 's':                        /* store data in one or more buffers */
      do
        {
          ptr = XtRealloc (ptr, nb + BUFINC);
          i = BUFINC;
          do
            {
              n = read (0, ptr + nb, i);
              nb += n;
              i -= n;
            }
          while (n > 0 && i > 0);
        }
      while (n > 0);

      if (n == -1)
        {
          fprintf (stderr, "Read error\n");
          exit (1);
        }

      dolist (arg, doset, ptr, nb);
      XtFree (ptr);
      break;
    case 'S':
      dolist (arg, dogetseln, (char *) 0, 0);
      break;
    }
}

typedef struct
{
  int nbuffs;
  char *layout;
}
ares_t, *ares_ptr;

static ares_t ares;

static XtResource res[] =
{
#define offset(field)                XtOffset(ares_ptr, field)
  {XtNbufferCount, XtCBufferCount, XtRInt, sizeof (int),
   offset (nbuffs), XtRImmediate, (XtPointer) 8},
  {XtNlayout, XtCLayout, XtRString, sizeof (char *),
   offset (layout), XtRImmediate, "horiz"},
#undef offset
};

static char *def[] =
{                                /* default resource values */
  ".bufferCount:                8",
  ".layout:                horizontal",
  "*fontSet:                XtDefaultFontSet",
  "*Buffer.width:                60",
  "*Buffer.height:        60",
  0,
};

static XrmOptionDescRec opt[] =
{
  {"-n", ".bufferCount", XrmoptionSepArg, (caddr_t) 8},
  {"-l", ".layout", XrmoptionSepArg, (caddr_t) "horiz"},
};

/*
 * Parse the command line options, and
 * perform all the windows initializations.
 */
static void
init (argc, argv)
     int argc;
     char **argv;
{
  int i, n;
  char **p;
  char name[16];
  Arg args[3];

  /*
   * Set up the atoms that we already know about.
   */
  natoms = 8;
  atom = (Atom *) XtMalloc (natoms * sizeof (Atom));
  atom[0] = XA_CUT_BUFFER0;
  atom[1] = XA_CUT_BUFFER1;
  atom[2] = XA_CUT_BUFFER2;
  atom[3] = XA_CUT_BUFFER3;
  atom[4] = XA_CUT_BUFFER4;
  atom[5] = XA_CUT_BUFFER5;
  atom[6] = XA_CUT_BUFFER6;
  atom[7] = XA_CUT_BUFFER7;

  /*
   * Initialize the toolkit, parse the command line,
   * initialize the resources database, and find out
   * how many buffers to deal with.
   */
  XtSetLanguageProc (0, 0, 0);
  top = XtAppInitialize (&app, PGM_CLASS, opt, 2, &argc, argv, def, 0, 0);
  dpy = XtDisplay (top);
  root = RootWindow (dpy, DefaultScreen (dpy));

  XtSetTypeConverter(XtRString,   /* source type */
                     XtRFontSet,             /* target type */
                     CvtStringToFontSet,     /* converter routine */
                     (XtConvertArgList) NULL,
                     /* args for converter routine */
                     0,                      /*# args for converter routine */
                     XtCacheAll,             /*  caching instructions */
                     NULL);                  /*  destructor function */

  XtGetApplicationResources (top, &ares, res, XtNumber (res), 0, 0);
  nbuffs = max (ares.nbuffs, 1);

  /* search for the -u and -v switches first */
  for (p = argv + 1; p < argv + argc; p++)
    {
      if (eq (*p, "-u"))
        use_utf8 = 1;
      else if (eq (*p, "-V"))
        {
          printf ("xcb version %s\n", version);
          exit (0);
        }
    }

  /*
   * If the command line contains one of the task mode
   * switches (print, set, rotate), it is processed here.
   * If there are multiple task mode args, only the first
   * one is processed.
   */
  for (p = argv + 1; p < argv + argc; p++)
    {
      if (eq (*p, "-p") || eq (*p, "-r") || eq (*p, "-s") || eq (*p, "-S"))
        {
          if (p == argv + argc - 1)
            usage ();

          dotask (p[0][1], p[1]);
          XCloseDisplay (dpy);
          exit (0);
        }
    }

  /*
   * If no task mode request has been made of us, this code
   * proceeds with the rest of the windows initialization.
   * The container widget is created, the sub-widgets are
   * created and then everything is realized.
   */
  if (argc > 1)
    usage ();

  wdg = (Widget *) XtMalloc (nbuffs * sizeof (Widget));

#ifdef ATHENA
  box = XtCreateWidget ("container", formWidgetClass, top, 0, 0);
  XtManageChild (box);

  for (i = 0; i < nbuffs; i++)
    {
      XtSetArg (args[0], XtNatom, get_atom (i, False));
      n = 1;
      if (i > 0)
        {
          if (ares.layout[0] == 'h')
            {
              XtSetArg (args[1], XtNfromHoriz, wdg[i - 1]);
              XtSetArg (args[2], XtNfromVert, 0);
              n = 3;
            }

          if (ares.layout[0] == 'v')
            {
              XtSetArg (args[1], XtNfromVert, wdg[i - 1]);
              XtSetArg (args[2], XtNfromHoriz, 0);
              n = 3;
            }
        }

      sprintf (name, "buffer%d", i);
      wdg[i] = XtCreateWidget (name, cbWidgetClass, box, args, n);
      XtManageChild (wdg[i]);
    }
#endif

#ifdef MOTIF
  n = 0;

  if (ares.layout[0] == 'h')
    {
      XtSetArg (args[0], XmNorientation, XmHORIZONTAL);
      n = 1;
    }

  if (ares.layout[0] == 'v')
    {
      XtSetArg (args[0], XmNorientation, XmVERTICAL);
      n = 1;
    }

  box = XtCreateWidget ("container", xmRowColumnWidgetClass, top, args, n);
  XtManageChild (box);

  for (i = 0; i < nbuffs; i++)
    {
      Widget frame;

      XtSetArg (args[0], XtNatom, get_atom (i, False));
      n = 1;

      sprintf (name, "frame%d", i);
      frame = XmCreateFrame (box, name, 0, 0);
      XtManageChild (frame);

      sprintf (name, "buffer%d", i);
      wdg[i] = XtCreateWidget (name, cbWidgetClass, frame, args, n);
      XtManageChild (wdg[i]);
    }
#endif

  XSelectInput (dpy, root, PropertyChangeMask);
  XSetIOErrorHandler (xioerror);

  XtRealizeWidget (top);

  delwin = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
  XSetWMProtocols (dpy, XtWindow (top), &delwin, 1);
  XtAddEventHandler (top, 0, True, wmdel, (XtPointer) 0);

  xa_compound_text = XInternAtom (dpy, "COMPOUND_TEXT", False);
  xa_utf8_string = XInternAtom (dpy, "UTF8_STRING", False);
  xa_text = XInternAtom (dpy, "TEXT", False);

  if (!xa_utf8_string)
    xa_utf8_string = xa_compound_text;

  initialize_properties ();
}

/*
 * Process incoming events.
 * The program needs to know when a cut buffer value changes.
 * We achieve this by eavesdropping on PropertyNotify events arising
 * on the root window.  If such an event is delivered to us, the
 * function immediately clears the window displaying that property
 * and causes an Expose event to be generated for the window.
 * This is horrible nasty stuff.
 */
static void
xevents ()
{
  XEvent event;
  int i;

  for (;;)
    {
      XtAppNextEvent (app, &event);

      if (event.type == PropertyNotify)
        {
          for (i = 0; i < nbuffs; i++)
            if (event.xproperty.atom == atom[i])
              XClearArea (dpy, XtWindow (wdg[i]), 0, 0, 0, 0, True);
        }
      else if (event.type == SelectionNotify)
        {
          if (event.xselection.property)
            {
              /* horribly inefficient... */
              XConvertSelection (dpy, XA_PRIMARY, event.xselection.target,
                                 convert_to, root, event.xselection.time);
            }
          else
            {
              Atom target;

              if (event.xselection.target == xa_text)
                target = XA_STRING;
              else if (event.xselection.target == xa_compound_text)
                target = xa_text;
              else if (event.xselection.target == xa_utf8_string)
                target = xa_compound_text;
              else
                target = None;

              if (target != None)
                XConvertSelection (dpy, XA_PRIMARY, target,
                                   convert_to, event.xselection.requestor, event.xselection.time);
            }
        }
      else
        (void) XtDispatchEvent (&event);
    }
}

main (argc, argv)
     int argc;
     char **argv;
{
  init (argc, argv);
  xevents ();
  return 0;
}
