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 }