[comp.sources.x] v09i047: xicon --- stupid little icons representing programs to be run, Part01/01

trost%REED.BITNET@CORNELLC.cit.cornell.edu (09/25/90)

Submitted-by: trost%REED.BITNET@CORNELLC.cit.cornell.edu
Posting-number: Volume 9, Issue 47
Archive-name: xicon/part01

This is a little hack inspired by something I saw under Motif.  It is
probably gratuitously incompatible.  As a simple try-out, run
something like

   xicon -xrm '*icons: xlogo, xterm, xclock, emacs'

That is, you get 4 default icons -- when you click on each one,
the corresponding program starts.

Read the man page for complete info.

#!/bin/sh
# to extract, remove the header and type "sh filename"
if `test ! -s ./Imakefile`
then
echo "writing ./Imakefile"
cat > ./Imakefile << '\End\Of\Shar\'
        DEPLIBS = $(DEPXLIB)
LOCAL_LIBRARIES = $(XLIB)
           SRCS = xicon.c
           OBJS = xicon.o

ComplexProgramTarget(xicon)
\End\Of\Shar\
else
  echo "will not over write ./Imakefile"
fi
if `test ! -s ./patchlevel.h`
then
echo "writing ./patchlevel.h"
cat > ./patchlevel.h << '\End\Of\Shar\'
#define PATCHLEVEL 0
\End\Of\Shar\
else
  echo "will not over write ./patchlevel.h"
fi
if `test ! -s ./xicon.c`
then
echo "writing ./xicon.c"
cat > ./xicon.c << '\End\Of\Shar\'
#include <stdio.h>

#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/errno.h>

#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>

static char* programName;
#define programClass  "XIcon"

typedef struct {
    Window window;
    Bool running;
    char* name;
    char* title;
    char* class;
    char* icon_name;
    char* command;
} Icon;

static childDied = 0;
static XrmDatabase initialDatabase, serverDatabase = 0;

static XrmOptionDescRec switches[] = {
    { "-display",       ".display",     XrmoptionSepArg,
          (caddr_t) NULL },
    { "-xrm",           NULL,           XrmoptionResArg,
          (caddr_t) NULL },
};

static void
usage()
{
    fprintf(stderr,
            "usage: %s [ -display display ] [ -xrm resource [ ... ] ]\n",
            programName);
    exit(1);
}

static char*
GetStringResource(name, class)
char* name;
char* class;
{
    char* type;
    XrmValue value;

    if (!XrmGetResource(initialDatabase, name, class, &type, &value))
        if (serverDatabase &&
            !XrmGetResource(serverDatabase, name, class, &type, &value))
            return 0;
    return (char*) value.addr;
}

static Window
CreateIconWindow(display, icon)
Display* display;
Icon* icon;
{
    Window window = XCreateSimpleWindow(display, DefaultRootWindow(display),
                                        0, 0,  150, 1,  0, 0,  None);
    XClassHint  classHint;
    XSizeHints  sizeHints;
    XWMHints    wmHints;
    char        hostname[255];

    XSelectInput(display, window, StructureNotifyMask);

    classHint.res_name = icon->name;
    classHint.res_class = icon->class;
    XSetClassHint(display, window, &classHint);

    /*
      Obsolete, but we should set for old wm's.  And maybe new wm's
      will be nice and do it for us anyhow (since I can't figure out
      the new way of doing it).
     */
    sizeHints.flags = PPosition;
    sizeHints.x = -200;
    sizeHints.y = -200;
    XSetNormalHints(display, window, &sizeHints);

    XStoreName(display, window, icon->title);
    XSetIconName(display, window, icon->icon_name);

    wmHints.flags = StateHint;
    wmHints.initial_state = IconicState;
    XSetWMHints(display, window, &wmHints);

    gethostname(hostname, sizeof(hostname));
    XChangeProperty(display, window, XA_WM_CLIENT_MACHINE, XA_STRING, 8,
                    PropModeReplace, (unsigned char*) hostname,
                    strlen(hostname));

    return window;
}

static void
CreateIcon(display, icons, name)
Display* display;
XContext icons;
char* name;
{
    char        resource[255], class[255];
    char        baseResource[255];
    char        baseClass[255];
    Icon*       icon = (Icon*) Xpermalloc(sizeof(Icon));

    icon->name = name;

    sprintf(baseResource, "%s.%s.%%s", programName, name);
    sprintf(baseClass, "%s.%s.%%s", programClass, name);

    sprintf(resource, baseResource, "class");
    sprintf(class, baseClass, "Class");
    if (icon->class = GetStringResource(resource, class))
        sprintf(baseClass, "%s.%s.%%s", programClass, icon->class);
    else
        icon->class = name;

    sprintf(resource, baseResource, "title");
    sprintf(class, baseClass, "Title");
    icon->title = GetStringResource(resource, class);
    if (!icon->title)
        icon->title = name;

    sprintf(resource, baseResource, "iconName");
    sprintf(class, baseClass, "IconName");
    icon->icon_name = GetStringResource(resource, class);
    if (!icon->icon_name)
        icon->icon_name = icon->title ? icon->title : name;

    sprintf(resource, baseResource, "command");
    sprintf(class, baseClass, "Command");
    icon->command = GetStringResource(resource, class);
    if (!icon->command)
        icon->command = name;

    icon->running = 0;
    icon->window = CreateIconWindow(display, icon);

    XMapWindow(display, icon->window);

    XSaveContext(display, icon->window, icons, icon);
}

