00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 #include "config.h"
00011 #ifdef X_DISPLAY_MISSING
00012 #error "You can't compile gifview without X."
00013 #endif
00014 
00015 #include "gifx.h"
00016 #include "clp.h"
00017 #include <X11/Xlib.h>
00018 #include <X11/Xutil.h>
00019 #include <X11/Xos.h>
00020 #include <X11/keysym.h>
00021 #include <X11/cursorfont.h>
00022 #ifdef HAVE_SYS_SELECT_H
00023 # include <sys/select.h>
00024 #endif
00025 #include <string.h>
00026 #include <stdio.h>
00027 #include <stdlib.h>
00028 #include <stdarg.h>
00029 #include <ctype.h>
00030 #include <errno.h>
00031 #include <assert.h>
00032 
00033 #ifdef __cplusplus
00034 #define EXTERN extern "C"
00035 #else
00036 #define EXTERN extern
00037 #endif
00038 
00039 #define SAVE_FRAMES 10
00040 
00041 
00042 
00043 
00044 
00045 #define MICRO_PER_SEC 1000000
00046 
00047 #define xwADDTIME(result, a, b) do { \
00048         (result).tv_sec = (a).tv_sec + (b).tv_sec; \
00049         if (((result).tv_usec = (a).tv_usec + (b).tv_usec) >= MICRO_PER_SEC) { \
00050                 (result).tv_sec++; \
00051                 (result).tv_usec -= MICRO_PER_SEC; \
00052         } } while (0)
00053 
00054 #define xwSUBTIME(result, a, b) do { \
00055         (result).tv_sec = (a).tv_sec - (b).tv_sec; \
00056         if (((result).tv_usec = (a).tv_usec - (b).tv_usec) < 0) { \
00057                 (result).tv_sec--; \
00058                 (result).tv_usec += MICRO_PER_SEC; \
00059         } } while (0)
00060 
00061 #define xwSETMINTIME(a, b) do { \
00062         if ((b).tv_sec < (a).tv_sec || \
00063             ((b).tv_sec == (a).tv_sec && (b).tv_usec < (a).tv_usec)) \
00064                 (a) = (b); \
00065         } while (0)
00066 
00067 #define xwTIMEGEQ(a, b) ((a).tv_sec > (b).tv_sec || \
00068         ((a).tv_sec == (b).tv_sec && (a).tv_usec >= (b).tv_usec))
00069 
00070 #define xwTIMEGT(a, b) ((a).tv_sec > (b).tv_sec || \
00071         ((a).tv_sec == (b).tv_sec && (a).tv_usec > (b).tv_usec))
00072 
00073 #define xwTIMELEQ0(a) ((a).tv_sec < 0 || ((a).tv_sec == 0 && (a).tv_usec <= 0))
00074 
00075 #ifdef X_GETTIMEOFDAY
00076 # define xwGETTIMEOFDAY(a) X_GETTIMEOFDAY(a)
00077 #elif GETTIMEOFDAY_PROTO == 0
00078 EXTERN int gettimeofday(struct timeval *, struct timezone *);
00079 # define xwGETTIMEOFDAY(a) gettimeofday((a), 0)
00080 #elif GETTIMEOFDAY_PROTO == 1
00081 # define xwGETTIMEOFDAY(a) gettimeofday((a))
00082 #else
00083 # define xwGETTIMEOFDAY(a) gettimeofday((a), 0)
00084 #endif
00085 
00086 #define xwGETTIME(a) do { xwGETTIMEOFDAY(&(a)); xwSUBTIME((a), (a), genesis_time); } while (0)
00087 struct timeval genesis_time;
00088 
00089 
00090 
00091 
00092 
00093 
00094 typedef struct Gt_Viewer {
00095   
00096   Display *display;
00097   int screen_number;
00098   Visual *visual;
00099   int depth;
00100   Colormap colormap;
00101   Gif_XContext *gfx;
00102   Cursor arrow_cursor;
00103   Cursor wait_cursor;
00104   
00105   Window parent;
00106   int top_level;
00107   
00108   Window window;
00109   int use_window;
00110   int width;
00111   int height;
00112   int resizable;
00113   int being_deleted;
00114   
00115   Gif_Stream *gfs;
00116   char *name;
00117   
00118   Gif_Image **im;
00119   int nim;
00120   
00121   Pixmap pixmap;
00122   int im_pos;
00123   int was_unoptimized;
00124   int free_pixmaps;
00125   
00126   Pixmap *unoptimized_pixmaps;
00127   
00128   struct Gt_Viewer *next;
00129   
00130   int can_animate;
00131   int animating;
00132   int unoptimizing;
00133   int scheduled;
00134   struct Gt_Viewer *anim_next;
00135   struct timeval timer;
00136   int anim_loop;
00137   
00138 } Gt_Viewer;
00139 
00140 const char *program_name = "gifview";
00141 
00142 static char *cur_display_name = 0;
00143 static Display *cur_display = 0;
00144 static char *cur_geometry_spec = 0;
00145 static Cursor cur_arrow_cursor = 0;
00146 static Cursor cur_wait_cursor = 0;
00147 static const char *cur_resource_name;
00148 static Window cur_use_window = None;
00149 static const char *cur_background_color = "black";
00150 
00151 static Gt_Viewer *viewers;
00152 static Gt_Viewer *animations;
00153 static int animating = 0;
00154 static int unoptimizing = 0;
00155 static int install_colormap = 0;
00156 static int interactive = 1;
00157 
00158 
00159 #define DISPLAY_OPT             300
00160 #define UNOPTIMIZE_OPT          301
00161 #define VERSION_OPT             302
00162 #define ANIMATE_OPT             303
00163 #define GEOMETRY_OPT            304
00164 #define NAME_OPT                305
00165 #define HELP_OPT                306
00166 #define WINDOW_OPT              307
00167 #define INSTALL_COLORMAP_OPT    308
00168 #define INTERACTIVE_OPT         309
00169 #define BACKGROUND_OPT          310
00170 
00171 #define WINDOW_TYPE             (Clp_MaxDefaultType + 1)
00172 
00173 Clp_Option options[] = {
00174   { "animate", 'a', ANIMATE_OPT, 0, Clp_Negate },
00175   { "background", 'b', BACKGROUND_OPT, Clp_ArgString, 0 },
00176   { "bg", 0, BACKGROUND_OPT, Clp_ArgString, 0 },
00177   { "display", 'd', DISPLAY_OPT, Clp_ArgStringNotOption, 0 },
00178   { "geometry", 'g', GEOMETRY_OPT, Clp_ArgString, 0 },
00179   { "install-colormap", 'i', INSTALL_COLORMAP_OPT, 0, Clp_Negate },
00180   { "interactive", 'e', INTERACTIVE_OPT, 0, Clp_Negate },
00181   { "help", 0, HELP_OPT, 0, 0 },
00182   { "name", 0, NAME_OPT, Clp_ArgString, 0 },
00183   { "unoptimize", 'U', UNOPTIMIZE_OPT, 0, Clp_Negate },
00184   { "version", 0, VERSION_OPT, 0, 0 },
00185   { "window", 'w', WINDOW_OPT, WINDOW_TYPE, 0 },
00186 };
00187 
00188 
00189 
00190 
00191 
00192 
00193 void
00194 fatal_error(char *message, ...)
00195 {
00196   va_list val;
00197   va_start(val, message);
00198   fprintf(stderr, "%s: ", program_name);
00199   vfprintf(stderr, message, val);
00200   fputc('\n', stderr);
00201   exit(1);
00202 }
00203 
00204 void
00205 error(char *message, ...)
00206 {
00207   va_list val;
00208   va_start(val, message);
00209   fprintf(stderr, "%s: ", program_name);
00210   vfprintf(stderr, message, val);
00211   fputc('\n', stderr);
00212 }
00213 
00214 void
00215 warning(char *message, ...)
00216 {
00217   va_list val;
00218   va_start(val, message);
00219   fprintf(stderr, "%s: warning: ", program_name);
00220   vfprintf(stderr, message, val);
00221   fputc('\n', stderr);
00222 }
00223 
00224 void
00225 short_usage(void)
00226 {
00227   fprintf(stderr, "Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\
00228 Try `%s --help' for more information.\n",
00229           program_name, program_name);
00230 }
00231 
00232 void
00233 usage(void)
00234 {
00235   printf("\
00236 `Gifview' is a lightweight GIF viewer for X. It can display animated GIFs as\n\
00237 slideshows, one frame at a time, or as animations.\n\
00238 \n\
00239 Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\
00240 \n\
00241 Options are:\n\
00242   -a, --animate                 Animate multiframe GIFs.\n\
00243   -U, --unoptimize              Unoptimize displayed GIFs.\n\
00244   -d, --display DISPLAY         Set display to DISPLAY.\n\
00245       --name NAME               Set application resource name to NAME.\n\
00246   -g, --geometry GEOMETRY       Set window geometry.\n\
00247   -w, --window WINDOW           Show GIF in existing WINDOW.\n\
00248   -i, --install-colormap        Use a private colormap.\n\
00249   --bg, --background COLOR      Use COLOR for transparent pixels.\n\
00250   +e, --no-interactive          Ignore buttons and keystrokes.\n\
00251       --help                    Print this message and exit.\n\
00252       --version                 Print version number and exit.\n\
00253 \n\
00254 Frame selections:               #num, #num1-num2, #num1-, #name\n\
00255 \n\
00256 Keystrokes:\n\
00257   [Space] Go to next frame.             [B] Go to previous frame.\n\
00258   [R]/[<] Go to first frame.            [>] Go to last frame.\n\
00259   [ESC] Stop animation.                 [S]/[A] Toggle animation.\n\
00260   [U] Toggle unoptimization.            [Backspace]/[W] Delete window.\n\
00261   [Q] Quit.\n\
00262 \n\
00263 Left mouse button goes to next frame, right mouse button deletes window.\n\
00264 \n\
00265 Report bugs to <eddietwo@lcs.mit.edu>.\n", program_name);
00266 }
00267 
00268 
00269 
00270 
00271 
00272 
00273 #if defined(__cplusplus) || defined(c_plusplus)
00274 #define VISUAL_CLASS c_class
00275 #else
00276 #define VISUAL_CLASS class
00277 #endif
00278 
00279 static void
00280 choose_visual(Gt_Viewer *viewer)
00281 {
00282   Display *display = viewer->display;
00283   int screen_number = viewer->screen_number;
00284   VisualID default_visualid = DefaultVisual(display, screen_number)->visualid;
00285   
00286   XVisualInfo visi_template;
00287   int nv, i;
00288   XVisualInfo *v, *best_v = 0;
00289   Gt_Viewer *trav;
00290   
00291   
00292   if (!install_colormap)
00293     for (trav = viewers; trav; trav = trav->next)
00294       if (trav != viewer && trav->display == display
00295           && trav->screen_number == screen_number) {
00296         viewer->visual = trav->visual;
00297         viewer->depth = trav->depth;
00298         viewer->colormap = trav->colormap;
00299         viewer->gfx = trav->gfx;
00300         viewer->gfx->refcount++;
00301         return;
00302       }
00303   
00304   
00305   visi_template.screen = screen_number;
00306   v = XGetVisualInfo(display, VisualScreenMask, &visi_template, &nv);
00307   for (i = 0; i < nv && !best_v; i++)
00308     if (v[i].visualid == default_visualid)
00309       best_v = &v[i];
00310   
00311   if (!best_v) {
00312     
00313     
00314 
00315     viewer->visual = DefaultVisual(display, screen_number);
00316     viewer->depth = DefaultDepth(display, screen_number);
00317     viewer->colormap = DefaultColormap(display, screen_number);
00318     
00319   } else {
00320     
00321     
00322 
00323 
00324 
00325     for (i = 0; i < nv; i++)
00326       if (v[i].depth > best_v->depth && v[i].VISUAL_CLASS == TrueColor)
00327         best_v = &v[i];
00328     
00329     viewer->visual = best_v->visual;
00330     viewer->depth = best_v->depth;
00331     if (best_v->visualid != default_visualid
00332         || (best_v->VISUAL_CLASS == PseudoColor && install_colormap))
00333       viewer->colormap =
00334         XCreateColormap(display, RootWindow(display, screen_number),
00335                         viewer->visual, AllocNone);
00336     else
00337       viewer->colormap = DefaultColormap(display, screen_number);
00338     
00339   }
00340   
00341   viewer->gfx = Gif_NewXContextFromVisual
00342     (display, screen_number, viewer->visual, viewer->depth, viewer->colormap);
00343   viewer->gfx->refcount++;
00344   
00345   if (v) XFree(v);
00346 }
00347 
00348 
00349 Gt_Viewer *
00350 new_viewer(Display *display, Window use_window, Gif_Stream *gfs, char *name)
00351 {
00352   Gt_Viewer *viewer;
00353   int i;
00354   
00355   
00356   viewer = Gif_New(Gt_Viewer);
00357   viewer->display = display;
00358   
00359   if (use_window) {
00360     XWindowAttributes attr;
00361 
00362     if (use_window == -1) {     
00363       viewer->screen_number = DefaultScreen(display);
00364       use_window = RootWindow(display, viewer->screen_number);
00365     }
00366     
00367     XGetWindowAttributes(display, use_window, &attr);
00368     
00369     viewer->screen_number = -1;
00370     for (i = 0; i < ScreenCount(display); i++)
00371       if (ScreenOfDisplay(display, i) == attr.screen)
00372         viewer->screen_number = i;
00373     assert(viewer->screen_number >= 0);
00374     
00375     viewer->visual = attr.visual;
00376     viewer->depth = attr.depth;
00377     viewer->colormap = attr.colormap;
00378     
00379     viewer->gfx = Gif_NewXContextFromVisual
00380       (display, viewer->screen_number, viewer->visual, viewer->depth,
00381        viewer->colormap);
00382     viewer->gfx->refcount++;
00383 
00384     
00385 
00386     viewer->window = (use_window == attr.root ? use_window : None);
00387     viewer->parent = use_window;
00388     viewer->use_window = (use_window == attr.root ? 1 : 0);
00389     viewer->top_level = 0;
00390     viewer->resizable = 0;
00391     
00392   } else {
00393     viewer->screen_number = DefaultScreen(display);
00394     choose_visual(viewer);
00395     viewer->window = None;
00396     viewer->parent = RootWindow(display, viewer->screen_number);
00397     viewer->use_window = 0;
00398     viewer->top_level = 1;
00399     viewer->resizable = 1;
00400   }
00401   
00402   
00403   if (cur_background_color) {
00404     XColor color;
00405     if (!XParseColor(viewer->display, viewer->colormap, cur_background_color,
00406                      &color)) {
00407       error("invalid background color `%s'", cur_background_color);
00408       cur_background_color = 0;
00409     } else if (!XAllocColor(viewer->display, viewer->colormap, &color))
00410       warning("can't allocate background color");
00411     else {
00412       unsigned long pixel = color.pixel;
00413       Gif_XContext *gfx = viewer->gfx;
00414       if (pixel != gfx->transparent_pixel && gfx->refcount > 1) {
00415         
00416         viewer->gfx = Gif_NewXContextFromVisual
00417           (gfx->display, gfx->screen_number, gfx->visual, gfx->depth,
00418            gfx->colormap);
00419         viewer->gfx->refcount++;
00420         gfx->refcount--;
00421       }
00422       viewer->gfx->transparent_pixel = pixel;
00423     }
00424   }
00425 
00426   if (!cur_arrow_cursor) {
00427     cur_arrow_cursor = XCreateFontCursor(display, XC_left_ptr);
00428     cur_wait_cursor = XCreateFontCursor(display, XC_watch);
00429   }
00430   viewer->arrow_cursor = cur_arrow_cursor;
00431   viewer->wait_cursor = cur_wait_cursor;
00432   
00433   viewer->being_deleted = 0;
00434   viewer->gfs = gfs;
00435   gfs->refcount++;
00436   viewer->name = name;
00437   viewer->nim = Gif_ImageCount(gfs);
00438   viewer->im = Gif_NewArray(Gif_Image *, viewer->nim);
00439   for (i = 0; i < viewer->nim; i++)
00440     viewer->im[i] = gfs->images[i];
00441   viewer->pixmap = None;
00442   viewer->im_pos = -1;
00443   viewer->was_unoptimized = 0;
00444   viewer->free_pixmaps = 1;
00445   viewer->unoptimized_pixmaps = Gif_NewArray(Pixmap, viewer->nim);
00446   for (i = 0; i < viewer->nim; i++)
00447     viewer->unoptimized_pixmaps[i] = None;
00448   viewer->next = viewers;
00449   viewers = viewer;
00450   viewer->animating = 0;
00451   viewer->unoptimizing = unoptimizing;
00452   viewer->scheduled = 0;
00453   viewer->anim_next = 0;
00454   viewer->anim_loop = 0;
00455   viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
00456   
00457   return viewer;
00458 }
00459 
00460 
00461 void
00462 delete_viewer(Gt_Viewer *viewer)
00463 {
00464   Gt_Viewer *prev = 0, *trav;
00465   int i;
00466   if (viewer->pixmap && !viewer->was_unoptimized)
00467     XFreePixmap(viewer->display, viewer->pixmap);
00468   for (i = 0; i < viewer->nim; i++)
00469     if (viewer->unoptimized_pixmaps[i])
00470       XFreePixmap(viewer->display, viewer->unoptimized_pixmaps[i]);
00471   
00472   for (trav = viewers; trav != viewer; prev = trav, trav = trav->next)
00473     ;
00474   if (prev) prev->next = viewer->next;
00475   else viewers = viewer->next;
00476   
00477   Gif_DeleteStream(viewer->gfs);
00478   Gif_DeleteArray(viewer->unoptimized_pixmaps);
00479   Gif_DeleteArray(viewer->im);
00480   Gif_DeleteXContext(viewer->gfx);
00481   Gif_Delete(viewer);
00482 }
00483 
00484 
00485 static Gt_Viewer *
00486 next_viewer(Gif_Stream *gfs, char *name)
00487 {
00488   Gt_Viewer *viewer = new_viewer(cur_display, cur_use_window, gfs, name);
00489   if (cur_use_window)
00490     cur_use_window = None;
00491   return viewer;
00492 }
00493 
00494 static Gt_Viewer *
00495 get_input_stream(char *name)
00496 {
00497   FILE *f;
00498   Gif_Stream *gfs = 0;
00499   
00500   if (name == 0 || strcmp(name, "-") == 0) {
00501     f = stdin;
00502     name = "<stdin>";
00503   } else
00504     f = fopen(name, "rb");
00505   if (!f) {
00506     error("%s: %s", name, strerror(errno));
00507     return 0;
00508   }
00509   
00510   gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, 0);
00511   fclose(f);
00512   
00513   if (!gfs || Gif_ImageCount(gfs) == 0) {
00514     error("`%s' doesn't seem to contain a GIF", name);
00515     Gif_DeleteStream(gfs);
00516     return 0;
00517   }
00518   
00519   if (!cur_display) {
00520     cur_display = XOpenDisplay(cur_display_name);
00521     if (!cur_display) {
00522       error("can't open display");
00523       return 0;
00524     }
00525   }
00526 
00527   return next_viewer(gfs, name);
00528 }
00529 
00530 
00531 
00532 
00533 
00534 
00535 void
00536 switch_animating(Gt_Viewer *viewer, int animating)
00537 {
00538   int i;
00539   Gif_Stream *gfs = viewer->gfs;
00540   if (animating == viewer->animating || !viewer->can_animate)
00541     return;
00542   for (i = 0; i < gfs->nimages; i++)
00543     viewer->im[i] = gfs->images[i];
00544   viewer->animating = animating;
00545   if (!animating)
00546     viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
00547 }
00548 
00549 
00550 void
00551 unschedule(Gt_Viewer *viewer)
00552 {
00553   Gt_Viewer *prev, *trav;
00554   if (!viewer->scheduled) return;
00555   for (prev = 0, trav = animations; trav; prev = trav, trav = trav->anim_next)
00556     if (trav == viewer)
00557       break;
00558   if (trav) {
00559     if (prev) prev->anim_next = viewer->anim_next;
00560     else animations = viewer->anim_next;
00561   }
00562   viewer->scheduled = 0;
00563   viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
00564 }
00565 
00566 
00567 void
00568 schedule_next_frame(Gt_Viewer *viewer)
00569 {
00570   struct timeval interval;
00571   Gt_Viewer *prev, *trav;
00572   int delay = viewer->im[viewer->im_pos]->delay;
00573 
00574   if (viewer->timer.tv_sec == 0 && viewer->timer.tv_usec == 0)
00575     xwGETTIME(viewer->timer);
00576   interval.tv_sec = delay / 100;
00577   interval.tv_usec = (delay % 100) * (MICRO_PER_SEC / 100);
00578   xwADDTIME(viewer->timer, viewer->timer, interval);
00579 
00580   if (viewer->scheduled)
00581     unschedule(viewer);
00582   
00583   prev = 0;
00584   for (trav = animations; trav; prev = trav, trav = trav->anim_next)
00585     if (xwTIMEGEQ(trav->timer, viewer->timer))
00586       break;
00587   if (prev) {
00588     viewer->anim_next = trav;
00589     prev->anim_next = viewer;
00590   } else {
00591     viewer->anim_next = animations;
00592     animations = viewer;
00593   }
00594   viewer->scheduled = 1;
00595 }
00596 
00597 
00598 
00599 
00600 
00601 
00602 int
00603 parse_geometry(const char *const_g, XSizeHints *sh, int screen_width,
00604                int screen_height)
00605 {
00606   char *g = (char *)const_g;
00607   sh->flags = 0;
00608   
00609   if (isdigit(*g)) {
00610     sh->flags |= USSize;
00611     sh->width = strtol(g, &g, 10);
00612     if (g[0] == 'x' && isdigit(g[1]))
00613       sh->height = strtol(g + 1, &g, 10);
00614     else
00615       goto error;
00616   } else if (!*g)
00617     goto error;
00618   
00619   if (*g == '+' || *g == '-') {
00620     int x_minus, y_minus;
00621     sh->flags |= USPosition | PWinGravity;
00622     x_minus = *g == '-';
00623     sh->x = strtol(g + 1, &g, 10);
00624     if (x_minus) sh->x = screen_width - sh->x - sh->width;
00625     
00626     y_minus = *g == '-';
00627     if (*g == '-' || *g == '+')
00628       sh->y = strtol(g + 1, &g, 10);
00629     else
00630       goto error;
00631     if (y_minus) sh->y = screen_height - sh->y - sh->height;
00632     
00633     if (x_minus)
00634       sh->win_gravity = y_minus ? SouthEastGravity : NorthEastGravity;
00635     else
00636       sh->win_gravity = y_minus ? SouthWestGravity : NorthWestGravity;
00637     
00638   } else if (*g)
00639     goto error;
00640   
00641   return 1;
00642   
00643  error:
00644   warning("bad geometry specification");
00645   sh->flags = 0;
00646   return 0;
00647 }
00648 
00649 
00650 static Atom wm_delete_window_atom;
00651 static Atom wm_protocols_atom;
00652 
00653 void
00654 create_viewer_window(Gt_Viewer *viewer, int w, int h)
00655 {
00656   Display *display = viewer->display;
00657   char *stringlist[2];
00658   XTextProperty window_name, icon_name;
00659   XClassHint classh;
00660   XSizeHints *sizeh = XAllocSizeHints(); 
00661 
00662   
00663   sizeh->width = w;
00664   sizeh->height = h;
00665   if (cur_geometry_spec) {
00666     int scr_width = DisplayWidth(viewer->display, viewer->screen_number);
00667     int scr_height = DisplayHeight(viewer->display, viewer->screen_number);
00668     parse_geometry(cur_geometry_spec, sizeh, scr_width, scr_height);
00669   }
00670   
00671   
00672   if (!viewer->window) {
00673     XSetWindowAttributes x_set_attr;
00674     unsigned long x_set_attr_mask;
00675     x_set_attr.colormap = viewer->colormap;
00676     x_set_attr.backing_store = NotUseful;
00677     x_set_attr.save_under = False;
00678     x_set_attr.border_pixel = 0;
00679     x_set_attr.background_pixel = 0;
00680     x_set_attr_mask = CWColormap | CWBorderPixel | CWBackPixel
00681       | CWBackingStore | CWSaveUnder;
00682     
00683     viewer->window = XCreateWindow
00684       (display, viewer->parent,
00685        sizeh->x, sizeh->y, sizeh->width, sizeh->height, 0,
00686        viewer->depth, InputOutput, viewer->visual,
00687        x_set_attr_mask, &x_set_attr);
00688     XDefineCursor(display, viewer->window, viewer->arrow_cursor);
00689   }
00690 
00691   
00692   if (sizeh->flags & USSize)
00693     viewer->resizable = 0;
00694   viewer->width = w;
00695   viewer->height = h;
00696   
00697   
00698   if (viewer->top_level) {
00699     stringlist[0] = "gifview";
00700     stringlist[1] = 0;
00701     XStringListToTextProperty(stringlist, 1, &window_name);
00702     XStringListToTextProperty(stringlist, 1, &icon_name);
00703     classh.res_name = (char *)cur_resource_name;
00704     classh.res_class = "Gifview";
00705     XSetWMProperties(display, viewer->window, &window_name, &icon_name,
00706                      NULL, 0, sizeh, NULL, &classh);
00707     XFree(window_name.value);
00708     XFree(icon_name.value);
00709   
00710     if (!wm_delete_window_atom) {
00711       wm_delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False);
00712       wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False);
00713     }
00714     XSetWMProtocols(display, viewer->window, &wm_delete_window_atom, 1);
00715   }
00716 
00717   if (interactive)
00718     XSelectInput(display, viewer->window, ButtonPressMask | KeyPressMask
00719                  | StructureNotifyMask);
00720   else
00721     XSelectInput(display, viewer->window, StructureNotifyMask);
00722   
00723   XFree(sizeh);
00724 }
00725 
00726 
00727 void
00728 pre_delete_viewer(Gt_Viewer *viewer)
00729 {
00730   if (viewer->being_deleted) return;
00731   viewer->being_deleted = 1;
00732   
00733   if (viewer->scheduled) unschedule(viewer);
00734   
00735   if (viewer->window && !viewer->use_window)
00736     XDestroyWindow(viewer->display, viewer->window);
00737   else
00738     delete_viewer(viewer);
00739 }
00740 
00741 
00742 Gt_Viewer *
00743 find_viewer(Display *display, Window window)
00744 {
00745   Gt_Viewer *v;
00746   for (v = viewers; v; v = v->next)
00747     if (v->display == display && v->window == window)
00748       return v;
00749   return 0;
00750 }
00751 
00752 
00753 void
00754 set_viewer_name(Gt_Viewer *viewer, int slow_number)
00755 {
00756   Gif_Image *gfi;
00757   char *strs[2];
00758   char *identifier;
00759   XTextProperty name_prop;
00760   int im_pos = (slow_number >= 0 ? slow_number : viewer->im_pos);
00761   int len;
00762   
00763   if (!viewer->top_level || im_pos >= viewer->nim || viewer->being_deleted)
00764     return;
00765 
00766   gfi = viewer->im[im_pos];
00767   len = strlen(viewer->name) + 21;
00768   identifier = (slow_number >= 0 ? (char *)0 : gfi->identifier);
00769   if (identifier)
00770     len += 2 + strlen(identifier);
00771   
00772   strs[0] = Gif_NewArray(char, len);
00773   if (slow_number >= 0)
00774     sprintf(strs[0], "gifview: %s [#%d]", viewer->name, im_pos);
00775   else if (viewer->nim == 1 && identifier)
00776     sprintf(strs[0], "gifview: %s #%s", viewer->name, identifier);
00777   else if (viewer->animating || viewer->nim == 1)
00778     sprintf(strs[0], "gifview: %s", viewer->name);
00779   else if (!identifier)
00780     sprintf(strs[0], "gifview: %s #%d", viewer->name, im_pos);
00781   else
00782     sprintf(strs[0], "gifview: %s #%d #%s", viewer->name, im_pos, identifier);
00783   strs[1] = 0;
00784   
00785   XStringListToTextProperty(strs, 1, &name_prop);
00786   XSetWMName(viewer->display, viewer->window, &name_prop);
00787   XSetWMIconName(viewer->display, viewer->window, &name_prop);
00788   
00789   XFree(name_prop.value);
00790   Gif_DeleteArray(strs[0]);
00791 }
00792 
00793 static Pixmap
00794 unoptimized_frame(Gt_Viewer *viewer, int frame, int slow)
00795 {
00796   
00797   if (!viewer->unoptimized_pixmaps[frame]) {
00798     Gif_Stream *gfs = viewer->gfs;
00799     Pixmap last = None, last_last = None;
00800     if (frame >= 2 && gfs->images[frame-1]->disposal == GIF_DISPOSAL_PREVIOUS)
00801       last_last = unoptimized_frame(viewer, frame - 2, slow);
00802     if (frame >= 1)
00803       last = unoptimized_frame(viewer, frame - 1, slow);
00804     
00805     viewer->unoptimized_pixmaps[frame] =
00806       Gif_XNextImage(viewer->gfx, last_last, last, gfs, frame);
00807 
00808     if (viewer->free_pixmaps && frame > 1 && frame % SAVE_FRAMES == 1) {
00809       int kill = frame - 1 - SAVE_FRAMES;
00810       assert(kill >= 0);
00811       for (; kill < frame - 1; kill++)
00812         if (viewer->unoptimized_pixmaps[kill] && kill % 50 != 0) {
00813           XFreePixmap(viewer->display, viewer->unoptimized_pixmaps[kill]);
00814           viewer->unoptimized_pixmaps[kill] = None;
00815         }
00816     }
00817 
00818     if (slow) {
00819       set_viewer_name(viewer, frame);
00820       XFlush(viewer->display);
00821     }
00822   }
00823   
00824   return viewer->unoptimized_pixmaps[frame];
00825 }
00826 
00827 static void
00828 kill_unoptimized_frames(Gt_Viewer *viewer, int new_frame)
00829 {
00830   int nimages = viewer->nim;
00831   int kill = nimages;
00832   if (!viewer->free_pixmaps)
00833     return;
00834   
00835   if (new_frame == 0)
00836     kill = 3;
00837   else if (new_frame < viewer->im_pos)
00838     kill = new_frame + 1;
00839   
00840   for (; kill < nimages; kill++)
00841     if (viewer->unoptimized_pixmaps[kill] && kill % 50 != 0) {
00842       XFreePixmap(viewer->display, viewer->unoptimized_pixmaps[kill]);
00843       viewer->unoptimized_pixmaps[kill] = None;
00844     }
00845 }
00846 
00847 void
00848 view_frame(Gt_Viewer *viewer, int frame)
00849 {
00850   Display *display = viewer->display;
00851   Window window = viewer->window;
00852   Pixmap old_pixmap = viewer->pixmap;
00853   int need_set_name = 0;
00854   
00855   if (viewer->being_deleted)
00856     return;
00857   
00858   if (frame < 0)
00859     frame = 0;
00860   if (frame > viewer->nim - 1 && viewer->animating) {
00861     int loopcount = viewer->gfs->loopcount;
00862     if (loopcount == 0 || loopcount > viewer->anim_loop) {
00863       viewer->anim_loop++;
00864       frame = 0;
00865     } else {
00866       switch_animating(viewer, 0);
00867       need_set_name = 1;
00868     }
00869   }
00870   if (frame > viewer->nim - 1)
00871     frame = viewer->nim - 1;
00872   
00873   if (frame != viewer->im_pos) {
00874     Gif_Image *gfi = viewer->im[frame];
00875     int width, height, changed_cursor = 0;
00876 
00877     
00878     if ((viewer->animating || viewer->unoptimizing)
00879         && !viewer->unoptimized_pixmaps[frame]) {
00880       if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) {
00881         changed_cursor = 1;
00882         XDefineCursor(display, window, viewer->wait_cursor);
00883         XFlush(display);
00884       }
00885     }
00886     
00887     
00888 
00889     if (viewer->animating || viewer->unoptimizing)
00890       viewer->pixmap = unoptimized_frame(viewer, frame, changed_cursor);
00891     else
00892       viewer->pixmap = Gif_XImage(viewer->gfx, viewer->gfs, gfi);
00893     
00894     
00895     if (viewer->animating || viewer->unoptimizing)
00896       width = viewer->gfs->screen_width, height = viewer->gfs->screen_height;
00897     else
00898       width = Gif_ImageWidth(gfi), height = Gif_ImageHeight(gfi);
00899     if (!window) {
00900       create_viewer_window(viewer, width, height);
00901       window = viewer->window;
00902     }
00903     XSetWindowBackgroundPixmap(display, window, viewer->pixmap);
00904     if (old_pixmap || viewer->use_window) 
00905       XClearWindow(display, window);
00906     
00907     if ((viewer->width != width || viewer->height != height)
00908         && viewer->resizable) {
00909       XWindowChanges winch;
00910       winch.width = viewer->width = width;
00911       winch.height = viewer->height = height;
00912       XReconfigureWMWindow
00913         (display, window, viewer->screen_number,
00914          CWWidth | CWHeight, &winch);
00915     }
00916 
00917     
00918     if (changed_cursor)
00919       XDefineCursor(display, window, viewer->arrow_cursor);
00920     
00921     
00922     if (viewer->was_unoptimized)
00923       kill_unoptimized_frames(viewer, frame);
00924     else if (old_pixmap)
00925       XFreePixmap(display, old_pixmap);
00926     viewer->was_unoptimized = viewer->animating || viewer->unoptimizing;
00927     
00928     
00929     if ((!viewer->animating && Gif_ImageCount(viewer->gfs) > 1)
00930         || old_pixmap == None)
00931       need_set_name = 1;
00932     
00933     viewer->im_pos = frame;
00934   }
00935   
00936   if (need_set_name)
00937     set_viewer_name(viewer, -1);
00938   
00939   if (!old_pixmap && !viewer->use_window)
00940     
00941     XMapRaised(display, window);
00942   else if (viewer->animating)
00943     
00944     schedule_next_frame(viewer);
00945 }
00946 
00947 
00948 
00949 
00950 
00951 
00952 int
00953 frame_argument(Gt_Viewer *viewer, char *arg)
00954 {
00955   char *c = arg;
00956   int n1 = 0;
00957   
00958   
00959   if (isdigit(c[0]))
00960     n1 = strtol(arg, &c, 10);
00961   
00962   
00963 
00964   if (c[0] != 0) {
00965     Gif_Image *gfi = Gif_GetNamedImage(viewer->gfs, c);
00966     if (!gfi)
00967       error("no frame named `%s'", c);
00968     else
00969       n1 = Gif_ImageNumber(viewer->gfs, gfi);
00970   } else {
00971     if (n1 < 0 || n1 >= viewer->nim) {
00972       error("no frame number %d", n1);
00973       n1 = 0;
00974     }
00975   }
00976   
00977   return n1;
00978 }
00979 
00980 
00981 void
00982 input_stream_done(Gt_Viewer *viewer, int first_frame)
00983 {
00984   viewer->can_animate = Gif_ImageCount(viewer->gfs) > 1;
00985   switch_animating(viewer, animating && viewer->can_animate);
00986   if (first_frame < 0)
00987     first_frame = 0;
00988   view_frame(viewer, first_frame);
00989 }
00990 
00991 
00992 void
00993 key_press(Gt_Viewer *viewer, XKeyEvent *e)
00994 {
00995   char buf[32];
00996   KeySym key;
00997   int nbuf = XLookupString(e, buf, 32, &key, 0);
00998   if (nbuf > 1) buf[0] = 0;     
00999   
01000   if (key == XK_space || key == XK_F || key == XK_f)
01001     
01002     view_frame(viewer, viewer->im_pos + 1);
01003   
01004   else if (key == XK_B || key == XK_b)
01005     
01006     view_frame(viewer, viewer->im_pos - 1);
01007   
01008   else if (key == XK_W || key == XK_w || key == XK_BackSpace)
01009     
01010     pre_delete_viewer(viewer);
01011   
01012   else if (key == XK_Q || key == XK_q)
01013     
01014     exit(0);
01015   
01016   else if (key == XK_S || key == XK_s || key == XK_a || key == XK_A) {
01017     
01018     switch_animating(viewer, !viewer->animating);
01019     
01020     if (viewer->animating) {
01021       int pos = viewer->im_pos;
01022       if (viewer->im_pos >= viewer->nim - 1) {
01023         pos = 0;
01024         viewer->anim_loop = 0;
01025       }
01026       view_frame(viewer, pos);
01027       
01028     } else
01029       unschedule(viewer);
01030     
01031     set_viewer_name(viewer, -1);
01032     
01033   } else if (key == XK_U || key == XK_u) {
01034     
01035     int pos = viewer->im_pos;
01036     viewer->unoptimizing = !viewer->unoptimizing;
01037     if (!viewer->animating) {
01038       viewer->im_pos = -1;
01039       view_frame(viewer, pos);
01040       set_viewer_name(viewer, -1);
01041     }
01042     
01043   } else if (key == XK_R || key == XK_r
01044              || (nbuf == 1 && buf[0] == '<')) {
01045     
01046     unschedule(viewer);
01047     viewer->anim_loop = 0;
01048     view_frame(viewer, 0);
01049     
01050   } else if (nbuf == 1 && buf[0] == '>') {
01051     
01052     unschedule(viewer);
01053     viewer->anim_loop = 0;
01054     view_frame(viewer, viewer->nim - 1);
01055     
01056   } else if (key == XK_Escape && viewer->animating) {
01057     
01058     switch_animating(viewer, 0);
01059     unschedule(viewer);
01060     set_viewer_name(viewer, -1);
01061     
01062   } else if (key == XK_Z || key == XK_z) {
01063     
01064     viewer->resizable = !viewer->resizable;
01065   }
01066 }
01067 
01068 
01069 void
01070 loop(void)
01071 {
01072   struct timeval now;
01073   fd_set xfds;
01074   XEvent e;
01075   int pending;
01076   Gt_Viewer *v;
01077   Display *display = viewers->display;
01078   int x_socket = ConnectionNumber(display);
01079   
01080   xwGETTIME(now);
01081   FD_ZERO(&xfds);
01082   
01083   while (viewers) {
01084     
01085     
01086     
01087 
01088 
01089     pending = 0;
01090     while (animations && xwTIMEGEQ(now, animations->timer) && pending < 25) {
01091       v = animations;
01092       animations = v->anim_next;
01093       v->scheduled = 0;
01094       view_frame(v, v->im_pos + 1);
01095       xwGETTIME(now);
01096       pending++;
01097     }
01098     
01099     pending = XPending(display);
01100     if (!pending) {
01101       
01102       struct timeval timeout, *timeout_ptr;
01103       int retval;
01104 
01105       if (animations) {
01106         xwSUBTIME(timeout, animations->timer, now);
01107         timeout_ptr = &timeout;
01108       } else
01109         timeout_ptr = 0;
01110     
01111       FD_SET(x_socket, &xfds);
01112       retval = select(x_socket + 1, &xfds, 0, 0, timeout_ptr);
01113       pending = (retval <= 0 ? 0 : FD_ISSET(x_socket, &xfds));
01114     }
01115     
01116     if (pending)
01117       while (XPending(display)) {
01118         XNextEvent(display, &e);
01119         v = find_viewer(e.xany.display, e.xany.window);
01120         if (v) {
01121           if (interactive) {
01122             if (e.type == ButtonPress && e.xbutton.button == 1)
01123               
01124               view_frame(v, v->im_pos + 1);
01125             
01126             else if (e.type == ButtonPress && e.xbutton.button == 3)
01127               
01128               pre_delete_viewer(v);
01129             
01130             else if (e.type == KeyPress)
01131               
01132               key_press(v, &e.xkey);
01133           }
01134           
01135           if (e.type == ClientMessage
01136               && e.xclient.message_type == wm_protocols_atom
01137               && (Atom)(e.xclient.data.l[0]) == wm_delete_window_atom)
01138             
01139             pre_delete_viewer(v);
01140           
01141           else if (e.type == MapNotify && v->animating
01142                    && v->scheduled == 0)
01143             
01144             schedule_next_frame(v);
01145           
01146           else if (e.type == DestroyNotify)
01147             
01148             delete_viewer(v);
01149         }
01150       }
01151     
01152     xwGETTIME(now);
01153   }
01154 }
01155 
01156 
01157 int
01158 main(int argc, char **argv)
01159 {
01160   Gt_Viewer *viewer = 0;
01161   int viewer_given = 0;
01162   int any_errors = 0;
01163   int first_frame = -1;
01164   
01165   Clp_Parser *clp =
01166     Clp_NewParser(argc, argv,
01167                   sizeof(options) / sizeof(options[0]), options);
01168   Clp_SetOptionChar(clp, '+', Clp_ShortNegated);
01169   Clp_AddStringListType
01170     (clp, WINDOW_TYPE, Clp_AllowNumbers,
01171      "root", -1,
01172      0);
01173   program_name = cur_resource_name = Clp_ProgramName(clp);
01174   
01175   xwGETTIMEOFDAY(&genesis_time);
01176   
01177   while (1) {
01178     int opt = Clp_Next(clp);
01179     switch (opt) {
01180       
01181      case DISPLAY_OPT:
01182       if (cur_display)
01183         fatal_error("`--display' must come before all other options");
01184       cur_display_name = clp->arg;
01185       cur_display = 0;
01186       cur_arrow_cursor = cur_wait_cursor = None;
01187       break;
01188       
01189      case GEOMETRY_OPT:
01190       cur_geometry_spec = clp->arg;
01191       break;
01192       
01193      case NAME_OPT:
01194       cur_resource_name = clp->arg;
01195       break;
01196       
01197      case UNOPTIMIZE_OPT:
01198       unoptimizing = clp->negated ? 0 : 1;
01199       break;
01200       
01201      case BACKGROUND_OPT:
01202       cur_background_color = clp->arg;
01203       break;
01204       
01205      case ANIMATE_OPT:
01206       animating = clp->negated ? 0 : 1;
01207       break;
01208       
01209      case INSTALL_COLORMAP_OPT:
01210       install_colormap = clp->negated ? 0 : 1;
01211       break;
01212       
01213      case WINDOW_OPT:
01214       cur_use_window = clp->val.u;
01215       break;
01216       
01217      case INTERACTIVE_OPT:
01218       interactive = clp->negated ? 0 : 1;
01219       break;
01220       
01221      case VERSION_OPT:
01222       printf("gifview (LCDF Gifsicle) %s\n", VERSION);
01223       printf("Copyright (C) 1997-2001 Eddie Kohler\n\
01224 This is free software; see the source for copying conditions.\n\
01225 There is NO warranty, not even for merchantability or fitness for a\n\
01226 particular purpose.\n");
01227       exit(0);
01228       break;
01229       
01230      case HELP_OPT:
01231       usage();
01232       exit(0);
01233       break;
01234       
01235      case Clp_NotOption:
01236       if (clp->arg[0] == '#') {
01237         if (!viewer_given) {
01238           viewer = get_input_stream(0);
01239           viewer_given = 1;
01240         }
01241         if (viewer && first_frame >= 0) {
01242           
01243           input_stream_done(viewer, first_frame);
01244           viewer = next_viewer(viewer->gfs, viewer->name);
01245         }
01246         if (viewer)
01247           first_frame = frame_argument(viewer, clp->arg + 1);
01248       } else {
01249         if (viewer) input_stream_done(viewer, first_frame);
01250         first_frame = -1;
01251         viewer = get_input_stream(clp->arg);
01252         viewer_given = 1;
01253       }
01254       break;
01255       
01256      case Clp_Done:
01257       goto done;
01258       
01259      case Clp_BadOption:
01260       any_errors = 1;
01261       break;
01262       
01263      default:
01264       break;
01265       
01266     }
01267   }
01268   
01269  done:
01270   
01271   if (!viewer_given) {
01272     if (any_errors) {
01273       short_usage();
01274       exit(1);
01275     }
01276     viewer = get_input_stream(0);
01277   }
01278   if (viewer) input_stream_done(viewer, first_frame);
01279   
01280   if (viewers) loop();
01281   
01282 #ifdef DMALLOC
01283   dmalloc_report();
01284 #endif
01285   return 0;
01286 }