about summary refs log tree commit diff
path: root/other/pamx/window.c
diff options
context:
space:
mode:
Diffstat (limited to 'other/pamx/window.c')
-rw-r--r--other/pamx/window.c1209
1 files changed, 1209 insertions, 0 deletions
diff --git a/other/pamx/window.c b/other/pamx/window.c
new file mode 100644
index 00000000..1a6e510b
--- /dev/null
+++ b/other/pamx/window.c
@@ -0,0 +1,1209 @@
+/*
+   Functions to allocate and deallocate structures and structure data
+ 
+   By Jim Frost 1989.10.03, Bryan Henderson 2006.03.25.
+ 
+   See COPYRIGHT file for copyright information.
+*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/Xdefs.h>  /* Needed by Xutil.h */
+#include <X11/X.h>      /* Needed by Xutil.h */
+#include <X11/Xlib.h>   /* Needed by Xutil.h */
+#include <X11/Xutil.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pm.h"
+#include "ximageinfo.h"
+#include "send.h"
+#include "image.h"
+#include "window.h"
+
+/* A viewer object is something in which you can display an image.  You
+   can display multiple images in the same viewer, sequentially.
+
+   The viewer has a permanent window, called the viewport.  When you display
+   an image, it has a second window, called the image window, which is a
+   child of the viewport.
+*/
+
+struct viewer {
+    Display *    dispP;
+    int          scrn;
+    Window       imageWin;
+    Window       viewportWin;
+    Colormap     imageColormap;
+    Cursor       cursor;
+    unsigned int xpos, ypos;
+    unsigned int width, height;
+    bool         fullscreen;
+    bool         userChoseGeometry;
+    Atom         deleteAtom;
+    bool         blank;
+        /* I'm just guessing, but it seems that a "paint" operation is
+           necessary the first time a viewport is used, but not when
+           displaying a new image in an old viewport.  I assume that's
+           because the new viewport is blank, and that's what this
+           value means.
+        */
+    Pixmap       pixmap;
+};
+
+
+
+static void
+setXloadimageClassHint(Display * const dispP,
+                       Window    const window) {
+
+    XClassHint classhint;
+
+    classhint.res_class = (char*)"Xloadimage";
+    classhint.res_name  = (char*)"xloadimage";
+
+    XSetClassHint(dispP, window, &classhint);
+}
+
+
+
+static void
+setDeleteWindow(viewer * const viewerP) {
+
+    Atom const protoAtom = XInternAtom(viewerP->dispP, "WM_PROTOCOLS", False);
+    
+    viewerP->deleteAtom = XInternAtom(viewerP->dispP,
+                                      "WM_DELETE_WINDOW", False);
+    
+    if ((protoAtom != None) && (viewerP->deleteAtom != None))
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        protoAtom, XA_ATOM, 32, PropModeReplace,
+                        (unsigned char *)&viewerP->deleteAtom, 1);
+}
+
+
+
+static void
+getInitialViewerGeometry(const char *   const geometryString,
+                         bool           const fullscreen,
+                         Display *      const dispP,
+                         int            const scrn,
+                         unsigned int * const xposP,
+                         unsigned int * const yposP,
+                         unsigned int * const widthP,
+                         unsigned int * const heightP,
+                         bool *         const userChoseP) {
+
+    unsigned int const defaultWidth  = 10;
+    unsigned int const defaultHeight = 10;
+
+    if (fullscreen) {
+        *widthP  = DisplayWidth(dispP, scrn);
+        *heightP = DisplayHeight(dispP, scrn);
+        *xposP   = 0;
+        *yposP   = 0;
+        *userChoseP = TRUE;
+    } else if (geometryString) {
+        const char * defGeom;
+        asprintfN(&defGeom, "%ux%u+0+0", defaultWidth, defaultHeight);
+        XGeometry(dispP, scrn, geometryString, defGeom, 0, 1, 1, 0, 0,
+                  (int *)xposP, (int *)yposP,
+                  (int *)widthP, (int *)heightP);
+        strfree(defGeom);
+        *userChoseP = TRUE;
+    } else {
+        *widthP     = defaultWidth;
+        *heightP    = defaultHeight;
+        *xposP      = 0;
+        *yposP      = 0;
+        *userChoseP = FALSE;
+    }
+}
+
+
+
+void
+createViewer(viewer **     const viewerPP,
+             Display *     const dispP,
+             int           const scrn,
+             const char *  const geometryString,
+             bool          const fullscreen) {
+
+    viewer * viewerP;
+    
+    XSetWindowAttributes  swa_view;
+
+    MALLOCVAR_NOFAIL(viewerP);
+
+    viewerP->dispP       = dispP;
+    viewerP->scrn        = scrn;
+    viewerP->fullscreen  = fullscreen;
+    viewerP->imageWin    = 0;
+    viewerP->viewportWin = 0;
+
+    getInitialViewerGeometry(geometryString, fullscreen, dispP, scrn,
+                             &viewerP->xpos, &viewerP->ypos,
+                             &viewerP->width, &viewerP->height,
+                             &viewerP->userChoseGeometry);
+
+    viewerP->cursor = XCreateFontCursor(dispP, XC_watch);
+
+    swa_view.background_pixel = WhitePixel(dispP, scrn);
+    swa_view.backing_store    = NotUseful;
+    swa_view.cursor           = viewerP->cursor;
+    swa_view.event_mask       =
+        ButtonPressMask | Button1MotionMask | KeyPressMask |
+        StructureNotifyMask | EnterWindowMask | LeaveWindowMask;
+    swa_view.save_under       = FALSE;
+    
+    viewerP->viewportWin =
+        XCreateWindow(dispP, RootWindow(dispP, scrn),
+                      viewerP->xpos, viewerP->ypos,
+                      viewerP->width, viewerP->height, 0,
+                      DefaultDepth(dispP, scrn), InputOutput,
+                      DefaultVisual(dispP, scrn),
+                      CWBackingStore | CWBackPixel | CWCursor |
+                      CWEventMask | CWSaveUnder,
+                      &swa_view);
+
+    setXloadimageClassHint(viewerP->dispP, viewerP->viewportWin);
+    
+    setDeleteWindow(viewerP);
+
+    viewerP->blank = TRUE;
+
+    *viewerPP = viewerP;
+}
+
+
+
+static void
+determineRepaintStrategy(viewer  *    const viewerP,
+                         bool         const userWantsPixmap,
+                         bool         const verbose,
+                         XImageInfo * const ximageinfoP,
+                         Pixmap *     const pixmapP) {
+                        
+    /* Decide how we're going to handle repaints.  We have three modes:
+       use backing-store, use background pixmap, and use exposures.
+       If the server supports backing-store, we enable it and use it.
+       This really helps servers which are memory constrained.  If the
+       server does not have backing-store, we try to send the image to
+       a pixmap and use that as backing-store.  If that fails, we use
+       exposures to blit the image (which is ugly but it works).
+       
+       'use_pixmap' forces background pixmap mode, which may improve
+       performance.
+    */
+
+    ximageinfoP->drawable = viewerP->imageWin;
+    if ((DoesBackingStore(ScreenOfDisplay(viewerP->dispP, viewerP->scrn)) ==
+         NotUseful) ||
+        userWantsPixmap) {
+        *pixmapP = ximageToPixmap(viewerP->dispP, viewerP->imageWin,
+                                  ximageinfoP);
+        if (*pixmapP == None && verbose)
+            pm_message("Cannot create image in server; "
+                       "repaints will be ugly!");
+    } else
+        *pixmapP = None;
+}
+
+
+
+static void
+setImageWindowAttr(Display * const dispP,
+                   int       const scrn,
+                   Window    const imageWindow,
+                   Colormap  const cmap,
+                   Pixmap    const pixmap) {
+
+    /* build window attributes for the image window */
+
+    XSetWindowAttributes swa_img;
+    unsigned int         wa_mask_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = cmap;
+    swa_img.border_pixel = 0;
+
+    wa_mask_img = 0;  /* initial value */
+
+    if (pixmap == None) {
+        /* No pixmap.  Must paint over the wire.  Ask for BackingStore
+           to cut down on the painting.  But, ask for Exposures so we can
+           paint both viewables and backingstore.
+        */
+
+        swa_img.background_pixel = WhitePixel(dispP,scrn);
+        wa_mask_img |= CWBackPixel;
+        swa_img.event_mask = ExposureMask;
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = WhenMapped;
+        wa_mask_img |= CWBackingStore;
+    } else {
+        /* We have a pixmap so tile the window.  to move the image we only
+           have to move the window and the server should do the rest.
+         */
+
+        swa_img.background_pixmap = pixmap;
+        wa_mask_img |= CWBackPixmap;
+        swa_img.event_mask = 0; /* no exposures please */
+        wa_mask_img |= CWEventMask;
+        swa_img.backing_store = NotUseful;
+        wa_mask_img |= CWBackingStore;
+    }
+    XChangeWindowAttributes(dispP, imageWindow, wa_mask_img, &swa_img);
+}
+
+
+
+static void
+createImageWindow(viewer *      const viewerP,
+                  XImageInfo *  const ximageInfoP,
+                  Image *       const imageP,
+                  Visual *      const visualP,
+                  bool          const userWantsPixmap,
+                  bool          const verbose) {
+
+    XSetWindowAttributes  swa_img;
+
+    swa_img.bit_gravity  = NorthWestGravity;
+    swa_img.save_under   = FALSE;
+    swa_img.colormap     = ximageInfoP->cmap;
+    swa_img.border_pixel = 0;
+    viewerP->imageWin = XCreateWindow(viewerP->dispP, viewerP->viewportWin,
+                                      viewerP->xpos, viewerP->ypos,
+                                      imageP->width, imageP->height, 0,
+                                      ximageInfoP->depth, InputOutput, visualP,
+                                      CWBitGravity | CWColormap | CWSaveUnder |
+                                      CWBorderPixel, &swa_img);
+    viewerP->imageColormap = ximageInfoP->cmap;
+    setXloadimageClassHint(viewerP->dispP, viewerP->imageWin);
+
+    determineRepaintStrategy(viewerP, userWantsPixmap, verbose, ximageInfoP,
+                             &viewerP->pixmap);
+
+    setImageWindowAttr(viewerP->dispP, viewerP->scrn,
+                       viewerP->imageWin, ximageInfoP->cmap,
+                       viewerP->pixmap);
+}
+
+
+
+static void
+destroyImageWindow(viewer * const viewerP) {
+    
+    if (viewerP->imageWin) {
+        if (viewerP->imageColormap &&
+            (viewerP->imageColormap != DefaultColormap(viewerP->dispP, viewerP->scrn)))
+            XFreeColormap(viewerP->dispP, viewerP->imageColormap);
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    }
+}
+                       
+
+
+static void
+changeCursor(viewer *      const viewerP,
+             unsigned int  const imageWidth,
+             unsigned int  const imageHeight) {
+
+    Cursor cursor;
+    XSetWindowAttributes swa;
+
+    if ((viewerP->width >= imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_icon);
+    else if ((viewerP->width < imageWidth) && (viewerP->height >= imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_h_double_arrow);
+    else if ((viewerP->width >= imageWidth) && (viewerP->height < imageHeight))
+        cursor = XCreateFontCursor(viewerP->dispP, XC_sb_v_double_arrow);
+    else
+        cursor = XCreateFontCursor(viewerP->dispP, XC_fleur);
+
+    swa.cursor = cursor;
+    XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                            CWCursor, &swa);
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    viewerP->cursor = cursor;
+}
+
+
+
+static void
+placeImage(viewer * const viewerP,
+           int      const width,
+           int      const height,
+           int *    const rxP,     /* input and output */
+           int *    const ryP) {   /* input and output */
+
+    int pixx, pixy;
+    
+    pixx = *rxP;
+    pixy = *ryP;
+    
+    if (viewerP->width > width)
+        pixx = (viewerP->width - width) / 2;
+    else {
+        if ((pixx < 0) && (pixx + width < viewerP->width))
+            pixx = viewerP->width - width;
+        if (pixx > 0)
+            pixx = 0;
+    }
+    if (viewerP->height > height)
+        pixy = (viewerP->height - height) / 2;
+    else {
+        if ((pixy < 0) && (pixy + height < viewerP->height))
+            pixy = viewerP->height - viewerP->height;
+        if (pixy > 0)
+            pixy = 0;
+    }
+    XMoveWindow(viewerP->dispP, viewerP->imageWin, pixx, pixy);
+
+    *rxP = pixx;
+    *ryP = pixy;
+}
+
+
+
+static void
+blitImage(XImageInfo * const ximageinfoP,
+          unsigned int const width,
+          unsigned int const height,
+          int          const xArg,
+          int          const yArg,
+          int          const wArg,
+          int          const hArg) {
+
+    int w, h;
+    int x, y;
+
+    w = MIN(wArg, width);
+    h = MIN(hArg, height);
+
+    x = xArg;
+    y = yArg;
+
+    if (x < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, -x, h, False);
+        w -= (0 - x);
+        x = 0;
+    }
+    if (y < 0) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y, w, -y, False);
+        h -= (0 - y);
+        y = 0;
+    }
+    if (x + w > width) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x + width, y, x + w - width, h, False);
+        w -= x + w - width;
+    }
+    if (y + h > height) {
+        XClearArea(ximageinfoP->disp, ximageinfoP->drawable,
+                   x, y + height, w, y + h - height, False);
+        h -= y + h - height;
+    }
+    sendXImage(ximageinfoP, x, y, x, y, w, h);
+}
+
+
+
+void
+destroyViewer(viewer * const viewerP) {
+/*----------------------------------------------------------------------------
+   Clean up static window.
+-----------------------------------------------------------------------------*/
+    if (viewerP->pixmap != None)
+        XFreePixmap(viewerP->dispP, viewerP->pixmap);
+
+    if (viewerP->imageWin)
+        XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+    viewerP->imageWin = 0;
+    
+    if (viewerP->viewportWin)
+        XDestroyWindow(viewerP->dispP, viewerP->viewportWin);
+    
+    viewerP->viewportWin = 0;
+
+    XFreeCursor(viewerP->dispP, viewerP->cursor);
+
+    free(viewerP);
+}
+
+
+
+static void
+setViewportColormap(viewer *  const viewerP,
+                    Visual *  const visualP) {
+/*----------------------------------------------------------------------------
+  Set the colormap and WM_COLORMAP_WINDOWS properly for the viewport.
+-----------------------------------------------------------------------------*/
+    static Atom cmap_atom = None;
+    XSetWindowAttributes swa;
+    Window cmap_windows[2];
+
+    if (cmap_atom == None)
+        cmap_atom = XInternAtom(viewerP->dispP, "WM_COLORMAP_WINDOWS", False);
+    
+    /* If the visual we're using is the same as the default visual (used by
+       the viewport window) then we can set the viewport window to use the
+       image's colormap.  This keeps most window managers happy.
+    */
+    
+    if (visualP == DefaultVisual(viewerP->dispP, viewerP->scrn)) {
+        swa.colormap = viewerP->imageColormap;
+        XChangeWindowAttributes(viewerP->dispP, viewerP->viewportWin,
+                                CWColormap, &swa);
+        XDeleteProperty(viewerP->dispP, viewerP->viewportWin, cmap_atom);
+    } else {
+        /* Smart window managers can handle it when we use a different colormap
+           in our subwindow so long as we set the WM_COLORMAP_WINDOWS property
+           ala ICCCM.
+        */
+        cmap_windows[0] = viewerP->imageWin;
+        cmap_windows[1] = viewerP->viewportWin;
+        XChangeProperty(viewerP->dispP, viewerP->viewportWin,
+                        cmap_atom, XA_WINDOW, 32,
+                        PropModeReplace, (unsigned char *)cmap_windows, 2);
+    }
+}
+
+
+
+static const char *
+iconName(const char * const s) {
+/*----------------------------------------------------------------------------
+   Return an icon name suitable for an image titled 's'.
+
+   s == NULL means untitled.
+-----------------------------------------------------------------------------*/
+    const char * retval;
+    char buf[BUFSIZ];
+
+    if (!s)
+        STRSCPY(buf, "Unnamed");
+    else {
+        char * t;
+
+        STRSCPY(buf, s);
+        /* strip off stuff following 1st word.  This strips
+           info added by processing functions too.
+        */
+        t = index(buf, ' ');
+        if (t)
+            *t = '\0';
+    
+        /* Strip off leading path.  if you don't use unix-style paths,
+           You might want to change this.
+        */
+    
+        t= rindex(buf, '/');
+        if (t) {
+            char * p;
+            for (p = buf, ++t; *t; ++p, ++t)
+                *p = *t;
+            *p = '\0';
+        }
+        /* look for an extension and strip it off */
+        t = index(buf, '.');
+        if (t)
+            *t = '\0';
+    }
+
+    retval = strdup(buf);
+    if (retval == NULL)
+        pm_error("Out of memory");
+    return retval;
+}
+
+
+
+/* visual class to name table  */
+
+static struct visual_class_name {
+    int          class; /* numerical value of class */
+    const char * name;  /* actual name of class */
+} const VisualClassName[] = {
+    {TrueColor,   "TrueColor"   } ,
+    {DirectColor, "DirectColor" } ,
+    {PseudoColor, "PseudoColor" } ,
+    {StaticColor, "StaticColor" } ,
+    {GrayScale,   "GrayScale"   } ,
+    {StaticGray,  "StaticGray"  } ,
+    {StaticGray,  "StaticGrey"  } ,
+    {-1,          NULL          }
+};
+
+
+
+int
+visualClassFromName(const char * const name) {
+
+    unsigned int a;
+    int class;
+    bool found;
+    
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (STRCASEEQ(VisualClassName[a].name, name)) {
+            /* Check for uniqueness.  We special-case StaticGray
+               because we have two spellings but they are unique if
+               we find either.
+            */
+            if (found && class != StaticGray)
+                pm_error("'%s' does not uniquely describe a visual class",
+                         name);
+
+            class = VisualClassName[a].class;
+            found = TRUE;
+        }
+    }
+    if (!found)
+        pm_error("'%s' is not a visual class name", name);
+
+    return class;
+}
+
+
+
+static const char *
+nameOfVisualClass(int const class) {
+
+    unsigned int a;
+    const char * name;
+    bool found;
+
+    for (a = 0, found = FALSE; VisualClassName[a].name; ++a) {
+        if (VisualClassName[a].class == class) {
+            name = VisualClassName[a].name;
+            found = TRUE;
+        }
+    }
+    if (found)
+        return name;
+    else
+        return "[Unknown Visual Class]";
+}
+
+
+
+/* find the best visual of a particular class with a particular depth
+ */
+
+static Visual *
+bestVisualOfClassAndDepth(Display *    const disp,
+                          int          const scrn,
+                          int          const class,
+                          unsigned int const depth) {
+
+    long const vinfoMask =
+        VisualScreenMask | VisualClassMask | VisualDepthMask;
+
+    Visual * best;
+    XVisualInfo template;
+    XVisualInfo * infoP;
+    unsigned int nvisuals;
+
+    best = NULL;  /* initial value */
+
+    template.screen = scrn;
+    template.class  = class;
+    template.depth  = depth;
+    infoP = XGetVisualInfo(disp, vinfoMask, &template, (int*)&nvisuals);
+    if (infoP) {
+        /* There are visuals with this depth */
+
+        /* Not sure what to do if this gives more than one visual of a
+           particular class and depth, so just return the first one.
+        */
+
+        best = infoP->visual;
+        XFree((char *)infoP);
+    }
+    return best;
+}
+
+
+
+static void
+bestVisual(Display *      const disp,
+           int            const scrn,
+           Image *        const imageP,
+           Visual **      const rvisualPP,
+           unsigned int * const rdepthP) {
+/*----------------------------------------------------------------------------
+  Try to determine the best available visual to use for a particular
+  image
+-----------------------------------------------------------------------------*/
+    unsigned int  depth, a;
+    Screen * screen;
+    Visual * visualP;
+    Visual * default_visualP;
+
+    /* Figure out the best depth the server supports.  note that some servers
+       (such as the HP 11.3 server) actually say they support some depths but
+       have no visuals that support that depth.  Seems silly to me ...
+    */
+
+    depth = 0;
+    screen = ScreenOfDisplay(disp, scrn);
+    for (a = 0; a < screen->ndepths; ++a) {
+        if (screen->depths[a].nvisuals &&
+            ((!depth ||
+              ((depth < imageP->depth) && (screen->depths[a].depth > depth)) ||
+              ((screen->depths[a].depth >= imageP->depth) &&
+               (screen->depths[a].depth < depth)))))
+            depth = screen->depths[a].depth;
+    }
+    if (!depth) {
+        /* this shouldn't happen */
+        pm_message("bestVisual: didn't find any depths?!?");
+        depth = DefaultDepth(disp, scrn);
+    }
+    
+    /* given this depth, find the best possible visual
+     */
+    
+    default_visualP = DefaultVisual(disp, scrn);
+    switch (imageP->type) {
+    case ITRUE: {
+        /* If the default visual is DirectColor or TrueColor prioritize such
+           that we use the default type if it exists at this depth
+        */
+
+        if (default_visualP->class == TrueColor) {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn,
+                                                    DirectColor, depth);
+        } else {
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+            if (!visualP)
+                visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor,
+                                                    depth);
+        }
+        
+        if (!visualP || ((depth <= 8) &&
+                         bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                   depth)))
+            visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+        
+    case IRGB: {
+        /* if it's an RGB image, we want PseudoColor if we can get it */
+
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+    } break;
+
+    case IBITMAP: {
+        visualP = bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
+
+        /* It seems pretty wasteful to use a TrueColor or DirectColor visual
+           to display a bitmap (2-color) image, so we look for those last
+        */
+
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, DirectColor,
+                                                depth);
+        if (!visualP)
+            visualP = bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
+    } break;
+    }
+
+    if (!visualP) { /* this shouldn't happen */
+        pm_message("bestVisual: couldn't find one?!?");
+        depth = DefaultDepth(disp, scrn);
+        visualP = DefaultVisual(disp, scrn);
+    }
+    *rvisualPP = visualP;
+    *rdepthP   = depth;
+}
+
+
+
+static void
+bestVisualOfClass(Display *      const disp,
+                  int            const scrn,
+                  Image *        const image,
+                  int            const visual_class,
+                  Visual **      const rvisual,
+                  unsigned int * const rdepth) {
+/*----------------------------------------------------------------------------
+  Given a visual class, try to find the best visual of that class at
+  the best depth.  Return a null visual and depth if we couldn't find
+  any visual of that type at any depth.
+-----------------------------------------------------------------------------*/
+  Visual       *visual;
+  Screen       *screen;
+  unsigned int  a, b, depth;
+
+  /* loop through depths looking for a visual of a good depth which matches
+   * our visual class.
+   */
+
+  screen= ScreenOfDisplay(disp, scrn);
+  visual= (Visual *)NULL;
+  depth= 0;
+  for (a= 0; a < screen->ndepths; a++) {
+    for (b= 0; b < screen->depths[a].nvisuals; b++) {
+      if ((screen->depths[a].visuals[b].class == visual_class) &&
+      (!depth ||
+       ((depth < image->depth) && (screen->depths[a].depth > depth)) ||
+       ((screen->depths[a].depth >= image->depth) &&
+        (screen->depths[a].depth < depth)))) {
+    depth= screen->depths[a].depth;
+    visual= &(screen->depths[a].visuals[b]);
+      }
+    }
+  }
+  *rvisual= visual;
+  *rdepth= depth;
+}
+
+
+
+static void
+getImageDispDimensions(viewer *       const viewerP,
+                       Image *        const imageP,
+                       unsigned int * const widthP,
+                       unsigned int * const heightP) {
+
+    if (viewerP->userChoseGeometry) {
+        *widthP  = viewerP->width;
+        *heightP = viewerP->height;
+    } else {
+        unsigned int const displayWidth = 
+            DisplayWidth(viewerP->dispP, viewerP->scrn);
+        unsigned int const displayHeight =
+            DisplayHeight(viewerP->dispP, viewerP->scrn);
+
+        /* We don't use more than 90% of display real estate unless user
+           explicitly asked for it.
+        */
+        *widthP = MIN(imageP->width, (unsigned)(displayWidth * 0.9));
+        *heightP = MIN(imageP->height, (unsigned)(displayHeight * 0.9));
+    }
+}
+
+
+
+static void
+getVisualAndDepth(Image *        const imageP,
+                  Display *      const dispP,
+                  int            const scrn,
+                  bool           const fit,
+                  bool           const visualSpec,
+                  unsigned int   const visualClass,
+                  Visual **      const visualPP,
+                  unsigned int * const depthP) {
+
+
+    /* If the user told us to fit the colormap, we must use the default
+       visual.
+    */
+
+    if (fit) {
+        *visualPP = DefaultVisual(dispP, scrn);
+        *depthP   = DefaultDepth(dispP, scrn);
+    } else {
+        Visual * visualP;
+        unsigned int depth;
+
+        visualP = NULL;
+        if (!visualSpec) {
+            /* Try to pick the best visual for the image. */
+
+            bestVisual(dispP, scrn, imageP, &visualP, &depth);
+        } else {
+            /* Try to find a visual of the specified class */
+
+            bestVisualOfClass(dispP, scrn, imageP, visualClass,
+                              &visualP, &depth);
+            if (!visualP) {
+                bestVisual(dispP, scrn, imageP, &visualP, &depth);
+                pm_message("Server does not provide %s visual, using %s",
+                           nameOfVisualClass(visualClass),
+                           nameOfVisualClass(visualP->class));
+            }
+        }
+        *visualPP = visualP;
+        *depthP   = depth;
+    }
+}
+
+
+
+static void
+setNormalSizeHints(viewer *     const viewerP,
+                   Image *      const imageP) {
+    
+    XSizeHints sh;
+    
+    sh.width  = viewerP->width;
+    sh.height = viewerP->height;
+    if (viewerP->fullscreen) {
+        sh.min_width  = sh.max_width  = viewerP->width;
+        sh.min_height = sh.max_height = viewerP->height;
+    } else {
+        sh.min_width  = 1;
+        sh.min_height = 1;
+        sh.max_width  = imageP->width;
+        sh.max_height = imageP->height;
+    }
+    sh.width_inc  = 1;
+    sh.height_inc = 1;
+
+    sh.flags = PMinSize | PMaxSize | PResizeInc;
+    if (viewerP->userChoseGeometry)
+        sh.flags |= USSize;
+    else
+        sh.flags |= PSize;
+
+    if (viewerP->userChoseGeometry) {
+        sh.x = viewerP->xpos;
+        sh.y = viewerP->ypos;
+        sh.flags |= USPosition;
+    }
+    XSetNormalHints(viewerP->dispP, viewerP->viewportWin, &sh);
+
+    sh.min_width  = sh.max_width;
+    sh.min_height = sh.max_height;
+    XSetNormalHints(viewerP->dispP, viewerP->imageWin, &sh);
+        /* Image doesn't shrink */
+}
+
+
+
+static void
+setWMHints(viewer * const viewerP) {
+
+    XWMHints wmh;
+
+    wmh.input = TRUE;
+    wmh.flags = InputHint;
+    XSetWMHints(viewerP->dispP, viewerP->viewportWin, &wmh);
+}
+
+
+#define CTL_C '\003'
+
+typedef enum exitReason {
+    EXIT_NONE,
+    EXIT_QUIT,
+    EXIT_WM_KILL,
+    EXIT_DESTROYED
+} exitReason;
+
+
+
+static void
+run(viewer *     const viewerP,
+    Image *      const imageP,
+    XImageInfo * const ximageInfoP,
+    int          const initPixx,
+    int          const initPixy,
+    bool         const install,
+    exitReason * const exitReasonP) {
+
+    int lastx, lasty;
+    int pixx, pixy;
+    union {
+        XEvent              event;
+        XAnyEvent           any;
+        XButtonEvent        button;
+        XKeyEvent           key;
+        XConfigureEvent     configure;
+        XExposeEvent        expose;
+        XMotionEvent        motion;
+        XResizeRequestEvent resize;
+        XClientMessageEvent message;
+    } event;
+    exitReason exitReason;
+
+    lastx = lasty = -1;
+    pixx   = initPixx;
+    pixy   = initPixy;
+
+    exitReason = EXIT_NONE;  /* No reason to exit yet */
+
+    while (exitReason == EXIT_NONE) {
+        XNextEvent(viewerP->dispP, &event.event);
+
+        switch (event.any.type) {
+        case ButtonPress:
+            if (event.button.button == 1) {
+                lastx = event.button.x;
+                lasty = event.button.y;
+            }
+            break;
+
+        case KeyPress: {
+            char buf[128];
+            KeySym ks;
+            XComposeStatus status;
+            Status rc;
+            
+            rc = XLookupString(&event.key, buf, 128, &ks, &status);
+            if (rc == 1) {
+                char const ret = buf[0];
+                char const lowerRet = tolower(ret);
+
+                switch (lowerRet) {
+                case CTL_C:
+                case 'q':
+                    exitReason = EXIT_QUIT;
+                    break;
+                }
+            }
+        } break;
+
+        case MotionNotify: {
+            int mousex, mousey;
+
+            if (imageP->width  <= viewerP->width &&
+                imageP->height <= viewerP->height) {
+                /* we're AT&T */
+            } else {
+                mousex = event.button.x;
+                mousey = event.button.y;
+                while (XCheckTypedEvent(viewerP->dispP, MotionNotify,
+                                        (XEvent*)&event)) {
+                    mousex = event.button.x;
+                    mousey = event.button.y;
+                }
+                pixx -= (lastx - mousex);
+                pixy -= (lasty - mousey);
+                lastx = mousex;
+                lasty = mousey;
+                placeImage(viewerP, imageP->width, imageP->height,
+                           &pixx, &pixy);
+            }
+        } break;
+
+        case ConfigureNotify:
+            viewerP->width  = event.configure.width;
+            viewerP->height = event.configure.height;
+
+            placeImage(viewerP, imageP->width, imageP->height,
+                       &pixx, &pixy);
+
+            /* Configure the cursor to indicate which directions we can drag
+             */
+
+            changeCursor(viewerP, imageP->width, imageP->height);
+            break;
+
+        case DestroyNotify:
+            exitReason = EXIT_DESTROYED;
+            break;
+
+        case Expose:
+            blitImage(ximageInfoP, imageP->width, imageP->height,
+                      event.expose.x, event.expose.y,
+                      event.expose.width, event.expose.height);
+            break;
+
+        case EnterNotify:
+            if (install)
+                XInstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case LeaveNotify:
+            if (install)
+                XUninstallColormap(viewerP->dispP, ximageInfoP->cmap);
+            break;
+
+        case ClientMessage:
+            /* If we get a client message for the viewport window
+               which has the value of the delete atom, it means the
+               window manager wants us to die.
+            */
+
+            if ((event.message.window == viewerP->viewportWin) &&
+                (event.message.data.l[0] == viewerP->deleteAtom)) {
+                exitReason = EXIT_WM_KILL;
+            }
+            break;
+        }
+    }
+    *exitReasonP = exitReason;
+}
+
+
+
+static int
+retvalueFromExitReason(exitReason const exitReason) {
+
+    int retval;
+
+    switch (exitReason) {
+    case EXIT_NONE:      assert(false); break;
+    case EXIT_QUIT:      retval =  0;     break;
+    case EXIT_WM_KILL:   retval = 10;     break;
+    case EXIT_DESTROYED: retval = 20;     break;
+    }
+    return retval;
+}
+
+
+
+static void
+resizeViewer(viewer *     const viewerP,
+             unsigned int const newWidth,
+             unsigned int const newHeight) {
+    
+    if (viewerP->width != newWidth || viewerP->height != newHeight) {
+        XResizeWindow(viewerP->dispP, viewerP->viewportWin,
+                      newWidth, newHeight);
+        viewerP->width  = newWidth;
+        viewerP->height = newHeight;
+    }
+}
+
+
+
+void
+displayInViewer(viewer *       const viewerP,
+                struct Image * const imageP,
+                bool           const install,
+                bool           const userWantsPrivateCmap,
+                bool           const fit,
+                bool           const userWantsPixmap,
+                bool           const visualSpec,
+                unsigned int   const visualClass,
+                const char *   const title,
+                bool           const verbose,
+                int *          const retvalP) {
+    
+    XImageInfo *     ximageInfoP;
+    Visual *         visual;
+    unsigned int     depth;
+    bool             privateCmap;
+    int              pixx, pixy;
+    exitReason       exitReason;
+    unsigned int     windowWidth, windowHeight;
+
+    pixx = -1; pixy = -1;  /* initial values */
+
+    getImageDispDimensions(viewerP, imageP, &windowWidth, &windowHeight);
+
+    resizeViewer(viewerP, windowWidth, windowHeight);
+
+    getVisualAndDepth(imageP, viewerP->dispP, viewerP->scrn,
+                      fit, visualSpec, visualClass,
+                      &visual, &depth);
+    
+    if (verbose && (visual != DefaultVisual(viewerP->dispP, viewerP->scrn)))
+        pm_message("Using %s visual", nameOfVisualClass(visual->class));
+
+    /* If we're in slideshow mode and the user told us to fit the colormap,
+       free it here.
+    */
+
+    if (viewerP->blank) {
+        /* For the first image we display we can use the default cmap.
+           subsequent images use a private colormap (unless they're
+           bitmaps) so we don't get color erosion when switching
+           images.
+        */
+
+        if (fit) {
+            XDestroyWindow(viewerP->dispP, viewerP->imageWin);
+            viewerP->imageWin = 0;
+            viewerP->imageColormap = 0;
+            privateCmap = userWantsPrivateCmap;
+        } else if (!BITMAPP(imageP))
+            privateCmap = TRUE;
+    } else
+        privateCmap = userWantsPrivateCmap;
+
+    ximageInfoP = imageToXImage(viewerP->dispP, viewerP->scrn, visual, depth,
+                                imageP, privateCmap, fit, verbose);
+    if (!ximageInfoP)
+        pm_error("INTERNAL ERROR: Cannot convert Image to XImage");
+
+    destroyImageWindow(viewerP);
+
+    createImageWindow(viewerP, ximageInfoP, imageP, visual,
+                      userWantsPixmap, verbose);
+
+    if (title)
+        XStoreName(viewerP->dispP, viewerP->viewportWin, title);
+    else
+        XStoreName(viewerP->dispP, viewerP->viewportWin, "Unnamed");
+
+    {
+        const char * const name = iconName(title);
+        XSetIconName(viewerP->dispP, viewerP->viewportWin, name);
+        strfree(name);
+    }
+    setNormalSizeHints(viewerP, imageP);
+
+    setWMHints(viewerP);
+
+    setViewportColormap(viewerP, visual);
+
+    /* Map (display) windows */
+
+    XMapWindow(viewerP->dispP, viewerP->imageWin);
+    XMapWindow(viewerP->dispP, viewerP->viewportWin);
+
+    /* Start displaying image */
+
+    placeImage(viewerP, imageP->width, imageP->height, &pixx, &pixy);
+    if (!viewerP->blank) {
+        XResizeWindow(viewerP->dispP, viewerP->imageWin,
+                      imageP->width, imageP->height);
+        /* Clear the image window.  Ask for exposure if there is no tile. */
+        XClearArea(viewerP->dispP, viewerP->imageWin, 0, 0, 0, 0,
+                   (viewerP->pixmap == None));
+        viewerP->blank = FALSE;
+    }
+
+    changeCursor(viewerP, imageP->width, imageP->height);
+
+    /* Process X events, continuously */
+    run(viewerP, imageP, ximageInfoP, pixx, pixy, install, &exitReason);
+
+    freeXImage(imageP, ximageInfoP);
+
+    *retvalP = retvalueFromExitReason(exitReason);
+}