/* gifview.c - gifview's main loop. Copyright (C) 1997-2001 Eddie Kohler, eddietwo@lcs.mit.edu This file is part of gifview, in the gifsicle package. Gifview is free software. It is distributed under the GNU Public License, version 2 or later; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #include "config.h" #ifdef X_DISPLAY_MISSING #error "You can't compile gifview without X." #endif #include "gifx.h" #include "clp.h" #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H # include #endif #include #include #include #include #include #include #include #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN extern #endif #define SAVE_FRAMES 10 /***** * TIME STUFF (from xwrits) **/ #define MICRO_PER_SEC 1000000 #define xwADDTIME(result, a, b) do { \ (result).tv_sec = (a).tv_sec + (b).tv_sec; \ if (((result).tv_usec = (a).tv_usec + (b).tv_usec) >= MICRO_PER_SEC) { \ (result).tv_sec++; \ (result).tv_usec -= MICRO_PER_SEC; \ } } while (0) #define xwSUBTIME(result, a, b) do { \ (result).tv_sec = (a).tv_sec - (b).tv_sec; \ if (((result).tv_usec = (a).tv_usec - (b).tv_usec) < 0) { \ (result).tv_sec--; \ (result).tv_usec += MICRO_PER_SEC; \ } } while (0) #define xwSETMINTIME(a, b) do { \ if ((b).tv_sec < (a).tv_sec || \ ((b).tv_sec == (a).tv_sec && (b).tv_usec < (a).tv_usec)) \ (a) = (b); \ } while (0) #define xwTIMEGEQ(a, b) ((a).tv_sec > (b).tv_sec || \ ((a).tv_sec == (b).tv_sec && (a).tv_usec >= (b).tv_usec)) #define xwTIMEGT(a, b) ((a).tv_sec > (b).tv_sec || \ ((a).tv_sec == (b).tv_sec && (a).tv_usec > (b).tv_usec)) #define xwTIMELEQ0(a) ((a).tv_sec < 0 || ((a).tv_sec == 0 && (a).tv_usec <= 0)) #ifdef X_GETTIMEOFDAY # define xwGETTIMEOFDAY(a) X_GETTIMEOFDAY(a) #elif GETTIMEOFDAY_PROTO == 0 EXTERN int gettimeofday(struct timeval *, struct timezone *); # define xwGETTIMEOFDAY(a) gettimeofday((a), 0) #elif GETTIMEOFDAY_PROTO == 1 # define xwGETTIMEOFDAY(a) gettimeofday((a)) #else # define xwGETTIMEOFDAY(a) gettimeofday((a), 0) #endif #define xwGETTIME(a) do { xwGETTIMEOFDAY(&(a)); xwSUBTIME((a), (a), genesis_time); } while (0) struct timeval genesis_time; /***** * THE VIEWER STRUCTURE **/ typedef struct Gt_Viewer { Display *display; int screen_number; Visual *visual; int depth; Colormap colormap; Gif_XContext *gfx; Cursor arrow_cursor; Cursor wait_cursor; Window parent; int top_level; Window window; int use_window; int width; int height; int resizable; int being_deleted; Gif_Stream *gfs; char *name; Gif_Image **im; int nim; Pixmap pixmap; int im_pos; int was_unoptimized; int free_pixmaps; Pixmap *unoptimized_pixmaps; struct Gt_Viewer *next; int can_animate; int animating; int unoptimizing; int scheduled; struct Gt_Viewer *anim_next; struct timeval timer; int anim_loop; } Gt_Viewer; const char *program_name = "gifview"; static char *cur_display_name = 0; static Display *cur_display = 0; static char *cur_geometry_spec = 0; static Cursor cur_arrow_cursor = 0; static Cursor cur_wait_cursor = 0; static const char *cur_resource_name; static Window cur_use_window = None; static const char *cur_background_color = "black"; static Gt_Viewer *viewers; static Gt_Viewer *animations; static int animating = 0; static int unoptimizing = 0; static int install_colormap = 0; static int interactive = 1; #define DISPLAY_OPT 300 #define UNOPTIMIZE_OPT 301 #define VERSION_OPT 302 #define ANIMATE_OPT 303 #define GEOMETRY_OPT 304 #define NAME_OPT 305 #define HELP_OPT 306 #define WINDOW_OPT 307 #define INSTALL_COLORMAP_OPT 308 #define INTERACTIVE_OPT 309 #define BACKGROUND_OPT 310 #define WINDOW_TYPE (Clp_MaxDefaultType + 1) Clp_Option options[] = { { "animate", 'a', ANIMATE_OPT, 0, Clp_Negate }, { "background", 'b', BACKGROUND_OPT, Clp_ArgString, 0 }, { "bg", 0, BACKGROUND_OPT, Clp_ArgString, 0 }, { "display", 'd', DISPLAY_OPT, Clp_ArgStringNotOption, 0 }, { "geometry", 'g', GEOMETRY_OPT, Clp_ArgString, 0 }, { "install-colormap", 'i', INSTALL_COLORMAP_OPT, 0, Clp_Negate }, { "interactive", 'e', INTERACTIVE_OPT, 0, Clp_Negate }, { "help", 0, HELP_OPT, 0, 0 }, { "name", 0, NAME_OPT, Clp_ArgString, 0 }, { "unoptimize", 'U', UNOPTIMIZE_OPT, 0, Clp_Negate }, { "version", 0, VERSION_OPT, 0, 0 }, { "window", 'w', WINDOW_OPT, WINDOW_TYPE, 0 }, }; /***** * Diagnostics **/ void fatal_error(char *message, ...) { va_list val; va_start(val, message); fprintf(stderr, "%s: ", program_name); vfprintf(stderr, message, val); fputc('\n', stderr); exit(1); } void error(char *message, ...) { va_list val; va_start(val, message); fprintf(stderr, "%s: ", program_name); vfprintf(stderr, message, val); fputc('\n', stderr); } void warning(char *message, ...) { va_list val; va_start(val, message); fprintf(stderr, "%s: warning: ", program_name); vfprintf(stderr, message, val); fputc('\n', stderr); } void short_usage(void) { fprintf(stderr, "Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\ Try `%s --help' for more information.\n", program_name, program_name); } void usage(void) { printf("\ `Gifview' is a lightweight GIF viewer for X. It can display animated GIFs as\n\ slideshows, one frame at a time, or as animations.\n\ \n\ Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\ \n\ Options are:\n\ -a, --animate Animate multiframe GIFs.\n\ -U, --unoptimize Unoptimize displayed GIFs.\n\ -d, --display DISPLAY Set display to DISPLAY.\n\ --name NAME Set application resource name to NAME.\n\ -g, --geometry GEOMETRY Set window geometry.\n\ -w, --window WINDOW Show GIF in existing WINDOW.\n\ -i, --install-colormap Use a private colormap.\n\ --bg, --background COLOR Use COLOR for transparent pixels.\n\ +e, --no-interactive Ignore buttons and keystrokes.\n\ --help Print this message and exit.\n\ --version Print version number and exit.\n\ \n\ Frame selections: #num, #num1-num2, #num1-, #name\n\ \n\ Keystrokes:\n\ [Space] Go to next frame. [B] Go to previous frame.\n\ [R]/[<] Go to first frame. [>] Go to last frame.\n\ [ESC] Stop animation. [S]/[A] Toggle animation.\n\ [U] Toggle unoptimization. [Backspace]/[W] Delete window.\n\ [Q] Quit.\n\ \n\ Left mouse button goes to next frame, right mouse button deletes window.\n\ \n\ Report bugs to .\n", program_name); } /***** * Window creation **/ #if defined(__cplusplus) || defined(c_plusplus) #define VISUAL_CLASS c_class #else #define VISUAL_CLASS class #endif static void choose_visual(Gt_Viewer *viewer) { Display *display = viewer->display; int screen_number = viewer->screen_number; VisualID default_visualid = DefaultVisual(display, screen_number)->visualid; XVisualInfo visi_template; int nv, i; XVisualInfo *v, *best_v = 0; Gt_Viewer *trav; /* Look for an existing Gt_Viewer with the same display and screen number */ if (!install_colormap) for (trav = viewers; trav; trav = trav->next) if (trav != viewer && trav->display == display && trav->screen_number == screen_number) { viewer->visual = trav->visual; viewer->depth = trav->depth; viewer->colormap = trav->colormap; viewer->gfx = trav->gfx; viewer->gfx->refcount++; return; } /* Find the default visual's XVisualInfo & put it in best_v */ visi_template.screen = screen_number; v = XGetVisualInfo(display, VisualScreenMask, &visi_template, &nv); for (i = 0; i < nv && !best_v; i++) if (v[i].visualid == default_visualid) best_v = &v[i]; if (!best_v) { /* This should never happen. If we can't find the default visual's XVisualInfo, we just use the default visual */ viewer->visual = DefaultVisual(display, screen_number); viewer->depth = DefaultDepth(display, screen_number); viewer->colormap = DefaultColormap(display, screen_number); } else { /* Which visual to choose? This isn't exactly a simple decision, since we want to avoid colormap flashing while choosing a nice visual. So here's the algorithm: Prefer the default visual, or take a TrueColor visual with strictly greater depth. */ for (i = 0; i < nv; i++) if (v[i].depth > best_v->depth && v[i].VISUAL_CLASS == TrueColor) best_v = &v[i]; viewer->visual = best_v->visual; viewer->depth = best_v->depth; if (best_v->visualid != default_visualid || (best_v->VISUAL_CLASS == PseudoColor && install_colormap)) viewer->colormap = XCreateColormap(display, RootWindow(display, screen_number), viewer->visual, AllocNone); else viewer->colormap = DefaultColormap(display, screen_number); } viewer->gfx = Gif_NewXContextFromVisual (display, screen_number, viewer->visual, viewer->depth, viewer->colormap); viewer->gfx->refcount++; if (v) XFree(v); } Gt_Viewer * new_viewer(Display *display, Window use_window, Gif_Stream *gfs, char *name) { Gt_Viewer *viewer; int i; /* Make the Gt_Viewer structure */ viewer = Gif_New(Gt_Viewer); viewer->display = display; if (use_window) { XWindowAttributes attr; if (use_window == -1) { /* means use root window */ viewer->screen_number = DefaultScreen(display); use_window = RootWindow(display, viewer->screen_number); } XGetWindowAttributes(display, use_window, &attr); viewer->screen_number = -1; for (i = 0; i < ScreenCount(display); i++) if (ScreenOfDisplay(display, i) == attr.screen) viewer->screen_number = i; assert(viewer->screen_number >= 0); viewer->visual = attr.visual; viewer->depth = attr.depth; viewer->colormap = attr.colormap; viewer->gfx = Gif_NewXContextFromVisual (display, viewer->screen_number, viewer->visual, viewer->depth, viewer->colormap); viewer->gfx->refcount++; /* use root window, if that's what we were given; otherwise, create a child of the window we were given */ viewer->window = (use_window == attr.root ? use_window : None); viewer->parent = use_window; viewer->use_window = (use_window == attr.root ? 1 : 0); viewer->top_level = 0; viewer->resizable = 0; } else { viewer->screen_number = DefaultScreen(display); choose_visual(viewer); viewer->window = None; viewer->parent = RootWindow(display, viewer->screen_number); viewer->use_window = 0; viewer->top_level = 1; viewer->resizable = 1; } /* assign background color */ if (cur_background_color) { XColor color; if (!XParseColor(viewer->display, viewer->colormap, cur_background_color, &color)) { error("invalid background color `%s'", cur_background_color); cur_background_color = 0; } else if (!XAllocColor(viewer->display, viewer->colormap, &color)) warning("can't allocate background color"); else { unsigned long pixel = color.pixel; Gif_XContext *gfx = viewer->gfx; if (pixel != gfx->transparent_pixel && gfx->refcount > 1) { /* copy X context */ viewer->gfx = Gif_NewXContextFromVisual (gfx->display, gfx->screen_number, gfx->visual, gfx->depth, gfx->colormap); viewer->gfx->refcount++; gfx->refcount--; } viewer->gfx->transparent_pixel = pixel; } } if (!cur_arrow_cursor) { cur_arrow_cursor = XCreateFontCursor(display, XC_left_ptr); cur_wait_cursor = XCreateFontCursor(display, XC_watch); } viewer->arrow_cursor = cur_arrow_cursor; viewer->wait_cursor = cur_wait_cursor; viewer->being_deleted = 0; viewer->gfs = gfs; gfs->refcount++; viewer->name = name; viewer->nim = Gif_ImageCount(gfs); viewer->im = Gif_NewArray(Gif_Image *, viewer->nim); for (i = 0; i < viewer->nim; i++) viewer->im[i] = gfs->images[i]; viewer->pixmap = None; viewer->im_pos = -1; viewer->was_unoptimized = 0; viewer->free_pixmaps = 1; viewer->unoptimized_pixmaps = Gif_NewArray(Pixmap, viewer->nim); for (i = 0; i < viewer->nim; i++) viewer->unoptimized_pixmaps[i] = None; viewer->next = viewers; viewers = viewer; viewer->animating = 0; viewer->unoptimizing = unoptimizing; viewer->scheduled = 0; viewer->anim_next = 0; viewer->anim_loop = 0; viewer->timer.tv_sec = viewer->timer.tv_usec = 0; return viewer; } void delete_viewer(Gt_Viewer *viewer) { Gt_Viewer *prev = 0, *trav; int i; if (viewer->pixmap && !viewer->was_unoptimized) XFreePixmap(viewer->display, viewer->pixmap); for (i = 0; i < viewer->nim; i++) if (viewer->unoptimized_pixmaps[i]) XFreePixmap(viewer->display, viewer->unoptimized_pixmaps[i]); for (trav = viewers; trav != viewer; prev = trav, trav = trav->next) ; if (prev) prev->next = viewer->next; else viewers = viewer->next; Gif_DeleteStream(viewer->gfs); Gif_DeleteArray(viewer->unoptimized_pixmaps); Gif_DeleteArray(viewer->im); Gif_DeleteXContext(viewer->gfx); Gif_Delete(viewer); } static Gt_Viewer * next_viewer(Gif_Stream *gfs, char *name) { Gt_Viewer *viewer = new_viewer(cur_display, cur_use_window, gfs, name); if (cur_use_window) cur_use_window = None; return viewer; } static Gt_Viewer * get_input_stream(char *name) { FILE *f; Gif_Stream *gfs = 0; if (name == 0 || strcmp(name, "-") == 0) { f = stdin; name = ""; } else f = fopen(name, "rb"); if (!f) { error("%s: %s", name, strerror(errno)); return 0; } gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, 0); fclose(f); if (!gfs || Gif_ImageCount(gfs) == 0) { error("`%s' doesn't seem to contain a GIF", name); Gif_DeleteStream(gfs); return 0; } if (!cur_display) { cur_display = XOpenDisplay(cur_display_name); if (!cur_display) { error("can't open display"); return 0; } } return next_viewer(gfs, name); } /***** * Schedule stuff **/ void switch_animating(Gt_Viewer *viewer, int animating) { int i; Gif_Stream *gfs = viewer->gfs; if (animating == viewer->animating || !viewer->can_animate) return; for (i = 0; i < gfs->nimages; i++) viewer->im[i] = gfs->images[i]; viewer->animating = animating; if (!animating) viewer->timer.tv_sec = viewer->timer.tv_usec = 0; } void unschedule(Gt_Viewer *viewer) { Gt_Viewer *prev, *trav; if (!viewer->scheduled) return; for (prev = 0, trav = animations; trav; prev = trav, trav = trav->anim_next) if (trav == viewer) break; if (trav) { if (prev) prev->anim_next = viewer->anim_next; else animations = viewer->anim_next; } viewer->scheduled = 0; viewer->timer.tv_sec = viewer->timer.tv_usec = 0; } void schedule_next_frame(Gt_Viewer *viewer) { struct timeval interval; Gt_Viewer *prev, *trav; int delay = viewer->im[viewer->im_pos]->delay; if (viewer->timer.tv_sec == 0 && viewer->timer.tv_usec == 0) xwGETTIME(viewer->timer); interval.tv_sec = delay / 100; interval.tv_usec = (delay % 100) * (MICRO_PER_SEC / 100); xwADDTIME(viewer->timer, viewer->timer, interval); if (viewer->scheduled) unschedule(viewer); prev = 0; for (trav = animations; trav; prev = trav, trav = trav->anim_next) if (xwTIMEGEQ(trav->timer, viewer->timer)) break; if (prev) { viewer->anim_next = trav; prev->anim_next = viewer; } else { viewer->anim_next = animations; animations = viewer; } viewer->scheduled = 1; } /***** * X stuff **/ int parse_geometry(const char *const_g, XSizeHints *sh, int screen_width, int screen_height) { char *g = (char *)const_g; sh->flags = 0; if (isdigit(*g)) { sh->flags |= USSize; sh->width = strtol(g, &g, 10); if (g[0] == 'x' && isdigit(g[1])) sh->height = strtol(g + 1, &g, 10); else goto error; } else if (!*g) goto error; if (*g == '+' || *g == '-') { int x_minus, y_minus; sh->flags |= USPosition | PWinGravity; x_minus = *g == '-'; sh->x = strtol(g + 1, &g, 10); if (x_minus) sh->x = screen_width - sh->x - sh->width; y_minus = *g == '-'; if (*g == '-' || *g == '+') sh->y = strtol(g + 1, &g, 10); else goto error; if (y_minus) sh->y = screen_height - sh->y - sh->height; if (x_minus) sh->win_gravity = y_minus ? SouthEastGravity : NorthEastGravity; else sh->win_gravity = y_minus ? SouthWestGravity : NorthWestGravity; } else if (*g) goto error; return 1; error: warning("bad geometry specification"); sh->flags = 0; return 0; } static Atom wm_delete_window_atom; static Atom wm_protocols_atom; void create_viewer_window(Gt_Viewer *viewer, int w, int h) { Display *display = viewer->display; char *stringlist[2]; XTextProperty window_name, icon_name; XClassHint classh; XSizeHints *sizeh = XAllocSizeHints(); /* sets all fields to 0 */ /* Set the window's geometry */ sizeh->width = w; sizeh->height = h; if (cur_geometry_spec) { int scr_width = DisplayWidth(viewer->display, viewer->screen_number); int scr_height = DisplayHeight(viewer->display, viewer->screen_number); parse_geometry(cur_geometry_spec, sizeh, scr_width, scr_height); } /* Open the display and create the window */ if (!viewer->window) { XSetWindowAttributes x_set_attr; unsigned long x_set_attr_mask; x_set_attr.colormap = viewer->colormap; x_set_attr.backing_store = NotUseful; x_set_attr.save_under = False; x_set_attr.border_pixel = 0; x_set_attr.background_pixel = 0; x_set_attr_mask = CWColormap | CWBorderPixel | CWBackPixel | CWBackingStore | CWSaveUnder; viewer->window = XCreateWindow (display, viewer->parent, sizeh->x, sizeh->y, sizeh->width, sizeh->height, 0, viewer->depth, InputOutput, viewer->visual, x_set_attr_mask, &x_set_attr); XDefineCursor(display, viewer->window, viewer->arrow_cursor); } /* If user gave us geometry, don't change the size later */ if (sizeh->flags & USSize) viewer->resizable = 0; viewer->width = w; viewer->height = h; /* Set the window's title and class (for window manager resources) */ if (viewer->top_level) { stringlist[0] = "gifview"; stringlist[1] = 0; XStringListToTextProperty(stringlist, 1, &window_name); XStringListToTextProperty(stringlist, 1, &icon_name); classh.res_name = (char *)cur_resource_name; classh.res_class = "Gifview"; XSetWMProperties(display, viewer->window, &window_name, &icon_name, NULL, 0, sizeh, NULL, &classh); XFree(window_name.value); XFree(icon_name.value); if (!wm_delete_window_atom) { wm_delete_window_atom = XInternAtom(display, "WM_DELETE_WINDOW", False); wm_protocols_atom = XInternAtom(display, "WM_PROTOCOLS", False); } XSetWMProtocols(display, viewer->window, &wm_delete_window_atom, 1); } if (interactive) XSelectInput(display, viewer->window, ButtonPressMask | KeyPressMask | StructureNotifyMask); else XSelectInput(display, viewer->window, StructureNotifyMask); XFree(sizeh); } void pre_delete_viewer(Gt_Viewer *viewer) { if (viewer->being_deleted) return; viewer->being_deleted = 1; if (viewer->scheduled) unschedule(viewer); if (viewer->window && !viewer->use_window) XDestroyWindow(viewer->display, viewer->window); else delete_viewer(viewer); } Gt_Viewer * find_viewer(Display *display, Window window) { Gt_Viewer *v; for (v = viewers; v; v = v->next) if (v->display == display && v->window == window) return v; return 0; } void set_viewer_name(Gt_Viewer *viewer, int slow_number) { Gif_Image *gfi; char *strs[2]; char *identifier; XTextProperty name_prop; int im_pos = (slow_number >= 0 ? slow_number : viewer->im_pos); int len; if (!viewer->top_level || im_pos >= viewer->nim || viewer->being_deleted) return; gfi = viewer->im[im_pos]; len = strlen(viewer->name) + 21; identifier = (slow_number >= 0 ? (char *)0 : gfi->identifier); if (identifier) len += 2 + strlen(identifier); strs[0] = Gif_NewArray(char, len); if (slow_number >= 0) sprintf(strs[0], "gifview: %s [#%d]", viewer->name, im_pos); else if (viewer->nim == 1 && identifier) sprintf(strs[0], "gifview: %s #%s", viewer->name, identifier); else if (viewer->animating || viewer->nim == 1) sprintf(strs[0], "gifview: %s", viewer->name); else if (!identifier) sprintf(strs[0], "gifview: %s #%d", viewer->name, im_pos); else sprintf(strs[0], "gifview: %s #%d #%s", viewer->name, im_pos, identifier); strs[1] = 0; XStringListToTextProperty(strs, 1, &name_prop); XSetWMName(viewer->display, viewer->window, &name_prop); XSetWMIconName(viewer->display, viewer->window, &name_prop); XFree(name_prop.value); Gif_DeleteArray(strs[0]); } static Pixmap unoptimized_frame(Gt_Viewer *viewer, int frame, int slow) { /* create a new unoptimized frame; maybe kill some of the old ones */ if (!viewer->unoptimized_pixmaps[frame]) { Gif_Stream *gfs = viewer->gfs; Pixmap last = None, last_last = None; if (frame >= 2 && gfs->images[frame-1]->disposal == GIF_DISPOSAL_PREVIOUS) last_last = unoptimized_frame(viewer, frame - 2, slow); if (frame >= 1) last = unoptimized_frame(viewer, frame - 1, slow); viewer->unoptimized_pixmaps[frame] = Gif_XNextImage(viewer->gfx, last_last, last, gfs, frame); if (viewer->free_pixmaps && frame > 1 && frame % SAVE_FRAMES == 1) { int kill = frame - 1 - SAVE_FRAMES; assert(kill >= 0); for (; kill < frame - 1; kill++) if (viewer->unoptimized_pixmaps[kill] && kill % 50 != 0) { XFreePixmap(viewer->display, viewer->unoptimized_pixmaps[kill]); viewer->unoptimized_pixmaps[kill] = None; } } if (slow) { set_viewer_name(viewer, frame); XFlush(viewer->display); } } return viewer->unoptimized_pixmaps[frame]; } static void kill_unoptimized_frames(Gt_Viewer *viewer, int new_frame) { int nimages = viewer->nim; int kill = nimages; if (!viewer->free_pixmaps) return; if (new_frame == 0) kill = 3; else if (new_frame < viewer->im_pos) kill = new_frame + 1; for (; kill < nimages; kill++) if (viewer->unoptimized_pixmaps[kill] && kill % 50 != 0) { XFreePixmap(viewer->display, viewer->unoptimized_pixmaps[kill]); viewer->unoptimized_pixmaps[kill] = None; } } void view_frame(Gt_Viewer *viewer, int frame) { Display *display = viewer->display; Window window = viewer->window; Pixmap old_pixmap = viewer->pixmap; int need_set_name = 0; if (viewer->being_deleted) return; if (frame < 0) frame = 0; if (frame > viewer->nim - 1 && viewer->animating) { int loopcount = viewer->gfs->loopcount; if (loopcount == 0 || loopcount > viewer->anim_loop) { viewer->anim_loop++; frame = 0; } else { switch_animating(viewer, 0); need_set_name = 1; } } if (frame > viewer->nim - 1) frame = viewer->nim - 1; if (frame != viewer->im_pos) { Gif_Image *gfi = viewer->im[frame]; int width, height, changed_cursor = 0; /* Change cursor if we need to wait. */ if ((viewer->animating || viewer->unoptimizing) && !viewer->unoptimized_pixmaps[frame]) { if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) { changed_cursor = 1; XDefineCursor(display, window, viewer->wait_cursor); XFlush(display); } } /* 5/26/98 Do some noodling around to try and use memory most effectively. If animating, keep the uncompressed frame; otherwise, throw it away. */ if (viewer->animating || viewer->unoptimizing) viewer->pixmap = unoptimized_frame(viewer, frame, changed_cursor); else viewer->pixmap = Gif_XImage(viewer->gfx, viewer->gfs, gfi); /* put the image on the window */ if (viewer->animating || viewer->unoptimizing) width = viewer->gfs->screen_width, height = viewer->gfs->screen_height; else width = Gif_ImageWidth(gfi), height = Gif_ImageHeight(gfi); if (!window) { create_viewer_window(viewer, width, height); window = viewer->window; } XSetWindowBackgroundPixmap(display, window, viewer->pixmap); if (old_pixmap || viewer->use_window) /* clear if using existing window */ XClearWindow(display, window); /* Only change size after changing pixmap. */ if ((viewer->width != width || viewer->height != height) && viewer->resizable) { XWindowChanges winch; winch.width = viewer->width = width; winch.height = viewer->height = height; XReconfigureWMWindow (display, window, viewer->screen_number, CWWidth | CWHeight, &winch); } /* Restore cursor */ if (changed_cursor) XDefineCursor(display, window, viewer->arrow_cursor); /* Get rid of old pixmaps */ if (viewer->was_unoptimized) kill_unoptimized_frames(viewer, frame); else if (old_pixmap) XFreePixmap(display, old_pixmap); viewer->was_unoptimized = viewer->animating || viewer->unoptimizing; /* Do we need a new name? */ if ((!viewer->animating && Gif_ImageCount(viewer->gfs) > 1) || old_pixmap == None) need_set_name = 1; viewer->im_pos = frame; } if (need_set_name) set_viewer_name(viewer, -1); if (!old_pixmap && !viewer->use_window) /* first image; map the window */ XMapRaised(display, window); else if (viewer->animating) /* only schedule next frame if image is already mapped */ schedule_next_frame(viewer); } /***** * Command line arguments: marking frames, being done with streams **/ int frame_argument(Gt_Viewer *viewer, char *arg) { char *c = arg; int n1 = 0; /* Get a number range (#x, #x-y, #x-, or #-y). First, read x. */ if (isdigit(c[0])) n1 = strtol(arg, &c, 10); /* It really was a number range only if c is now at the end of the argument. */ if (c[0] != 0) { Gif_Image *gfi = Gif_GetNamedImage(viewer->gfs, c); if (!gfi) error("no frame named `%s'", c); else n1 = Gif_ImageNumber(viewer->gfs, gfi); } else { if (n1 < 0 || n1 >= viewer->nim) { error("no frame number %d", n1); n1 = 0; } } return n1; } void input_stream_done(Gt_Viewer *viewer, int first_frame) { viewer->can_animate = Gif_ImageCount(viewer->gfs) > 1; switch_animating(viewer, animating && viewer->can_animate); if (first_frame < 0) first_frame = 0; view_frame(viewer, first_frame); } void key_press(Gt_Viewer *viewer, XKeyEvent *e) { char buf[32]; KeySym key; int nbuf = XLookupString(e, buf, 32, &key, 0); if (nbuf > 1) buf[0] = 0; /* ignore multikey sequences */ if (key == XK_space || key == XK_F || key == XK_f) /* space or F: one frame ahead */ view_frame(viewer, viewer->im_pos + 1); else if (key == XK_B || key == XK_b) /* B: one frame back */ view_frame(viewer, viewer->im_pos - 1); else if (key == XK_W || key == XK_w || key == XK_BackSpace) /* backspace: delete viewer */ pre_delete_viewer(viewer); else if (key == XK_Q || key == XK_q) /* Q: quit applicaton */ exit(0); else if (key == XK_S || key == XK_s || key == XK_a || key == XK_A) { /* S or A: toggle animation */ switch_animating(viewer, !viewer->animating); if (viewer->animating) { int pos = viewer->im_pos; if (viewer->im_pos >= viewer->nim - 1) { pos = 0; viewer->anim_loop = 0; } view_frame(viewer, pos); } else unschedule(viewer); set_viewer_name(viewer, -1); } else if (key == XK_U || key == XK_u) { /* U: toggle unoptimizing */ int pos = viewer->im_pos; viewer->unoptimizing = !viewer->unoptimizing; if (!viewer->animating) { viewer->im_pos = -1; view_frame(viewer, pos); set_viewer_name(viewer, -1); } } else if (key == XK_R || key == XK_r || (nbuf == 1 && buf[0] == '<')) { /* R or <: reset to first frame */ unschedule(viewer); viewer->anim_loop = 0; view_frame(viewer, 0); } else if (nbuf == 1 && buf[0] == '>') { /* >: reset to last frame */ unschedule(viewer); viewer->anim_loop = 0; view_frame(viewer, viewer->nim - 1); } else if (key == XK_Escape && viewer->animating) { /* Escape: stop animation */ switch_animating(viewer, 0); unschedule(viewer); set_viewer_name(viewer, -1); } else if (key == XK_Z || key == XK_z) { /* Z: trigger resizability */ viewer->resizable = !viewer->resizable; } } void loop(void) { struct timeval now; fd_set xfds; XEvent e; int pending; Gt_Viewer *v; Display *display = viewers->display; int x_socket = ConnectionNumber(display); xwGETTIME(now); FD_ZERO(&xfds); while (viewers) { /* Check for any animations */ /* 13.Feb.2001 - Use the 'pending' counter to avoid a tight loop if all the frames in an animation have delay 0. Reported by Franc,ois Petitjean. */ pending = 0; while (animations && xwTIMEGEQ(now, animations->timer) && pending < 25) { v = animations; animations = v->anim_next; v->scheduled = 0; view_frame(v, v->im_pos + 1); xwGETTIME(now); pending++; } pending = XPending(display); if (!pending) { /* select() until event arrives */ struct timeval timeout, *timeout_ptr; int retval; if (animations) { xwSUBTIME(timeout, animations->timer, now); timeout_ptr = &timeout; } else timeout_ptr = 0; FD_SET(x_socket, &xfds); retval = select(x_socket + 1, &xfds, 0, 0, timeout_ptr); pending = (retval <= 0 ? 0 : FD_ISSET(x_socket, &xfds)); } if (pending) while (XPending(display)) { XNextEvent(display, &e); v = find_viewer(e.xany.display, e.xany.window); if (v) { if (interactive) { if (e.type == ButtonPress && e.xbutton.button == 1) /* Left mouse button: go to next frame */ view_frame(v, v->im_pos + 1); else if (e.type == ButtonPress && e.xbutton.button == 3) /* Right mouse button: delete window */ pre_delete_viewer(v); else if (e.type == KeyPress) /* Key press: call function */ key_press(v, &e.xkey); } if (e.type == ClientMessage && e.xclient.message_type == wm_protocols_atom && (Atom)(e.xclient.data.l[0]) == wm_delete_window_atom) /* WM_DELETE_WINDOW message: delete window */ pre_delete_viewer(v); else if (e.type == MapNotify && v->animating && v->scheduled == 0) /* Window was just mapped; now, start animating it */ schedule_next_frame(v); else if (e.type == DestroyNotify) /* Once the window has been destroyed, delete related state */ delete_viewer(v); } } xwGETTIME(now); } } int main(int argc, char **argv) { Gt_Viewer *viewer = 0; int viewer_given = 0; int any_errors = 0; int first_frame = -1; Clp_Parser *clp = Clp_NewParser(argc, argv, sizeof(options) / sizeof(options[0]), options); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); Clp_AddStringListType (clp, WINDOW_TYPE, Clp_AllowNumbers, "root", -1, 0); program_name = cur_resource_name = Clp_ProgramName(clp); xwGETTIMEOFDAY(&genesis_time); while (1) { int opt = Clp_Next(clp); switch (opt) { case DISPLAY_OPT: if (cur_display) fatal_error("`--display' must come before all other options"); cur_display_name = clp->arg; cur_display = 0; cur_arrow_cursor = cur_wait_cursor = None; break; case GEOMETRY_OPT: cur_geometry_spec = clp->arg; break; case NAME_OPT: cur_resource_name = clp->arg; break; case UNOPTIMIZE_OPT: unoptimizing = clp->negated ? 0 : 1; break; case BACKGROUND_OPT: cur_background_color = clp->arg; break; case ANIMATE_OPT: animating = clp->negated ? 0 : 1; break; case INSTALL_COLORMAP_OPT: install_colormap = clp->negated ? 0 : 1; break; case WINDOW_OPT: cur_use_window = clp->val.u; break; case INTERACTIVE_OPT: interactive = clp->negated ? 0 : 1; break; case VERSION_OPT: printf("gifview (LCDF Gifsicle) %s\n", VERSION); printf("Copyright (C) 1997-2001 Eddie Kohler\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty, not even for merchantability or fitness for a\n\ particular purpose.\n"); exit(0); break; case HELP_OPT: usage(); exit(0); break; case Clp_NotOption: if (clp->arg[0] == '#') { if (!viewer_given) { viewer = get_input_stream(0); viewer_given = 1; } if (viewer && first_frame >= 0) { /* copy viewer if 2 frame specs given */ input_stream_done(viewer, first_frame); viewer = next_viewer(viewer->gfs, viewer->name); } if (viewer) first_frame = frame_argument(viewer, clp->arg + 1); } else { if (viewer) input_stream_done(viewer, first_frame); first_frame = -1; viewer = get_input_stream(clp->arg); viewer_given = 1; } break; case Clp_Done: goto done; case Clp_BadOption: any_errors = 1; break; default: break; } } done: if (!viewer_given) { if (any_errors) { short_usage(); exit(1); } viewer = get_input_stream(0); } if (viewer) input_stream_done(viewer, first_frame); if (viewers) loop(); #ifdef DMALLOC dmalloc_report(); #endif return 0; }