/* Last edited by Patrick Gundlach (only collected the diffs)
 *  (gundlach@irb.cs.uni-dortmund.de)
 *
 * $Date: 1998/09/17 12:59:34 $
 * $Id: find_window.c,v 1.4 1998/09/17 12:59:34 gundlach Exp $
 * $Log: find_window.c,v $
 * Revision 1.4  1998/09/17 12:59:34  gundlach
 * Workaround for the problem in CDE 1.2: when raising the window on a new
 * workspace the window was raised on the originating workspace as well.
 *
 * Revision 1.3  1998/08/11 08:44:39  gundlach
 * Patch from Larry W. Virden (lvirden@cas.org)
 *
 * "The following patches address a variety of warnings issued by my compiler
 * or lint program."
 *
 * Revision 1.2  1998/08/07 08:33:14  gundlach
 *
 * Patch from rlhamil@smart.net (Richard L. Hamilton):
 *
 * * Previously, when raising a window, the current workspace would always
 *   be changed to the first workspace the window occupied.  Now, it checks
 *   first whether the target window also occupies the current workspace, and
 *   only changes the current workspace if it doesn't.
 *
 * * I added a call to XtFree() to release the list of workspaces created
 *   by DtWsmGetWorkspacesOccupied().  This does *not* mean the program
 *   has been checked for other memory leaks, only that that particular one
 *   is plugged.
 *
 * Revision 1.1  1998/08/06 12:43:35  gundlach
 * Initial revision
 *
 * 
 * 
 * Original: Tue Oct  8 10:35:35 1996 by David Kaelbling <drk@x.org>
 * Changes made by
 * David M. Karr (dkarr@nmo.gtegsc.com)
 * Richard L. Hamilton (rlhamil@mindwarp.smart.net) 
 * Michael Piotrowski (mxp@linguistik.uni-erlangen.de)
 * Mike Stroyan (mike_stroyan@fc.hp.com)

 * A tool to help find lost windows.  Requires Motif 1.2 and libXmu,
 * although support for Motif 2.x and CDE 1.x is included.
 *
 * Future ideas:
 *      - Recognize/show/change-to workspaces of windows.
 *      - Auto-pan to windows.
 *      - Special treatment for window groups and transient windows.
 *      - Automatic updates based on top-level window events.
 *      - Show geometry?
 *      - Flash selected windows?
 *
 *
 * It was successfully compiled under solaris SunOS 5.5.1 with 
 * cc -c find_window.c -I/usr/openwin/include -I/usr/dt/share/include  -DCDE
 * cc -o find_window find_window.o  -L/usr/openwin/lib  -L/usr/dt/lib \ 
 *    -R/usr/dt/lib -R/usr/openwin/lib -lDtSvc -ltt -lm -lXm -lXt -lX11 -lXmu 
 *
 * It was compiled under HP-UX 10.20 with
 * cc -Ae -DCDE -I/usr/dt/include -L/usr/dt/lib -o find_window \
 *  find_window.c -lDtSvc -ltt -lXm -lXmu -lXt -lX11
 *
 * Original comment: To compile and link:
 * cc -g -I/cs/include/motif-1.2 -c find_window.c
 * cc -o find_window find_window.o -L/cs/lib/motif -R/cs/lib/motif \
 *      -lXm -lXmu -lXt -lX11 -lgen
 *
 * Compile time options:
 * To include support for CDE workspaces compile with -DCDE and link
 * with -lDtSvc -ltt (before -lXm).
 *
 * Some of this code was borrowed from xlsclients, which requires the
 * following copyright notice.
 */

/*
 * Copyright 1989 Massachusetts Institute of Technology
 *
 * 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 appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>             /* for qsort() */
#include <ctype.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/Xmu/WinUtil.h>
#include <X11/Intrinsic.h>
#include <Xm/XmAll.h>
#ifdef CDE
#include <Dt/Wsm.h>
#endif

#if XmVERSION >= 2
#define TAB_PAD         1.9     /* Spacing (in TAB_UNITS) between columns. */
#define TAB_UNITS       XmFONT_UNITS
#define RENDITION_TAG   XmFONTLIST_DEFAULT_TAG
#endif /* XmVERSION >= 2 */

/* Our application resource class. */
#define APP_CLASS       "WindowFinder"

/* mwm version of the xPropWMState structure: */
typedef struct _PropWMState {
  unsigned long state;
  unsigned long icon;
} PropWMState;

#define PROP_WM_STATE_ELEMENTS          2

/* Using XmStrings this way is very inefficient in Motif 1.2, but is */
/* the only standard method for dealing with compound text properties. */
typedef struct _WindowDataRec {
  Window   window;
  int      state;
  int      screen;
  XmString machine;             /* May be derived from compound text. */
  XmString instance;            /* Must be in XA_STRING format. */
  XmString app_class;           /* Must be in XA_STRING format. */
  XmString name;                /* May be derived from compound text. */
  XmString icon_name;           /* May be derived from compound text. */
  XmString command;             /* Unknown format, assumed to be XA_STRING. */
  XmString item;
  char    *item_text;           /* Used for sorting items. */
} WindowDataRec, *WindowData;

/* Application Resources */

typedef struct _ApplicationData {
  XmString      iconic;         /* Used to display window state. */
  XmString      normal;         /* Used to display window state. */
  XmString      withdrawn;      /* Used to display window state. */
  XmString      unknown;        /* Used to display window state. */
} ApplicationData;

static XtResource app_resources[] = {
  { "iconic", XmCXmString, XmRXmString,
      sizeof(XmString), XtOffsetOf(ApplicationData, iconic),
      XtRString, "iconic" },
  { "normal", XmCXmString, XmRXmString,
      sizeof(XmString), XtOffsetOf(ApplicationData, normal),
      XtRString, "normal" },
  { "withdrawn", XmCXmString, XmRXmString,
      sizeof(XmString), XtOffsetOf(ApplicationData, withdrawn),
      XtRString, "withdrawn" },
  { "unknown", XmCXmString, XmRXmString,
      sizeof(XmString), XtOffsetOf(ApplicationData, unknown),
      XtRString, "unknown" }
};

ApplicationData app_data;

/* Command line options. */

