diff --git a/display.c b/display.c index f3df215f4..0cdf6eb7f 100644 --- a/display.c +++ b/display.c @@ -530,12 +530,14 @@ drawAnchorCursor0(Buffer *buf, AnchorList *al, int hseq, int prevhseq, int start_pos = an->start.pos; int end_pos = an->end.pos; for (i = an->start.pos; i < an->end.pos; i++) { - if (enable_inline_image && (l->propBuf[i] & PE_IMAGE)) { +#ifdef USE_IMAGE + if (enable_inline_image && (l->propBuf[i] & PE_IMAGE)) { if (start_pos == i) start_pos = i + 1; else if (end_pos == an->end.pos) end_pos = i - 1; } +#endif if (l->propBuf[i] & (PE_IMAGE | PE_ANCHOR | PE_FORM)) { if (active) l->propBuf[i] |= PE_ACTIVE; diff --git a/fm.h b/fm.h index e6c760ec4..95c2e331d 100644 --- a/fm.h +++ b/fm.h @@ -317,11 +317,14 @@ extern int REV_LB[]; #define EOL(l) (&(l)->ptr[(l)->length]) #define IS_EOL(p,l) ((p)==&(l)->ptr[(l)->length]) +#ifdef USE_IMAGE +#define INLINE_IMG_AUTO -1 #define INLINE_IMG_NONE 0 #define INLINE_IMG_OSC5379 1 #define INLINE_IMG_SIXEL 2 #define INLINE_IMG_ITERM2 3 #define INLINE_IMG_KITTY 4 +#endif /* * Types. @@ -940,7 +943,10 @@ global char *CurrentKeyData; global char *CurrentCmdData; global char *w3m_reqlog; extern char *w3m_version; -extern int enable_inline_image; +#ifdef USE_IMAGE +global unsigned char enable_inline_image init(INLINE_IMG_NONE); +global signed char enable_inline_image_config init(INLINE_IMG_AUTO); +#endif #define DUMP_BUFFER 0x01 #define DUMP_HEAD 0x02 diff --git a/image.c b/image.c index ee5e51be2..1b7d5a5c8 100644 --- a/image.c +++ b/image.c @@ -16,7 +16,7 @@ static int image_index = 0; /* display image */ -typedef struct _termialImage { +typedef struct _terminalImage { ImageCache *cache; short x; short y; @@ -34,10 +34,15 @@ static pid_t Imgdisplay_pid = 0; static int openImgdisplay(void); static void closeImgdisplay(void); static int getCharSize(void); +static int inline_img_protocol_autodetect(void); void initImage() { + enable_inline_image = enable_inline_image_config == INLINE_IMG_AUTO + ? inline_img_protocol_autodetect() + : enable_inline_image_config; + if (activeImage) return; if (getCharSize()) @@ -133,6 +138,37 @@ openImgdisplay() return FALSE; } +static int +inline_img_protocol_autodetect(void) +{ + int result; + const char *env_term, *konsole_version; + + if ((env_term = getenv("TERM"))) { + if (strcmp(env_term, "xterm-kitty") == 0) + return INLINE_IMG_KITTY; + if (strcmp(env_term, "xterm-ghostty") == 0) + return INLINE_IMG_KITTY; + /* yaft doesn't correctly respond to \e[c, but is sixel-capable + * anyway. Thanks to hackerb9/lsix */ + if (strncmp(env_term, "yaft", 4) == 0) + return INLINE_IMG_SIXEL; + } + + if ((konsole_version = getenv("KONSOLE_VERSION")) + && strcmp(konsole_version, "220770") >= 0) + return INLINE_IMG_KITTY; + + if ((result = img_protocol_test_for_sixel()) != INLINE_IMG_NONE) + return result; + + /* If mlterm too old to support sixel, probably supports older OSC5379 */ + if (env_term && strncmp(env_term, "mlterm", 6) == 0) + return INLINE_IMG_OSC5379; + + return INLINE_IMG_NONE; +} + static void closeImgdisplay(void) { @@ -256,6 +292,11 @@ drawImage(void) put_image_iterm2(url, x, y, sw, sh); } else if (enable_inline_image == INLINE_IMG_KITTY) { put_image_kitty(url, x, y, i->width, i->height, i->sx, i->sy, sw * pixel_per_char, sh * pixel_per_line_i, sw, sh); +#ifdef DEBUG + } else { + fprintf(stderr, "Unrecognised inline image protocol: %d\n", + enable_inline_image); +#endif } continue ; diff --git a/main.c b/main.c index 710db9c16..20b9b1ad2 100644 --- a/main.c +++ b/main.c @@ -127,8 +127,6 @@ static int searchKeyNum(void); #define help() fusage(stdout, 0) #define usage() fusage(stderr, 1) -int enable_inline_image; - static void fversion(FILE * f) { @@ -719,13 +717,13 @@ main(int argc, char **argv) set_pixel_per_line = TRUE; } } -#endif else if (!strcmp("-ri", argv[i])) { enable_inline_image = INLINE_IMG_OSC5379; } else if (!strcmp("-sixel", argv[i])) { enable_inline_image = INLINE_IMG_SIXEL; } +#endif else if (!strcmp("-num", argv[i])) showLineNum = TRUE; else if (!strcmp("-no-proxy", argv[i])) @@ -6006,11 +6004,13 @@ deleteFiles() } while ((f = popText(fileToDelete)) != NULL) { unlink(f); +#ifdef USE_IMAGE if (enable_inline_image == INLINE_IMG_SIXEL && strcmp(f+strlen(f)-4, ".gif") == 0) { Str firstframe = Strnew_charp(f); Strcat_charp(firstframe, "-1"); unlink(firstframe->ptr); } +#endif } } diff --git a/rc.c b/rc.c index 45ab14fec..1aa799503 100644 --- a/rc.c +++ b/rc.c @@ -377,6 +377,7 @@ static struct sel_c graphic_char_str[] = { #ifdef USE_IMAGE static struct sel_c inlineimgstr[] = { + {N_S(INLINE_IMG_AUTO), N_("auto-select protocol")}, {N_S(INLINE_IMG_NONE), N_("external command")}, {N_S(INLINE_IMG_OSC5379), N_("OSC 5379 (mlterm)")}, {N_S(INLINE_IMG_SIXEL), N_("sixel (img2sixel)")}, @@ -449,7 +450,7 @@ struct param_ptr params1[] = { CMT_EXT_IMAGE_VIEWER, NULL}, {"image_scale", P_SCALE, PI_TEXT, (void *)&image_scale, CMT_IMAGE_SCALE, NULL}, - {"inline_img_protocol", P_INT, PI_SEL_C, (void *)&enable_inline_image, + {"inline_img_protocol", P_CHARINT, PI_SEL_C, (void *)&enable_inline_image_config, CMT_INLINE_IMG_PROTOCOL, (void *)inlineimgstr}, {"imgdisplay", P_STRING, PI_TEXT, (void *)&Imgdisplay, CMT_IMGDISPLAY, NULL}, @@ -1313,7 +1314,7 @@ sync_with_option(void) init_migemo(); #endif #ifdef USE_IMAGE - if (fmInitialized && (displayImage || enable_inline_image)) + if (fmInitialized && (displayImage || enable_inline_image_config)) initImage(); #else displayImage = FALSE; /* XXX */ diff --git a/terms.c b/terms.c index b0a15f7fd..21e8924f7 100644 --- a/terms.c +++ b/terms.c @@ -856,6 +856,117 @@ put_image_sixel(char *url, int x, int y, int w, int h, int sx, int sy, int sw, i MOVE(Currentbuf->cursorY,Currentbuf->cursorX); } +static int +have_img2sixel(void) +{ + pid_t child_pid; + int wstatus; + + if (getenv("W3M_IMG2SIXEL")) + return TRUE; + + switch (child_pid = fork()) { + case -1: + return FALSE; + case 0: + close(STDOUT_FILENO); + close(STDERR_FILENO); + execlp("img2sixel", "img2sixel", "--version", NULL); + /* if exec fails */ + _exit(-1); + default: + return +#ifdef HAVE_WAITPID + waitpid(child_pid, &wstatus, 0) != -1 +#else + wait(&wstatus) != -1 +#endif + && WIFEXITED(wstatus) + && WEXITSTATUS(wstatus) == 0; + } +} + +/* + * NB: in theory, user input could be snarfed up with the read(2); in practice, + * only museum pieces have such low latency, so we should get the device + * attributes (and only the device attributes) immediately. If ever this becomes + * an issue (which it won't), ungetch(3) can be used on any extraneous input + */ +int +img_protocol_test_for_sixel(void) +{ + static const char errstr[] = "Can't get terminal attributes"; + size_t response_size = 256, len = 0; + char *response = GC_MALLOC_ATOMIC(response_size); + + /* request tty send primary device attributes */ + write(tty, "\033[c", 3); + + /* loop until we get the whole response */ + for (; response; response = GC_REALLOC(response, response_size *= 2)) { + ssize_t nchars_read; + int nret; + fd_set fds; + /* wait 0.2s (from get_pixel_per_char()) for input */ + struct timeval timeout = { 0, 0.2 * 1000000 }; + FD_ZERO(&fds); + FD_SET(tty, &fds); + if ((nret = select(tty + 1, &fds, NULL, NULL, &timeout)) <= 0) { + fprintf(stderr, "%s: %s\n", + errstr, nret == 0 ? "timed out" : strerror(errno)); + return INLINE_IMG_NONE; + } + + nchars_read = read(tty, response + len, response_size - len - 1); + if (nchars_read < 0) { + perror(errstr); + return INLINE_IMG_NONE; + } + + /* first useful iteration; validate response */ + if (nchars_read + len >= 3 && + (memcmp(response, "\033[", 2) != 0 || + (response[2] != '?' && response[2] != '='))) + { + fputs("Malformed terminal attributes\n", stderr); + return INLINE_IMG_NONE; + } + + /* NUL-terminate the terminal response */ + { + char *ptr = memchr(response + len, 'c', nchars_read); + if (ptr) { + ptr[1] = '\0'; + break; + } + } + + /* if we can't find 'c', then the response is incomplete; try again */ + len += (size_t)nchars_read; + } + + /* separate the response parameters by ';' and look for '4' */ + if (response) { + char *ptr; + if (response[2] == '=') + return INLINE_IMG_NONE; /* SyncTERM */ + for (ptr = response; (ptr = strchr(ptr, ';'));) { + if (*++ptr != '4') + continue; + switch (*++ptr) { + case ';': + case 'c': + if (have_img2sixel()) + return INLINE_IMG_SIXEL; + } + } + } else + perror(errstr); + + /* default */ + return INLINE_IMG_NONE; +} + int get_pixel_per_cell(int *ppc, int *ppl) { diff --git a/terms.h b/terms.h index c45db2ea8..9ef324a21 100644 --- a/terms.h +++ b/terms.h @@ -35,6 +35,7 @@ extern void put_image_osc5379(char *url, int x, int y, int w, int h, int sx, int extern void put_image_sixel(char *url, int x, int y, int w, int h, int sx, int sy, int sw, int sh, int n_terminal_image); extern void put_image_iterm2(char *url, int x, int y, int w, int h); extern void put_image_kitty(char *url, int x, int y, int w, int h, int sx, int sy, int sw, int sh, int c, int r); +extern int img_protocol_test_for_sixel(void); extern int get_pixel_per_cell(int *ppc, int *ppl); #endif