static char**
ParseIconList(list)
char* list;
{
    int i;
    static char* rv[255];
    char buf[1024];

    if (!list) {
        rv[0] = 0;
        return rv;
    }

    /*
      The upper bound for the loop has been decreased by one so that
      we can add the terminating null pointer to the array if our
      array fills up.
     */
    for (i = 0; i < sizeof(rv)/sizeof(rv[0]) - 1; i++) {
        char* s = buf;
        /* eat leading whitespace */
        while (*list == ' ' || *list == '\t')
            list++;
        while (*list && *list != ',')
            *s++ = *list++;
        *s++ = '\0';
        rv[i] = Xpermalloc(s - buf);
        bcopy(buf, rv[i], s - buf);
        if (!*list) {
            rv[i + 1] = 0;
            return rv;
        }
        list++;
    }
    fprintf(stderr, "Sorry, \"only\" 255 icons supported; continuing....\n");
    rv[i] = 0;
    return rv;
}

/*
  We need a mapping from pids to windows.
 */

typedef struct pidmap {
    int pid;
    Window window;
    struct pidmap* next;
} pidmap;

static pidmap* processes = 0;

static void
HandleDeadChildren(display, icons)
Display* display;
XContext icons;
{
    while (childDied) {
        int pid = wait(0);
        pidmap** process = &processes;
        pidmap* old;
        Icon* icon;

        while (*process && (*process)->pid != pid)
            process = &(*process)->next;

        if (!*process) {
            fprintf(stderr, "Programmer error --- lost a process!\n");
            abort();
        }

        old = *process;
        *process = old->next;
        XUnmapWindow(display, old->window);
        XMapWindow(display, old->window);
        if (XFindContext(display, old->window, icons,
                         (caddr_t) &icon) != XCSUCCESS) {
            fprintf(stderr, "Programmer error --- lost an icon!\n");
            abort();
        }
        icon->running = 0;
        free((char*) old);
        childDied--;
    }
}

static void
HandleSIGCHLD()
{
    childDied++;
}

static void
AddProcess(event, icons)
XMapEvent* event;
XContext icons;
{
    Icon* icon;
    int pid;
    XUnmapEvent sendEvent;

    if (XFindContext(event->display, event->window, icons,
                     (caddr_t) &icon) != XCSUCCESS) {
        fprintf(stderr, "Programmer error --- lost an icon!\n");
        abort();
    }

    if (icon->running)
        /* Prevent a race-sort of condition if the child dies too quickly? */
        HandleDeadChildren(event->display, icons);
    else {
        pidmap* process;
        char* command = icon->command;
        char* s;

        icon->running = 1;

        /* ??? We need to conditionally redefine vfork as fork. */

        switch (pid = vfork()) {
        case -1:
            perror("fork");
            exit(2);
            /* NOT REACHED */
        case 0:
            setpgrp(0, getpid());
            for (s = command; *s; s++)
                if (index(" \t\n|<>&'\"*?$()[]{}=\\;~`", *s)) {
                    execl(getenv("SHELL"), "$SHELL", "-c", command, 0);
                    break;
                }
            if (!*s)
                execlp(command, command, 0);
            fprintf(stderr, "Couldn't run \"%s\".\n", command);
            perror("exec");
            exit(0);
            /* NOT REACHED */
        }

        process = (pidmap*) malloc(sizeof(*process));
        process->window = icon->window;
        process->pid = pid;
        process->next = processes;
        processes = process;
    }

    XUnmapWindow(event->display, icon->window);

    /*
      ICCCM says we must do this, as the window may have since
      become unmapped....
      */
    sendEvent.event = DefaultRootWindow(event->display);
    sendEvent.window = icon->window;
    sendEvent.from_configure = 0;
    XSendEvent(event->display, sendEvent.event, 0,
               SubstructureRedirectMask | SubstructureNotifyMask,
               (XEvent*) &sendEvent);
}

static void
Dispatch(event, icons)
XEvent* event;
XContext icons;
{
    switch (event->type) {
    case MapNotify:
        AddProcess(event, icons);
        break;
    case UnmapNotify:
    case ReparentNotify:
    case ConfigureNotify:
        /* ignore */
        break;
    default:
        fprintf(stderr, "Strange event %d.\n", event->type);
        break;
    }
}