/* Add parallel entries in option_help! */
static XrmOptionDescRec options[] = {
  { "-auto-exit", "*auto_exit.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+auto-exit", "*auto_exit.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-all-screens", "*all_screens.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+all-screens", "*all_screens.set", XrmoptionNoArg, (XtPointer) "Off" },
#ifdef CDE
  { "-all-workspaces", "*all_workspaces.set",
      XrmoptionNoArg, (XtPointer) "On" },
  { "+all-workspaces", "*all_workspaces.set",
      XrmoptionNoArg, (XtPointer) "Off" },
#endif /* CDE */
  { "-deformed-windows", "*allow_deformed.set",
      XrmoptionNoArg, (XtPointer) "On" },
  { "+deformed-windows", "*allow_deformed.set",
      XrmoptionNoArg, (XtPointer) "Off" },
  { "-iconic-windows", "*list_iconic.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+iconic-windows", "*list_iconic.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-normal-windows", "*list_normal.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+normal-windows", "*list_normal.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-withdrawn-windows", "*list_withdrawn.set",
      XrmoptionNoArg, (XtPointer) "On" },
  { "+withdrawn-windows", "*list_withdrawn.set",
      XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-id", "*window.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-id", "*window.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-state", "*state.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-state", "*state.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-screen", "*screen.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-screen", "*screen.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-machine", "*machine.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-machine", "*machine.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-instance", "*instance.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-instance", "*instance.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-class", "*class.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-class", "*class.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-name", "*name.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-name", "*name.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-icon-name", "*icon_name.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-icon-name", "*icon_name.set", XrmoptionNoArg, (XtPointer) "Off" },
  { "-show-command", "*command.set", XrmoptionNoArg, (XtPointer) "On" },
  { "+show-command", "*command.set", XrmoptionNoArg, (XtPointer) "Off" }
};

/* Help text for options. */
static char *option_help[] = {
  "    %s\t\texit after performing an action\n",
  NULL,
  "    %s\tsearch all screens for windows\n",
  NULL,
#ifdef CDE
  "    %s\tsearch all workspaces for windows\n",
  NULL,
#endif
  "    %s\tinclude deformed windows\n",
  NULL,
  "    %s\tinclude iconified windows\n",
  NULL,
  "    %s\tinclude normal windows\n",
  NULL,
  "    %s\tinclude withdrawn windows\n",
  NULL,
  "    %s\t\tdisplay the window id\n",
  NULL,
  "    %s\t\tdisplay window state\n",
  NULL,
  "    %s\tdisplay window screen number\n",
  NULL,
  "    %s\tdisplay client machine name\n",
  NULL,
  "    %s\tdisplay client instance name\n",
  NULL,
  "    %s\t\tdisplay client class name\n",
  NULL,
  "    %s\t\tdisplay window name\n",
  NULL,
  "    %s\tdisplay window icon name\n",
  NULL,
  "    %s\tdisplay client restart command\n",
  "All boolean options may be negated by preceding them with a '+',\n\
as in \"%s\".\n"
};

static char *fallbacks[] = {
  "*file.labelString:           File",
  "*raise.labelString:          Raise windows",
  "*lower.labelString:          Lower windows",
  "*iconify.labelString:        Iconify windows",
  "*kill.labelString:           Kill clients",
  "*auto_exit.labelString:      Auto-exit",
  "*exit.labelString:           Exit",
  "*exit.mnemonic:              x",
  "*display.labelString:        Display",
  "*all_screens.labelString:    All Screens",
  "*all_workspaces.labelString: All Workspaces",
  "*allow_deformed.labelString: Deformed windows",
  "*list_iconic.labelString:    Iconified windows",
  "*list_normal.labelString:    Normal windows",
  "*list_withdrawn.labelString: Withdrawn windows",
  "*window.labelString:         Show Window ID",
  "*state.labelString:          Show Window State",
  "*screen.labelString:         Show Screen Number",
  "*machine.labelString:        Show Machine",
  "*instance.labelString:       Show Instance",
  "*class.labelString:          Show Class",
  "*name.labelString:           Show Name",
  "*icon_name.labelString:      Show Icon Name",
  "*command.labelString:        Show Command",
  "*help.labelString:           Help",
  "*overview.labelString:       Overview",
  "*overview.mnemonic:          O",
  "*template.okLabelString:     Raise",
  "*filter.labelString:         Filter",
  "*sort.labelString:           Sort",
  "*template.cancelLabelString: Exit",
  "*confirm_kill.dialogTitle:   Confirm killing clients",
  "*confirm_kill.messageString: \
Do you really want to kill the selected window's clients?",
  "*helpbox.dialogTitle:        General Help",
  "*helpbox.okLabelString:      Close",
  "*helpbox.messageString:      \
This program will help you find lost windows.  To use it select\\n\
a window from the list and hit Return.  That window will be raised\\n\
to the top of your window stack.  Multiple windows may be selected.\\n\
\\n\
If auto_exit is set, the program will terminate after raising a window.\\n\
By setting the toggles in the \"Display\" menu you can control which\\n\
windows are presented and what information is displayed for each window.\\n\
Displaying deformed or withdrawn windows is rarely interesting.\\n\
\\n\
The \"Filter\" button will cause the program to rebuild the list of windows.\\n\
This may take several seconds to complete, during which time the program\\n\
will be non-responsive.  Use this after changing items in the top half of\\n\
the \"Display\" menu, or after windows have been created or destroyed.  The\\n\
\"Sort\" button will sort the current list of windows alphabetically.\\n\
\\n\
You can also raise, lower, or iconify the selected windows, or kill their\\n\
associated clients (a very dangerous operation!) via the \"File\" menu.",

  "*list.listSizePolicy:        XmRESIZE_IF_POSSIBLE",
  "*list.selectionPolicy:       XmEXTENDED_SELECT",

  /* Default settings: show all normal window titles. */
  "*list_normal.set:            On",
#ifdef CDE
  "*all_workspaces.set:         On",
#endif
  "*name.set:                   On",

  /* Resize our top level window when contents change. */
  "*allowShellResize:           True",

  NULL
};

WindowData data = NULL;
Cardinal   n_data = 0;

Cardinal ignore_errors = 0;
XErrorHandler old_handler;

/* Controls that should be insensitive when nothing is selected. */
Cardinal num_selection_controls = 0;
Widget selection_controls[20];

/* Controls that should be insensitive when nothing is displayed. */
Cardinal num_list_controls = 0;
Widget list_controls[20];

Widget top_level;
Widget list;

Boolean auto_exit;              /* Exit after doing something. */

Boolean all_screens;            /* Search all screens for windows. */
Boolean all_workspaces;         /* CDE: Include windows in all workspaces. */
Boolean allow_deformed;         /* Include deformed (non-ICCCM) windows. */
Boolean list_iconic;            /* Include iconic windows. */
Boolean list_normal;            /* Include normal windows. */
Boolean list_withdrawn;         /* Include withdrawn windows. */

Boolean show_window;            /* Display the hex window id. */
Boolean show_state;             /* Display the window state. */
Boolean show_screen;            /* Display each window's screen number. */
Boolean show_machine;           /* Display the source machine */
Boolean show_instance;          /* Display the client instance name. */
Boolean show_class;             /* Display the client class name. */
Boolean show_name;              /* Display the window name. */
Boolean show_icon_name;         /* Display the window icon name. */
Boolean show_command;           /* Display the window restart command. */

/* Atoms we intern. */
#define WM_STATE                        "WM_STATE"
#define COMPOUND_TEXT                   "COMPOUND_TEXT"

Atom XA_COMPOUND_TEXT;
Atom XA_WM_STATE;

static char *Nil = "(nil)";
XmString Xm_Nil;

/* Local function declarations. */
#ifdef _NO_PROTO

static int IgnoreError();
static PropWMState* GetWMState();
static XmString print_text_field();
static char* print_quoted_word();
static void set_data();
static void print_client_properties();
static void FindData();
static void FreeData();
static void ShowItems();
static void FormatItems();
static void ExitCB();
static void ToggleCB();
static void DisplayCB();
static void HelpCB();
static int CompareData();
static void ListCB();
static void SortCB();
static void FilterCB();
static void RaiseCB();
static void LowerCB();
static void IconifyCB();
static void KillCB();
static void ConfirmKillCB();
static void Usage();

#else

static int IgnoreError(Display *, XErrorEvent *);
static PropWMState* GetWMState(Display *, Window);
static XmString print_text_field(Display *, XTextProperty *);
static char* print_quoted_word(char *, Boolean force_quote);
static void set_data(WindowData data_item,
                     Display *dpy,
                     Window w,
                     int state,
                     int screen,
                     XTextProperty *mach_tp,
                     XTextProperty *name_tp,
                     XTextProperty *icon_tp,
                     int cliargc,
                     char **cliargv,
                     XClassHint *class_hint);
static void print_client_properties(Display *, Window, int screen, Atom ws);
static void FindData(Display *, Window root, int screen, Atom workspace);
static void FreeData(Boolean item_only);
static void ShowItems(void);
static void FormatItems(void);
static void ExitCB(Widget, XtPointer, XtPointer);
static void ToggleCB(Widget, XtPointer, XtPointer);
static void DisplayCB(Widget, XtPointer, XtPointer);
static void HelpCB(Widget, XtPointer, XtPointer);
static int CompareData(const void *, const void *);
static void ListCB(Widget, XtPointer, XtPointer);
static void SortCB(Widget, XtPointer, XtPointer);
static void FilterCB(Widget, XtPointer, XtPointer);
static void RaiseCB(Widget, XtPointer, XtPointer);
static void LowerCB(Widget, XtPointer, XtPointer);
static void IconifyCB(Widget, XtPointer, XtPointer);
static void KillCB(Widget, XtPointer, XtPointer);
static void ConfirmKillCB(Widget, XtPointer, XtPointer);
static void Usage(int argc, char **argv);

#endif /* _NO_PROTO */

#if (XmVERSION < 2)

/* Compatability routine for pre-Motif 2.0 systems. */

static XmString
#ifdef _NO_PROTO
XmStringConcatAndFree(a, b)
     XmString a;
     XmString b;
#else
XmStringConcatAndFree(XmString a,
                      XmString b)
#endif /* _NO_PROTO */
{
  XmString result = XmStringConcat(a, b);

  XmStringFree(a);
  XmStringFree(b);
  return result;
}

#endif /* XmVERSION < 2 */

/* An XErrorHandler to suppress protocol errors when we know we're */
/* doing operations on potentially stale data. */

static int
#ifdef _NO_PROTO
IgnoreError(dpy, event)
     Display *dpy;
     XErrorEvent *event;
#else
IgnoreError(Display *dpy, XErrorEvent *event)
#endif /* _NO_PROTO */
{
  /*
   * This is needed since windows may disappear after we create our
   * list.  I.e. the window list received from XQueryTree may not be
   * valid after XQueryTree returns.
   */
  if (!ignore_errors)
    return (old_handler)(dpy, event);
  else
    return 0;
}

/* Retrieve the WM_STATE property off a top-level window. */

static PropWMState *
#ifdef _NO_PROTO
GetWMState(dpy, window)
     Display *dpy;
     Window window;
#else
GetWMState(Display *dpy,
           Window window)
#endif /* _NO_PROTO */
{
  int ret_val;
  PropWMState *property = NULL;
  Atom actual_type;
  int actual_format;
  unsigned long nitems;
  unsigned long leftover;

  /* Get the property. */
  ret_val = XGetWindowProperty(dpy, window, XA_WM_STATE,
                               0L, PROP_WM_STATE_ELEMENTS,
                               False, XA_WM_STATE,
                               &actual_type, &actual_format,
                               &nitems, &leftover,
                               (unsigned char **)&property);

  if ((ret_val != Success) ||
      (actual_type != XA_WM_STATE) ||
      (nitems != PROP_WM_STATE_ELEMENTS))
    {
      /* The property could not be retrieved or is not correctly set up. */
      if (property)
        {
          XFree((char *)property);
          property = NULL;
        }
    }

  return property;
}

/* Convert a text property to an XmString. */

static XmString
#ifdef _NO_PROTO
print_text_field (dpy, tp)
    Display *dpy;
    XTextProperty *tp;
#else
print_text_field (Display *dpy,
                  XTextProperty *tp)
#endif
{
  if ((tp->encoding == None) || (tp->format == 0))
    return XmStringCreateLocalized("''");

#if (XmVERSION >= 2)
  /* XmCvtTextPropertyToXmString was unclean in Motif 2.0. */
  if (xmUseVersion > 2000)
    {
      XmStringTable table = NULL;
      int count;
      int status;

      /* Let Motif have first crack at doing the conversion. */
      status = XmCvtTextPropertyToXmStringTable(dpy, tp, &table, &count);
      if (status == Success)
        {
          XmString result = NULL;
          int i;

          for (i = 0; i < count; i++)
            result = XmStringConcatAndFree(result, table[i]);

          XtFree((char*) table);
          return result;
        }

      /* Must have gotten XConverterNotFound or XLocaleNotSupported. */
      /* Fall through and try to convert this property ourselves... */
    }
#endif /* XmVERSION >= 2 */

  if ((tp->encoding == XA_STRING) && (tp->format == 8))
    {
      char **strings;
      int count;

      if (!tp->value)
        return XmStringCopy(Xm_Nil);
      else if (XTextPropertyToStringList(tp, &strings, &count) == Success)
        {
          int i;
          XmString result = NULL;

          for (i = 0; i < count; i++)
            {
              if (result)
                result = XmStringConcatAndFree(result,
                                               XmStringCreateLocalized(" "));
              result =
                XmStringConcatAndFree(result,
                                      XmStringCreateLocalized(strings[i]));
            }

          XFreeStringList(strings);
          return result;
        }
      else
        return XmStringCreateLocalized((char *) tp->value);
    }

  else if (tp->encoding == XA_COMPOUND_TEXT)
    return XmCvtCTToXmString((char *) tp->value);

  else
    {
      char buf[256];
      char *tmp = NULL;

      if (tp->encoding == None)
        sprintf(buf, "<unknown type %s (%ld) or format %d>",
                "None", tp->encoding, tp->format);
      else if ((tmp = XGetAtomName(dpy, tp->encoding)) != NULL)
        sprintf(buf, "<unknown type %s (%ld) or format %d>",
                tmp, tp->encoding, tp->format);
      else
        sprintf(buf, "<unknown type %s (%ld) or format %d>",
                Nil, tp->encoding, tp->format);

      if (tmp)
        XFree(tmp);

      return XmStringCreateLocalized(buf);
    }
}

/* Quote special characters in a word. */

static char*
#ifdef _NO_PROTO
print_quoted_word(s, force_quote)
     char *s;
     Boolean force_quote;
#else
print_quoted_word(char *s,
                  Boolean force_quote)
#endif
{
  register char *cp;
  char *result;
  Bool need_quote = force_quote, in_quote = False;
  char quote_char = '\'', other_quote = '"';
  int len;

  if (!need_quote)
    {
      /* Walk down seeing whether or not we need to quote */
      for (cp = s; *cp; cp++)
        {
          if (! ((isascii(*cp) && isalnum(*cp)) ||
                 (*cp == '-' || *cp == '_' || *cp == '.' || *cp == '+' ||
                  *cp == '/' || *cp == '=' || *cp == ':' || *cp == ',')))
            {
              need_quote = True;
              break;
            }
        }
    }

  /* Compute the length of the result. */
  len = 0;
  in_quote = need_quote;
  if (need_quote)
    len++;
  for (cp = s; *cp; cp++)
    {
      if (*cp == quote_char)
        {
          if (in_quote)
            len++;
          len++;
          in_quote = True;
        }
      len++;
    }
  if (in_quote)
    len++;

  result = XtMalloc(len + 1);
  len = 0;

  /*
   * write out the string: if we hit a quote, then close any previous quote,
   * emit the other quote, swap quotes and continue on.
   */
  in_quote = need_quote;
  if (need_quote)
    result[len++] = quote_char;

  for (cp = s; *cp; cp++)
    {
      if (*cp == quote_char)
        {
          if (in_quote)
            result[len++] = quote_char;
          result[len++] = other_quote;
          {
            char tmp = other_quote;
            other_quote = quote_char; quote_char = tmp;
          }
          in_quote = True;
        }
      result[len++] = *cp;
    }

  /* close the quote if we opened one and if we printed the whole string */
  if (in_quote)
    result[len++] = quote_char;
  result[len] = '\0';

  return result;
}

/* Convert and store data into a WindowData record. */
static void
#ifdef _NO_PROTO
set_data(data_item, dpy, w, state, screen, mach_tp, name_tp, icon_tp,
         cliargc, cliargv, class_hint)
     WindowData data_item;
     Display *dpy;
     Window w;
     int state;
     int screen;
     XTextProperty *mach_tp;
     XTextProperty *name_tp;
     XTextProperty *icon_tp;
     int cliargc;
     char **cliargv;
     XClassHint *class_hint;
#else
set_data(WindowData data_item,
         Display *dpy,
         Window w,
         int state,
         int screen,
         XTextProperty *mach_tp,
         XTextProperty *name_tp,
         XTextProperty *icon_tp,
         int cliargc,
         char **cliargv,
         XClassHint *class_hint)
#endif /* _NO_PROTO */
{
  int i;
  char *buf;

  data_item->window = w;
  data_item->state = state;
  data_item->screen = screen;

  data_item->machine = print_text_field(dpy, mach_tp);

  if (class_hint->res_name)
    data_item->instance = XmStringCreateLocalized(class_hint->res_name);
  else
    data_item->instance = XmStringCopy(Xm_Nil);

  if (class_hint->res_class)
    data_item->app_class = XmStringCreateLocalized(class_hint->res_class);
  else
    data_item->app_class = XmStringCopy(Xm_Nil);

  if (name_tp->value)
    data_item->name = print_text_field(dpy, name_tp);
  else
    data_item->name = XmStringCopy(Xm_Nil);

  if (icon_tp->value)
    data_item->icon_name = print_text_field(dpy, icon_tp);
  else
    data_item->icon_name = XmStringCopy(Xm_Nil);

  buf = NULL;
  for (i = 0; i < cliargc; i++)
    {
      char *word = print_quoted_word(cliargv[i], FALSE);

      if (buf)
        {
          int old_len = strlen(buf);
          char *source = word;
          char *target;

          buf = XtRealloc(buf, old_len + 1 + strlen(word) + 1);
          buf[old_len] = ' ';
          target = buf + old_len + 1;
          while ((*target++ = *source++) != 0)
            /*EMPTY*/;

          XtFree(word);
        }
      else
        buf = word;
    }
  if (buf)
    {
      data_item->command = XmStringCreateLocalized(buf);
      XtFree(buf);
    }
  else
    data_item->command = XmStringCopy(Xm_Nil);

  data_item->item = NULL;
  data_item->item_text = NULL;
}

static void
#ifdef _NO_PROTO
print_client_properties (dpy, w, screen, workspace)
     Display *dpy;
     Window w;
     int screen;
     Atom workspace;
#else
print_client_properties (Display *dpy,
                         Window w,
                         int screen,
                         Atom workspace)
#endif
{
  XTextProperty name_tp, mach_tp;
  Boolean valid = True;
  int state;

  name_tp.value = mach_tp.value = NULL;

  if (!XGetWMClientMachine (dpy, w, &mach_tp))
    {
      /* Windows with no WM_CLIENT_MACHINE are deformed. */
      mach_tp.value = NULL;
      mach_tp.encoding = None;
      valid = allow_deformed;
    }

  if (valid)
    {
      /* Windows with no WM_NAME property are deformed. */
      if (!XGetWMName(dpy, w, &name_tp))
        {
          name_tp.value = NULL;
          name_tp.encoding = None;
          valid = allow_deformed;
        }
    }

  /* The WM_HINTS property reflects the window's desired initial state. */
  /* The window manager sets WM_STATE to reflect the current state. */
  if (valid)
    {
      PropWMState *wm_state = GetWMState(dpy, w);

      if (wm_state != NULL)
        {
          state = wm_state->state;
          XFree((char*) wm_state);
        }
      else
        {
          /* Withdrawn windows need not have a WM_STATE. */
          state = WithdrawnState;
        }

      switch (state)
        {
        case WithdrawnState:
          valid = list_withdrawn;
          break;
        case NormalState:
          valid = list_normal;
          break;
        case IconicState:
          valid = list_iconic;
          break;
        default:
          /* Old window managers may not obey the new ICCCM format. */
          break;
        }
    }

#ifdef CDE
  if (valid && !all_workspaces && (workspace != None))
    {
      Atom *ws = NULL;
      unsigned long num_ws;

      /* Is this window being displayed in the current workspace? */
      if (DtWsmGetWorkspacesOccupied(dpy, w, &ws, &num_ws) == Success)
        {
          unsigned long i;

          valid = FALSE;
          for (i = 0; i < num_ws; i++)
            valid |= (ws[i] == workspace);
        }
      XtFree((char*)ws);
    }
#endif

  /* We've found an interesting window! */
  if (valid)
    {
      XClassHint class_hint;
      XTextProperty icon_tp;
      char **cliargv = NULL;
      int cliargc;

      /* Retrieve additional information. */
      if (! XGetCommand (dpy, w, &cliargv, &cliargc))
        cliargc = 0;
      if (! XGetWMIconName(dpy, w, &icon_tp))
        icon_tp.value = NULL;
      if (! XGetClassHint(dpy, w, &class_hint))
        class_hint.res_name = class_hint.res_class = NULL;
#ifdef CDE
      /* don't show window manager windows or us */
      else if (!strcmp(class_hint.res_class,"Dtwm") ||
               !strcmp(class_hint.res_class,APP_CLASS))
        goto cleanup;
#endif
      /* Save the window data. */
      n_data++;
      data = (WindowData) XtRealloc((char*)data,
                                    sizeof(WindowDataRec) * n_data);
      set_data(&data[n_data - 1], dpy, w, state, screen, &mach_tp,
               &name_tp, &icon_tp, cliargc, cliargv, &class_hint);
#ifdef CDE
cleanup:
#endif
      /* Free the additional information. */
      if (class_hint.res_name)
        XFree(class_hint.res_name);
      if (class_hint.res_class)
        XFree(class_hint.res_class);
      if (icon_tp.value)
        XFree ((char *) icon_tp.value);
      XFreeStringList(cliargv);
    }

  /* Free memory. */
  if (name_tp.value)
    XFree ((char *) name_tp.value);
  if (mach_tp.value)
    XFree ((char *) mach_tp.value);
}

static void
#ifdef _NO_PROTO
FindData(dpy, root, screen, workspace)
     Display *dpy;
     Window root;
     int screen;
     Atom workspace;
#else
FindData(Display *dpy,
         Window root,
         int screen,
         Atom workspace)
#endif
{
  Window dummy, *children = NULL, client;
  unsigned int i, nchildren = 0;

  /*
   * Clients are not allowed to stomp on the root and ICCCM doesn't yet
   * say anything about window managers putting stuff there; but, try
   * anyway.
   */
  print_client_properties (dpy, root, screen, workspace);

  /*
   * then, get the list of windows
   */
  if (XQueryTree (dpy, root, &dummy, &dummy, &children, &nchildren))
    {
      for (i = 0; i < nchildren; i++)
        {
          if (XtIsRealized(top_level))
            XmUpdateDisplay(top_level);

          client = XmuClientWindow (dpy, children[i]);
          if (client != None)
            print_client_properties (dpy, client, screen, workspace);
        }

      XFree(children);
    }
}

/* Free data storage. */

static void
#ifdef _NO_PROTO
FreeData(item_only)
     Boolean item_only;
#else
FreeData(Boolean item_only)
#endif /* _NO_PROTO */
{
  int i;

  for (i = 0; i < n_data; i++)
    {
      if (!item_only)
        {
          XmStringFree(data[i].machine);
          XmStringFree(data[i].instance);
          XmStringFree(data[i].app_class);
          XmStringFree(data[i].name);
          XmStringFree(data[i].icon_name);
          XmStringFree(data[i].command);
        }
      XmStringFree(data[i].item);
      XtFree(data[i].item_text);
    }

  if (!item_only)
    {
      XtFree((char*)data);
      data = NULL;
      n_data = 0;
    }
}

/* Set the list items. */

static void
#ifdef _NO_PROTO
ShowItems()
#else
ShowItems(void)
#endif
{
  int i;
  Arg args[5];
  Cardinal nargs;
#if XmVERSION >= 2
  XmRenderTable render_table = NULL;
#endif /* XmVersion >= 2 */
  XmString *items = (XmString*) XtMalloc(sizeof(XmString) * n_data);

  /* Gather the data into an XmStringList. */
  for (i = 0; i < n_data; i++)
    items[i] = data[i].item;

#if XmVERSION >= 2
  /* Get an updated tab list. */
  if (n_data)
    {
      XmRenderTable old_table;
      XmRendition rend;
      XmTabList tabs =
        XmStringTableProposeTablist(items, n_data, list, TAB_PAD, XmRELATIVE);

      nargs = 0;
      XtSetArg(args[nargs], XmNrenderTable, &old_table), nargs++;
      assert(nargs <= XtNumber(args));
      XtGetValues(list, args, nargs);

      render_table = XmRenderTableCopy(old_table, NULL, 0);
      rend = XmRenderTableGetRendition(render_table, RENDITION_TAG);

      nargs = 0;
      XtSetArg(args[nargs], XmNtabList, tabs),          nargs++;
      assert(nargs <= XtNumber(args));
      if (rend)
        XmRenditionUpdate(rend, args, nargs);
      else
        rend = XmRenditionCreate(list, RENDITION_TAG, args, nargs);

# if XmVersion == 2000
      /*
       * In Motif 2.0 XmREPLACE had two different values, depending on
       * which header files you included.  In Motif 2.1 this was
       * resolved, preserving the oldest reference (in RowColumnP.h).
       * The new render table XmREPLACE symbol was renamed XmMERGE_REPLACE.
       */
      render_table =
        XmRenderTableAddRenditions(render_table, &rend, 1, XmREPLACE);
# else
      render_table =
        XmRenderTableAddRenditions(render_table, &rend, 1, XmMERGE_REPLACE);
# endif
      XmRenditionFree(rend);
      XmTabListFree(tabs);
    }
#endif /* XmVERSION >= 2 */

  nargs = 0;
  XtSetArg(args[nargs], XmNitemCount, n_data),          nargs++;
  XtSetArg(args[nargs], XmNitems, items),               nargs++;
  if (n_data)
    {
      XtSetArg(args[nargs], XmNvisibleItemCount, n_data), nargs++;
#if XmVERSION >= 2
      XtSetArg(args[nargs], XmNrenderTable, render_table), nargs++;
#endif /* XmVERSION >= 2 */
    }
  assert(nargs <= XtNumber(args));
  XtSetValues(list, args, nargs);

  XtFree((char*)items);
#if XmVERSION >= 2
  XmRenderTableFree(render_table);
#endif /* XmVERSION >= 2 */

  /* Set sensitivity for controls that depend on list state. */
  ListCB(list, NULL, NULL);
  for (i = 0; i < num_list_controls; i++)
    XtSetSensitive(list_controls[i], (n_data != 0));
}

/* Rebuild the list items. */

static void
#ifdef _NO_PROTO
FormatItems()
#else
FormatItems(void)
#endif
{
  static char window_format[10];
  static Boolean initialized = False;
  static XmString separator;
  static XmString open_paren;
  static XmString close_paren;
#if XmVERSION >= 2
  static XmString rend_begin;
  static XmString rend_end;
#endif /* XmVERSION >= 2 */

  int i;

  /* Initialize the window_format. */
  if (!initialized)
    {
      initialized = True;
      sprintf(window_format, "0x%%0%ulX", (unsigned) sizeof(Window) * 2);
      open_paren = XmStringCreateLocalized("(");
      close_paren = XmStringCreateLocalized(")");
#if XmVERSION < 2
      separator = XmStringCreateLocalized(" ");
#else
      separator = XmStringComponentCreate(XmSTRING_COMPONENT_TAB, 0, NULL);
      rend_begin =
        XmStringComponentCreate(XmSTRING_COMPONENT_RENDITION_BEGIN,
                                strlen(RENDITION_TAG), RENDITION_TAG);
      rend_end =
        XmStringComponentCreate(XmSTRING_COMPONENT_RENDITION_END,
                                strlen(RENDITION_TAG), RENDITION_TAG);
#endif /* XmVERSION < 2 */
    }

  /* Discard old formatted item data. */
  FreeData(TRUE);

  /* Format data in the new fashion. */
  for (i = 0; i < n_data; i++)
    {
      char buf[64];
      XmString result = NULL;

      /* Assemble the desired output string. */
      if (show_window)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          sprintf(buf, window_format, data[i].window);
          result = XmStringConcatAndFree(result, XmStringCreateLocalized(buf));
        }

      if (show_state)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          switch(data[i].state)
            {
            case WithdrawnState:
              result = XmStringConcatAndFree(result,
                                             XmStringCopy(app_data.withdrawn));
              break;

            case IconicState:
              result = XmStringConcatAndFree(result,
                                             XmStringCopy(app_data.iconic));
              break;

            case NormalState:
              result = XmStringConcatAndFree(result,
                                             XmStringCopy(app_data.normal));
              break;

            default:
              result = XmStringConcatAndFree(result,
                                             XmStringCopy(app_data.unknown));
              break;
            }
        }

      if (show_screen)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          sprintf(buf, "%x", data[i].screen);
          result = XmStringConcatAndFree(result, XmStringCreateLocalized(buf));
        }

      if (show_machine)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          result = XmStringConcatAndFree(result, XmStringCopy(data[i].machine));
        }

      if (show_instance)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          result = XmStringConcatAndFree(result,
                                         XmStringCopy(data[i].instance));
        }

      if (show_class)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          result = XmStringConcatAndFree(result,
                                         XmStringCopy(data[i].app_class));
        }

      if (show_name)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          result = XmStringConcatAndFree(result, XmStringCopy(data[i].name));
        }

      if (show_icon_name)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          result = XmStringConcatAndFree(result,
                                         XmStringCopy(data[i].icon_name));
        }

      /* Always show something! */
      if (show_command || !result)
        {
          if (result)
            result = XmStringConcatAndFree(result, XmStringCopy(separator));
#if XmVERSION >= 2
          else
            result = XmStringCopy(rend_begin);
#endif /* XmVERSION >= 2 */

          /* Default command to name if that's all we're showing. */
          if (!show_instance && !show_class && !show_name && !show_icon_name &&
              XmStringCompare(data[i].command, Xm_Nil))
            {
              result = XmStringConcatAndFree(result, XmStringCopy(open_paren));

              result = XmStringConcatAndFree(result,
                                             XmStringCopy(data[i].name));

              result = XmStringConcatAndFree(result, XmStringCopy(close_paren));
            }
          else
            {
              result = XmStringConcatAndFree(result,
                                             XmStringCopy(data[i].command));
            }
        }

