"Fossies" - the Fresh Open Source Software Archive

Member "gifsicle-1.92/src/gifview.c" (18 Apr 2019, 38902 Bytes) of package /linux/misc/gifsicle-1.92.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "gifview.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.91_vs_1.92.

    1 /* gifview.c - gifview's main loop.
    2    Copyright (C) 1997-2019 Eddie Kohler, ekohler@gmail.com
    3    This file is part of gifview, in the gifsicle package.
    4 
    5    Gifview is free software. It is distributed under the GNU Public License,
    6    version 2; you can copy, distribute, or alter it at will, as long
    7    as this notice is kept intact and this source code is made available. There
    8    is no warranty, express or implied. */
    9 
   10 #include <config.h>
   11 #ifdef X_DISPLAY_MISSING
   12 #error "You can't compile gifview without X."
   13 #endif
   14 
   15 #include <lcdfgif/gifx.h>
   16 #include <lcdf/clp.h>
   17 #include <X11/Xlib.h>
   18 #include <X11/Xutil.h>
   19 #include <X11/Xos.h>
   20 #include <X11/keysym.h>
   21 #include <X11/cursorfont.h>
   22 #if HAVE_SYS_SELECT_H
   23 # include <sys/select.h>
   24 #endif
   25 #include <string.h>
   26 #include <stdio.h>
   27 #include <stdlib.h>
   28 #include <stdarg.h>
   29 #include <ctype.h>
   30 #include <errno.h>
   31 #include <assert.h>
   32 #if HAVE_UNISTD_H
   33 # include <unistd.h>
   34 #endif
   35 
   36 #ifdef __cplusplus
   37 #define EXTERN extern "C"
   38 #else
   39 #define EXTERN extern
   40 #endif
   41 
   42 /*****
   43  * TIME STUFF (from xwrits)
   44  **/
   45 
   46 #define MICRO_PER_SEC 1000000
   47 
   48 #define xwADDTIME(result, a, b) do { \
   49     (result).tv_sec = (a).tv_sec + (b).tv_sec; \
   50     if (((result).tv_usec = (a).tv_usec + (b).tv_usec) >= MICRO_PER_SEC) { \
   51         (result).tv_sec++; \
   52         (result).tv_usec -= MICRO_PER_SEC; \
   53     } } while (0)
   54 
   55 #define xwSUBTIME(result, a, b) do { \
   56     (result).tv_sec = (a).tv_sec - (b).tv_sec; \
   57     if (((result).tv_usec = (a).tv_usec - (b).tv_usec) < 0) { \
   58         (result).tv_sec--; \
   59         (result).tv_usec += MICRO_PER_SEC; \
   60     } } while (0)
   61 
   62 #define xwSETMINTIME(a, b) do { \
   63     if ((b).tv_sec < (a).tv_sec || \
   64         ((b).tv_sec == (a).tv_sec && (b).tv_usec < (a).tv_usec)) \
   65         (a) = (b); \
   66     } while (0)
   67 
   68 #define xwTIMEGEQ(a, b) ((a).tv_sec > (b).tv_sec || \
   69     ((a).tv_sec == (b).tv_sec && (a).tv_usec >= (b).tv_usec))
   70 
   71 #define xwTIMEGT(a, b) ((a).tv_sec > (b).tv_sec || \
   72     ((a).tv_sec == (b).tv_sec && (a).tv_usec > (b).tv_usec))
   73 
   74 #define xwTIMELEQ0(a) ((a).tv_sec < 0 || ((a).tv_sec == 0 && (a).tv_usec <= 0))
   75 
   76 #ifdef X_GETTIMEOFDAY
   77 # define xwGETTIMEOFDAY(a) X_GETTIMEOFDAY(a)
   78 #elif GETTIMEOFDAY_PROTO == 0
   79 EXTERN int gettimeofday(struct timeval *, struct timezone *);
   80 # define xwGETTIMEOFDAY(a) gettimeofday((a), 0)
   81 #elif GETTIMEOFDAY_PROTO == 1
   82 # define xwGETTIMEOFDAY(a) gettimeofday((a))
   83 #else
   84 # define xwGETTIMEOFDAY(a) gettimeofday((a), 0)
   85 #endif
   86 
   87 #define xwGETTIME(a) do { xwGETTIMEOFDAY(&(a)); xwSUBTIME((a), (a), genesis_time); } while (0)
   88 struct timeval genesis_time;
   89 
   90 
   91 /*****
   92  * THE VIEWER STRUCTURE
   93  **/
   94 
   95 static unsigned pixel_memory_limit_kb = 40000;
   96 static unsigned pixel_memory_kb;
   97 
   98 typedef struct Gt_Viewer {
   99 
  100   Display *display;
  101   int screen_number;
  102   Visual *visual;
  103   int depth;
  104   Colormap colormap;
  105   Gif_XContext *gfx;
  106   Cursor arrow_cursor;
  107   Cursor wait_cursor;
  108 
  109   Window parent;
  110   int top_level;
  111 
  112   Window window;
  113   int use_window;
  114   int width;
  115   int height;
  116   int resizable;
  117   int being_deleted;
  118 
  119   Gif_Stream *gfs;
  120   const char *name;
  121   const char *title;
  122 
  123   Gif_Image **im;
  124   int nim;
  125 
  126   Pixmap pixmap;
  127   int im_pos;
  128   int was_unoptimized;
  129 
  130   Gif_XFrame *unoptimized_frames;
  131   int n_unoptimized_frames;
  132 
  133   struct Gt_Viewer *next;
  134 
  135   int can_animate;
  136   int animating;
  137   int unoptimizing;
  138   int scheduled;
  139   int preparing;
  140   struct Gt_Viewer *anim_next;
  141   struct timeval timer;
  142   int anim_loop;
  143 
  144 } Gt_Viewer;
  145 
  146 const char *program_name = "gifview";
  147 static Clp_Parser* clp;
  148 
  149 static const char *cur_display_name = 0;
  150 static Display *cur_display = 0;
  151 static const char *cur_geometry_spec = 0;
  152 static Cursor cur_arrow_cursor = 0;
  153 static Cursor cur_wait_cursor = 0;
  154 static const char *cur_resource_name;
  155 static const char *cur_window_title = 0;
  156 static Window cur_use_window = None;
  157 static int cur_use_window_new = 0;
  158 static const char *cur_background_color = "black";
  159 
  160 static Gt_Viewer *viewers;
  161 static Gt_Viewer *animations;
  162 static int animating = 0;
  163 static int unoptimizing = 0;
  164 static int install_colormap = 0;
  165 static int interactive = 1;
  166 static int min_delay = 0;
  167 static int fallback_delay = 0;
  168 
  169 static struct timeval preparation_time;
  170 
  171 
  172 #define DISPLAY_OPT     300
  173 #define UNOPTIMIZE_OPT      301
  174 #define VERSION_OPT     302
  175 #define ANIMATE_OPT     303
  176 #define GEOMETRY_OPT        304
  177 #define NAME_OPT        305
  178 #define HELP_OPT        306
  179 #define WINDOW_OPT      307
  180 #define INSTALL_COLORMAP_OPT    308
  181 #define INTERACTIVE_OPT     309
  182 #define BACKGROUND_OPT      310
  183 #define NEW_WINDOW_OPT      311
  184 #define TITLE_OPT       312
  185 #define MIN_DELAY_OPT       313
  186 #define FALLBACK_DELAY_OPT  314
  187 #define MEMORY_LIMIT_OPT    315
  188 
  189 #define WINDOW_TYPE     (Clp_ValFirstUser)
  190 
  191 const Clp_Option options[] = {
  192   { "animate", 'a', ANIMATE_OPT, 0, Clp_Negate },
  193   { "background", 'b', BACKGROUND_OPT, Clp_ValString, 0 },
  194   { "bg", 't', BACKGROUND_OPT, Clp_ValString, 0 },
  195   { "display", 'd', DISPLAY_OPT, Clp_ValStringNotOption, 0 },
  196   { "geometry", 'g', GEOMETRY_OPT, Clp_ValString, 0 },
  197   { "install-colormap", 'i', INSTALL_COLORMAP_OPT, 0, Clp_Negate },
  198   { "interactive", 'e', INTERACTIVE_OPT, 0, Clp_Negate },
  199   { "help", 0, HELP_OPT, 0, 0 },
  200   { "memory-limit", 0, MEMORY_LIMIT_OPT, Clp_ValUnsigned, Clp_Negate },
  201   { "min-delay", 0, MIN_DELAY_OPT, Clp_ValInt, Clp_Negate },
  202   { "fallback-delay", 0, FALLBACK_DELAY_OPT, Clp_ValInt, Clp_Negate },
  203   { "name", 0, NAME_OPT, Clp_ValString, 0 },
  204   { "title", 'T', TITLE_OPT, Clp_ValString, 0 },
  205   { "unoptimize", 'U', UNOPTIMIZE_OPT, 0, Clp_Negate },
  206   { "version", 0, VERSION_OPT, 0, 0 },
  207   { "window", 'w', WINDOW_OPT, WINDOW_TYPE, 0 },
  208   { "new-window", 0, NEW_WINDOW_OPT, WINDOW_TYPE, 0 }
  209 };
  210 
  211 
  212 /*****
  213  * Diagnostics
  214  **/
  215 
  216 void fatal_error(const char* format, ...) {
  217     char buf[BUFSIZ];
  218     int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
  219     va_list val;
  220     va_start(val, format);
  221     Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
  222     va_end(val);
  223     fputs(buf, stderr);
  224     exit(1);
  225 }
  226 
  227 void error(const char* format, ...) {
  228     char buf[BUFSIZ];
  229     int n = snprintf(buf, BUFSIZ, "%s: ", program_name);
  230     va_list val;
  231     va_start(val, format);
  232     Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
  233     va_end(val);
  234     fputs(buf, stderr);
  235 }
  236 
  237 void warning(const char* format, ...) {
  238     char buf[BUFSIZ];
  239     int n = snprintf(buf, BUFSIZ, "%s: warning: ", program_name);
  240     va_list val;
  241     va_start(val, format);
  242     Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val);
  243     va_end(val);
  244     fputs(buf, stderr);
  245 }
  246 
  247 void short_usage(void) {
  248     Clp_fprintf(clp, stderr, "\
  249 Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\
  250 Try %<%s --help%> for more information.\n",
  251                 program_name, program_name);
  252 }
  253 
  254 void usage(void) {
  255     Clp_fprintf(clp, stdout, "\
  256 %<Gifview%> is a lightweight GIF viewer for X. It can display animated GIFs as\n\
  257 slideshows, one frame at a time, or as animations.\n\
  258 \n\
  259 Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\n", program_name);
  260     Clp_fprintf(clp, stdout, "\
  261 Options are:\n\
  262   -a, --animate                 Animate multiframe GIFs.\n\
  263   -U, --unoptimize              Unoptimize displayed GIFs.\n\
  264   -d, --display DISPLAY         Set display to DISPLAY.\n\
  265       --name NAME               Set application resource name to NAME.\n\
  266   -g, --geometry GEOMETRY       Set window geometry.\n\
  267   -T, --title TITLE             Set window title.\n");
  268     Clp_fprintf(clp, stdout, "\
  269   -w, --window WINDOW           Show GIF in existing WINDOW.\n\
  270       --new-window WINDOW       Show GIF in new child of existing WINDOW.\n\
  271   -i, --install-colormap        Use a private colormap.\n\
  272   --bg, --background COLOR      Use COLOR for transparent pixels.\n\
  273       --min-delay DELAY         Set minimum frame delay to DELAY/100 sec.\n\
  274       --fallback-delay DELAY    Set fallback frame delay to DELAY/100 sec.\n\
  275   +e, --no-interactive          Ignore buttons and keystrokes.\n");
  276     Clp_fprintf(clp, stdout, "\
  277       --memory-limit LIM        Cache at most LIM megabytes of animation.\n\
  278       --help                    Print this message and exit.\n\
  279       --version                 Print version number and exit.\n\
  280 \n\
  281 Frame selections:               #num, #num1-num2, #num1-, #name\n\n");
  282     Clp_fprintf(clp, stdout, "\
  283 Keystrokes:\n\
  284   [N]/[Space] Go to next frame.         [P]/[B] Go to previous frame.\n\
  285   [R]/[<] Go to first frame.            [>] Go to last frame.\n\
  286   [ESC] Stop animation.                 [S]/[A] Toggle animation.\n\
  287   [U] Toggle unoptimization.            [Backspace]/[W] Delete window.\n\
  288   [Q] Quit.\n\
  289 \n\
  290 Left mouse button goes to next frame, right mouse button deletes window.\n\
  291 \n\
  292 Report bugs to <ekohler@gmail.com>.\n");
  293 }
  294 
  295 
  296 /*****
  297  * Window creation
  298  **/
  299 
  300 #if defined(__cplusplus) || defined(c_plusplus)
  301 #define VISUAL_CLASS c_class
  302 #else
  303 #define VISUAL_CLASS class
  304 #endif
  305 
  306 static void
  307 choose_visual(Gt_Viewer *viewer)
  308 {
  309   Display *display = viewer->display;
  310   int screen_number = viewer->screen_number;
  311   VisualID default_visualid = DefaultVisual(display, screen_number)->visualid;
  312 
  313   XVisualInfo visi_template;
  314   int nv, i;
  315   XVisualInfo *v, *best_v = 0;
  316   Gt_Viewer *trav;
  317 
  318   /* Look for an existing Gt_Viewer with the same display and screen number */
  319   if (!install_colormap)
  320     for (trav = viewers; trav; trav = trav->next)
  321       if (trav != viewer && trav->display == display
  322       && trav->screen_number == screen_number) {
  323     viewer->visual = trav->visual;
  324     viewer->depth = trav->depth;
  325     viewer->colormap = trav->colormap;
  326     viewer->gfx = trav->gfx;
  327     viewer->gfx->refcount++;
  328     return;
  329       }
  330 
  331   /* Find the default visual's XVisualInfo & put it in best_v */
  332   visi_template.screen = screen_number;
  333   v = XGetVisualInfo(display, VisualScreenMask, &visi_template, &nv);
  334   for (i = 0; i < nv && !best_v; i++)
  335     if (v[i].visualid == default_visualid)
  336       best_v = &v[i];
  337 
  338   if (!best_v) {
  339 
  340     /* This should never happen. If we can't find the default visual's
  341        XVisualInfo, we just use the default visual */
  342     viewer->visual = DefaultVisual(display, screen_number);
  343     viewer->depth = DefaultDepth(display, screen_number);
  344     viewer->colormap = DefaultColormap(display, screen_number);
  345 
  346   } else {
  347 
  348     /* Which visual to choose? This isn't exactly a simple decision, since
  349        we want to avoid colormap flashing while choosing a nice visual. So
  350        here's the algorithm: Prefer the default visual, or take a TrueColor
  351        visual with strictly greater depth. */
  352     for (i = 0; i < nv; i++)
  353       if (v[i].depth > best_v->depth && v[i].VISUAL_CLASS == TrueColor)
  354     best_v = &v[i];
  355 
  356     viewer->visual = best_v->visual;
  357     viewer->depth = best_v->depth;
  358     if (best_v->visualid != default_visualid
  359     || (best_v->VISUAL_CLASS == PseudoColor && install_colormap))
  360       viewer->colormap =
  361     XCreateColormap(display, RootWindow(display, screen_number),
  362             viewer->visual, AllocNone);
  363     else
  364       viewer->colormap = DefaultColormap(display, screen_number);
  365 
  366   }
  367 
  368   viewer->gfx = Gif_NewXContextFromVisual
  369     (display, screen_number, viewer->visual, viewer->depth, viewer->colormap);
  370   viewer->gfx->refcount++;
  371 
  372   if (v) XFree(v);
  373 }
  374 
  375 
  376 Gt_Viewer *
  377 new_viewer(Display *display, Gif_Stream *gfs, const char *name)
  378 {
  379   Gt_Viewer *viewer;
  380   int i;
  381 
  382   /* Make the Gt_Viewer structure */
  383   viewer = Gif_New(Gt_Viewer);
  384   viewer->display = display;
  385 
  386   if (cur_use_window) {
  387     XWindowAttributes attr;
  388 
  389     if (cur_use_window == (Window)(-1)) {   /* means use root window */
  390       viewer->screen_number = DefaultScreen(display);
  391       cur_use_window = RootWindow(display, viewer->screen_number);
  392     }
  393 
  394     XGetWindowAttributes(display, cur_use_window, &attr);
  395 
  396     viewer->screen_number = -1;
  397     for (i = 0; i < ScreenCount(display); i++)
  398       if (ScreenOfDisplay(display, i) == attr.screen)
  399     viewer->screen_number = i;
  400     assert(viewer->screen_number >= 0);
  401 
  402     viewer->visual = attr.visual;
  403     viewer->depth = attr.depth;
  404     viewer->colormap = attr.colormap;
  405 
  406     viewer->gfx = Gif_NewXContextFromVisual
  407       (display, viewer->screen_number, viewer->visual, viewer->depth,
  408        viewer->colormap);
  409     viewer->gfx->refcount++;
  410 
  411     /* Before -- use root window, if that's what we were given; otherwise,
  412        create a child of the window we were given */
  413     /* 13.Nov.2001 - don't make a child of the window we were given! */
  414     if (cur_use_window_new) {
  415       viewer->window = None;
  416       viewer->use_window = 0;
  417     } else {
  418       viewer->window = cur_use_window;
  419       viewer->use_window = 1;
  420     }
  421     viewer->parent = cur_use_window;
  422     viewer->top_level = 0;
  423     viewer->resizable = 0;
  424 
  425   } else {
  426     viewer->screen_number = DefaultScreen(display);
  427     choose_visual(viewer);
  428     viewer->window = None;
  429     viewer->parent = RootWindow(display, viewer->screen_number);
  430     viewer->use_window = 0;
  431     viewer->top_level = 1;
  432     viewer->resizable = 1;
  433   }
  434 
  435   /* assign background color */
  436   if (cur_background_color) {
  437     XColor color;
  438     if (!XParseColor(viewer->display, viewer->colormap, cur_background_color,
  439              &color)) {
  440       error("invalid background color %<%s%>\n", cur_background_color);
  441       cur_background_color = 0;
  442     } else if (!XAllocColor(viewer->display, viewer->colormap, &color))
  443       warning("can%,t allocate background color\n");
  444     else {
  445       unsigned long pixel = color.pixel;
  446       Gif_XContext *gfx = viewer->gfx;
  447       if (pixel != gfx->transparent_pixel && gfx->refcount > 1) {
  448     /* copy X context */
  449     viewer->gfx = Gif_NewXContextFromVisual
  450       (gfx->display, gfx->screen_number, gfx->visual, gfx->depth,
  451        gfx->colormap);
  452     viewer->gfx->refcount++;
  453     gfx->refcount--;
  454       }
  455       viewer->gfx->transparent_pixel = pixel;
  456     }
  457   }
  458 
  459   if (!cur_arrow_cursor) {
  460     cur_arrow_cursor = XCreateFontCursor(display, XC_left_ptr);
  461     cur_wait_cursor = XCreateFontCursor(display, XC_watch);
  462   }
  463   viewer->arrow_cursor = cur_arrow_cursor;
  464   viewer->wait_cursor = cur_wait_cursor;
  465 
  466   viewer->being_deleted = 0;
  467   viewer->gfs = gfs;
  468   gfs->refcount++;
  469   viewer->name = name;
  470   viewer->title = cur_window_title;
  471   viewer->nim = Gif_ImageCount(gfs);
  472   viewer->im = Gif_NewArray(Gif_Image *, viewer->nim);
  473   for (i = 0; i < viewer->nim; i++)
  474     viewer->im[i] = gfs->images[i];
  475   viewer->pixmap = None;
  476   viewer->im_pos = -1;
  477   viewer->was_unoptimized = 0;
  478   viewer->unoptimized_frames = Gif_NewXFrames(gfs);
  479   viewer->n_unoptimized_frames = 0;
  480   viewer->next = viewers;
  481   viewers = viewer;
  482   viewer->animating = 0;
  483   viewer->unoptimizing = unoptimizing;
  484   viewer->scheduled = 0;
  485   viewer->preparing = 0;
  486   viewer->anim_next = 0;
  487   viewer->anim_loop = 0;
  488   viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
  489 
  490   return viewer;
  491 }
  492 
  493 
  494 void
  495 delete_viewer(Gt_Viewer *viewer)
  496 {
  497   Gt_Viewer *prev = 0, *trav;
  498   if (viewer->pixmap && !viewer->was_unoptimized)
  499     XFreePixmap(viewer->display, viewer->pixmap);
  500 
  501   for (trav = viewers; trav != viewer; prev = trav, trav = trav->next)
  502     ;
  503   if (prev) prev->next = viewer->next;
  504   else viewers = viewer->next;
  505 
  506   Gif_DeleteXFrames(viewer->gfx, viewer->gfs, viewer->unoptimized_frames);
  507   Gif_DeleteStream(viewer->gfs);
  508   Gif_DeleteArray(viewer->im);
  509   Gif_DeleteXContext(viewer->gfx);
  510   Gif_Delete(viewer);
  511 }
  512 
  513 
  514 static Gt_Viewer *
  515 next_viewer(Gif_Stream *gfs, const char *name)
  516 {
  517   Gt_Viewer *viewer = new_viewer(cur_display, gfs, name);
  518   cur_use_window = None;
  519   return viewer;
  520 }
  521 
  522 static Gt_Viewer *
  523 get_input_stream(const char *name)
  524 {
  525   FILE *f;
  526   Gif_Stream *gfs = 0;
  527 
  528   if (name == 0 || strcmp(name, "-") == 0) {
  529 #ifndef OUTPUT_GIF_TO_TERMINAL
  530     if (isatty(fileno(stdin))) {
  531       error("<stdin>: is a terminal\n");
  532       return NULL;
  533     }
  534 #endif
  535     f = stdin;
  536 #if defined(_MSDOS) || defined(_WIN32)
  537     _setmode(_fileno(stdin), _O_BINARY);
  538 #elif defined(__DJGPP__)
  539     setmode(fileno(stdin), O_BINARY);
  540 #elif defined(__EMX__)
  541     _fsetmode(stdin, "b");
  542 #endif
  543     name = "<stdin>";
  544   } else
  545     f = fopen(name, "rb");
  546   if (!f) {
  547     error("%s: %s\n", name, strerror(errno));
  548     return 0;
  549   }
  550 
  551   gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, 0);
  552   fclose(f);
  553 
  554   if (!gfs || Gif_ImageCount(gfs) == 0) {
  555     error("%s: file not in GIF format\n", name);
  556     Gif_DeleteStream(gfs);
  557     return 0;
  558   }
  559 
  560   if (!cur_display) {
  561     cur_display = XOpenDisplay(cur_display_name);
  562     if (!cur_display) {
  563       error("can%,t open display\n");
  564       return 0;
  565     }
  566   }
  567 
  568   return next_viewer(gfs, name);
  569 }
  570 
  571 
  572 /*****
  573  * Schedule stuff
  574  **/
  575 
  576 void
  577 switch_animating(Gt_Viewer *viewer, int animating)
  578 {
  579   int i;
  580   Gif_Stream *gfs = viewer->gfs;
  581   if (animating == viewer->animating || !viewer->can_animate)
  582     return;
  583   for (i = 0; i < gfs->nimages; i++)
  584     viewer->im[i] = gfs->images[i];
  585   viewer->animating = animating;
  586   if (!animating)
  587     viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
  588 }
  589 
  590 
  591 void
  592 unschedule(Gt_Viewer *viewer)
  593 {
  594   Gt_Viewer *prev, *trav;
  595   if (!viewer->scheduled) return;
  596   for (prev = 0, trav = animations; trav; prev = trav, trav = trav->anim_next)
  597     if (trav == viewer)
  598       break;
  599   if (trav) {
  600     if (prev) prev->anim_next = viewer->anim_next;
  601     else animations = viewer->anim_next;
  602   }
  603   viewer->scheduled = 0;
  604   viewer->timer.tv_sec = viewer->timer.tv_usec = 0;
  605 }
  606 
  607 void
  608 schedule(Gt_Viewer *viewer)
  609 {
  610   Gt_Viewer *prev, *trav;
  611 
  612   if (viewer->scheduled)
  613     unschedule(viewer);
  614 
  615   prev = 0;
  616   for (trav = animations; trav; prev = trav, trav = trav->anim_next)
  617     if (xwTIMEGEQ(trav->timer, viewer->timer))
  618       break;
  619   if (prev) {
  620     viewer->anim_next = trav;
  621     prev->anim_next = viewer;
  622   } else {
  623     viewer->anim_next = animations;
  624     animations = viewer;
  625   }
  626 
  627   viewer->scheduled = 1;
  628 }
  629 
  630 void
  631 schedule_next_frame(Gt_Viewer *viewer)
  632 {
  633   struct timeval interval;
  634   int delay = viewer->im[viewer->im_pos]->delay;
  635   int next_pos = viewer->im_pos + 1;
  636   if (delay < 1)
  637     delay = fallback_delay;
  638   if (delay < min_delay)
  639     delay = min_delay;
  640   if (next_pos == viewer->nim)
  641     next_pos = 0;
  642 
  643   if (viewer->timer.tv_sec == 0 && viewer->timer.tv_usec == 0)
  644     xwGETTIME(viewer->timer);
  645 
  646   interval.tv_sec = delay / 100;
  647   interval.tv_usec = (delay % 100) * (MICRO_PER_SEC / 100);
  648   if (delay == 0)
  649     interval.tv_usec = 2000;
  650   xwADDTIME(viewer->timer, viewer->timer, interval);
  651 
  652   /* 1.Aug.2002 - leave some time to prepare the frame if necessary */
  653   if (viewer->unoptimized_frames[next_pos].pixmap) {
  654     xwSUBTIME(viewer->timer, viewer->timer, preparation_time);
  655     viewer->preparing = 1;
  656   }
  657 
  658   schedule(viewer);
  659 }
  660 
  661 
  662 /*****
  663  * X stuff
  664  **/
  665 
  666 int
  667 parse_geometry(const char *const_g, XSizeHints *sh, int screen_width,
  668            int screen_height)
  669 {
  670   char *g = (char *)const_g;
  671   sh->flags = 0;
  672 
  673   if (isdigit(*g)) {
  674     sh->flags |= USSize;
  675     sh->width = strtol(g, &g, 10);
  676     if (g[0] == 'x' && isdigit(g[1]))
  677       sh->height = strtol(g + 1, &g, 10);
  678     else
  679       goto error;
  680   } else if (!*g)
  681     goto error;
  682 
  683   if (*g == '+' || *g == '-') {
  684     int x_minus, y_minus;
  685     sh->flags |= USPosition | PWinGravity;
  686     x_minus = *g == '-';
  687     sh->x = strtol(g + 1, &g, 10);
  688     if (x_minus) sh->x = screen_width - sh->x - sh->width;
  689 
  690     y_minus = *g == '-';
  691     if (*g == '-' || *g == '+')
  692       sh->y = strtol(g + 1, &g, 10);
  693     else
  694       goto error;
  695     if (y_minus) sh->y = screen_height - sh->y - sh->height;
  696 
  697     if (x_minus)
  698       sh->win_gravity = y_minus ? SouthEastGravity : NorthEastGravity;
  699     else
  700       sh->win_gravity = y_minus ? SouthWestGravity : NorthWestGravity;
  701 
  702   } else if (*g)
  703     goto error;
  704 
  705   return 1;
  706 
  707  error:
  708   warning("bad geometry specification\n");
  709   sh->flags = 0;
  710   return 0;
  711 }
  712 
  713 
  714 static Atom wm_delete_window_atom;
  715 static Atom wm_protocols_atom;
  716 
  717 void
  718 create_viewer_window(Gt_Viewer *viewer, int w, int h)
  719 {
  720   Display *display = viewer->display;
  721   char *stringlist[2];
  722   XTextProperty window_name, icon_name;
  723   XClassHint classh;
  724   XSizeHints *sizeh = XAllocSizeHints(); /* sets all fields to 0 */
  725 
  726   /* Set the window's geometry */
  727   sizeh->width = w ? w : 1;
  728   sizeh->height = h ? h : 1;
  729   if (cur_geometry_spec) {
  730     int scr_width = DisplayWidth(viewer->display, viewer->screen_number);
  731     int scr_height = DisplayHeight(viewer->display, viewer->screen_number);
  732     parse_geometry(cur_geometry_spec, sizeh, scr_width, scr_height);
  733   }
  734 
  735   /* Open the display and create the window */
  736   if (!viewer->window) {
  737     XSetWindowAttributes x_set_attr;
  738     unsigned long x_set_attr_mask;
  739     x_set_attr.colormap = viewer->colormap;
  740     x_set_attr.backing_store = NotUseful;
  741     x_set_attr.save_under = False;
  742     x_set_attr.border_pixel = 0;
  743     x_set_attr.background_pixel = 0;
  744     x_set_attr_mask = CWColormap | CWBorderPixel | CWBackPixel
  745       | CWBackingStore | CWSaveUnder;
  746 
  747     viewer->window = XCreateWindow
  748       (display, viewer->parent,
  749        sizeh->x, sizeh->y, sizeh->width, sizeh->height, 0,
  750        viewer->depth, InputOutput, viewer->visual,
  751        x_set_attr_mask, &x_set_attr);
  752     XDefineCursor(display, viewer->window, viewer->arrow_cursor);
  753   }
  754 
  755   /* If user gave us geometry, don't change the size later */
  756   if (sizeh->flags & USSize)
  757     viewer->resizable = 0;
  758   viewer->width = w;
  759   viewer->height = h;
  760 
  761   /* Set the window's title and class (for window manager resources) */
  762   if (viewer->top_level) {
  763     stringlist[0] = "gifview";
  764     stringlist[1] = 0;
  765     XStringListToTextProperty(stringlist, 1, &window_name);
  766     XStringListToTextProperty(stringlist, 1, &icon_name);
  767     classh.res_name = (char *)cur_resource_name;
  768     classh.res_class = "Gifview";
  769     XSetWMProperties(display, viewer->window, &window_name, &icon_name,
  770              NULL, 0, sizeh, NULL, &classh);
  771     XFree(window_name.value);
  772     XFree(icon_name.value);
  773 
  774     if (!wm_delete_window_atom) {
  775       wm_delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False);
  776       wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False);
  777     }
  778     XSetWMProtocols(display, viewer->window, &wm_delete_window_atom, 1);
  779   }
  780 
  781   if (interactive)
  782     XSelectInput(display, viewer->window, ButtonPressMask | KeyPressMask
  783          | StructureNotifyMask);
  784   else
  785     XSelectInput(display, viewer->window, StructureNotifyMask);
  786 
  787   XFree(sizeh);
  788 }
  789 
  790 
  791 void
  792 pre_delete_viewer(Gt_Viewer *viewer)
  793 {
  794   if (viewer->being_deleted) return;
  795   viewer->being_deleted = 1;
  796 
  797   if (viewer->scheduled) unschedule(viewer);
  798 
  799   if (viewer->window && !viewer->use_window)
  800     XDestroyWindow(viewer->display, viewer->window);
  801   else
  802     delete_viewer(viewer);
  803 }
  804 
  805 
  806 Gt_Viewer *
  807 find_viewer(Display *display, Window window)
  808 {
  809   Gt_Viewer *v;
  810   for (v = viewers; v; v = v->next)
  811     if (v->display == display && v->window == window)
  812       return v;
  813   return 0;
  814 }
  815 
  816 
  817 void
  818 set_viewer_name(Gt_Viewer *viewer, int slow_number)
  819 {
  820   Gif_Image *gfi;
  821   char *strs[2];
  822   char *identifier;
  823   XTextProperty name_prop;
  824   int im_pos = (slow_number >= 0 ? slow_number : viewer->im_pos);
  825   int len;
  826 
  827   if (!viewer->top_level || im_pos >= viewer->nim || viewer->being_deleted)
  828     return;
  829 
  830   gfi = viewer->im[im_pos];
  831   len = strlen(viewer->title) + strlen(viewer->name) + 14;
  832   identifier = (slow_number >= 0 ? (char *)0 : gfi->identifier);
  833   if (identifier)
  834     len += 2 + strlen(identifier);
  835 
  836   strs[0] = Gif_NewArray(char, len);
  837   if (strcmp(viewer->title, "gifview") != 0)
  838     strcpy(strs[0], viewer->title);
  839   else if (slow_number >= 0)
  840     sprintf(strs[0], "gifview: %s [#%d]", viewer->name, im_pos);
  841   else if (viewer->nim == 1 && identifier)
  842     sprintf(strs[0], "gifview: %s #%s", viewer->name, identifier);
  843   else if (viewer->animating || viewer->nim == 1)
  844     sprintf(strs[0], "gifview: %s", viewer->name);
  845   else if (!identifier)
  846     sprintf(strs[0], "gifview: %s #%d", viewer->name, im_pos);
  847   else
  848     sprintf(strs[0], "gifview: %s #%d #%s", viewer->name, im_pos, identifier);
  849   strs[1] = 0;
  850 
  851   XStringListToTextProperty(strs, 1, &name_prop);
  852   XSetWMName(viewer->display, viewer->window, &name_prop);
  853   XSetWMIconName(viewer->display, viewer->window, &name_prop);
  854 
  855   XFree(name_prop.value);
  856   Gif_DeleteArray(strs[0]);
  857 }
  858 
  859 static unsigned screen_memory_kb(const Gt_Viewer* viewer) {
  860   return 1 + ((unsigned) (viewer->gfs->screen_width
  861                           * viewer->gfs->screen_height) / 334);
  862 }
  863 
  864 static Pixmap
  865 unoptimized_frame(Gt_Viewer *viewer, int frame, int slow)
  866 {
  867   /* create a new unoptimized frame if necessary */
  868   if (!viewer->unoptimized_frames[frame].pixmap) {
  869     (void) Gif_XNextImage(viewer->gfx, viewer->gfs, frame,
  870               viewer->unoptimized_frames);
  871     pixel_memory_kb += screen_memory_kb(viewer);
  872     viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data =
  873       frame;
  874     ++viewer->n_unoptimized_frames;
  875     if (slow) {
  876       set_viewer_name(viewer, frame);
  877       XFlush(viewer->display);
  878     }
  879   }
  880 
  881   /* kill some old frames if over the memory limit */
  882   while (pixel_memory_limit_kb != (unsigned) -1
  883          && pixel_memory_limit_kb < pixel_memory_kb
  884          && viewer->n_unoptimized_frames > 1) {
  885     int killidx, killframe, i = 0;
  886     do {
  887       killidx = random() % viewer->n_unoptimized_frames;
  888       killframe = viewer->unoptimized_frames[killidx].user_data;
  889       ++i;
  890     } while (killframe == frame
  891              || (i < 10 && killframe > frame && killframe < frame + 5)
  892              || (i < 10 && (killframe % 50) == 0));
  893     XFreePixmap(viewer->display, viewer->unoptimized_frames[killframe].pixmap);
  894     viewer->unoptimized_frames[killframe].pixmap = None;
  895     --viewer->n_unoptimized_frames;
  896     viewer->unoptimized_frames[killidx].user_data =
  897       viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data;
  898     pixel_memory_kb -= screen_memory_kb(viewer);
  899   }
  900 
  901   return viewer->unoptimized_frames[frame].pixmap;
  902 }
  903 
  904 void
  905 prepare_frame(Gt_Viewer *viewer, int frame)
  906 {
  907   Display *display = viewer->display;
  908   Window window = viewer->window;
  909   int changed_cursor = 0;
  910 
  911   if (viewer->being_deleted || !viewer->animating)
  912     return;
  913 
  914   if (frame < 0 || frame > viewer->nim - 1)
  915     frame = 0;
  916 
  917   /* Change cursor if we need to wait. */
  918   if ((viewer->animating || viewer->unoptimizing)
  919       && !viewer->unoptimized_frames[frame].pixmap) {
  920     if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) {
  921       changed_cursor = 1;
  922       XDefineCursor(display, window, viewer->wait_cursor);
  923       XFlush(display);
  924     }
  925   }
  926 
  927   /* Prepare the frame */
  928   (void) unoptimized_frame(viewer, frame, changed_cursor && !viewer->animating);
  929 
  930   /* Restore cursor */
  931   if (changed_cursor)
  932     XDefineCursor(display, window, viewer->arrow_cursor);
  933 
  934   /* schedule actual view of window */
  935   xwADDTIME(viewer->timer, viewer->timer, preparation_time);
  936   viewer->preparing = 0;
  937   schedule(viewer);
  938 }
  939 
  940 void
  941 view_frame(Gt_Viewer *viewer, int frame)
  942 {
  943   Display *display = viewer->display;
  944   Window window = viewer->window;
  945   Pixmap old_pixmap = viewer->pixmap;
  946   int need_set_name = 0;
  947 
  948   if (viewer->being_deleted)
  949     return;
  950 
  951   if (frame < 0)
  952     frame = 0;
  953   if (frame > viewer->nim - 1 && viewer->animating) {
  954     int loopcount = viewer->gfs->loopcount;
  955     if (loopcount == 0 || loopcount > viewer->anim_loop) {
  956       viewer->anim_loop++;
  957       frame = 0;
  958     } else {
  959       switch_animating(viewer, 0);
  960       need_set_name = 1;
  961     }
  962   }
  963   if (frame > viewer->nim - 1)
  964     frame = viewer->nim - 1;
  965 
  966   if (frame != viewer->im_pos) {
  967     Gif_Image *gfi = viewer->im[frame];
  968     int width, height, changed_cursor = 0;
  969 
  970     /* Change cursor if we need to wait. */
  971     if ((viewer->animating || viewer->unoptimizing)
  972     && !viewer->unoptimized_frames[frame].pixmap) {
  973       if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) {
  974     changed_cursor = 1;
  975     XDefineCursor(display, window, viewer->wait_cursor);
  976     XFlush(display);
  977       }
  978     }
  979 
  980     /* 5/26/98 Do some noodling around to try and use memory most effectively.
  981        If animating, keep the uncompressed frame; otherwise, throw it away. */
  982     if (viewer->animating || viewer->unoptimizing)
  983       viewer->pixmap = unoptimized_frame(viewer, frame,
  984                                          changed_cursor && !viewer->animating);
  985     else
  986       viewer->pixmap = Gif_XImage(viewer->gfx, viewer->gfs, gfi);
  987 
  988     /* put the image on the window */
  989     if (viewer->animating || viewer->unoptimizing)
  990       width = viewer->gfs->screen_width, height = viewer->gfs->screen_height;
  991     else
  992       width = Gif_ImageWidth(gfi), height = Gif_ImageHeight(gfi);
  993     if (!window) {
  994       create_viewer_window(viewer, width, height);
  995       window = viewer->window;
  996     }
  997     XSetWindowBackgroundPixmap(display, window, viewer->pixmap);
  998     if (old_pixmap || viewer->use_window) /* clear existing window */
  999       XClearWindow(display, window);
 1000     /* Only change size after changing pixmap. */
 1001     if ((viewer->width != width || viewer->height != height)
 1002     && viewer->resizable) {
 1003       XWindowChanges winch;
 1004       winch.width = viewer->width = width;
 1005       winch.height = viewer->height = height;
 1006       XReconfigureWMWindow
 1007     (display, window, viewer->screen_number,
 1008      CWWidth | CWHeight, &winch);
 1009     }
 1010 
 1011     /* Get rid of old pixmaps */
 1012     if (!viewer->was_unoptimized && old_pixmap)
 1013       XFreePixmap(display, old_pixmap);
 1014     viewer->was_unoptimized = viewer->animating || viewer->unoptimizing;
 1015 
 1016     /* Restore cursor */
 1017     if (changed_cursor)
 1018       XDefineCursor(display, window, viewer->arrow_cursor);
 1019 
 1020     /* Do we need a new name? */
 1021     if ((!viewer->animating && Gif_ImageCount(viewer->gfs) > 1)
 1022     || old_pixmap == None)
 1023       need_set_name = 1;
 1024   }
 1025 
 1026   viewer->im_pos = frame;
 1027   viewer->preparing = 0;
 1028 
 1029   if (need_set_name)
 1030     set_viewer_name(viewer, -1);
 1031 
 1032   if (!old_pixmap && !viewer->use_window)
 1033     /* first image; map the window */
 1034     XMapRaised(display, window);
 1035   else if (viewer->animating)
 1036     /* only schedule next frame if image is already mapped */
 1037     schedule_next_frame(viewer);
 1038 }
 1039 
 1040 
 1041 /*****
 1042  * Command line arguments: marking frames, being done with streams
 1043  **/
 1044 
 1045 int
 1046 frame_argument(Gt_Viewer *viewer, const char *arg)
 1047 {
 1048   const char *c = arg;
 1049   int n1 = 0;
 1050 
 1051   /* Get a number range (#x, #x-y, #x-, or #-y). First, read x. */
 1052   if (isdigit(c[0]))
 1053     n1 = strtol(arg, (char **)&c, 10);
 1054 
 1055   /* It really was a number range only if c is now at the end of the
 1056      argument. */
 1057   if (c[0] != 0) {
 1058     Gif_Image *gfi = Gif_GetNamedImage(viewer->gfs, c);
 1059     if (!gfi)
 1060       error("no frame named %<%s%>\n", c);
 1061     else
 1062       n1 = Gif_ImageNumber(viewer->gfs, gfi);
 1063   } else {
 1064     if (n1 < 0 || n1 >= viewer->nim) {
 1065       error("no frame number %d\n", n1);
 1066       n1 = 0;
 1067     }
 1068   }
 1069 
 1070   return n1;
 1071 }
 1072 
 1073 
 1074 void
 1075 input_stream_done(Gt_Viewer *viewer, int first_frame)
 1076 {
 1077   viewer->can_animate = Gif_ImageCount(viewer->gfs) > 1;
 1078   switch_animating(viewer, animating && viewer->can_animate);
 1079   if (first_frame < 0)
 1080     first_frame = 0;
 1081   view_frame(viewer, first_frame);
 1082 }
 1083 
 1084 
 1085 void
 1086 key_press(Gt_Viewer *viewer, XKeyEvent *e)
 1087 {
 1088   char buf[32];
 1089   KeySym key;
 1090   int nbuf = XLookupString(e, buf, 32, &key, 0);
 1091   if (nbuf > 1) buf[0] = 0; /* ignore multikey sequences */
 1092 
 1093   if (key == XK_space || key == XK_F || key == XK_f || key == XK_N || key == XK_n)
 1094     /* space, N or F: one frame ahead */
 1095     view_frame(viewer, viewer->im_pos + 1);
 1096 
 1097   else if (key == XK_B || key == XK_b || key == XK_P || key == XK_p)
 1098     /* B or P: one frame back */
 1099     view_frame(viewer, viewer->im_pos - 1);
 1100 
 1101   else if (key == XK_W || key == XK_w || key == XK_BackSpace)
 1102     /* backspace: delete viewer */
 1103     pre_delete_viewer(viewer);
 1104 
 1105   else if (key == XK_Q || key == XK_q)
 1106     /* Q: quit applicaton */
 1107     exit(0);
 1108 
 1109   else if (key == XK_S || key == XK_s || key == XK_a || key == XK_A) {
 1110     /* S or A: toggle animation */
 1111     switch_animating(viewer, !viewer->animating);
 1112 
 1113     if (viewer->animating) {
 1114       int pos = viewer->im_pos;
 1115       if (viewer->im_pos >= viewer->nim - 1) {
 1116     pos = 0;
 1117     viewer->anim_loop = 0;
 1118       }
 1119       view_frame(viewer, pos);
 1120 
 1121     } else
 1122       unschedule(viewer);
 1123 
 1124     set_viewer_name(viewer, -1);
 1125 
 1126   } else if (key == XK_U || key == XK_u) {
 1127     /* U: toggle unoptimizing */
 1128     int pos = viewer->im_pos;
 1129     viewer->unoptimizing = !viewer->unoptimizing;
 1130     if (!viewer->animating) {
 1131       viewer->im_pos = -1;
 1132       view_frame(viewer, pos);
 1133       set_viewer_name(viewer, -1);
 1134     }
 1135 
 1136   } else if (key == XK_R || key == XK_r
 1137          || (nbuf == 1 && buf[0] == '<')) {
 1138     /* R or <: reset to first frame */
 1139     unschedule(viewer);
 1140     viewer->anim_loop = 0;
 1141     view_frame(viewer, 0);
 1142 
 1143   } else if (nbuf == 1 && buf[0] == '>') {
 1144     /* >: reset to last frame */
 1145     unschedule(viewer);
 1146     viewer->anim_loop = 0;
 1147     view_frame(viewer, viewer->nim - 1);
 1148 
 1149   } else if (key == XK_Escape && viewer->animating) {
 1150     /* Escape: stop animation */
 1151     switch_animating(viewer, 0);
 1152     unschedule(viewer);
 1153     set_viewer_name(viewer, -1);
 1154 
 1155   } else if (key == XK_Z || key == XK_z) {
 1156     /* Z: trigger resizability */
 1157     viewer->resizable = !viewer->resizable;
 1158   }
 1159 }
 1160 
 1161 
 1162 void
 1163 loop(void)
 1164 {
 1165   struct timeval now, stop_loop, stop_delta;
 1166   fd_set xfds;
 1167   XEvent e;
 1168   int pending;
 1169   Gt_Viewer *v;
 1170   Display *display = viewers->display;
 1171   int x_socket = ConnectionNumber(display);
 1172   stop_delta.tv_sec = 0;
 1173   stop_delta.tv_usec = 200000;
 1174 
 1175   xwGETTIME(now);
 1176   FD_ZERO(&xfds);
 1177 
 1178   while (viewers) {
 1179 
 1180     /* Check for any animations */
 1181     /* 13.Feb.2001 - Use the 'pending' counter to avoid a tight loop
 1182        if all the frames in an animation have delay 0. Reported by
 1183        Franc,ois Petitjean. */
 1184     /* 1.Aug.2002 - Switch to running the loop for max 0.2s. */
 1185     xwADDTIME(stop_loop, now, stop_delta);
 1186     while (animations && xwTIMEGEQ(now, animations->timer)
 1187        && xwTIMEGEQ(stop_loop, now)) {
 1188       v = animations;
 1189       animations = v->anim_next;
 1190       v->scheduled = 0;
 1191       if (v->preparing)
 1192     prepare_frame(v, v->im_pos + 1);
 1193       else {
 1194     if (xwTIMEGEQ(now, v->timer))
 1195       v->timer = now;
 1196     view_frame(v, v->im_pos + 1);
 1197       }
 1198       xwGETTIME(now);
 1199     }
 1200 
 1201     pending = XPending(display);
 1202     if (!pending) {
 1203       /* select() until event arrives */
 1204       struct timeval timeout, *timeout_ptr;
 1205       int retval;
 1206 
 1207       if (animations) {
 1208     xwSUBTIME(timeout, animations->timer, now);
 1209     timeout_ptr = &timeout;
 1210       } else
 1211     timeout_ptr = 0;
 1212 
 1213       FD_SET(x_socket, &xfds);
 1214       retval = select(x_socket + 1, &xfds, 0, 0, timeout_ptr);
 1215       pending = (retval <= 0 ? 0 : FD_ISSET(x_socket, &xfds));
 1216     }
 1217 
 1218     if (pending)
 1219       while (XPending(display)) {
 1220     XNextEvent(display, &e);
 1221     v = find_viewer(e.xany.display, e.xany.window);
 1222     if (v) {
 1223       if (interactive) {
 1224         if (e.type == ButtonPress && e.xbutton.button == 1)
 1225           /* Left mouse button: go to next frame */
 1226           view_frame(v, v->im_pos + 1);
 1227 
 1228       else if (e.type == ButtonPress && e.xbutton.button == 4)
 1229         /* mousewheel forward */
 1230         view_frame(v, v->im_pos + 1);
 1231 
 1232       else if (e.type == ButtonPress && e.xbutton.button == 5)
 1233         /* mousewheel backward */
 1234         view_frame(v, v->im_pos - 1);
 1235 
 1236         else if (e.type == ButtonPress && e.xbutton.button == 3)
 1237           /* Right mouse button: delete window */
 1238           pre_delete_viewer(v);
 1239 
 1240         else if (e.type == KeyPress)
 1241           /* Key press: call function */
 1242           key_press(v, &e.xkey);
 1243       }
 1244 
 1245       if (e.type == ClientMessage
 1246           && e.xclient.message_type == wm_protocols_atom
 1247           && (Atom)(e.xclient.data.l[0]) == wm_delete_window_atom)
 1248         /* WM_DELETE_WINDOW message: delete window */
 1249         pre_delete_viewer(v);
 1250 
 1251       else if (e.type == MapNotify && v->animating
 1252            && v->scheduled == 0)
 1253         /* Window was just mapped; now, start animating it */
 1254         schedule_next_frame(v);
 1255 
 1256       else if (e.type == DestroyNotify)
 1257         /* Once the window has been destroyed, delete related state */
 1258         delete_viewer(v);
 1259     }
 1260       }
 1261 
 1262     xwGETTIME(now);
 1263   }
 1264 }
 1265 
 1266 
 1267 int
 1268 main(int argc, char *argv[])
 1269 {
 1270   Gt_Viewer *viewer = 0;
 1271   int viewer_given = 0;
 1272   int any_errors = 0;
 1273   int first_frame = -1;
 1274 
 1275   clp = Clp_NewParser(argc, (const char * const *)argv,
 1276                       sizeof(options) / sizeof(options[0]), options);
 1277   Clp_SetOptionChar(clp, '+', Clp_ShortNegated);
 1278   Clp_AddStringListType
 1279     (clp, WINDOW_TYPE, Clp_AllowNumbers | Clp_StringListLong,
 1280      "root", (long) -1,
 1281      (const char*) 0);
 1282   program_name = cur_resource_name = cur_window_title = Clp_ProgramName(clp);
 1283 
 1284   xwGETTIMEOFDAY(&genesis_time);
 1285   preparation_time.tv_sec = 0;
 1286   preparation_time.tv_usec = 200000;
 1287 
 1288   while (1) {
 1289     int opt = Clp_Next(clp);
 1290     switch (opt) {
 1291 
 1292      case DISPLAY_OPT:
 1293       if (cur_display)
 1294     fatal_error("%<--display%> must come before all other options\n");
 1295       cur_display_name = clp->vstr;
 1296       cur_display = 0;
 1297       cur_arrow_cursor = cur_wait_cursor = None;
 1298       break;
 1299 
 1300      case TITLE_OPT:
 1301       cur_window_title = clp->vstr;
 1302       break;
 1303 
 1304      case GEOMETRY_OPT:
 1305       cur_geometry_spec = clp->vstr;
 1306       break;
 1307 
 1308      case NAME_OPT:
 1309       cur_resource_name = clp->vstr;
 1310       break;
 1311 
 1312      case UNOPTIMIZE_OPT:
 1313       unoptimizing = clp->negated ? 0 : 1;
 1314       break;
 1315 
 1316      case BACKGROUND_OPT:
 1317       cur_background_color = clp->vstr;
 1318       break;
 1319 
 1320      case ANIMATE_OPT:
 1321       animating = clp->negated ? 0 : 1;
 1322       break;
 1323 
 1324      case INSTALL_COLORMAP_OPT:
 1325       install_colormap = clp->negated ? 0 : 1;
 1326       break;
 1327 
 1328      case WINDOW_OPT:
 1329       cur_use_window = clp->val.ul;
 1330       cur_use_window_new = 0;
 1331       break;
 1332 
 1333      case NEW_WINDOW_OPT:
 1334       cur_use_window = clp->val.ul;
 1335       cur_use_window_new = 1;
 1336       break;
 1337 
 1338      case INTERACTIVE_OPT:
 1339       interactive = clp->negated ? 0 : 1;
 1340       break;
 1341 
 1342     case MIN_DELAY_OPT:
 1343       min_delay = clp->negated ? 0 : clp->val.i;
 1344       break;
 1345 
 1346     case FALLBACK_DELAY_OPT:
 1347       fallback_delay = clp->negated ? 0 : clp->val.i;
 1348       break;
 1349 
 1350     case MEMORY_LIMIT_OPT:
 1351       if (clp->negated || clp->val.u >= ((unsigned) -1 / 1000))
 1352         pixel_memory_limit_kb = (unsigned) -1;
 1353       else
 1354         pixel_memory_limit_kb = clp->val.u * 1000;
 1355       break;
 1356 
 1357      case VERSION_OPT:
 1358       printf("gifview (LCDF Gifsicle) %s\n", VERSION);
 1359       printf("Copyright (C) 1997-2019 Eddie Kohler\n\
 1360 This is free software; see the source for copying conditions.\n\
 1361 There is NO warranty, not even for merchantability or fitness for a\n\
 1362 particular purpose.\n");
 1363       exit(0);
 1364       break;
 1365 
 1366      case HELP_OPT:
 1367       usage();
 1368       exit(0);
 1369       break;
 1370 
 1371      case Clp_NotOption:
 1372       if (clp->vstr[0] == '#') {
 1373     if (!viewer_given) {
 1374       viewer = get_input_stream(0);
 1375       viewer_given = 1;
 1376     }
 1377     if (viewer && first_frame >= 0) {
 1378       /* copy viewer if 2 frame specs given */
 1379       input_stream_done(viewer, first_frame);
 1380       viewer = next_viewer(viewer->gfs, viewer->name);
 1381     }
 1382     if (viewer)
 1383       first_frame = frame_argument(viewer, clp->vstr + 1);
 1384       } else {
 1385         if (viewer) input_stream_done(viewer, first_frame);
 1386     first_frame = -1;
 1387         viewer = get_input_stream(clp->vstr);
 1388     viewer_given = 1;
 1389       }
 1390       break;
 1391 
 1392      case Clp_Done:
 1393       goto done;
 1394 
 1395      case Clp_BadOption:
 1396       any_errors = 1;
 1397       break;
 1398 
 1399      default:
 1400       break;
 1401 
 1402     }
 1403   }
 1404 
 1405  done:
 1406 
 1407   if (!viewer_given) {
 1408     if (any_errors) {
 1409       short_usage();
 1410       exit(1);
 1411     }
 1412     viewer = get_input_stream(0);
 1413   }
 1414   if (viewer) input_stream_done(viewer, first_frame);
 1415 
 1416   if (viewers) loop();
 1417 
 1418   return 0;
 1419 }