/* * image viewer, for framebuffer devices * * (c) 1998-2002 Gerd Knorr * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBLIRC # include "lirc/lirc_client.h" # include "lirc.h" #endif #include "readers.h" #include "dither.h" #include "fbtools.h" #include "fb-gui.h" #include "filter.h" #include "desktop.h" #include "list.h" #include "parseconfig.h" #include "fbiconfig.h" #include "jpeg/transupp.h" /* Support routines for jpegtran */ #include "jpegtools.h" #define TRUE 1 #define FALSE 0 #define MAX(x,y) ((x)>(y)?(x):(y)) #define MIN(x,y) ((x)<(y)?(x):(y)) #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define KEY_EOF -1 /* ^D */ #define KEY_ESC -2 #define KEY_SPACE -3 #define KEY_Q -4 #define KEY_PGUP -5 #define KEY_PGDN -6 #define KEY_TIMEOUT -7 #define KEY_TAGFILE -8 #define KEY_PLUS -9 #define KEY_MINUS -10 #define KEY_VERBOSE -11 #define KEY_ASCALE -12 #define KEY_DESC -13 /* with arg */ #define KEY_GOTO -100 #define KEY_SCALE -101 /* edit */ #define KEY_DELETE -200 #define KEY_ROT_CW -201 #define KEY_ROT_CCW -202 #define DEFAULT_DEVICE "/dev/fb0" /* ---------------------------------------------------------------------- */ /* lirc fd */ int lirc = -1; /* variables for read_image */ int32_t lut_red[256], lut_green[256], lut_blue[256]; int dither = FALSE, pcd_res = 3, steps = 50; int textreading = 0, redraw = 0, statusline = 1; int new_image; int left, top; /* file list */ struct flist { int nr; int tag; char *name; struct list_head list; }; static LIST_HEAD(flist); static int fcount; static struct flist *fcurrent; /* framebuffer */ char *fbdev = NULL; char *fbmode = NULL; int fd, switch_last, debug; unsigned short red[256], green[256], blue[256]; struct fb_cmap cmap = { 0, 256, red, green, blue }; static float fbgamma = 1; /* Command line options. */ int autodown = 0; int autoup = 0; int comments = 0; struct option fbi_options[] = { {"version", no_argument, NULL, 'V'}, /* version */ {"help", no_argument, NULL, 'h'}, /* help */ {"device", required_argument, NULL, 'd'}, /* device */ {"mode", required_argument, NULL, 'm'}, /* video mode */ {"gamma", required_argument, NULL, 'g'}, /* set gamma */ {"quiet", no_argument, NULL, 'q'}, /* quiet */ {"verbose", no_argument, NULL, 'v'}, /* verbose */ {"scroll", required_argument, NULL, 's'}, /* set scrool */ {"timeout", required_argument, NULL, 't'}, /* timeout value */ {"once", no_argument, NULL, '1'}, /* loop only once */ {"resolution", required_argument, NULL, 'r'}, /* select resolution */ {"random", no_argument, NULL, 'u'}, /* randomize images */ {"font", required_argument, NULL, 'f'}, /* font */ {"autozoom", no_argument, NULL, 'a'}, {"edit", no_argument, NULL, 'e'}, /* enable editing */ {"list", required_argument, NULL, 'l'}, {"vt", required_argument, NULL, 'T'}, {"backup", no_argument, NULL, 'b'}, {"preserve", no_argument, NULL, 'p'}, /* long-only options */ {"autoup", no_argument, &autoup, 1 }, {"autodown", no_argument, &autodown, 1 }, {"comments", no_argument, &comments, 1 }, {0,0,0,0} }; /* font handling */ static char *fontname = NULL; /* ---------------------------------------------------------------------- */ static void version(void) { fprintf(stderr, "fbi version " VERSION " (c) 1999-2003 Gerd Knorr; compiled on %s.\n", __DATE__ ); } static void usage(char *name) { char *h; if (NULL != (h = strrchr(name, '/'))) name = h+1; fprintf(stderr, "\n" "This program displays images using the Linux framebuffer device.\n" "Supported formats: PhotoCD, jpeg, ppm, gif, tiff, xwd, bmp, png.\n" "It tries to use ImageMagick's convert for unknown file formats.\n" "\n" " Usage: %s [ options ] file1 file2 ... fileN\n" "\n" " --help [-h] Print this text\n" " --version [-V] Show the fbi version number\n" " --device [-d] dev Framebuffer device [%s]\n" " --mode [-m] mode Video mode (must be listed in /etc/fb.modes)\n" " - Default is current mode.\n" " --gamma [-g] f Set gamma\n" " --scroll [-s] n Set scroll steps in pixels (default: 50)\n" " --quiet [-q] don't print anything at all\n" " --verbose [-v] show print filenames all the time\n" " --timeout [-t] n Load next image after N sec without any keypress\n" " --once [-1] Don't loop (for use with -t).\n" " --resolution [-r] n Select resolution [1..5] (PhotoCD)\n" " --random [-u] Show file1 .. fileN in a random order\n" " --font [-f] fn Use font fn (either console psf file or\n" " X11 font spec if a font server is available\n" " --autozoom [-a] Automagically pick useful zoom factor.\n" " --autoup Like the above, but upscale only.\n" " --autodown Like the above, but downscale only.\n" " --edit [-e] enable editing commands (see man page).\n" " --backup [-b] create backup files when editing.\n" " --preserve [-p] preserve timestamps when editing.\n" " --list [-l] file read list of images from file\n" " --comments display image comments\n" " --vt [-T] vt start on console #vt\n" "\n" "Large images can be scrolled using the cursor keys. Zoom in/out\n" "works with '+' and '-'. Use ESC or 'q' to quit. Space and PgDn\n" "show the next, PgUp shows the previous image. Jumping to a image\n" "works with g. Return acts like Space but additionally\n" "prints the filename of the currently displayed image to stdout.\n" "\n", name, fbdev ? fbdev : "/dev/fb0"); } /* ---------------------------------------------------------------------- */ static int flist_add(char *filename) { struct flist *f; f = malloc(sizeof(*f)); memset(f,0,sizeof(*f)); f->name = strdup(filename); list_add_tail(&f->list,&flist); return 0; } static int flist_add_list(char *listfile) { char filename[256]; FILE *list; list = fopen(listfile,"r"); if (NULL == list) { fprintf(stderr,"open %s: %s\n",listfile,strerror(errno)); return -1; } while (NULL != fgets(filename,sizeof(filename)-1,list)) { size_t off = strcspn(filename,"\r\n"); if (off) filename[off] = 0; flist_add(filename); } fclose(list); return 0; } static int flist_del(struct flist *f) { list_del(&f->list); free(f->name); free(f); return 0; } static void flist_renumber(void) { struct list_head *item; struct flist *f; int i = 0; list_for_each(item,&flist) { f = list_entry(item, struct flist, list); f->nr = ++i; } fcount = i; } static int flist_islast(struct flist *f) { return (&flist == f->list.next) ? 1 : 0; } static int flist_isfirst(struct flist *f) { return (&flist == f->list.prev) ? 1 : 0; } static struct flist* flist_first(void) { return list_entry(flist.next, struct flist, list); } static struct flist* flist_next(struct flist *f, int eof, int loop) { if (flist_islast(f)) { if (eof) return NULL; if (loop) return flist_first(); return f; } return list_entry(f->list.next, struct flist, list); } static struct flist* flist_prev(struct flist *f) { if (flist_isfirst(f)) return f; return list_entry(f->list.prev, struct flist, list); } static struct flist* flist_goto(int dest) { struct list_head *item; struct flist *f; list_for_each(item,&flist) { f = list_entry(item, struct flist, list); if (f->nr == dest) return f; } return NULL; } static void flist_randomize(void) { struct flist *f; int count; srand((unsigned)time(NULL)); flist_renumber(); for (count = fcount; count > 0; count--) { f = flist_goto((rand() % count)+1); list_del(&f->list); list_add_tail(&f->list,&flist); flist_renumber(); } } static void flist_print_tagged(FILE *fp) { struct list_head *item; struct flist *f; list_for_each(item,&flist) { f = list_entry(item, struct flist, list); if (f->tag) fprintf(fp,"%s\n",f->name); } } /* ---------------------------------------------------------------------- */ static void status(unsigned char *desc, char *info) { int chars, ilen; char *str; if (!statusline) return; chars = fb_var.xres / fb_font_width(); str = malloc(chars+1); if (info) { ilen = strlen(info); sprintf(str, "%-*.*s [ %s ] H - Help", chars-14-ilen, chars-14-ilen, desc, info); } else { sprintf(str, "%-*.*s | H - Help", chars-11, chars-11, desc); } fb_status_line(str); free(str); } static void show_error(unsigned char *msg) { fb_status_line(msg); sleep(2); } static void show_exif(struct flist *f) { static unsigned int tags[] = { 0x010f, // Manufacturer 0x0110, // Model 0x0112, // Orientation 0x0132, // Date and Time 0x01e3, // White Point 0x829a, // Exposure Time 0x829d, // FNumber 0x9206, // Subject Distance 0xa40c, // Subject Distance Range 0xa405, // Focal Length In 35mm Film 0x9209, // Flash }; ExifData *ed; ExifEntry *ee; unsigned int tag,l1,l2,len,count,i; const char *title[ARRAY_SIZE(tags)]; char *value[ARRAY_SIZE(tags)]; char *linebuffer[ARRAY_SIZE(tags)]; if (!visible) return; ed = exif_data_new_from_file(f->name); if (NULL == ed) { status("image has no EXIF data", NULL); return; } /* pass one -- get data + calc size */ l1 = 0; l2 = 0; for (tag = 0; tag < ARRAY_SIZE(tags); tag++) { ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tags[tag]); if (NULL == ee) ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tags[tag]); if (NULL == ee) { title[tag] = NULL; value[tag] = NULL; continue; } title[tag] = exif_tag_get_title(tags[tag]); value[tag] = strdup(exif_entry_get_value(ee)); len = strlen(title[tag]); if (l1 < len) l1 = len; len = strlen(value[tag]); if (l2 < len) l2 = len; } /* pass two -- print stuff */ count = 0; for (tag = 0; tag < ARRAY_SIZE(tags); tag++) { if (NULL == title[tag]) continue; linebuffer[count] = malloc(l1+l2+8); sprintf(linebuffer[count],"%-*.*s : %-*.*s", l1, l1, title[tag], l2, l2, value[tag]); count++; } fb_text_box(24,16,linebuffer,count); /* pass three -- free data */ for (tag = 0; tag < ARRAY_SIZE(tags); tag++) if (NULL != value[tag]) free(value[tag]); exif_data_unref (ed); for (i = 0; i < count; i++) free(linebuffer[i]); } static void show_help(void) { static char *help[] = { "keyboard commands", "~~~~~~~~~~~~~~~~~", " ESC, Q - quit", " pgdn, space - next image", " pgup - previous image", " +/- - zoom in/out", " A - autozoom image", " cursor keys - scroll image", "", " H - show this help text", " I - show EXIF info", " P - pause slideshow", " V - toggle statusline", "", "available if started with --edit switch,", "rotation works for jpeg images only:", " shift+D - delete image", " R - rotate clockwise", " L - rotate counter-clockwise", }; fb_text_box(24,16,help,ARRAY_SIZE(help)); } /* ---------------------------------------------------------------------- */ struct termios saved_attributes; int saved_fl; static void tty_raw(void) { struct termios tattr; fcntl(0,F_GETFL,&saved_fl); tcgetattr (0, &saved_attributes); fcntl(0,F_SETFL,O_NONBLOCK); memcpy(&tattr,&saved_attributes,sizeof(struct termios)); tattr.c_lflag &= ~(ICANON|ECHO); tattr.c_cc[VMIN] = 1; tattr.c_cc[VTIME] = 0; tcsetattr (0, TCSAFLUSH, &tattr); } static void tty_restore(void) { fcntl(0,F_SETFL,saved_fl); tcsetattr (0, TCSANOW, &saved_attributes); } /* testing: find key codes */ static void debug_key(char *key) { char linebuffer[128]; int i,len; len = sprintf(linebuffer,"key: "); for (i = 0; key[i] != '\0'; i++) len += sprintf(linebuffer+len, "%s%c", key[i] < 0x20 ? "^" : "", key[i] < 0x20 ? key[i] + 0x40 : key[i]); status(linebuffer, NULL); } static void console_switch(int is_busy) { switch (fb_switch_state) { case FB_REL_REQ: fb_switch_release(); case FB_INACTIVE: visible = 0; break; case FB_ACQ_REQ: fb_switch_acquire(); case FB_ACTIVE: visible = 1; redraw = 1; ioctl(fd,FBIOPAN_DISPLAY,&fb_var); fb_clear_screen(); if (is_busy) status("busy, please wait ...", NULL); break; default: break; } switch_last = fb_switch_state; return; } /* ---------------------------------------------------------------------- */ static void free_image(struct ida_image *img) { if (img) { if (img->data) free(img->data); free(img); } } static struct ida_image* read_image(char *filename) { char command[1024]; struct ida_loader *loader = NULL; struct ida_image *img; struct list_head *item; char blk[512]; FILE *fp; unsigned int y; void *data; new_image = 1; /* open file */ if (NULL == (fp = fopen(filename, "r"))) { fprintf(stderr,"open %s: %s\n",filename,strerror(errno)); return NULL; } memset(blk,0,sizeof(blk)); fread(blk,1,sizeof(blk),fp); rewind(fp); /* pick loader */ list_for_each(item,&loaders) { loader = list_entry(item, struct ida_loader, list); if (NULL == loader->magic) break; if (0 == memcmp(blk+loader->moff,loader->magic,loader->mlen)) break; loader = NULL; } if (NULL == loader) { /* no loader found, try to use ImageMagick's convert */ sprintf(command,"convert -depth 8 \"%s\" ppm:-",filename); if (NULL == (fp = popen(command,"r"))) return NULL; loader = &ppm_loader; } /* load image */ img = malloc(sizeof(*img)); memset(img,0,sizeof(*img)); data = loader->init(fp,filename,0,&img->i,0); if (NULL == data) { fprintf(stderr,"loading %s [%s] FAILED\n",filename,loader->name); free_image(img); return NULL; } img->data = malloc(img->i.width * img->i.height * 3); for (y = 0; y < img->i.height; y++) { if (switch_last != fb_switch_state) console_switch(1); loader->read(img->data + img->i.width * 3 * y, y, data); } loader->done(data); return img; } static struct ida_image* scale_image(struct ida_image *src, float scale) { struct op_resize_parm p; struct ida_rect rect; struct ida_image *dest; void *data; unsigned int y; dest = malloc(sizeof(*dest)); memset(dest,0,sizeof(*dest)); memset(&rect,0,sizeof(rect)); memset(&p,0,sizeof(p)); p.width = src->i.width * scale; p.height = src->i.height * scale; p.dpi = src->i.dpi; if (0 == p.width) p.width = 1; if (0 == p.height) p.height = 1; data = desc_resize.init(src,&rect,&dest->i,&p); dest->data = malloc(dest->i.width * dest->i.height * 3); for (y = 0; y < dest->i.height; y++) { if (switch_last != fb_switch_state) console_switch(1); desc_resize.work(src,&rect, dest->data + 3 * dest->i.width * y, y, data); } desc_resize.done(data); return dest; } static float auto_scale(struct ida_image *img) { float xs,ys,scale; xs = (float)fb_var.xres / img->i.width; ys = (float)fb_var.yres / img->i.height; scale = (xs < ys) ? xs : ys; return scale; } /* ---------------------------------------------------------------------- */ static unsigned char * convert_line(int bpp, int line, int owidth, char unsigned *dest, char unsigned *buffer) { unsigned char *ptr = (void*)dest; unsigned short *ptr2 = (void*)dest; unsigned long *ptr4 = (void*)dest; int x; switch (fb_var.bits_per_pixel) { case 8: dither_line(buffer, ptr, line, owidth); ptr += owidth; return ptr; case 15: case 16: for (x = 0; x < owidth; x++) { ptr2[x] = lut_red[buffer[x*3]] | lut_green[buffer[x*3+1]] | lut_blue[buffer[x*3+2]]; } ptr2 += owidth; return (char*)ptr2; case 24: for (x = 0; x < owidth; x++) { ptr[3*x+2] = buffer[3*x+0]; ptr[3*x+1] = buffer[3*x+1]; ptr[3*x+0] = buffer[3*x+2]; } ptr += owidth * 3; return ptr; case 32: for (x = 0; x < owidth; x++) { ptr4[x] = lut_red[buffer[x*3]] | lut_green[buffer[x*3+1]] | lut_blue[buffer[x*3+2]]; } ptr4 += owidth; return (char*)ptr4; default: /* keep compiler happy */ return NULL; } } /* ---------------------------------------------------------------------- */ static void init_one(int32_t *lut, int bits, int shift) { int i; if (bits > 8) for (i = 0; i < 256; i++) lut[i] = (i << (bits + shift - 8)); else for (i = 0; i < 256; i++) lut[i] = (i >> (8 - bits)) << shift; } static void lut_init(int depth) { if (fb_var.red.length && fb_var.green.length && fb_var.blue.length) { /* fb_var.{red|green|blue} looks sane, use it */ init_one(lut_red, fb_var.red.length, fb_var.red.offset); init_one(lut_green, fb_var.green.length, fb_var.green.offset); init_one(lut_blue, fb_var.blue.length, fb_var.blue.offset); } else { /* fallback */ int i; switch (depth) { case 15: for (i = 0; i < 256; i++) { lut_red[i] = (i & 0xf8) << 7; /* bits -rrrrr-- -------- */ lut_green[i] = (i & 0xf8) << 2; /* bits ------gg ggg----- */ lut_blue[i] = (i & 0xf8) >> 3; /* bits -------- ---bbbbb */ } break; case 16: for (i = 0; i < 256; i++) { lut_red[i] = (i & 0xf8) << 8; /* bits rrrrr--- -------- */ lut_green[i] = (i & 0xfc) << 3; /* bits -----ggg ggg----- */ lut_blue[i] = (i & 0xf8) >> 3; /* bits -------- ---bbbbb */ } break; case 24: for (i = 0; i < 256; i++) { lut_red[i] = i << 16; /* byte -r-- */ lut_green[i] = i << 8; /* byte --g- */ lut_blue[i] = i; /* byte ---b */ } break; } } } static unsigned short calc_gamma(int n, int max) { int ret =65535.0 * pow((float)n/(max), 1 / fbgamma); if (ret > 65535) ret = 65535; if (ret < 0) ret = 0; return ret; } static void linear_palette(int bit) { int i, size = 256 >> (8 - bit); for (i = 0; i < size; i++) red[i] = green[i] = blue[i] = calc_gamma(i,size); } static void svga_dither_palette(int r, int g, int b) { int rs, gs, bs, i; rs = 256 / (r - 1); gs = 256 / (g - 1); bs = 256 / (b - 1); for (i = 0; i < 256; i++) { red[i] = calc_gamma(rs * ((i / (g * b)) % r), 255); green[i] = calc_gamma(gs * ((i / b) % g), 255); blue[i] = calc_gamma(bs * ((i) % b), 255); } } static void svga_display_image(struct ida_image *img, int xoff, int yoff) { unsigned int dwidth = MIN(img->i.width, fb_var.xres); unsigned int dheight = MIN(img->i.height, fb_var.yres); unsigned int data, video, bank, offset, bytes, y; if (!visible) return; bytes = (fb_var.bits_per_pixel+7)/8; /* offset for image data (image > screen, select visible area) */ offset = (yoff * img->i.width + xoff) * 3; /* offset for video memory (image < screen, center image) */ video = 0, bank = 0; if (img->i.width < fb_var.xres) video += bytes * ((fb_var.xres - img->i.width) / 2); if (img->i.height < fb_var.yres) video += fb_fix.line_length * ((fb_var.yres - img->i.height) / 2); /* go ! */ for (data = 0, y = 0; data < img->i.width * img->i.height * 3 && data / img->i.width / 3 < dheight; data += img->i.width * 3, video += fb_fix.line_length) { convert_line(fb_var.bits_per_pixel, y++, dwidth, fb_mem+video, img->data + data + offset); } } static int svga_show(struct ida_image *img, int timeout, char *desc, char *info, int *nr) { static int paused = 0; int exif = 0, help = 0; int rc; char key[11]; fd_set set; struct timeval limit; char linebuffer[80]; int fdmax; *nr = 0; if (NULL == img) return KEY_SPACE; /* skip */ if (new_image) { /* start with centered image, if larger than screen */ if (img->i.width > fb_var.xres) left = (img->i.width - fb_var.xres) / 2; if (img->i.height > fb_var.yres && !textreading) top = (img->i.height - fb_var.yres) / 2; new_image = 0; } redraw = 1; for (;;) { if (redraw) { redraw = 0; if (img->i.height <= fb_var.yres) { top = 0; } else { if (top < 0) top = 0; if (top + fb_var.yres > img->i.height) top = img->i.height - fb_var.yres; } if (img->i.width <= fb_var.xres) { left = 0; } else { if (left < 0) left = 0; if (left + fb_var.xres > img->i.width) left = img->i.width - fb_var.xres; } svga_display_image(img, left, top); status(desc, info); } if (switch_last != fb_switch_state) { console_switch(0); continue; } FD_SET(0, &set); fdmax = 1; #ifdef HAVE_LIBLIRC if (-1 != lirc) { FD_SET(lirc,&set); fdmax = lirc+1; } #endif limit.tv_sec = timeout; limit.tv_usec = 0; rc = select(fdmax, &set, NULL, NULL, (-1 != timeout && !paused) ? &limit : NULL); if (switch_last != fb_switch_state) { console_switch(0); continue; } if (0 == rc) return KEY_TIMEOUT; if (FD_ISSET(0,&set)) { /* stdin, i.e. keyboard */ rc = read(0, key, sizeof(key)-1); if (rc < 1) { /* EOF */ return KEY_EOF; } key[rc] = 0; } #ifdef HAVE_LIBLIRC if (lirc != -1 && FD_ISSET(lirc,&set)) { /* lirc input */ if (-1 == lirc_fbi_havedata(&rc,key)) { fprintf(stderr,"lirc: connection lost\n"); close(lirc); lirc = -1; } key[rc] = 0; } #endif if (rc == 1 && (*key == 'q' || *key == 'Q' || *key == 'e' || *key == 'E' || *key == '\x1b' || *key == '\n')) { if (*key == '\n') return KEY_TAGFILE; if (*key == '\x1b') return KEY_ESC; return KEY_Q; } else if (0 == strcmp(key, " ")) { if (textreading && top < (int)(img->i.height - fb_var.yres)) { redraw = 1; top += (fb_var.yres-100); } else { return KEY_SPACE; } } else if (0 == strcmp(key, "\x1b[A") && img->i.height > fb_var.yres) { redraw = 1; top -= steps; } else if (0 == strcmp(key, "\x1b[B") && img->i.height > fb_var.yres) { redraw = 1; top += steps; } else if (0 == strcmp(key, "\x1b[1~") && img->i.height > fb_var.yres) { redraw = 1; top = 0; } else if (0 == strcmp(key, "\x1b[4~")) { redraw = 1; top = img->i.height - fb_var.yres; } else if (0 == strcmp(key, "\x1b[D") && img->i.width > fb_var.xres) { redraw = 1; left -= steps; } else if (0 == strcmp(key, "\x1b[C") && img->i.width > fb_var.xres) { redraw = 1; left += steps; } else if (0 == strcmp(key, "\x1b[5~")) { return KEY_PGUP; } else if (0 == strcmp(key, "\x1b[6~") || 0 == strcmp(key, "n") || 0 == strcmp(key, "N")) { return KEY_PGDN; } else if (0 == strcmp(key, "+")) { return KEY_PLUS; } else if (0 == strcmp(key, "-")) { return KEY_MINUS; } else if (0 == strcmp(key, "a") || 0 == strcmp(key, "A")) { return KEY_ASCALE; } else if (0 == strcmp(key, "p") || 0 == strcmp(key, "P")) { if (-1 != timeout) { paused = !paused; status(paused ? "pause on " : "pause off", NULL); } } else if (0 == strcmp(key, "D")) { return KEY_DELETE; } else if (0 == strcmp(key, "r") || 0 == strcmp(key, "R")) { return KEY_ROT_CW; } else if (0 == strcmp(key, "l") || 0 == strcmp(key, "L")) { return KEY_ROT_CCW; } else if (0 == strcmp(key, "h") || 0 == strcmp(key, "H")) { if (!help) { show_help(); help = 1; } else { redraw = 1; help = 0; } exif = 0; } else if (0 == strcmp(key, "i") || 0 == strcmp(key, "I")) { if (!exif) { show_exif(fcurrent); exif = 1; } else { redraw = 1; exif = 0; } help = 0; } else if (0 == strcmp(key, "v") || 0 == strcmp(key, "V")) { return KEY_VERBOSE; } else if (0 == strcmp(key, "t") || 0 == strcmp(key, "T")) { return KEY_DESC; } else if (rc == 1 && (*key == 'g' || *key == 'G')) { return KEY_GOTO; } else if (rc == 1 && (*key == 's' || *key == 'S')) { return KEY_SCALE; } else if (rc == 1 && *key >= '0' && *key <= '9') { *nr = *nr * 10 + (*key - '0'); sprintf(linebuffer, "> %d",*nr); status(linebuffer, NULL); } else { *nr = 0; #if 0 debug_key(key); #endif } } } static void scale_fix_top_left(float old, float new, struct ida_image *img) { unsigned int width, height; float cx,cy; cx = (float)(left + fb_var.xres/2) / (img->i.width * old); cy = (float)(top + fb_var.yres/2) / (img->i.height * old); width = img->i.width * new; height = img->i.height * new; left = cx * width - fb_var.xres/2; top = cy * height - fb_var.yres/2; } /* ---------------------------------------------------------------------- */ static char *my_basename(char *filename) { char *h; h = strrchr(filename,'/'); if (h) return h+1; return filename; } static char *file_desktop(char *filename) { static char desc[128]; char *h; strncpy(desc,filename,sizeof(desc)-1); if (NULL != (h = strrchr(filename,'/'))) { snprintf(desc,sizeof(desc),"%.*s/%s", (int)(h - filename), filename, ".directory"); } else { strcpy(desc,".directory"); } return desc; } static char *make_desc(struct ida_image_info *img, char *filename) { static char linebuffer[128]; struct ida_extra *extra; char *desc; int len; memset(linebuffer,0,sizeof(linebuffer)); strncpy(linebuffer,filename,sizeof(linebuffer)-1); if (comments) { extra = load_find_extra(img, EXTRA_COMMENT); if (extra) snprintf(linebuffer,sizeof(linebuffer),"%.*s", extra->size,extra->data); } else { desc = file_desktop(filename); len = desktop_read_entry(desc, "Comment=", linebuffer, sizeof(linebuffer)); if (0 != len) snprintf(linebuffer+len,sizeof(linebuffer)-len, " (%s)", my_basename(filename)); } return linebuffer; } static char *make_info(struct ida_image *img, float scale) { static char linebuffer[128]; snprintf(linebuffer, sizeof(linebuffer), "%s%.0f%% %dx%d %d/%d", fcurrent->tag ? "* " : "", scale*100, img->i.width, img->i.height, fcurrent->nr, fcount); return linebuffer; } static char edit_line(char *line, int max) { int len = strlen(line); int pos = len; int rc; char key[11]; fd_set set; do { fb_edit_line(line,pos); FD_SET(0, &set); rc = select(1, &set, NULL, NULL, NULL); if (switch_last != fb_switch_state) { console_switch(0); continue; } rc = read(0, key, sizeof(key)-1); if (rc < 1) { /* EOF */ return KEY_EOF; } key[rc] = 0; if (0 == strcmp(key,"\x0a")) { /* Enter */ return 0; } else if (0 == strcmp(key,"\x1b")) { /* ESC */ return KEY_ESC; } else if (0 == strcmp(key,"\x1b[C")) { /* cursor right */ if (pos < len) pos++; } else if (0 == strcmp(key,"\x1b[D")) { /* cursor left */ if (pos > 0) pos--; } else if (0 == strcmp(key,"\x1b[1~")) { /* home */ pos = 0; } else if (0 == strcmp(key,"\x1b[4~")) { /* end */ pos = len; } else if (0 == strcmp(key,"\x7f")) { /* backspace */ if (pos > 0) { memmove(line+pos-1,line+pos,len-pos+1); pos--; len--; } } else if (0 == strcmp(key,"\x1b[3~")) { /* delete */ if (pos < len) { memmove(line+pos,line+pos+1,len-pos); len--; } } else if (1 == rc && isprint(key[0]) && len < max) { /* new key */ if (pos < len) memmove(line+pos+1,line+pos,len-pos+1); line[pos] = key[0]; pos++; len++; line[len] = 0; } else if (0 /* debug */) { debug_key(key); sleep(1); } } while (1); } static void edit_desc(char *filename) { static char linebuffer[128]; char *desc; int len, rc; desc = file_desktop(filename); len = desktop_read_entry(desc, "Comment=", linebuffer, sizeof(linebuffer)); if (0 == len) { linebuffer[0] = 0; len = 0; } rc = edit_line(linebuffer, sizeof(linebuffer)-1); if (0 != rc) return; desktop_write_entry(desc, "Directory", "Comment=", linebuffer); } /* ---------------------------------------------------------------------- */ static void cleanup_and_exit(int code) { fb_clear_mem(); tty_restore(); fb_cleanup(); flist_print_tagged(stdout); exit(code); } int main(int argc, char *argv[]) { int timeout = -1; int randomize = -1; int opt_index = 0; int vt = 0; int backup = 0; int preserve = 0; struct ida_image *fimg = NULL; struct ida_image *simg = NULL; struct ida_image *img = NULL; float scale = 1; float newscale = 1; int c, editable = 0, once = 0; int need_read, need_refresh; int i, arg, key; char *line, *info, *desc; char linebuffer[128]; #if 0 /* debug aid */ fprintf(stderr,"pid %d\n",getpid()); sleep(30); #endif if (NULL != (line = getenv("FRAMEBUFFER"))) fbdev = line; if (NULL != (line = getenv("FBGAMMA"))) fbgamma = atof(line); if (NULL != (line = getenv("FBFONT"))) fontname = line; #ifdef HAVE_LIBLIRC lirc = lirc_fbi_init(); #endif fbi_read_config(); setlocale(LC_ALL,""); for (;;) { c = getopt_long(argc, argv, "u1evahPqVbpr:t:m:d:g:s:f:l:T:", fbi_options, &opt_index); if (c == -1) break; switch (c) { case 0: /* long option, nothing to do */ break; case '1': once = 1; break; case 'a': autoup = 1; autodown = 1; break; case 'q': statusline = 0; break; case 'v': statusline = 1; break; case 'P': textreading = 1; break; case 'g': fbgamma = atof(optarg); break; case 'r': pcd_res = atoi(optarg); break; case 's': steps = atoi(optarg); break; case 't': timeout = atoi(optarg); break; case 'u': randomize = 1; break; case 'd': fbdev = optarg; break; case 'm': fbmode = optarg; break; case 'f': fontname = optarg; break; case 'e': editable = 1; break; case 'b': backup = 1; break; case 'p': preserve = 1; break; case 'l': flist_add_list(optarg); break; case 'T': vt = atoi(optarg); break; case 'V': version(); exit(0); break; default: case 'h': usage(argv[0]); exit(1); } } for (i = optind; i < argc; i++) { flist_add(argv[i]); } flist_renumber(); if (0 == fcount) { usage(argv[0]); exit(1); } if (randomize != -1) flist_randomize(); fcurrent = flist_first(); need_read = 1; need_refresh = 1; fb_text_init1(fontname); fd = fb_init(fbdev, fbmode, vt); fb_catch_exit_signals(); fb_switch_init(); signal(SIGTSTP,SIG_IGN); fb_text_init2(); switch (fb_var.bits_per_pixel) { case 8: svga_dither_palette(8, 8, 4); dither = TRUE; init_dither(8, 8, 4, 2); dither_line = dither_line_color; break; case 15: case 16: if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR) linear_palette(5); if (fb_var.green.length == 5) { lut_init(15); } else { lut_init(16); } break; case 24: if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR) linear_palette(8); break; case 32: if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR) linear_palette(8); lut_init(24); break; default: fprintf(stderr, "Oops: %i bit/pixel ???\n", fb_var.bits_per_pixel); exit(1); } if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR || fb_var.bits_per_pixel == 8) { if (-1 == ioctl(fd,FBIOPUTCMAP,&cmap)) { perror("ioctl FBIOPUTCMAP"); exit(1); } } /* svga main loop */ tty_raw(); desc = NULL; info = NULL; for (;;) { if (need_read) { need_read = 0; need_refresh = 1; sprintf(linebuffer,"loading %s ...",fcurrent->name); status(linebuffer, NULL); free_image(fimg); free_image(simg); fimg = read_image(fcurrent->name); simg = NULL; img = NULL; scale = 1; if (fimg) { if (autoup || autodown) { scale = auto_scale(fimg); if (scale < 1 && !autodown) scale = 1; if (scale > 1 && !autoup) scale = 1; } if (scale != 1) { sprintf(linebuffer,"scaling (%.0f%%) %s ...", scale*100, fcurrent->name); status(linebuffer, NULL); simg = scale_image(fimg,scale); img = simg; } else { img = fimg; } desc = make_desc(&fimg->i,fcurrent->name); } if (!img) { sprintf(linebuffer,"%s: FAILED",fcurrent->name); show_error(linebuffer); } } if (img) { if (need_refresh) { need_refresh = 0; if (img->i.width < fb_var.xres || img->i.height < fb_var.yres) fb_clear_screen(); } info = make_info(fimg,scale); } switch (key = svga_show(img, timeout, desc, info, &arg)) { case KEY_DELETE: if (editable) { struct flist *fdel = fcurrent; if (flist_islast(fcurrent)) fcurrent = flist_prev(fcurrent); else fcurrent = flist_next(fcurrent,0,0); unlink(fdel->name); flist_del(fdel); flist_renumber(); need_read = 1; if (list_empty(&flist)) { /* deleted last one */ fb_clear_mem(); tty_restore(); fb_cleanup(); exit(0); } } else { show_error("readonly mode, sorry [start with --edit?]"); } break; case KEY_ROT_CW: case KEY_ROT_CCW: { if (editable) { sprintf(linebuffer,"rotating %s ...",fcurrent->name); status(linebuffer, NULL); jpeg_transform_inplace (fcurrent->name, (key == KEY_ROT_CW) ? JXFORM_ROT_90 : JXFORM_ROT_270, NULL, NULL,0, (backup ? JFLAG_FILE_BACKUP : 0) | (preserve ? JFLAG_FILE_KEEP_TIME : 0) | JFLAG_TRANSFORM_IMAGE | JFLAG_TRANSFORM_THUMBNAIL | JFLAG_UPDATE_ORIENTATION); need_read = 1; } else { show_error("readonly mode, sorry [start with --edit?]"); } break; } case KEY_TAGFILE: fcurrent->tag = !fcurrent->tag; /* fall throuth */ case KEY_SPACE: need_read = 1; fcurrent = flist_next(fcurrent,1,0); if (NULL != fcurrent) break; /* else fall */ case KEY_ESC: case KEY_Q: case KEY_EOF: cleanup_and_exit(0); break; case KEY_PGDN: need_read = 1; fcurrent = flist_next(fcurrent,0,0); break; case KEY_PGUP: need_read = 1; fcurrent = flist_prev(fcurrent); break; case KEY_TIMEOUT: need_read = 1; fcurrent = flist_next(fcurrent,once,1); if (NULL == fcurrent) { fb_clear_mem(); tty_restore(); fb_cleanup(); } /* FIXME: wrap around */ break; case KEY_PLUS: case KEY_MINUS: case KEY_ASCALE: case KEY_SCALE: if (key == KEY_PLUS) { newscale = scale * 1.6; } else if (key == KEY_MINUS) { newscale = scale / 1.6; } else if (key == KEY_ASCALE) { newscale = auto_scale(fimg); } else { newscale = arg / 100; } if (newscale < 0.1) newscale = 0.1; if (newscale > 10) newscale = 10; scale_fix_top_left(scale, newscale, img); scale = newscale; sprintf(linebuffer,"scaling (%.0f%%) %s ...", scale*100, fcurrent->name); status(linebuffer, NULL); free_image(simg); simg = scale_image(fimg,scale); img = simg; need_refresh = 1; break; case KEY_GOTO: if (arg > 0 && arg <= fcount) { need_read = 1; fcurrent = flist_goto(arg); } break; case KEY_VERBOSE: #if 0 /* fbdev testing/debugging hack */ { ioctl(fd,FBIOBLANK,1); sleep(1); ioctl(fd,FBIOBLANK,0); } #endif statusline = !statusline; need_refresh = 1; break; case KEY_DESC: if (!comments) { edit_desc(fcurrent->name); desc = make_desc(&fimg->i,fcurrent->name); } break; } } }