#if XmVERSION >= 2
      result = XmStringConcatAndFree(result, XmStringCopy(rend_end));
#endif /* XmVERSION >= 2 */

      data[i].item = result;
      data[i].item_text = XmCvtXmStringToCT(result);
    }
}

/* XtCallbackProc to exit the program. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
ExitCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;
     XtPointer call_data;
#else
ExitCB(Widget widget,
       XtPointer closure,
       XtPointer call_data)
#endif
{
  exit(0);
}

/* XtCallbackProc invoked when a global Boolean changes value. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
ToggleCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* Pointer to the global boolean variable */
     XtPointer call_data;
#else
ToggleCB(Widget widget,
         XtPointer closure,     /* Pointer to the global boolean variable */
         XtPointer call_data)
#endif
{
  assert(widget && XmIsToggleButton(widget));
  XtVaGetValues(widget, XmNset, closure, NULL);
}

/* XtCallbackProc invoked when a global display Boolean changes value. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
DisplayCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* Pointer to the global boolean variable */
     XtPointer call_data;
#else
DisplayCB(Widget widget,
          XtPointer closure,    /* Pointer to the global boolean variable */
          XtPointer call_data)
#endif
{
  /* Record the new value. */
  ToggleCB(widget, closure, call_data);

  /* Rebuild the list items. */
  FormatItems();
  ShowItems();
}

