/* gifview.c - gifview's main loop. Copyright (C) 1997-2017 Eddie Kohler, ekohler@gmail.com This file is part of gifview, in the gifsicle package. Gifview is free software. It is distributed under the GNU Public License, version 2; 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 #ifdef X_DISPLAY_MISSING #error "You can't compile gifview without X." #endif #include #include #include #include #include #include #include #if HAVE_SYS_SELECT_H # include #endif #include #include #include #include #include #include #include #if HAVE_UNISTD_H # include #endif #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN extern #endif /***** * 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 **/ static unsigned pixel_memory_limit_kb = 40000; static unsigned pixel_memory_kb; 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; const char *name; const char *title; Gif_Image **im; int nim; Pixmap pixmap; int im_pos; int was_unoptimized; Gif_XFrame *unoptimized_frames; int n_unoptimized_frames; struct Gt_Viewer *next; int can_animate; int animating; int unoptimizing; int scheduled; int preparing; struct Gt_Viewer *anim_next; struct timeval timer; int anim_loop; } Gt_Viewer; const char *program_name = "gifview"; static Clp_Parser* clp; static const char *cur_display_name = 0; static Display *cur_display = 0; static const char *cur_geometry_spec = 0; static Cursor cur_arrow_cursor = 0; static Cursor cur_wait_cursor = 0; static const char *cur_resource_name; static const char *cur_window_title = 0; static Window cur_use_window = None; static int cur_use_window_new = 0; 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; static int min_delay = 0; static int fallback_delay = 0; static struct timeval preparation_time; #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 NEW_WINDOW_OPT 311 #define TITLE_OPT 312 #define MIN_DELAY_OPT 313 #define FALLBACK_DELAY_OPT 314 #define MEMORY_LIMIT_OPT 315 #define WINDOW_TYPE (Clp_ValFirstUser) const Clp_Option options[] = { { "animate", 'a', ANIMATE_OPT, 0, Clp_Negate }, { "background", 'b', BACKGROUND_OPT, Clp_ValString, 0 }, { "bg", 't', BACKGROUND_OPT, Clp_ValString, 0 }, { "display", 'd', DISPLAY_OPT, Clp_ValStringNotOption, 0 }, { "geometry", 'g', GEOMETRY_OPT, Clp_ValString, 0 }, { "install-colormap", 'i', INSTALL_COLORMAP_OPT, 0, Clp_Negate }, { "interactive", 'e', INTERACTIVE_OPT, 0, Clp_Negate }, { "help", 0, HELP_OPT, 0, 0 }, { "memory-limit", 0, MEMORY_LIMIT_OPT, Clp_ValUnsigned, Clp_Negate }, { "min-delay", 0, MIN_DELAY_OPT, Clp_ValInt, Clp_Negate }, { "fallback-delay", 0, FALLBACK_DELAY_OPT, Clp_ValInt, Clp_Negate }, { "name", 0, NAME_OPT, Clp_ValString, 0 }, { "title", 'T', TITLE_OPT, Clp_ValString, 0 }, { "unoptimize", 'U', UNOPTIMIZE_OPT, 0, Clp_Negate }, { "version", 0, VERSION_OPT, 0, 0 }, { "window", 'w', WINDOW_OPT, WINDOW_TYPE, 0 }, { "new-window", 0, NEW_WINDOW_OPT, WINDOW_TYPE, 0 } }; /***** * Diagnostics **/ void fatal_error(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); exit(1); } void error(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); } void warning(const char* format, ...) { char buf[BUFSIZ]; int n = snprintf(buf, BUFSIZ, "%s: warning: ", program_name); va_list val; va_start(val, format); Clp_vsnprintf(clp, buf + n, BUFSIZ - n, format, val); va_end(val); fputs(buf, stderr); } void short_usage(void) { Clp_fprintf(clp, stderr, "\ Usage: %s [--display DISPLAY] [OPTION]... [FILE | FRAME]...\n\ Try %<%s --help%> for more information.\n", program_name, program_name); } void usage(void) { Clp_fprintf(clp, stdout, "\ % 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", program_name); Clp_fprintf(clp, stdout, "\ 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\ -T, --title TITLE Set window title.\n"); Clp_fprintf(clp, stdout, "\ -w, --window WINDOW Show GIF in existing WINDOW.\n\ --new-window WINDOW Show GIF in new child of existing WINDOW.\n\ -i, --install-colormap Use a private colormap.\n\ --bg, --background COLOR Use COLOR for transparent pixels.\n\ --min-delay DELAY Set minimum frame delay to DELAY/100 sec.\n\ --fallback-delay DELAY Set fallback frame delay to DELAY/100 sec.\n\ +e, --no-interactive Ignore buttons and keystrokes.\n"); Clp_fprintf(clp, stdout, "\ --memory-limit LIM Cache at most LIM megabytes of animation.\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"); Clp_fprintf(clp, stdout, "\ Keystrokes:\n\ [N]/[Space] Go to next frame. [P]/[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"); } /***** * 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, Gif_Stream *gfs, const char *name) { Gt_Viewer *viewer; int i; /* Make the Gt_Viewer structure */ viewer = Gif_New(Gt_Viewer); viewer->display = display; if (cur_use_window) { XWindowAttributes attr; if (cur_use_window == (Window)(-1)) { /* means use root window */ viewer->screen_number = DefaultScreen(display); cur_use_window = RootWindow(display, viewer->screen_number); } XGetWindowAttributes(display, cur_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++; /* Before -- use root window, if that's what we were given; otherwise, create a child of the window we were given */ /* 13.Nov.2001 - don't make a child of the window we were given! */ if (cur_use_window_new) { viewer->window = None; viewer->use_window = 0; } else { viewer->window = cur_use_window; viewer->use_window = 1; } viewer->parent = cur_use_window; 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%>\n", cur_background_color); cur_background_color = 0; } else if (!XAllocColor(viewer->display, viewer->colormap, &color)) warning("can%,t allocate background color\n"); 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->title = cur_window_title; 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->unoptimized_frames = Gif_NewXFrames(gfs); viewer->n_unoptimized_frames = 0; viewer->next = viewers; viewers = viewer; viewer->animating = 0; viewer->unoptimizing = unoptimizing; viewer->scheduled = 0; viewer->preparing = 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; if (viewer->pixmap && !viewer->was_unoptimized) XFreePixmap(viewer->display, viewer->pixmap); for (trav = viewers; trav != viewer; prev = trav, trav = trav->next) ; if (prev) prev->next = viewer->next; else viewers = viewer->next; Gif_DeleteXFrames(viewer->gfx, viewer->gfs, viewer->unoptimized_frames); Gif_DeleteStream(viewer->gfs); Gif_DeleteArray(viewer->im); Gif_DeleteXContext(viewer->gfx); Gif_Delete(viewer); } static Gt_Viewer * next_viewer(Gif_Stream *gfs, const char *name) { Gt_Viewer *viewer = new_viewer(cur_display, gfs, name); cur_use_window = None; return viewer; } static Gt_Viewer * get_input_stream(const char *name) { FILE *f; Gif_Stream *gfs = 0; if (name == 0 || strcmp(name, "-") == 0) { #ifndef OUTPUT_GIF_TO_TERMINAL if (isatty(fileno(stdin))) { error(": is a terminal\n"); return NULL; } #endif f = stdin; #if defined(_MSDOS) || defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #elif defined(__DJGPP__) setmode(fileno(stdin), O_BINARY); #elif defined(__EMX__) _fsetmode(stdin, "b"); #endif name = ""; } else f = fopen(name, "rb"); if (!f) { error("%s: %s\n", name, strerror(errno)); return 0; } gfs = Gif_FullReadFile(f, GIF_READ_COMPRESSED, 0, 0); fclose(f); if (!gfs || Gif_ImageCount(gfs) == 0) { error("%s: file not in GIF format\n", name); Gif_DeleteStream(gfs); return 0; } if (!cur_display) { cur_display = XOpenDisplay(cur_display_name); if (!cur_display) { error("can%,t open display\n"); 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(Gt_Viewer *viewer) { Gt_Viewer *prev, *trav; 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; } void schedule_next_frame(Gt_Viewer *viewer) { struct timeval interval; int delay = viewer->im[viewer->im_pos]->delay; int next_pos = viewer->im_pos + 1; if (delay < 1) delay = fallback_delay; if (delay < min_delay) delay = min_delay; if (next_pos == viewer->nim) next_pos = 0; 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); if (delay == 0) interval.tv_usec = 2000; xwADDTIME(viewer->timer, viewer->timer, interval); /* 1.Aug.2002 - leave some time to prepare the frame if necessary */ if (viewer->unoptimized_frames[next_pos].pixmap) { xwSUBTIME(viewer->timer, viewer->timer, preparation_time); viewer->preparing = 1; } schedule(viewer); } /***** * 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\n"); 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 ? w : 1; sizeh->height = h ? h : 1; 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->title) + strlen(viewer->name) + 14; identifier = (slow_number >= 0 ? (char *)0 : gfi->identifier); if (identifier) len += 2 + strlen(identifier); strs[0] = Gif_NewArray(char, len); if (strcmp(viewer->title, "gifview") != 0) strcpy(strs[0], viewer->title); else 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 unsigned screen_memory_kb(const Gt_Viewer* viewer) { return 1 + ((unsigned) (viewer->gfs->screen_width * viewer->gfs->screen_height) / 334); } static Pixmap unoptimized_frame(Gt_Viewer *viewer, int frame, int slow) { /* create a new unoptimized frame if necessary */ if (!viewer->unoptimized_frames[frame].pixmap) { (void) Gif_XNextImage(viewer->gfx, viewer->gfs, frame, viewer->unoptimized_frames); pixel_memory_kb += screen_memory_kb(viewer); viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data = frame; ++viewer->n_unoptimized_frames; if (slow) { set_viewer_name(viewer, frame); XFlush(viewer->display); } } /* kill some old frames if over the memory limit */ while (pixel_memory_limit_kb != (unsigned) -1 && pixel_memory_limit_kb < pixel_memory_kb && viewer->n_unoptimized_frames > 1) { int killidx, killframe, i = 0; do { killidx = random() % viewer->n_unoptimized_frames; killframe = viewer->unoptimized_frames[killidx].user_data; ++i; } while (killframe == frame || (i < 10 && killframe > frame && killframe < frame + 5) || (i < 10 && (killframe % 50) == 0)); XFreePixmap(viewer->display, viewer->unoptimized_frames[killframe].pixmap); viewer->unoptimized_frames[killframe].pixmap = None; --viewer->n_unoptimized_frames; viewer->unoptimized_frames[killidx].user_data = viewer->unoptimized_frames[viewer->n_unoptimized_frames].user_data; pixel_memory_kb -= screen_memory_kb(viewer); } return viewer->unoptimized_frames[frame].pixmap; } void prepare_frame(Gt_Viewer *viewer, int frame) { Display *display = viewer->display; Window window = viewer->window; int changed_cursor = 0; if (viewer->being_deleted || !viewer->animating) return; if (frame < 0 || frame > viewer->nim - 1) frame = 0; /* Change cursor if we need to wait. */ if ((viewer->animating || viewer->unoptimizing) && !viewer->unoptimized_frames[frame].pixmap) { if (frame > viewer->im_pos + 10 || frame < viewer->im_pos) { changed_cursor = 1; XDefineCursor(display, window, viewer->wait_cursor); XFlush(display); } } /* Prepare the frame */ (void) unoptimized_frame(viewer, frame, changed_cursor && !viewer->animating); /* Restore cursor */ if (changed_cursor) XDefineCursor(display, window, viewer->arrow_cursor); /* schedule actual view of window */ xwADDTIME(viewer->timer, viewer->timer, preparation_time); viewer->preparing = 0; schedule(viewer); } 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_frames[frame].pixmap) { 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 && !viewer->animating); 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 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); } /* Get rid of old pixmaps */ if (!viewer->was_unoptimized && old_pixmap) XFreePixmap(display, old_pixmap); viewer->was_unoptimized = viewer->animating || viewer->unoptimizing; /* Restore cursor */ if (changed_cursor) XDefineCursor(display, window, viewer->arrow_cursor); /* 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; viewer->preparing = 0; 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, const char *arg) { const 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, (char **)&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%>\n", c); else n1 = Gif_ImageNumber(viewer->gfs, gfi); } else { if (n1 < 0 || n1 >= viewer->nim) { error("no frame number %d\n", 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 || key == XK_N || key == XK_n) /* space, N or F: one frame ahead */ view_frame(viewer, viewer->im_pos + 1); else if (key == XK_B || key == XK_b || key == XK_P || key == XK_p) /* B or P: 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, stop_loop, stop_delta; fd_set xfds; XEvent e; int pending; Gt_Viewer *v; Display *display = viewers->display; int x_socket = ConnectionNumber(display); stop_delta.tv_sec = 0; stop_delta.tv_usec = 200000; 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. */ /* 1.Aug.2002 - Switch to running the loop for max 0.2s. */ xwADDTIME(stop_loop, now, stop_delta); while (animations && xwTIMEGEQ(now, animations->timer) && xwTIMEGEQ(stop_loop, now)) { v = animations; animations = v->anim_next; v->scheduled = 0; if (v->preparing) prepare_frame(v, v->im_pos + 1); else { if (xwTIMEGEQ(now, v->timer)) v->timer = now; view_frame(v, v->im_pos + 1); } xwGETTIME(now); } 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 == 4) /* mousewheel forward */ view_frame(v, v->im_pos + 1); else if (e.type == ButtonPress && e.xbutton.button == 5) /* mousewheel backward */ 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 = Clp_NewParser(argc, (const char * const *)argv, sizeof(options) / sizeof(options[0]), options); Clp_SetOptionChar(clp, '+', Clp_ShortNegated); Clp_AddStringListType (clp, WINDOW_TYPE, Clp_AllowNumbers | Clp_StringListLong, "root", (long) -1, (const char*) 0); program_name = cur_resource_name = cur_window_title = Clp_ProgramName(clp); xwGETTIMEOFDAY(&genesis_time); preparation_time.tv_sec = 0; preparation_time.tv_usec = 200000; while (1) { int opt = Clp_Next(clp); switch (opt) { case DISPLAY_OPT: if (cur_display) fatal_error("%<--display%> must come before all other options\n"); cur_display_name = clp->vstr; cur_display = 0; cur_arrow_cursor = cur_wait_cursor = None; break; case TITLE_OPT: cur_window_title = clp->vstr; break; case GEOMETRY_OPT: cur_geometry_spec = clp->vstr; break; case NAME_OPT: cur_resource_name = clp->vstr; break; case UNOPTIMIZE_OPT: unoptimizing = clp->negated ? 0 : 1; break; case BACKGROUND_OPT: cur_background_color = clp->vstr; 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.ul; cur_use_window_new = 0; break; case NEW_WINDOW_OPT: cur_use_window = clp->val.ul; cur_use_window_new = 1; break; case INTERACTIVE_OPT: interactive = clp->negated ? 0 : 1; break; case MIN_DELAY_OPT: min_delay = clp->negated ? 0 : clp->val.i; break; case FALLBACK_DELAY_OPT: fallback_delay = clp->negated ? 0 : clp->val.i; break; case MEMORY_LIMIT_OPT: if (clp->negated || clp->val.u >= ((unsigned) -1 / 1000)) pixel_memory_limit_kb = (unsigned) -1; else pixel_memory_limit_kb = clp->val.u * 1000; break; case VERSION_OPT: printf("gifview (LCDF Gifsicle) %s\n", VERSION); printf("Copyright (C) 1997-2017 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->vstr[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->vstr + 1); } else { if (viewer) input_stream_done(viewer, first_frame); first_frame = -1; viewer = get_input_stream(clp->vstr); 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(); return 0; }