/* 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. */ #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */ #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ #define _BSD_SOURCE /* Make sure strcaseeq() is in nstring.h */ #include #include #include #include #include #include #include #include /* Needed by Xutil.h */ #include /* Needed by Xutil.h */ #include /* Needed by Xutil.h */ #include #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; pm_asprintf(&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); pm_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 allows 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); /* chop off stuff following 1st word. This strips info added by processing functions too. */ t = strchr(buf, ' '); if (t) *t = '\0'; /* Strip off leading path. if you don't use unix-style paths, You might want to change this. */ t= strrchr(buf, '/'); if (t) { char * p; for (p = buf, ++t; *t; ++p, ++t) *p = *t; *p = '\0'; } /* Chop off any filename extension */ t = strchr(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 allows. note that some servers (such as the HP 11.3 server) actually say they allow some depths but have no visuals that allow 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 = 0; 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); pm_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); }