/* Popup the help dialog. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
HelpCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;
     XtPointer call_data;
#else
HelpCB(Widget widget,
       XtPointer closure,
       XtPointer call_data)
#endif
{
  static Widget help_dialog = NULL;

  if (!help_dialog)
    {
      Widget button;
      Arg args[10];
      Cardinal nargs;

      /* Create MessageBox dialog. */
      nargs = 0;
      assert(nargs <= XtNumber(args));
      help_dialog = XmCreateMessageDialog(widget, "helpbox", args, nargs);

      button = XmMessageBoxGetChild(help_dialog, XmDIALOG_CANCEL_BUTTON);
      if (button)
        XtUnmanageChild(button);
      button = XmMessageBoxGetChild(help_dialog, XmDIALOG_HELP_BUTTON);
      if (button)
        XtUnmanageChild(button);
    }

  XtManageChild(help_dialog);
}

/* Element comparison function for qsort(). */

static int
#ifdef _NO_PROTO
CompareData(a, b)
     char *a;
     char *b;
#else
CompareData(const void *a,
            const void *b)
#endif
{
  register WindowData a_data = (WindowData) a;
  register WindowData b_data = (WindowData) b;

  /* We could be more clever about comparing compound text, but this */
  /* is good enough. */
  return strcmp(a_data->item_text, b_data->item_text);
}