main(argc, argv)
int argc;
char** argv;
{
    Display* display;
    char* displayName;
    char resource[255], class[255];
    char** iconNames;
    char** name;
    XContext icons;
    char* rindex();
    unsigned long readfds[1], exceptfds[1];
    int fd;

    programName = rindex(argv[0], '/');
    if (programName)
        programName++;
    else
        programName = argv[0];

    XrmParseCommand(&initialDatabase,
                    switches, sizeof(switches)/sizeof(switches[0]),
                    programName, &argc, argv);

    if (argc > 1)
        usage();

    sprintf(resource, "%s.display", programName);
    display = XOpenDisplay(displayName = GetStringResource(resource, ""));

    if (!display) {
        fprintf(stderr, "Couldn't open display \"%s\"\n",
                XDisplayName(displayName));
        usage();
    }

    serverDatabase = XrmGetStringDatabase(display->xdefaults);

    sprintf(resource, "%s.icons", programName);
    sprintf(class, "%s.Icons", programClass);
    iconNames = ParseIconList(GetStringResource(resource, class));

    for (name = iconNames; *name; name++)
        CreateIcon(display, icons, *name);

    fd = ConnectionNumber(display);
    readfds[fd/32] = 1 << fd % 32;
    exceptfds[fd/32] = 1 << fd % 32;

    (void) signal(SIGCHLD, HandleSIGCHLD);

    /*
      Some machines (e.g., Tek 4317) start processes with some bogus
      sigmask.  If I were trying to port this program and sigsetmask
      failed to link, I'd comment out the following line....
     */
    sigsetmask(0);

    for (;;) {
        int i;
        extern int errno;

        while (i = XPending(display))
            while (i--) {
                XEvent event;

                XNextEvent(display, &event);
                Dispatch(&event, icons);
            }
        while (select(fd + 1, readfds, 0, exceptfds, 0) < 1) {
            if (errno != EINTR) {
                perror("select");
                exit(2);
            }
            HandleDeadChildren(display, icons);
            XFlush(display);
        }
    }
}
\End\Of\Shar\
else
  echo "will not over write ./xicon.c"
fi
if `test ! -s ./xicon.man`
then
echo "writing ./xicon.man"
cat > ./xicon.man << '\End\Of\Shar\'
.TH XICON 1 "20 September 1990" "X contributed software"
.SH NAME
xicon -- manage "inactive icons"
.SH SYNOPSIS
\fBxmines\fP
[-display \fIdisplay\fP]
[-xrm \fIresource\fP [...]]
.SH DESCRIPTION
\fIXicon\fP creates a set of icons; when one of these icons becomes
deiconified, a user-specified program is started.  When a program
started by \fIxicon\fP exits, the fake icon is restarted.
.PP
The program starts out by reading its "icons" (class "Icons")
resource, which is a comma-separated list of icons to create.
\fIXicon\fP then reads the resources specific to each of those icons
(described below).
.SH RESOURCES
\fIXicon\fP is of class "XIcon".
.PP
Icon resources are specified by "xicon.\fIicon\fP.\fIresource\fP."  If
a class name is specified for the icon, class lookups will be of the
form "XIcon.\fIclass\fP.\fIresource\fP."  Icon resources are as follows:
.TP 8
.B "class (\fPclass\fB Class)"
This is the first resource read for each icon.  It specifies the
icon's resource class, which can be used for further lookups (as
described above).
.TP 8
.B "command (\fPclass\fB Command)"
This specifies the command to be executed when the icon is activated.
The command is fed to $SHELL intact, so pipes, redirection, and the
"exec" shell builtin are all useful.  One useful command (for shells
supporting it, like GNU bash) is "kill $PPID", to exit \fIxicon\fP.
\fIXicon\fP could then be used as a session manager.
.TP 8
.B "iconName (\fPclass\fB IconName)"
This resource specifies the WM_ICON_NAME for the icon, and is used as
the name for the icon by most window managers.  It defaults to the
value of the title resource if not specified.
.TP 8
.B "title (\fPclass\fB Title)"
This resource specifies the WM_NAME resource for the icon, which is
used as the "window name" (as opposed to the icon name) for the icon.
This is of relevance in certain cases -- for instance, the window name
appears in TWM's window menu.
.PP
All resources default to icon's name as specified in the "XIcon.icons"
resource unless otherwise specified.
.SH ENVIRONMENT VARIABLES
.IP "DISPLAY" 8
Determines X server to use.
.IP "SHELL" 8
Specifies shell to be used for command invocation.
.SH BUGS
Under \fItwm\fP (and probably most other window managers)
there is a brief flash as the "real" xicon window gets mapped by the
window manager when the icon gets deiconified.  This is ugly, to say
the least.
.PP
\fIXdm\fP may fail to set the SHELL environment variable if it
is empty (used to imply a default of /bin/sh under some version of
Unix).
\fIXIcon\fP will either crash or fail on such machines.
A good workaround (even if you don't used \fIxicon\fP) is to
check for a null SHELL in the system Xsession and set it to /bin/sh.
.PP
Probably won't work under System V.  Someone should fix this.
.SH AUTHOR
Bill Trost (trost%reed@cse.ogi.edu)
\End\Of\Shar\
else
  echo "will not over write ./xicon.man"
fi
echo "Finished archive 1 of 1"
exit


dan
----------------------------------------------------
O'Reilly && Associates   argv@sun.com / argv@ora.com
Opinions expressed reflect those of the author only.