trost%reed@cse.ogi.edu (03/20/91)
Submitted-by: trost%reed@cse.ogi.edu Posting-number: Volume 12, Issue 42 Archive-name: xicon/part01 IT EVEN WORKS!!!! After months of eager anticipation, the new and *functional* version of xicon has been put together. This version was actually tested before distribution. I have been told it works under vtwm and tvtwm. Also, the problem with spinning xicons (xica?) seems to have been eliminated. Again, the patches were large enough warranting posting of the entire source --- not to mention the confusion caused by that broken distribution of xicon. I posted this with filesize checking, just in case the Bitnot should happen to get a hold of it. #!/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 [ `wc -c ./Imakefile | awk '{printf $1}'` -ne 136 ] then echo `wc -c ./Imakefile | awk '{print "Got " $1 ", Expected " 136}'` fi if `test ! -s ./patchlevel.h` then echo "writing ./patchlevel.h" cat > ./patchlevel.h << '\End\Of\Shar\' #define PATCHLEVEL 3 \End\Of\Shar\ else echo "will not over write ./patchlevel.h" fi if [ `wc -c ./patchlevel.h | awk '{printf $1}'` -ne 21 ] then echo `wc -c ./patchlevel.h | awk '{print "Got " $1 ", Expected " 21}'` fi if `test ! -s ./xicon.c` then echo "writing ./xicon.c" cat > ./xicon.c << '\End\Of\Shar\' #include <stdio.h> #include <ctype.h> #ifdef SYSV # define SIGCHLD SIGCLD # ifndef att # define sigsetmask(x) # endif /* !att */ #else /* SYSV */ # include <sys/wait.h> #endif /* SYSV */ #include <sys/signal.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 wmFrame; static XrmDatabase initialDatabase, serverDatabase = 0; static XrmOptionDescRec switches[] = { { "-display", ".display", XrmoptionSepArg, (caddr_t) NULL }, { "-wmframe", ".wmFrame", XrmoptionNoArg, (caddr_t) "true" }, { "-xrm", NULL, XrmoptionResArg, (caddr_t) NULL }, }; static void usage() { fprintf(stderr, "usage: %s [ -display display ] \ [ -wmframe ] [ -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 Bool GetBooleanResource(name, class, dflt) char* name; char* class; Bool dflt; { char* resource; char* s; char** p; static char* affirmation[] = { "yes", "on", "true", 0 }; static char* denial[] = { "no", "off", "false", 0 }; resource = GetStringResource(name, class); if (!resource) return dflt; for (s = resource; *s; s++) if (isupper(*s)) *s = tolower(*s); p = affirmation; while (*p) if (strcmp(*p++, resource) == 0) return 1; p = denial; while (*p) if (strcmp(*p++, resource) == 0) return 0; return dflt; } /* 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); /* Thanks to jik@pit-manager.mit.edu for the correct cast in XFindContext. I just *couldn't* figure out what gcc was complaining about.... */ 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(display, window, icons) Display* display; Window window; XContext icons; { Icon* icon; int pid; XUnmapEvent sendEvent; if (XFindContext(display, window, icons, (caddr_t*) &icon) != XCSUCCESS) { fprintf(stderr, "Programmer error --- lost an icon!\n"); abort(); } XUnmapWindow(display, icon->window); /* ICCCM says we must do this, as the window may have since become unmapped.... */ sendEvent.event = DefaultRootWindow(display); sendEvent.window = icon->window; sendEvent.from_configure = 0; XSendEvent(display, sendEvent.event, 0, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*) &sendEvent); XFlush(display); /* do it NOW! */ if (icon->running) /* Prevent a race-sort of condition if the child dies too quickly? */ HandleDeadChildren(display, icons); else { pidmap* process; char* command = icon->command; char* s; icon->running = 1; /* ??? We need to conditionally redefine vfork as fork. */ /* Never you mind the above; there is a bug with Ultrix 4.0+ RISC; if the child process from a vfork fails to do an exec, the parent never receives a SIGCHLD. -|trost Fri Feb 8 12:00:06 1991 */ switch (pid = fork()) { case -1: perror("fork"); exit(2); /* NOT REACHED */ case 0: setpgrp(0, getpid()); for (s = command; *s; s++) if (index(" \t\n|<>&'\"*?$()[]{}=\\;~`", *s)) { char* getenv(); char* shell = getenv("SHELL"); execl(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; } } static Window CreateIconWindow(display, icon) Display* display; Icon* icon; { Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, 2, 2, 0, 0, None); XClassHint classHint; XSizeHints sizeHints; XWMHints wmHints; char hostname[255]; XSetWindowAttributes attributes; XSelectInput(display, window, ExposureMask | 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); /* This was hacked in so that tvtwm will put window on-screen. */ /* Transient for self? Sure, why not? */ XSetTransientForHint(display, window, window); XStoreName(display, window, icon->title); XSetIconName(display, window, icon->icon_name); wmHints.flags = StateHint; wmHints.initial_state = IconicState; XSetWMHints(display, window, &wmHints); attributes.backing_store = NotUseful; XChangeWindowAttributes(display, window, CWBackingStore, &attributes); 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); XSaveContext(display, icon->window, icons, icon); sprintf(resource, baseResource, "startIconic"); sprintf(class, baseClass, "Command"); if (GetBooleanResource(resource, class, 1)) XMapWindow(display, icon->window); else AddProcess(display, icon->window, icons); } 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; } static void Dispatch(event, icons) XEvent* event; XContext icons; { XEvent junkEvent; switch (event->type) { case UnmapNotify: case ReparentNotify: case ConfigureNotify: /* ignore */ break; case MapNotify: XRaiseWindow(event->xany.display, event->xany.window); XMoveWindow(event->xany.display, event->xany.window, 5, 5); break; case Expose: /* Let's try sucking off all the exposes that might be pending for this window. */ if (wmFrame) { Window root, parent; Window* children; unsigned nchildren; /* should always be zero */ XQueryTree(event->xany.display, event->xany.window, &root, &parent, &children, &nchildren); XFree((char *) children); if (root == parent) break; } AddProcess(event->xany.display, event->xany.window, icons); 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* waitString; int i; char** iconNames; char** name; XContext icons; char* rindex(); 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.wmFrame", programName); sprintf(class, "%s.WmFrame", programClass); wmFrame = GetBooleanResource(resource, class, 0); 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); (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 remove the following lines, or, if you feel up to it, inspect the #ifdef junk at the top of the file. */ sigsetmask(0); for (;;) { static unsigned long readfds[1], exceptfds[1]; XEvent event; extern int errno; if (*readfds) { XNextEvent(display, &event); Dispatch(&event, icons); } while (i = XPending(display)) while (i--) { XNextEvent(display, &event); Dispatch(&event, icons); } XFlush(display); readfds[fd/32] = 1 << fd % 32; exceptfds[fd/32] = 1 << fd % 32; HandleDeadChildren(display, icons); /* race condition here: If a child dies here before the select starts, no one will ever know about it.... */ 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 [ `wc -c ./xicon.c | awk '{printf $1}'` -ne 12133 ] then echo `wc -c ./xicon.c | awk '{print "Got " $1 ", Expected " 12133}'` 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 \fBxicon\fP [-display \fIdisplay\fP] [-wmframe] [-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". .TP 8 .B "wmFrame (\fPclass\fB WmFrame)" This resource tells xicon that it should treat the icon as being deiconified only if there is a window-manager frame on the icon window. This is very useful if you have occasion to restart your window manager, or if you exit your session while xicon is still running (the window manager can get killed before the xicon does, meaning that \fIall\fP your icons become deiconified, and many will end up in the next person's session). .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 (unless the command looks like a simple single-word command, in which case it is executed directly), 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 property 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 (see below). .TP 8 .B "startIconic (\fPclass\fB StartIconic)" This resource, true by default, indicates whether \fIxicon\fP will start by displaying the icon or by immediately starting the program. Useful if you want to start some things running but would still like to have them managed by \fIxicon\fP. .TP 8 .B "title (\fPclass\fB Title)" This resource specifies the WM_NAME property 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 icon resources default to icon's name as specified in the "XIcon.icons" resource unless otherwise specified. .SH EXAMPLE The following is a reasonable (albeit somewhat contrived) set of X defaults: .EX 0 XIcon.icons: xterm, slowvax, exit, hibernate, xwininfo, xclock ! My window manager can do clever things with the name (like put ! parentheses around it. XIcon*class: XIcon ! Gotta start somewhere... XIcon.xterm.startIconic: false ! Both "exit" and "hibernate" are based on a bash feature which sets ! $PPID to the shell's parent process (oddly enough). ! Hibernate makes xicon go away and restart in 20 seconds. But make ! sure our parent isn't /etc/init.... XIcon.hibernate.command: \\ set `ps ww$PPID` ; kill $PPID ; shift 9 ; sleep 20 ; \\ [ $PPID -gt 1 ] && exec $* XIcon.exit.command: kill $PPID XIcon.slowvax.command: xterm -T reed -e rlogin reed XIcon.xwininfo.command: \\ xterm -T 'window info' -e sh -c 'xwininfo -all ; cat > /dev/null' ! Just some cute names for the xclock. XIcon.xclock.iconName: Clock XIcon.xclock.Title: Ugly Blue Clock .EE .SH ENVIRONMENT .IP "DISPLAY" 8 Determines X server to use. .IP "SHELL" 8 Specifies shell to be used for command invocation. .SH BUGS WmFrame should probably be true by default. .PP 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. .SH AUTHOR Bill Trost (trost%reed@cse.ogi.edu) \End\Of\Shar\ else echo "will not over write ./xicon.man" fi if [ `wc -c ./xicon.man | awk '{printf $1}'` -ne 4638 ] then echo `wc -c ./xicon.man | awk '{print "Got " $1 ", Expected " 4638}'` fi echo "Finished archive 1 of 1" exit -- Dan Heller ------------------------------------------------ O'Reilly && Associates Z-Code Software Senior Writer President argv@ora.com argv@zipcode.com ------------------------------------------------ General Email: argv@sun.com Comp-sources-x stuff: comp-sources.x@uunet.uu.net