/* XtCallbackProc invoked when the set of selected windows changes. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
ListCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;
     XtPointer call_data;
#else
ListCB(Widget widget,
       XtPointer closure,
       XtPointer call_data)
#endif
{
  XmListCallbackStruct *data = (XmListCallbackStruct*) call_data;
  Boolean was_sensitive;
  int selection_count;

  /* We may be invoked with NULL call_data by ShowItems. */
  if (call_data != NULL)
    {
      switch(data->reason)
        {
        case XmCR_SINGLE_SELECT:
        case XmCR_BROWSE_SELECT:
          selection_count = 1;
          break;

        case XmCR_MULTIPLE_SELECT:
        case XmCR_EXTENDED_SELECT:
          selection_count = data->selected_item_count;
          break;

        default:
          /* Should never happen. */
          exit(data->reason);
        }
    }
#if XmVERSION < 2
  else
    {
      /* XmNselectedItems may contain items not present in the list. */
      int *positions;
      if (XmListGetSelectedPos(list, &positions, &selection_count))
        XtFree((char*)positions);
      else
        selection_count = 0;
    }
#else
  else
    XtVaGetValues(list, XmNselectedPositionCount, &selection_count, NULL);
#endif /* XmVERSION < 2 */

  /* Determine whether the controls were already sensitive. */
  assert(num_selection_controls != 0);
  XtVaGetValues(selection_controls[0], XmNsensitive, &was_sensitive, NULL);

  if ((selection_count && !was_sensitive) ||
      (!selection_count && was_sensitive))
    {
      /* We need to change the state of the selection controls. */
      int i;
      for (i = 0; i < num_selection_controls; i++)
        XtSetSensitive(selection_controls[i], !was_sensitive);
    }
}

