1 /* window.c:
2  *
3  * display an image in a window
4  *
5  * jim frost 10.03.89
6  *
7  * Copyright 1989, 1990, 1991 Jim Frost.
8  * See included file "copyright.h" for complete copyright information.
9  */
10 
11 #include "copyright.h"
12 #include "xloadimage.h"
13 #include <ctype.h>
14 #include <X11/cursorfont.h>
15 #include <X11/Xatom.h>
16 #include <signal.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #ifdef SYSV
20 #include <unistd.h>
21 #endif
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32 #if HAVE_SYS_SELECT_H
33 # include <sys/select.h>
34 #endif
35 #if 1
36 #include <unistd.h>
37 #endif
38 
39 /* SUPPRESS 560 */
40 
41 static Window    ImageWindow= 0;
42 static Window    ViewportWin= 0;
43 static Colormap  ImageColormap;
44 
45 static int AlarmWentOff = 0;
46 
delayAlarmHandler()47 static RETSIGTYPE delayAlarmHandler()
48 {
49     AlarmWentOff = 1;
50 }
51 
52 /* this is a bogus function whose only purpose is to interrupt
53  * the XNextEvent signal call in imageInWindow().
54  * This is added to allow automatic cycling through the specified list
55  * of pictures. The amount of wait time is specified using the -delay
56  * option, which is the number of seconds to pause between pictures.
57  * - mfc 90/10/08
58  */
59 
getNextEventWithTimeout(disp,event)60 static int getNextEventWithTimeout(disp, event)
61      Display      *disp;
62      XEvent       *event;
63 {
64   fd_set rmask;
65   int nfound;
66 
67   /* force any output to occur before we set & spin
68    */
69 
70   XFlush(disp);
71 
72   /* wait for alarm
73    */
74 
75   while ((AlarmWentOff == 0)) {
76     if (XPending(disp)) {
77       XNextEvent(disp, event);
78       return(1);
79     }
80     FD_ZERO(&rmask);
81     FD_SET(ConnectionNumber(disp), &rmask);
82     nfound = select(ConnectionNumber(disp)+1, &rmask,
83 		    (fd_set *) 0, (fd_set *) 0, /*(struct timeval *)*/0);
84     switch (nfound) {
85     case -1:
86       if (errno == EINTR) {
87 	continue;
88       } else {
89 	perror("select");
90 	continue;
91       }
92     }
93   }
94   return(0);
95 }
96 
setCursor(disp,window,iw,ih,ww,wh,cursor)97 static void setCursor(disp, window, iw, ih, ww, wh, cursor)
98      Display      *disp;
99      Window        window;
100      unsigned int  iw, ih;
101      unsigned int  ww, wh;
102      Cursor       *cursor;
103 { XSetWindowAttributes swa;
104 
105   if ((ww >= iw) && (wh >= ih))
106     swa.cursor= XCreateFontCursor(disp, XC_icon);
107   else if ((ww < iw) && (wh >= ih))
108     swa.cursor= XCreateFontCursor(disp, XC_sb_h_double_arrow);
109   else if ((ww >= iw) && (wh < ih))
110     swa.cursor= XCreateFontCursor(disp, XC_sb_v_double_arrow);
111   else
112     swa.cursor= XCreateFontCursor(disp, XC_fleur);
113   XChangeWindowAttributes(disp, window, CWCursor, &swa);
114   XFreeCursor(disp, *cursor);
115   *cursor= swa.cursor;
116 }
117 
118 /* place an image
119  */
120 
placeImage(disp,width,height,winwidth,winheight,rx,ry)121 static void placeImage(disp, width, height, winwidth, winheight, rx, ry)
122      Display *disp;
123      int width, height, winwidth, winheight;
124      int *rx, *ry; /* supplied and returned */
125 { int pixx, pixy;
126 
127   pixx= *rx;
128   pixy= *ry;
129 
130   if (winwidth > width)
131     pixx= (winwidth - width) / 2;
132   else {
133     if ((pixx < 0) && (pixx + width < winwidth))
134       pixx= winwidth - width;
135     if (pixx > 0)
136       pixx= 0;
137   }
138   if (winheight > height)
139     pixy= (winheight - height) / 2;
140   else {
141     if ((pixy < 0) && (pixy + height < winheight))
142       pixy= winheight - height;
143     if (pixy > 0)
144       pixy= 0;
145   }
146   *rx= pixx;
147   *ry= pixy;
148   XMoveWindow(disp, ImageWindow, pixx, pixy);
149 }
150 
151 /* blit an image
152  */
153 
blitImage(ximageinfo,width,height,x,y,w,h)154 static void blitImage(ximageinfo, width, height,
155 		      x, y, w, h)
156      XImageInfo	  *ximageinfo;
157      unsigned int  width, height;
158      int	   x, y, w, h;
159 {
160   if (w > width)
161     w= width;
162   if (h > height)
163     h= height;
164   if (x < 0) {
165     XClearArea(ximageinfo->disp, ximageinfo->drawable, x, y, -x, h, False);
166     w -= (0 - x);
167     x= 0;
168   }
169   if (y < 0) {
170     XClearArea(ximageinfo->disp, ximageinfo->drawable, x, y, w, -y, False);
171     h -= (0 - y);
172     y= 0;
173   }
174   if (x + w > width) {
175     XClearArea(ximageinfo->disp, ximageinfo->drawable,
176 	       x + width, y, x + w - width, h, False);
177     w -= x + w - width;
178   }
179   if (y + h > height) {
180     XClearArea(ximageinfo->disp, ximageinfo->drawable,
181 	       x, y + height, w, y + h - height, False);
182     h -= y + h - height;
183   }
184   sendXImage(ximageinfo, x, y, x, y, w, h);
185 }
186 
187 /* clean up static window if we're through with it
188  */
189 
cleanUpWindow(disp)190 void cleanUpWindow(disp)
191      Display *disp;
192 {
193   if (ImageWindow)
194     XDestroyWindow(disp, ImageWindow);
195   ImageWindow= 0;
196   if (ViewportWin)
197     XDestroyWindow(disp, ViewportWin);
198   ViewportWin= 0;
199 }
200 
201 /* clean up after displaying an image
202  */
203 
cleanUpImage(disp,scrn,cursor,pixmap,image,ximageinfo)204 static void cleanUpImage(disp, scrn, cursor, pixmap, image, ximageinfo)
205      Display      *disp;
206      int           scrn;
207      Cursor        cursor;
208      Pixmap        pixmap;
209      Image        *image;
210      XImageInfo	  *ximageinfo;
211 {
212   XFreeCursor(disp, cursor);
213   if (pixmap != None)
214       XFreePixmap(disp, pixmap);
215   freeXImage(image, ximageinfo);
216 }
217 
218 /* this sets the colormap and WM_COLORMAP_WINDOWS properly for the
219  * viewport.
220  */
221 
setViewportColormap(disp,scrn,visual)222 void setViewportColormap(disp, scrn, visual)
223      Display *disp;
224      int scrn;
225      Visual *visual;
226 { XSetWindowAttributes swa;
227   static cmap_atom= None;
228   Window cmap_windows[2];
229 
230   if (cmap_atom == None)
231     cmap_atom = XInternAtom(disp, "WM_COLORMAP_WINDOWS", False);
232 
233   /* if the visual we're using is the same as the default visual (used by
234    * the viewport window) then we can set the viewport window to use the
235    * image's colormap.  this keeps most window managers happy.
236    */
237 
238   if (visual == DefaultVisual(disp, scrn)) {
239     swa.colormap= ImageColormap;
240     XChangeWindowAttributes(disp, ViewportWin, CWColormap, &swa);
241     XDeleteProperty(disp, ViewportWin, cmap_atom);
242   }
243 
244   /* smart window managers can handle it when we use a different colormap
245    * in our subwindow so long as we set the WM_COLORMAP_WINDOWS property
246    * ala ICCCM.
247    */
248 
249   else {
250     cmap_windows[0]= ImageWindow;
251     cmap_windows[1]= ViewportWin;
252     XChangeProperty(disp, ViewportWin, cmap_atom, XA_WINDOW, 32,
253 		    PropModePrepend, (unsigned char *) cmap_windows, 2);
254   }
255 
256 }
257 
258 /* this attempts to convert an image title into a reasonable icon name
259  */
260 
iconName(s)261 static char *iconName(s)
262      char *s;
263 { static char buf[BUFSIZ];
264   char *t;
265 
266   if (!s)
267     return("Unnamed");
268   buf[BUFSIZ - 1]= '\0';
269   strncpy(buf, s, BUFSIZ - 1);
270   t= index(buf, ' '); /* strip off stuff following 1st word.  this strips */
271   if (t)              /* info added by processing functions too. */
272     *t= '\0';
273 
274   /* strip off leading path.  if you don't use unix-style paths, you might
275    * want to change this.
276    */
277 
278   if ((t = rindex(buf, '/'))) {
279     for (s= buf, t++; *t; s++, t++)
280       *s= *t;
281     *s= '\0';
282   }
283   t= index(buf, '.'); /* look for an extension and strip it off */
284   if (t)
285     *t= '\0';
286   return(buf);
287 }
288 
289 /* visual class to name table
290  */
291 
292 static struct visual_class_name {
293   int   class; /* numerical value of class */
294   char *name;  /* actual name of class */
295 } VisualClassName[] = {
296   { TrueColor,   "TrueColor" },
297   { DirectColor, "DirectColor" },
298   { PseudoColor, "PseudoColor" },
299   { StaticColor, "StaticColor" },
300   { GrayScale,   "GrayScale" },
301   { StaticGray,  "StaticGray" },
302   { StaticGray,  "StaticGrey" },
303   { -1,          NULL }
304 };
305 
visualClassFromName(name)306 int visualClassFromName(name)
307      char *name;
308 { int a;
309   char *s1, *s2;
310   int class= -1;
311 
312   for (a= 0; VisualClassName[a].name; a++) {
313     for (s1= VisualClassName[a].name, s2= name; *s1 && *s2; s1++, s2++)
314       if ((isupper(*s1) ? tolower(*s1) : *s1) !=
315 	  (isupper(*s2) ? tolower(*s2) : *s2))
316 	break;
317 
318     if ((*s1 == '\0') || (*s2 == '\0')) {
319 
320       /* check for uniqueness.  we special-case StaticGray because we have two
321        * spellings but they are unique if either is found
322        */
323 
324       if ((class != -1) && (class != StaticGray)) {
325 	fprintf(stderr, "%s does not uniquely describe a visual class (ignored)\n", name);
326 	return(-1);
327       }
328       class= VisualClassName[a].class;
329     }
330   }
331   if (class == -1)
332     fprintf(stderr, "%s is not a visual class (ignored)\n", name);
333   return(class);
334 }
335 
nameOfVisualClass(class)336 char *nameOfVisualClass(class)
337      int class;
338 { int a;
339 
340   for (a= 0; VisualClassName[a].name; a++)
341     if (VisualClassName[a].class == class)
342       return(VisualClassName[a].name);
343   return("[Unknown Visual Class]");
344 }
345 
346 /* find the best visual of a particular class with a particular depth
347  */
348 
bestVisualOfClassAndDepth(disp,scrn,class,depth)349 static Visual *bestVisualOfClassAndDepth(disp, scrn, class, depth)
350      Display      *disp;
351      int           scrn;
352      int           class;
353      unsigned int  depth;
354 { Visual *best= NULL;
355   XVisualInfo template, *info;
356   int nvisuals;
357 
358   template.screen= scrn;
359   template.class= class;
360   template.depth= depth;
361   if (! (info= XGetVisualInfo(disp, VisualScreenMask | VisualClassMask |
362 			      VisualDepthMask, &template, &nvisuals)))
363     return(NULL); /* no visuals of this depth */
364 
365   /* not sure what to do if this gives more than one visual of a particular
366    * class and depth, so just return the first one.
367    */
368 
369   best= info->visual;
370   XFree(info);
371   return(best);
372 }
373 
374 /* this tries to determine the best available visual to use for a particular
375  * image
376  */
377 
bestVisual(disp,scrn,image,rvisual,rdepth)378 void bestVisual(disp, scrn, image, rvisual, rdepth)
379      Display       *disp;
380      int            scrn;
381      Image         *image;
382      Visual       **rvisual;
383      unsigned int  *rdepth;
384 { unsigned int  depth, a;
385   Screen       *screen;
386   Visual       *visual=NULL, *default_visual;
387 
388   /* figure out the best depth the server supports.  note that some servers
389    * (such as the HP 11.3 server) actually say they support some depths but
390    * have no visuals that support that depth.  seems silly to me....
391    */
392 
393   depth= 0;
394   screen= ScreenOfDisplay(disp, scrn);
395   for (a= 0; a < screen->ndepths; a++) {
396     if (screen->depths[a].nvisuals &&
397 	((!depth ||
398 	  ((depth < image->depth) && (screen->depths[a].depth > depth)) ||
399 	  ((screen->depths[a].depth >= image->depth) &&
400 	   (screen->depths[a].depth < depth)))))
401       depth= screen->depths[a].depth;
402   }
403   if (!depth) { /* this shouldn't happen */
404     fprintf(stderr, "bestVisual: didn't find any depths?!?\n");
405     depth= DefaultDepth(disp, scrn);
406   }
407 
408   /* given this depth, find the best possible visual
409    */
410 
411   default_visual= DefaultVisual(disp, scrn);
412   switch (image->type) {
413   case ITRUE:
414 
415     /* if the default visual is DirectColor or TrueColor prioritize such
416      * that we use the default type if it exists at this depth
417      */
418 
419     if (default_visual->class == TrueColor) {
420       visual= bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
421       if (!visual)
422 	visual= bestVisualOfClassAndDepth(disp, scrn, DirectColor, depth);
423     }
424     else {
425       visual= bestVisualOfClassAndDepth(disp, scrn, DirectColor, depth);
426       if (!visual)
427 	visual= bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
428     }
429 
430     if (!visual || ((depth <= 8) &&
431 		    bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth)))
432       visual= bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
433     if (!visual)
434       visual= bestVisualOfClassAndDepth(disp, scrn, StaticColor, depth);
435     if (!visual)
436       visual= bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
437     if (!visual)
438       visual= bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
439     break;
440 
441   case IRGB:
442 
443     /* if it's an RGB image, we want PseudoColor if we can get it
444      */
445 
446     visual= bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
447     if (!visual)
448       visual= bestVisualOfClassAndDepth(disp, scrn, DirectColor, depth);
449     if (!visual)
450       visual= bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
451     if (!visual)
452       visual= bestVisualOfClassAndDepth(disp, scrn, StaticColor, depth);
453     if (!visual)
454       visual= bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
455     if (!visual)
456       visual= bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
457     break;
458 
459   case IBITMAP:
460     visual= bestVisualOfClassAndDepth(disp, scrn, PseudoColor, depth);
461     if (!visual)
462       visual= bestVisualOfClassAndDepth(disp, scrn, StaticColor, depth);
463     if (!visual)
464       visual= bestVisualOfClassAndDepth(disp, scrn, GrayScale, depth);
465     if (!visual)
466       visual= bestVisualOfClassAndDepth(disp, scrn, StaticGray, depth);
467 
468     /* it seems pretty wasteful to use a TrueColor or DirectColor visual
469      * to display a bitmap (2-color) image, so we look for those last
470      */
471 
472     if (!visual)
473       visual= bestVisualOfClassAndDepth(disp, scrn, DirectColor, depth);
474     if (!visual)
475       visual= bestVisualOfClassAndDepth(disp, scrn, TrueColor, depth);
476     break;
477   }
478 
479   if (!visual) { /* this shouldn't happen */
480     fprintf(stderr, "bestVisual: couldn't find one?!?\n");
481     depth= DefaultDepth(disp, scrn);
482     visual= DefaultVisual(disp, scrn);
483   }
484   *rvisual= visual;
485   *rdepth= depth;
486 }
487 
488 /* given a visual class, try to find the best visual of that class at
489  * the best depth.  returns a null visual and depth if it couldn't find
490  * any visual of that type at any depth
491  */
492 
bestVisualOfClass(disp,scrn,image,visual_class,rvisual,rdepth)493 void bestVisualOfClass(disp, scrn, image, visual_class, rvisual, rdepth)
494      Display      *disp;
495      int           scrn;
496      Image        *image;
497      int           visual_class;
498      Visual      **rvisual;
499      unsigned int *rdepth;
500 {
501   Visual       *visual;
502   Screen       *screen;
503   unsigned int  a, b, depth;
504 
505   /* loop through depths looking for a visual of a good depth which matches
506    * our visual class.
507    */
508 
509   screen= ScreenOfDisplay(disp, scrn);
510   visual= (Visual *)NULL;
511   depth= 0;
512   for (a= 0; a < screen->ndepths; a++) {
513     for (b= 0; b < screen->depths[a].nvisuals; b++) {
514       if ((screen->depths[a].visuals[b].class == visual_class) &&
515 	  (!depth ||
516 	   ((depth < image->depth) && (screen->depths[a].depth > depth)) ||
517 	   ((screen->depths[a].depth >= image->depth) &&
518 	    (screen->depths[a].depth < depth)))) {
519 	depth= screen->depths[a].depth;
520 	visual= &(screen->depths[a].visuals[b]);
521       }
522     }
523   }
524   *rvisual= visual;
525   *rdepth= depth;
526 }
527 
imageInWindow(disp,scrn,image,user_geometry,fullscreen,install,private_cmap,fit,use_pixmap,delay,visual_class,argc,argv,verbose)528 char imageInWindow(disp, scrn, image, user_geometry, fullscreen, install,
529 		   private_cmap, fit, use_pixmap, delay, visual_class,
530 		   argc, argv, verbose)
531      Display      *disp;
532      int           scrn;
533      Image        *image;
534      char         *user_geometry;
535      unsigned int  fullscreen;
536      unsigned int  install;
537      unsigned int  private_cmap;
538      unsigned int  fit;
539      unsigned int  use_pixmap;
540      unsigned int  delay;
541      int           visual_class; /* visual class user wants (or -1) */
542      int           argc;
543      char         *argv[];
544      unsigned int  verbose;
545 { Pixmap                pixmap = None;
546   XImageInfo           *ximageinfo;
547   Visual               *visual;
548   unsigned int          depth;
549   Window                oldimagewindow;
550   Colormap              oldcmap=None;
551   XSetWindowAttributes  swa_img;
552   XSetWindowAttributes  swa_view;
553   XClassHint            classhint;
554   unsigned int          wa_mask_img;
555   XSizeHints            sh;
556   XWMHints              wmh;
557   int                   pixx= -1, pixy= -1;
558   int                   lastx=0, lasty, mousex, mousey;
559   int                   paint;
560   static int            old_width= -1, old_height= -1;
561   static Atom           proto_atom= None, delete_atom= None;
562   union {
563     XEvent              event;
564     XAnyEvent           any;
565     XButtonEvent        button;
566     XKeyEvent           key;
567     XConfigureEvent     configure;
568     XExposeEvent        expose;
569     XMotionEvent        motion;
570     XResizeRequestEvent resize;
571     XClientMessageEvent message;
572   } event;
573   unsigned int          winx, winy, winwidth, winheight;
574 
575   /* figure out the window size.  unless specifically requested to do so,
576    * we will not exceed 90% of display real estate.
577    */
578 
579   if (user_geometry == NULL) {
580     winx= winy= winwidth= winheight= 0;
581   }
582   else {
583     char                def_geom[30];
584 
585     sprintf(def_geom, "%ux%u+0+0", image->width, image->height);
586     XGeometry(disp, scrn, user_geometry, def_geom, 0, 1, 1, 0, 0,
587 	      &winx, &winy, &winwidth, &winheight);
588   }
589 
590   if (fullscreen) {
591     winwidth= DisplayWidth(disp, scrn);
592     winheight= DisplayHeight(disp, scrn);
593   }
594   else {
595     lastx= (winwidth || winheight); /* user set size flag */
596     if (!winwidth) {
597       winwidth= image->width;
598       if (winwidth > DisplayWidth(disp, scrn) * 0.9)
599 	winwidth= DisplayWidth(disp, scrn) * 0.9;
600     }
601     if (!winheight) {
602       winheight= image->height;
603       if (winheight > DisplayHeight(disp, scrn) * 0.9)
604 	winheight= DisplayHeight(disp, scrn) * 0.9;
605     }
606   }
607 
608   /* if the user told us to fit the colormap, we must use the default
609    * visual.
610    */
611 
612   if (fit) {
613     visual= DefaultVisual(disp, scrn);
614     depth= DefaultDepth(disp, scrn);
615   }
616   else {
617 
618     visual= (Visual *)NULL;
619     if (visual_class == -1) {
620 
621       /* try to pick the best visual for the image.
622        */
623 
624       bestVisual(disp, scrn, image, &visual, &depth);
625       if (verbose && (visual != DefaultVisual(disp, scrn)))
626 	fprintf(stderr, "  Using %s visual\n", nameOfVisualClass(visual->class));
627     }
628     else {
629 
630       /* try to find a visual of the specified class
631        */
632 
633       bestVisualOfClass(disp, scrn, image, visual_class, &visual, &depth);
634       if (!visual) {
635 	bestVisual(disp, scrn, image, &visual, &depth);
636 	fprintf(stderr, "Server does not support %s visual, using %s\n",
637 		nameOfVisualClass(visual_class),
638 		nameOfVisualClass(visual->class));
639       }
640     }
641   }
642 
643   /* if we're in slideshow mode and the user told us to fit the colormap,
644    * free it here.
645    */
646 
647   if (ViewportWin) {
648     if (fit) {
649       XDestroyWindow(disp, ImageWindow);
650       ImageWindow= 0;
651       ImageColormap= 0;
652     }
653 
654     /* for the 1st image we display we can use the default cmap.  subsequent
655      * images use a private colormap (unless they're bitmaps) so we don't get
656      * color erosion when switching images.
657      */
658 
659     else if (!BITMAPP(image))
660       private_cmap= 1;
661   }
662 
663   if (! (ximageinfo= imageToXImage(disp, scrn, visual, depth, image,
664 				   private_cmap, fit, verbose))) {
665     fprintf(stderr, "Cannot convert Image to XImage\n");
666     cleanup(-1);
667   }
668 
669   swa_view.background_pixel= WhitePixel(disp,scrn);
670   swa_view.backing_store= NotUseful;
671   swa_view.cursor= XCreateFontCursor(disp, XC_watch);
672   swa_view.event_mask= ButtonPressMask | Button1MotionMask | KeyPressMask |
673     StructureNotifyMask | EnterWindowMask | LeaveWindowMask;
674   swa_view.save_under= False;
675 
676   classhint.res_class = "Xloadimage";
677   classhint.res_name=NULL;
678   if (!ViewportWin) {
679     ViewportWin= XCreateWindow(disp, RootWindow(disp, scrn), winx, winy,
680 			       winwidth, winheight, 0,
681 			       DefaultDepth(disp, scrn), InputOutput,
682 			       DefaultVisual(disp, scrn),
683 			       CWBackingStore | CWBackPixel | CWCursor |
684 			       CWEventMask | CWSaveUnder,
685 			       &swa_view);
686     oldimagewindow= 0;
687     XSetCommand(disp, ViewportWin, argv, argc);
688     XSetClassHint(disp,ViewportWin,&classhint);
689     proto_atom = XInternAtom(disp, "WM_PROTOCOLS", False);
690     delete_atom = XInternAtom(disp, "WM_DELETE_WINDOW", False);
691     if ((proto_atom != None) && (delete_atom != None))
692       XChangeProperty(disp, ViewportWin, proto_atom, XA_ATOM, 32,
693 		      PropModePrepend, (unsigned char *) &delete_atom, 1);
694    paint= 0;
695   }
696   else {
697     oldimagewindow= ImageWindow;
698     oldcmap= ImageColormap;
699     paint= 1;
700   }
701 
702   /* create image window
703    */
704 
705   swa_img.bit_gravity= NorthWestGravity;
706   swa_img.save_under= False;
707   swa_img.colormap= ximageinfo->cmap;
708   swa_img.border_pixel= 0;
709   ImageWindow= XCreateWindow(disp, ViewportWin, winx, winy,
710 			     image->width, image->height, 0,
711 			     ximageinfo->depth, InputOutput, visual,
712 			     CWBitGravity | CWColormap | CWSaveUnder |
713 			     CWBorderPixel, &swa_img);
714   ImageColormap= ximageinfo->cmap;
715   XSetCommand(disp, ImageWindow, argv, argc);
716   XSetClassHint(disp,ImageWindow,&classhint);
717 
718   /* decide how we're going to handle repaints.  we have three modes:
719    * use backing-store, use background pixmap, and use exposures.
720    * if the server supports backing-store, we enable it and use it.
721    * this really helps servers which are memory constrained.  if the
722    * server does not have backing-store, we try to send the image to
723    * a pixmap and use that as backing-store.  if that fails, we use
724    * exposures to blit the image (which is ugly but it works).
725    *
726    * the "use_pixmap" flag forces background pixmap mode, which may
727    * improve performance.
728    */
729 
730   ximageinfo->drawable= ImageWindow;
731   if ((DoesBackingStore(ScreenOfDisplay(disp,scrn)) == NotUseful) ||
732       use_pixmap) {
733     if (((pixmap= ximageToPixmap(disp, ImageWindow, ximageinfo)) ==
734 	 None) && verbose)
735       fprintf(stderr, "  Cannot create image in server, repaints will be ugly!\n");
736   }
737 
738   /* build window attributes for the image window
739    */
740 
741   wa_mask_img= 0;
742   if (pixmap == None) {
743 
744     /* No pixmap.  Must paint over the wire.  Ask for BackingStore
745      * to cut down on the painting.  But, ask for Exposures so we can
746      * paint both viewables and backingstore.
747      */
748 
749     swa_img.background_pixel= WhitePixel(disp,scrn);
750     wa_mask_img |= CWBackPixel;
751     swa_img.event_mask= ExposureMask;
752     wa_mask_img |= CWEventMask;
753     swa_img.backing_store= WhenMapped;
754     wa_mask_img |= CWBackingStore;
755   }
756   else {
757 
758     /* we have a pixmap so tile the window.  to move the image we only
759      * have to move the window and the server should do the rest.
760      */
761 
762     swa_img.background_pixmap= pixmap;
763     wa_mask_img |= CWBackPixmap;
764     swa_img.event_mask= 0;	/* no exposures please */
765     wa_mask_img |= CWEventMask;
766     swa_img.backing_store= NotUseful;
767     wa_mask_img |= CWBackingStore;
768   }
769   XChangeWindowAttributes(disp, ImageWindow, wa_mask_img, &swa_img);
770 
771   if (image->title)
772     XStoreName(disp, ViewportWin, image->title);
773   else
774     XStoreName(disp, ViewportWin, "Unnamed");
775   XSetIconName(disp, ViewportWin, iconName(image->title));
776 
777   sh.width= winwidth;
778   sh.height= winheight;
779   if (fullscreen) {
780     sh.min_width= sh.max_width= winwidth;
781     sh.min_height= sh.max_height= winheight;
782   }
783   else {
784     sh.min_width= 1;
785     sh.min_height= 1;
786     sh.max_width= image->width;
787     sh.max_height= image->height;
788   }
789   sh.width_inc= 1;
790   sh.height_inc= 1;
791   sh.flags= PMinSize | PMaxSize | PResizeInc;
792   if (lastx || fullscreen)
793     sh.flags |= USSize;
794   else
795     sh.flags |= PSize;
796   if (fullscreen) {
797     sh.x= sh.y= 0;
798     sh.flags |= USPosition;
799   }
800   else if (winx || winy) {
801     sh.x= winx;
802     sh.y= winy;
803     sh.flags |= USPosition;
804   }
805   XSetNormalHints(disp, ViewportWin, &sh);
806   sh.min_width= sh.max_width;
807   sh.min_height= sh.max_height;
808   XSetNormalHints(disp, ImageWindow, &sh);	/* Image doesn't shrink */
809 
810   wmh.input= True;
811   wmh.flags= InputHint;
812   XSetWMHints(disp, ViewportWin, &wmh);
813 
814   setViewportColormap(disp, scrn, visual);
815 
816   /* map windows and clean up old window if there was one.
817    */
818 
819   XMapWindow(disp, ImageWindow);
820   XMapWindow(disp, ViewportWin);
821   if (oldimagewindow) {
822     if (oldcmap && (oldcmap != DefaultColormap(disp, scrn)))
823       XFreeColormap(disp, oldcmap);
824     XDestroyWindow(disp, oldimagewindow);
825   }
826 
827   /* start displaying image
828    */
829 
830   placeImage(disp, image->width, image->height, winwidth, winheight, &pixx, &pixy);
831   if (paint) {
832     if ((winwidth != old_width) || (winheight != old_height)) {
833 	XResizeWindow(disp, ViewportWin, winwidth, winheight);
834     }
835     XResizeWindow(disp, ImageWindow, image->width, image->height);
836     /* Clear the image window.  Ask for exposure if there is no tile. */
837     XClearArea(disp, ImageWindow, 0, 0, 0, 0, (pixmap == None));
838   }
839   old_width= winwidth;
840   old_height= winheight;
841 
842   /* flush output. this is so that -delay will be close to what was
843    * asked for (i.e., do the flushing of output outside of the loop).
844    */
845   XSync(disp,False);
846 
847   setCursor(disp, ViewportWin, image->width, image->height,
848 	    winwidth, winheight, &(swa_view.cursor));
849   lastx= lasty= -1;
850   if (delay) {
851       /* reset alarm to -delay seconds after every event */
852       AlarmWentOff = 0;
853       signal(SIGALRM, delayAlarmHandler);
854       alarm(delay);
855   }
856 
857   for (;;) {
858 
859     if (delay) {
860       if (!getNextEventWithTimeout(disp, &event.event)) {
861 	Cursor cursor= swa_view.cursor;
862 
863 	/* timeout expired.  clean up and exit.
864 	 */
865 
866 	swa_view.cursor= XCreateFontCursor(disp, XC_watch);
867 	XChangeWindowAttributes(disp, ImageWindow, CWCursor, &swa_view);
868 	XFreeCursor(disp, cursor);
869 	XFlush(disp);
870 	cleanUpImage(disp, scrn, swa_view.cursor, pixmap,
871 		     image, ximageinfo);
872 	return('n');
873       }
874     }
875     else
876       XNextEvent(disp, &event.event);
877 
878     switch (event.any.type) {
879     case ButtonPress:
880       if (event.button.button == 1) {
881 	lastx= event.button.x;
882 	lasty= event.button.y;
883 	break;
884       }
885       break;
886 
887     case KeyPress: {
888       char buf[128];
889       KeySym ks;
890       XComposeStatus status;
891       char ret;
892       Cursor cursor;
893 
894       if (XLookupString(&event.key,buf,128,&ks,&status) != 1)
895 	break;
896       ret= buf[0];
897       if (isupper(ret))
898 	ret= tolower(ret);
899       switch (ret) {
900       case ' ':
901       case 'n':
902       case 'p':
903 	if (delay)
904 	  alarm(0);
905 	cursor= swa_view.cursor;
906 	swa_view.cursor= XCreateFontCursor(disp, XC_watch);
907 	XChangeWindowAttributes(disp, ViewportWin, CWCursor, &swa_view);
908 	XFreeCursor(disp, cursor);
909 	XFlush(disp);
910 	cleanUpImage(disp, scrn, swa_view.cursor, pixmap,
911 		     image, ximageinfo);
912 	return(ret);
913       case '\003': /* ^C */
914       case 'q':
915 	if (delay)
916 	  alarm(0);
917 	cleanUpImage(disp, scrn, swa_view.cursor, pixmap,
918 		     image, ximageinfo);
919 	return(ret);
920       }
921       break;
922     }
923 
924     case MotionNotify:
925       if ((image->width <= winwidth) && (image->height <= winheight))
926 	break; /* we're AT&T */
927       mousex= event.button.x;
928       mousey= event.button.y;
929       /*XSync(disp, False); */
930       while (XCheckTypedEvent(disp, MotionNotify, (XEvent *) &event) == True) {
931 	mousex= event.button.x;
932 	mousey= event.button.y;
933       }
934       pixx -= (lastx - mousex);
935       pixy -= (lasty - mousey);
936       lastx= mousex;
937       lasty= mousey;
938       placeImage(disp, image->width, image->height, winwidth, winheight,
939 		 &pixx, &pixy);
940       break;
941 
942     case ConfigureNotify:
943       winwidth= old_width= event.configure.width;
944       winheight= old_height= event.configure.height;
945 
946       placeImage(disp, image->width, image->height, winwidth, winheight,
947 		 &pixx, &pixy);
948 
949       /* configure the cursor to indicate which directions we can drag
950        */
951 
952       setCursor(disp, ViewportWin, image->width, image->height,
953 		winwidth, winheight, &(swa_view.cursor));
954       break;
955 
956     case DestroyNotify:
957       cleanUpImage(disp, scrn, swa_view.cursor, pixmap,
958 		   image, ximageinfo);
959       return('\0');
960 
961     case Expose:
962       blitImage(ximageinfo, image->width, image->height,
963 		event.expose.x, event.expose.y,
964 		event.expose.width, event.expose.height);
965       break;
966 
967     case EnterNotify:
968       if (install)
969 	XInstallColormap(disp, ximageinfo->cmap);
970       break;
971 
972     case LeaveNotify:
973       if (install)
974 	XUninstallColormap(disp, ximageinfo->cmap);
975       break;
976 
977     case ClientMessage:
978       /* if we get a client message for the viewport window which has the
979        * value of the delete atom, it means the window manager wants us to
980        * die.
981        */
982 
983       if ((event.message.window == ViewportWin) &&
984 	  (event.message.data.l[0] == delete_atom)) {
985 	cleanUpImage(disp, scrn, swa_view.cursor, pixmap,
986 		      image, ximageinfo);
987 	return('q');
988       }
989       break;
990     }
991   }
992 }
993