/* XtCallbackProc invoked to sort the list of windows. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
SortCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;
     XtPointer call_data;
#else
SortCB(Widget widget,
       XtPointer closure,
       XtPointer call_data)
#endif
{
  /* Sort the items. */
  qsort((char*) data, n_data, sizeof(WindowDataRec), CompareData);

  /* Display the results. */
  ShowItems();
}

/* XtCallbackProc invoked to (re)generate the list of windows. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
FilterCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* The target display. */
     XtPointer call_data;
#else
FilterCB(Widget widget,
         XtPointer closure,     /* The target display. */
         XtPointer call_data)
#endif
{
  Display *dpy = (Display *)closure;
  int screen;
  Window root;
  Atom workspace = None;

  /* Mark us as busy. */
  if (XtIsRealized(top_level))
    {
      XtSetSensitive(top_level, False);
      XmUpdateDisplay(top_level);
    }

  /* Discard all the existing data. */
  FreeData(FALSE);

  /* Generate new data. */
  for (screen = 0; screen < ScreenCount(dpy); screen++)
    {
      if (all_screens || (screen == DefaultScreen(dpy)))
        {
          root = RootWindow(dpy, screen);

#ifdef CDE
          /* Store the current workspace for this screen in a global. */
          if (DtWsmGetCurrentWorkspace(dpy, root, &workspace) != Success)
            workspace = None;
#endif

          FindData(dpy, root, screen, workspace);
        }
    }

  /* Put the new data into the list. */
  FormatItems();

  /* Sort the data. */
  SortCB(widget, closure, call_data);

  /* We're done! */
  if (XtIsRealized(top_level))
    XtSetSensitive(top_level, True);
}

/* XtCallbackProc invoked to raise the selected windows. */
#ifdef CDE
/*ARGSUSED*/
static void
#ifdef _NO_PROTO
RaiseCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;		/* The target display. */
     XtPointer call_data;
#else
RaiseCB(Widget widget,
	XtPointer closure,	/* The target display. */
	XtPointer call_data)
#endif
{
   Display *dpy = (Display *)closure;
   Display *ourdpy;
   int *pos = NULL;
   int n_pos = 0;
   int i;
   int here = 0;
   Atom           * workspaces      = 0;
   Atom             currentWorkspace;
   unsigned long    numWorkspaces   = 0;
   DtWsmWorkspaceInfo   * wsinfo;
   Status dtStatus;

   ourdpy = XtDisplay(widget);
   /* Find the selected items. */
   (void) XmListGetSelectedPos(list, &pos, &n_pos);

   /* Raise them, ignoring X errors. */
   ignore_errors++;
   if (ourdpy == dpy) { /* if target display is our display ... */
         /* raise only last one, and jump to that workspace */
         if (n_pos>0) {
	    if (DtWsmGetWorkspacesOccupied(dpy, data[pos[n_pos-1] - 1].window,
		&workspaces, &numWorkspaces) == Success && (numWorkspaces != 0)) {
	      (void) DtWsmGetCurrentWorkspace(XtDisplay(widget),
		RootWindowOfScreen(XtScreen(widget)), &currentWorkspace);
	      for (i=0;i<numWorkspaces;i++)
		if (workspaces[i]==currentWorkspace) {
		   here++;
		   break;
		}
	      if (!here)
                (void) DtWsmSetCurrentWorkspace(widget, workspaces[0]);
	      sleep(2); /* on Sun Ultra 1 CDE 1.2 sleep(1) is ok, too */
	      XtFree((void *) workspaces); /* plug this leak */
            }
	    XMapRaised(dpy, data[pos[n_pos-1]-1].window);
         }
   }
   else /* target is another display, raise 'em all */
      for (i = 0; i < n_pos; i++)
         XMapRaised(dpy, data[pos[i]-1].window);

   XSync(dpy, False);
   --ignore_errors;

  /* We may actually have done something... */
   XtFree((char*) pos);
   if (n_pos && auto_exit)
      exit(0);
}
#else
/*ARGSUSED*/
static void
#ifdef _NO_PROTO
RaiseCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* The target display. */
     XtPointer call_data;
#else
RaiseCB(Widget widget,
        XtPointer closure,      /* The target display. */
        XtPointer call_data)
#endif
{
  Display *dpy = (Display *)closure;
  int *pos = NULL;
  int n_pos = 0;
  int i;

  /* Find the selected items. */
  (void) XmListGetSelectedPos(list, &pos, &n_pos);

  /* Raise them, ignoring X errors. */
  ignore_errors++;
  for (i = 0; i < n_pos; i++)
    XMapRaised(dpy, data[pos[i]-1].window);
  XSync(dpy, False);
  --ignore_errors;

  /* We may actually have done something... */
  XtFree((char*) pos);
  if (n_pos && auto_exit)
    exit(0);
}
#endif

/* XtCallbackProc invoked to lower the selected windows. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
LowerCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* The target display. */
     XtPointer call_data;
#else
LowerCB(Widget widget,
        XtPointer closure,      /* The target display. */
        XtPointer call_data)
#endif
{
  Display *dpy = (Display *)closure;
  int *pos = NULL;
  int n_pos = 0;
  int i;

  /* Find the selected items. */
  (void) XmListGetSelectedPos(list, &pos, &n_pos);

  /* Lower them, ignoring X errors. */
  ignore_errors++;
  for (i = 0; i < n_pos; i++)
    XLowerWindow(dpy, data[pos[i]-1].window);
  XSync(dpy, False);
  --ignore_errors;

  /* We may actually have done something... */
  XtFree((char*) pos);
  if (n_pos && auto_exit)
    exit(0);
}

/* XtCallbackProc invoked to iconify the selected windows. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
IconifyCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* The target display. */
     XtPointer call_data;
#else
IconifyCB(Widget widget,
        XtPointer closure,      /* The target display. */
        XtPointer call_data)
#endif
{
  Display *dpy = (Display *)closure;
  int *pos = NULL;
  int n_pos = 0;
  int i;

  /* Find the selected items. */
  (void) XmListGetSelectedPos(list, &pos, &n_pos);

  /* Iconify them, ignoring X errors. */
  ignore_errors++;
  for (i = 0; i < n_pos; i++)
    XIconifyWindow(dpy, data[pos[i]-1].window, data[pos[i]-1].screen);
  XSync(dpy, False);
  --ignore_errors;

  /* We may actually have done something... */
  XtFree((char*) pos);
  if (n_pos && auto_exit)
    exit(0);
}

/* XtCallbackProc invoked to kill the selected window clients. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
KillCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* The target display. */
     XtPointer call_data;
#else
KillCB(Widget widget,
       XtPointer closure,       /* The target display. */
       XtPointer call_data)
#endif
{
  Display *dpy = (Display *)closure;
  int *pos = NULL;
  int n_pos = 0;
  int i;

  /* Find the selected items. */
  (void) XmListGetSelectedPos(list, &pos, &n_pos);

  /* Destroy them, ignoring X errors. */
  ignore_errors++;
  for (i = 0; i < n_pos; i++)
    XKillClient(dpy, data[pos[i]-1].window);
  XSync(dpy, False);
  --ignore_errors;

  /* We may actually have done something... */
  XtFree((char*) pos);
  if (n_pos && auto_exit)
    exit(0);
}

/* XtCallbackProc to confirm killing of selected window clients. */

/*ARGSUSED*/
static void
#ifdef _NO_PROTO
ConfirmKillCB(widget, closure, call_data)
     Widget widget;
     XtPointer closure;         /* The target display. */
     XtPointer call_data;
#else
ConfirmKillCB(Widget widget,
              XtPointer closure, /* The target display. */
              XtPointer call_data)
#endif
{
  static Widget dialog = NULL;

  if (!dialog)
    {
      Widget button;
      Arg args[10];
      Cardinal nargs;

      /* Create a confirmation dialog. */
      nargs = 0;
      XtSetArg(args[nargs], XmNdialogStyle,
               XmDIALOG_PRIMARY_APPLICATION_MODAL),     nargs++;
      assert(nargs <= XtNumber(args));
      dialog = XmCreateQuestionDialog(widget, "confirm_kill", args, nargs);
      XtAddCallback(dialog, XmNokCallback, KillCB, closure);

      button = XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
      if (button)
        XtUnmanageChild(button);
    }

  XtManageChild(dialog);
}

/* Print unrecognized command line arguments, give usage options, and exit. */

static void
#ifdef _NO_PROTO
Usage(argc, argv)
     int argc;
     char **argv;
#else
Usage(int argc,
      char **argv)
#endif /* _NO_PROTO */
{
  int i;

  fprintf(stderr, "Usage: %s [ -options ] [ target-display ]\n", argv[0]);

  /* Print any unrecognized command line options. */
  if (argc > 1)
    {
      fprintf(stderr, "\nUnrecognized option%s:", ((argc > 2) ? "s" : ""));
      for (i = 1; i < argc; i++)
        {
          char *argument = print_quoted_word(argv[i], FALSE);
          fprintf(stderr, " %s", argument);
          XtFree(argument);
        }
      fprintf(stderr, "\n\n");
    }

  /* Print supported options. */
  fprintf(stderr,
          "The usual X command line options plus the following are allowed:\n");
  for (i = 0; i < XtNumber(options); i++)
    {
      if (option_help[i])
        fprintf(stderr, option_help[i], options[i].option);
    }

  fprintf(stderr, "\nAn optional target display name may be provided.\n");

  exit(1);
}

#ifdef CDE
#ifdef _NO_PROTO
static void wschangeCB(w, atom, client_data)
Widget  w;
Atom    atom;
XtPointer client_data;
#else
static void wschangeCB(Widget w, Atom atom, XtPointer client_data)
#endif
{
  if ((Display *) client_data == XtDisplay(w))
    /* hack hack -- regenerate list if target display is us */
    FilterCB(NULL,client_data,NULL);
}
#endif

int
#ifdef _NO_PROTO
main(argc, argv)
    int argc;
    char **argv;
#else
main(int argc,
     char **argv)
#endif
{
  XtAppContext app_con;
  Widget template, menu_bar, menu, widget;
  Arg args[10];
  Cardinal nargs;
  Display *target_dpy;

  /* Create a top-level shell. */
  top_level = XtAppInitialize(&app_con, APP_CLASS, options, XtNumber(options),
                              &argc, argv, fallbacks, NULL, 0);

  /* Attempt to open the target display connection. */
  if (argc == 2)
    {
      char *target_name = argv[--argc];

      target_dpy = XOpenDisplay(target_name);
      if (target_dpy == NULL)
        {
          fprintf(stderr, "%s: unable to open target display %s.\n\n",
                  argv[0], print_quoted_word(XDisplayName(target_name), TRUE));
          Usage(argc, argv);
        }
    }
  else
    {
      /* Re-use our display. */
      target_dpy = XtDisplay(top_level);
    }

  /* Handle unrecognized command line options. */
  assert(XtNumber(options) == XtNumber(option_help));
  if (argc > 1)
    Usage(argc, argv);

#if XmVERSION < 2
  /* Allow tear-off menus.  (Installed by default in Motif 2.0) */
  XmRepTypeInstallTearOffModelConverter();
#endif /* XmVERSION < 2 */

  /* Initialize some static variables. */
  Xm_Nil = XmStringCreateLocalized(Nil);
  XA_COMPOUND_TEXT = XInternAtom(target_dpy, COMPOUND_TEXT, False);
  XA_WM_STATE = XInternAtom(target_dpy, WM_STATE, False);

  XtGetApplicationResources(top_level, &app_data,
                            app_resources, XtNumber(app_resources),
                            NULL, 0);

  /* Create a template dialog. */
  nargs = 0;
  XtSetArg(args[nargs], XmNautoUnmanage, False),                nargs++;
  XtSetArg(args[nargs], XmNdialogType, XmDIALOG_TEMPLATE),      nargs++;
  assert(nargs <= XtNumber(args));
  template = XmCreateMessageBox(top_level, "template", args, nargs);
  XtManageChild(template);

  /* Create a menu bar. */
  nargs = 0;
  assert(nargs <= XtNumber(args));
  menu_bar = XmCreateMenuBar(template, "menu_bar", args, nargs);
  XtManageChild(menu_bar);

  /* Create the file menu. */
  nargs = 0;
  assert(nargs <= XtNumber(args));
  menu = XmCreatePulldownMenu(menu_bar, "file_menu", args, nargs);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(menu, "raise", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, RaiseCB, target_dpy);
  selection_controls[num_selection_controls++] = widget;
  assert(num_selection_controls <= XtNumber(selection_controls));

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(menu, "lower", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, LowerCB, target_dpy);
  selection_controls[num_selection_controls++] = widget;
  assert(num_selection_controls <= XtNumber(selection_controls));

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(menu, "iconify", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, IconifyCB, target_dpy);
  selection_controls[num_selection_controls++] = widget;
  assert(num_selection_controls <= XtNumber(selection_controls));

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(menu, "kill", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, ConfirmKillCB, target_dpy);
  selection_controls[num_selection_controls++] = widget;
  assert(num_selection_controls <= XtNumber(selection_controls));

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateSeparator(menu, "sep_1", args, nargs);
  XtManageChild(widget);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "auto_exit", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &auto_exit);
  XtVaGetValues(widget, XmNset, &auto_exit, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateSeparator(menu, "sep_2", args, nargs);
  XtManageChild(widget);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(menu, "exit", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, ExitCB, NULL);

  nargs = 0;
  XtSetArg(args[nargs], XmNsubMenuId, menu),            nargs++;
  assert(nargs <= XtNumber(args));
  widget = XmCreateCascadeButton(menu_bar, "file", args, nargs);
  XtManageChild(widget);

  /* Create the display menu. */
  nargs = 0;
  assert(nargs <= XtNumber(args));
  menu = XmCreatePulldownMenu(menu_bar, "display_menu", args, nargs);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "all_screens", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &all_screens);
  XtVaGetValues(widget, XmNset, &all_screens, NULL);

#ifdef CDE
  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "all_workspaces", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &all_workspaces);
  XtVaGetValues(widget, XmNset, &all_workspaces, NULL);
#endif

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "allow_deformed", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &allow_deformed);
  XtVaGetValues(widget, XmNset, &allow_deformed, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "list_iconic", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &list_iconic);
  XtVaGetValues(widget, XmNset, &list_iconic, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "list_normal", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &list_normal);
  XtVaGetValues(widget, XmNset, &list_normal, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "list_withdrawn", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, ToggleCB, &list_withdrawn);
  XtVaGetValues(widget, XmNset, &list_withdrawn, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateSeparator(menu, "sep_2", args, nargs);
  XtManageChild(widget);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "window", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_window);
  XtVaGetValues(widget, XmNset, &show_window, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "state", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_state);
  XtVaGetValues(widget, XmNset, &show_state, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "screen", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_screen);
  XtVaGetValues(widget, XmNset, &show_screen, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "machine", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_machine);
  XtVaGetValues(widget, XmNset, &show_machine, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "instance", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_instance);
  XtVaGetValues(widget, XmNset, &show_instance, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "class", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_class);
  XtVaGetValues(widget, XmNset, &show_class, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "name", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_name);
  XtVaGetValues(widget, XmNset, &show_name, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "icon_name", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_icon_name);
  XtVaGetValues(widget, XmNset, &show_icon_name, NULL);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreateToggleButton(menu, "command", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNvalueChangedCallback, DisplayCB, &show_command);
  XtVaGetValues(widget, XmNset, &show_command, NULL);

  nargs = 0;
  XtSetArg(args[nargs], XmNsubMenuId, menu),            nargs++;
  assert(nargs <= XtNumber(args));
  widget = XmCreateCascadeButton(menu_bar, "display", args, nargs);
  XtManageChild(widget);

  /* Create a help menu. */
  nargs = 0;
  assert(nargs <= XtNumber(args));
  menu = XmCreatePulldownMenu(menu_bar, "help_menu", args, nargs);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(menu, "overview", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, HelpCB, NULL);

  nargs = 0;
  XtSetArg(args[nargs], XmNsubMenuId, menu),            nargs++;
  assert(nargs <= XtNumber(args));
  widget = XmCreateCascadeButton(menu_bar, "help", args, nargs);
  XtManageChild(widget);

  nargs = 0;
  XtSetArg(args[nargs], XmNmenuHelpWidget, widget),     nargs++;
  assert(nargs <= XtNumber(args));
  XtSetValues(menu_bar, args, nargs);

  /* Create the scrolled list. */
  nargs = 0;
  XtSetArg(args[nargs], XmNvisibleItemCount, 1),        nargs++;
#if XmVERSION >= 2
  /* Force XmNunitType so that TAB_PAD is meaningful. */
  XtSetArg(args[nargs], XmNunitType, TAB_UNITS),        nargs++;
#endif /* XmVERSION >= 2 */
  assert(nargs <= XtNumber(args));
  list = XmCreateScrolledList(template, "list", args, nargs);
  XtManageChild(list);
  XtAddCallback(list, XmNbrowseSelectionCallback, ListCB, NULL);
  XtAddCallback(list, XmNextendedSelectionCallback, ListCB, NULL);
  XtAddCallback(list, XmNmultipleSelectionCallback, ListCB, NULL);
  XtAddCallback(list, XmNsingleSelectionCallback, ListCB, NULL);
  XtAddCallback(list, XmNdefaultActionCallback, RaiseCB, target_dpy);

  /* Create the extra buttons. */
  XtAddCallback(template, XmNokCallback, RaiseCB, target_dpy);
  widget = XmMessageBoxGetChild(template, XmDIALOG_OK_BUTTON);
  if (widget)
    {
      selection_controls[num_selection_controls++] = widget;
      assert(num_selection_controls <= XtNumber(selection_controls));
    }

  widget = XmMessageBoxGetChild(template, XmDIALOG_HELP_BUTTON);
  if (widget)
    XtUnmanageChild(widget);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(template, "filter", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, FilterCB, target_dpy);

  nargs = 0;
  assert(nargs <= XtNumber(args));
  widget = XmCreatePushButton(template, "sort", args, nargs);
  XtManageChild(widget);
  XtAddCallback(widget, XmNactivateCallback, SortCB, NULL);
  list_controls[num_list_controls++] = widget;
  assert(num_list_controls <= XtNumber(list_controls));

  XtAddCallback(template, XmNcancelCallback, ExitCB, NULL);

  /* Gather initial data. */
  old_handler = XSetErrorHandler(IgnoreError);
  FilterCB((Widget)NULL, (XtPointer)target_dpy, (XtPointer)NULL);

  /* Off we go! */
  XtRealizeWidget(top_level);
#ifdef CDE
  /* they say we have to call this for the other functions to work */
  DtWsmAddCurrentWorkspaceCallback(top_level,wschangeCB,(XtPointer)target_dpy);
  /* make sure we're on all workspaces */
  DtWsmOccupyAllWorkspaces(XtDisplay(top_level),XtWindow(top_level));
#endif
  XtAppMainLoop(app_con);

  /* We should never get here. */
  return(2);
}

