diff --git a/tools/chafa/Makefile.am b/tools/chafa/Makefile.am index 88914a7f..ca69601a 100644 --- a/tools/chafa/Makefile.am +++ b/tools/chafa/Makefile.am @@ -74,6 +74,9 @@ chafa_LDADD = $(GLIB_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $( if IS_WIN32_BUILD WIN32_LDADD = manifest.o +chafa_SOURCES += \ + conhost.c \ + conhost.h manifest.o: $(srcdir)/manifest.rc $(WINDRES) -o $@ $(srcdir)/manifest.rc else diff --git a/tools/chafa/chafa.c b/tools/chafa/chafa.c index 63f808c9..4bb1e713 100644 --- a/tools/chafa/chafa.c +++ b/tools/chafa/chafa.c @@ -49,6 +49,8 @@ #ifdef G_OS_WIN32 # ifdef HAVE_WINDOWS_H # include +# include +# include "conhost.h" # endif # include #endif @@ -133,6 +135,9 @@ typedef struct gboolean fuzz_options; ChafaTermInfo *term_info; + + gboolean output_utf_16_on_windows; + gboolean is_conhost_mode; } GlobalOptions; @@ -178,93 +183,51 @@ interruptible_usleep (gdouble us) } } -#ifdef G_OS_WIN32 - -/* We must determine if stdout is redirected to a file, and if so, use a - * different set of I/O functions. */ -static gboolean win32_stdout_is_file = FALSE; - -static gboolean -safe_WriteConsoleA (HANDLE chd, const gchar *data, gsize len) -{ - gsize total_written = 0; - - if (chd == INVALID_HANDLE_VALUE) - return FALSE; - - while (total_written < len) - { - DWORD n_written = 0; - - if (win32_stdout_is_file) - { - /* WriteFile() and fwrite() seem to work equally well despite various - * claims that the former does poorly in a UTF-8 environment. The - * resulting files look good in my tests, but note that catting them - * out with 'type' introduces lots of artefacts. */ -#if 0 - if (!WriteFile (chd, data, len - total_written, &n_written, NULL)) - return FALSE; -#else - if ((n_written = fwrite (data, 1, len - total_written, stdout)) < 1) - return FALSE; -#endif - } - else - { - if (!WriteConsoleA (chd, data, len - total_written, &n_written, NULL)) - return FALSE; - } - - data += n_written; - total_written += n_written; - } - - return TRUE; -} -#endif static gboolean write_to_stdout (gconstpointer buf, gsize len) { + gboolean result = TRUE; + if (len == 0) return TRUE; #ifdef G_OS_WIN32 - { - const gchar *p0, *p1, *end; - gsize total_written = 0; - - /* In order for UTF-8 to be handled correctly, we need to use WriteConsoleA() - * on MS Windows. We also convert line feeds to DOS-style CRLF as we go. */ + { HANDLE chd = GetStdHandle (STD_OUTPUT_HANDLE); + gsize total_written = 0; + const void *const newline = "\r\n"; - for (p0 = buf, end = p0 + len; - chd != INVALID_HANDLE_VALUE && total_written < len; - p0 = p1) { - p1 = memchr (p0, '\n', end - p0); - if (!p1) - p1 = end; - - if (!safe_WriteConsoleA (chd, p0, p1 - p0)) - break; - - total_written += p1 - p0; - if (p1 != end) + /* on MS Windows. We convert line feeds to DOS-style CRLF as we go. */ + const gchar * p0, * p1, * end; + for (p0 = buf, end = p0 + len; + chd != INVALID_HANDLE_VALUE && total_written < len; + p0 = p1) { - if (!safe_WriteConsoleA (chd, "\r\n", 2)) + p1 = memchr (p0, '\n', end - p0); + if (!p1) + p1 = end; + + if (!safe_WriteConsoleA (chd, p0, p1 - p0)) break; - p1++; - total_written += 1; + total_written += p1 - p0; + + if (p1 != end) + { + if (!safe_WriteConsoleA (chd, newline, 2)) + break; + + p1 += 1; + total_written += 1; + } } } - - return total_written == len ? TRUE : FALSE; + result = total_written == len ? TRUE : FALSE; } #else { @@ -276,12 +239,11 @@ write_to_stdout (gconstpointer buf, gsize len) len - total_written, stdout); total_written += n_written; if (total_written < len && n_written == 0 && errno != EINTR) - return FALSE; + result = FALSE; } } - - return TRUE; #endif + return result; } static guchar @@ -434,7 +396,7 @@ print_summary (void) "\nOutput encoding:\n" - " -f, --format=FORMAT Set output format; one of [iterm, kitty, sixels,\n" + " -f, --format=FORMAT Set output format; one of [conhost,iterm, kitty, sixels,\n" " symbols]. Iterm, kitty and sixels yield much higher\n" " quality but enjoy limited support. Symbols mode yields\n" " beautiful character art.\n" @@ -451,6 +413,8 @@ print_summary (void) " --polite=BOOL Polite mode [on, off]. Inhibits escape sequences that may\n" " confuse other programs. Defaults to off.\n" + /*" --utf16 Windows only, output using UTF16 functions,\n" + " required for compatibility with older versions of Windows\n"*/ "\nSize and layout:\n" " -C, --center=BOOL Center images horizontally in view [on, off]. Default off.\n" @@ -910,6 +874,13 @@ parse_format_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_ { pixel_mode = CHAFA_PIXEL_MODE_ITERM2; } + else if (!strcasecmp (value, "conhost")) + { +#ifdef G_OS_WIN32 + options.is_conhost_mode = TRUE; +#endif + pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; + } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, @@ -1523,13 +1494,19 @@ tty_options_init (void) /* Enable ANSI escape sequence parsing etc. on MS Windows command prompt */ if (chd != INVALID_HANDLE_VALUE) - { - if (!SetConsoleMode (chd, - ENABLE_PROCESSED_OUTPUT - | ENABLE_WRAP_AT_EOL_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING - | DISABLE_NEWLINE_AUTO_RETURN)) - win32_stdout_is_file = TRUE; + { + DWORD bitmask = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT; + if (!options.is_conhost_mode) + bitmask |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + if (!SetConsoleMode (chd, bitmask)) + { + if (GetLastError() == ERROR_INVALID_HANDLE) + win32_stdout_is_file = TRUE; + else + /* Compatibility with older Windowses */ + SetConsoleMode (chd,ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); + + } } /* Set UTF-8 code page output */ @@ -1557,7 +1534,7 @@ tty_options_init (void) } #endif - if (options.mode != CHAFA_CANVAS_MODE_FGBG) + if (options.mode != CHAFA_CANVAS_MODE_FGBG && !options.is_conhost_mode) { p0 = chafa_term_info_emit_disable_cursor (options.term_info, buf); write_to_stdout (buf, p0 - buf); @@ -1584,7 +1561,7 @@ tty_options_deinit (void) if (!options.polite) { - if (options.mode != CHAFA_CANVAS_MODE_FGBG) + if (options.mode != CHAFA_CANVAS_MODE_FGBG && !options.is_conhost_mode) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX]; gchar *p0; @@ -1641,7 +1618,6 @@ detect_terminal (ChafaTermInfo **term_info_out, ChafaCanvasMode *mode_out, pixel_mode = CHAFA_PIXEL_MODE_SIXELS; else pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; - if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SCREEN_PASSTHROUGH)) { /* We can do passthrough for sixels and iterm too, but we won't do so @@ -1819,6 +1795,7 @@ parse_options (int *argc, char **argv []) { "symbols", '\0', 0, G_OPTION_ARG_CALLBACK, parse_symbols_arg, "Output symbols", NULL }, { "threads", '\0', 0, G_OPTION_ARG_INT, &options.n_threads, "Number of threads", NULL }, { "threshold", 't', 0, G_OPTION_ARG_DOUBLE, &options.transparency_threshold, "Transparency threshold", NULL }, + /*{ "utf16", '\0', 0, G_OPTION_ARG_NONE, &options.output_utf_16_on_windows, "Use WriteConsoleW instead of WriteConsoleA", NULL},*/ { "view-size", '\0', 0, G_OPTION_ARG_CALLBACK, parse_view_size_arg, "View size", NULL }, { "watch", '\0', 0, G_OPTION_ARG_NONE, &options.watch, "Watch a file's contents", NULL }, /* Deprecated: Equivalent to --scale max */ @@ -1889,6 +1866,8 @@ parse_options (int *argc, char **argv []) options.file_duration_s = -1.0; /* Unset */ options.anim_fps = -1.0; options.anim_speed_multiplier = 1.0; + options.output_utf_16_on_windows = FALSE; + options.is_conhost_mode = FALSE; if (!g_option_context_parse (context, argc, argv, &error)) { @@ -2204,6 +2183,22 @@ parse_options (int *argc, char **argv []) chafa_set_n_threads (options.n_threads); +#ifdef G_OS_WIN32 + if (options.is_conhost_mode) + { + options.output_utf_16_on_windows = TRUE; + if (options.mode == CHAFA_CANVAS_MODE_INDEXED_240 || + options.mode == CHAFA_CANVAS_MODE_INDEXED_256 || + options.mode == CHAFA_CANVAS_MODE_TRUECOLOR ) + options.mode=CHAFA_CANVAS_MODE_INDEXED_16; + } + +#else + /*Force it to not output UTF16 when not Windows*/ + options.output_utf_16_on_windows = FALSE; + options.is_conhost_mode = FALSE; +#endif + result = TRUE; out: @@ -2453,21 +2448,13 @@ write_image (GString **gsa, gint dest_width) return result; } -static GString ** -build_strings (ChafaPixelType pixel_type, const guint8 *pixels, - gint src_width, gint src_height, gint src_rowstride, - gint dest_width, gint dest_height, - gboolean is_animation, - gint placement_id) +static ChafaCanvasConfig * +build_config (gint dest_width, gint dest_height, + gboolean is_animation) { ChafaCanvasConfig *config; - ChafaCanvas *canvas; - ChafaFrame *frame; - ChafaImage *image; - GString **gsa; config = chafa_canvas_config_new (); - chafa_canvas_config_set_geometry (config, dest_width, dest_height); chafa_canvas_config_set_canvas_mode (config, options.mode); chafa_canvas_config_set_pixel_mode (config, options.pixel_mode); @@ -2481,7 +2468,6 @@ build_strings (ChafaPixelType pixel_type, const guint8 *pixels, chafa_canvas_config_set_preprocessing_enabled (config, options.preprocess); chafa_canvas_config_set_fg_only_enabled (config, options.fg_only); chafa_canvas_config_set_passthrough (config, options.passthrough); - if (is_animation && options.pixel_mode == CHAFA_PIXEL_MODE_KITTY && !options.transparency_threshold_set) @@ -2500,6 +2486,18 @@ build_strings (ChafaPixelType pixel_type, const guint8 *pixels, chafa_canvas_config_set_work_factor (config, (options.work_factor - 1) / 8.0f); chafa_canvas_config_set_optimizations (config, options.optimizations); + return config; +} + +static ChafaCanvas * +build_canvas (ChafaPixelType pixel_type, const guint8 *pixels, + gint src_width, gint src_height, gint src_rowstride, + const ChafaCanvasConfig * config, + gint placement_id) +{ + ChafaFrame *frame; + ChafaImage *image; + ChafaCanvas *canvas; canvas = chafa_canvas_new (config); frame = chafa_frame_new_borrow ((gpointer) pixels, pixel_type, @@ -2507,14 +2505,9 @@ build_strings (ChafaPixelType pixel_type, const guint8 *pixels, image = chafa_image_new (); chafa_image_set_frame (image, frame); chafa_canvas_set_image (canvas, image, placement_id); - - chafa_canvas_print_rows (canvas, options.term_info, &gsa, NULL); - chafa_image_unref (image); chafa_frame_unref (frame); - chafa_canvas_unref (canvas); - chafa_canvas_config_unref (config); - return gsa; + return canvas; } static void @@ -2657,21 +2650,44 @@ run_generic (const gchar *filename, gboolean is_first_file, gboolean is_first_fr options.font_ratio, options.scale >= SCALE_MAX - 0.1 ? TRUE : FALSE, options.stretch); + ChafaCanvasConfig * cfg = build_config ( + dest_width, + dest_height, + is_animation + ); + ChafaCanvas * canvas = build_canvas ( + pixel_type, pixels, + src_width, src_height, src_rowstride, cfg, + placement_id >= 0 ? placement_id + ((frame_count++) % 2) : -1 + ); - gsa = build_strings (pixel_type, pixels, - src_width, src_height, src_rowstride, - dest_width, dest_height, - is_animation, - placement_id >= 0 ? placement_id + ((frame_count++) % 2) : -1); - - if (!write_image_prologue (is_first_file, is_first_frame, is_animation, dest_height) - || !write_image (gsa, dest_width) - || !write_image_epilogue (dest_width) - || fflush (stdout) != 0) - goto out; +#ifdef G_OS_WIN32 + if (options.is_conhost_mode) + { + ConhostRow *lines; + gsize s; - chafa_free_gstring_array (gsa); + s = canvas_to_conhost (canvas, &lines); + write_image_conhost (lines, s); + destroy_lines (lines,s); + } + else +#endif + { + chafa_canvas_print_rows (canvas, options.term_info, &gsa, NULL); + if (!write_image_prologue (is_first_file, is_first_frame, is_animation, dest_height) + || !write_image (gsa, dest_width) + || !write_image_epilogue (dest_width) + || fflush (stdout) != 0) + { + chafa_free_gstring_array (gsa); + goto out; + } + chafa_free_gstring_array (gsa); + } + chafa_canvas_unref (canvas); + chafa_canvas_config_unref (cfg); if (is_animation) { /* Account for time spent converting and printing frame */ @@ -2834,7 +2850,7 @@ int main (int argc, char *argv []) { int ret; - + proc_init (); if (!parse_options (&argc, &argv)) diff --git a/tools/chafa/conhost.c b/tools/chafa/conhost.c new file mode 100644 index 00000000..f0ee4c76 --- /dev/null +++ b/tools/chafa/conhost.c @@ -0,0 +1,199 @@ +#include "conhost.h" + +static gsize +unichar_to_utf16 (gunichar c, gunichar2 *str) +{ + if (c>=0x110000 || + (c<0xe000 && c>=0xd800) || + (c%0x10000 >= 0xfffe)) return 0; + if (c<0x10000){ + *str = (gunichar2)c; + return 1; + } else { + const gunichar temp=c-0x10000; + str[0] = (temp>>10)+0xd800; + str[1] = (temp&0x3ff)+0xdc00; + return 2; + } +} + + +#define FOREGROUND_ALL FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE +gsize +canvas_to_conhost (ChafaCanvas *canvas, ConhostRow **lines) +{ + gint width, height; + const ChafaCanvasConfig *config; + ChafaCanvasMode canvas_mode; + + config = chafa_canvas_peek_config (canvas); + canvas_mode = chafa_canvas_config_get_canvas_mode (config); + if ( + canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 || + canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 || + canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR + ) return (gsize) -1; + chafa_canvas_config_get_geometry (config, &width, &height); + (*lines) = g_malloc (height*sizeof(ConhostRow)); + static const gchar color_lut[16] = { + 0,4,2,6,1,5,3,7, + 8,12,10,14,9,13,11,15 + }; + for (gint y = 0; y0) + line->str[line->utf16_string_length++] = *utf16_codes; + if (s==2) + line->str[line->utf16_string_length++] = utf16_codes[1]; + + if (canvas_mode == CHAFA_CANVAS_MODE_FGBG) + line->attributes[x]=FOREGROUND_ALL; + else + { + gint fg_out, bg_out; + chafa_canvas_get_raw_colors_at (canvas,x,y,&fg_out, &bg_out); + if (canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) + line->attributes[x] = bg_out?FOREGROUND_ALL:COMMON_LVB_REVERSE_VIDEO|FOREGROUND_ALL; + else + { + fg_out=color_lut[fg_out]; + bg_out=color_lut[bg_out]; + line->attributes[x] = (bg_out<<4)|fg_out; + } + + } + } + line->str=g_realloc (line->str, line->utf16_string_length*sizeof(gunichar2)); + } + return height; +} +void +write_image_conhost (const ConhostRow * lines, gsize s) +{ + HANDLE outh; + COORD curpos; + DWORD idc; + + outh = GetStdHandle (STD_OUTPUT_HANDLE); + { + CONSOLE_SCREEN_BUFFER_INFO bufinfo; + GetConsoleScreenBufferInfo (outh, &bufinfo); + curpos = bufinfo.dwCursorPosition; + } + + for (gsize y=0 ;y +gboolean win32_stdout_is_file = FALSE; +gboolean +safe_WriteConsoleA (HANDLE chd, const gchar *data, gsize len) +{ + gsize total_written = 0; + + if (chd == INVALID_HANDLE_VALUE) + return FALSE; + + while (total_written < len) + { + DWORD n_written = 0; + + if (win32_stdout_is_file) + { + /* WriteFile() and fwrite() seem to work equally well despite various + * claims that the former does poorly in a UTF-8 environment. The + * resulting files look good in my tests, but note that catting them + * out with 'type' introduces lots of artefacts. */ +#if 0 + if (!WriteFile (chd, data, len - total_written, &n_written, NULL)) + return FALSE; +#else + if ((n_written = fwrite (data, 1, len - total_written, stdout)) < 1) + return FALSE; +#endif + } + else + { + if (!WriteConsoleA (chd, data, len - total_written, &n_written, NULL)) + return FALSE; + } + + data += n_written; + total_written += n_written; + } + + return TRUE; +} +#if 0 +gboolean +safe_WriteConsoleW (HANDLE chd, const gunichar2 *data, gsize len) +{ + gsize total_written = 0; + if (chd == INVALID_HANDLE_VALUE) + return FALSE; + + while (total_written < len) + { + DWORD n_written = 0; + if (win32_stdout_is_file) + { + /* WriteFile() and fwrite() seem to work equally well despite various + * claims that the former does poorly in a UTF-8 environment. The + * resulting files look good in my tests, but note that catting them + * out with 'type' introduces lots of artefacts. */ +#if 0 + if (!WriteFile (chd, data, (len - total_written)*2, &n_written, NULL)) + return FALSE; +#else + if ((n_written = fwrite (data, 2, len - total_written, stdout)) < 1) + return FALSE; +#endif + } + else + { + if (!WriteConsoleW (chd, data, len - total_written, &n_written, NULL)) + return FALSE; + } + + data += n_written; + total_written += n_written; + } + + return TRUE; +} +#endif \ No newline at end of file diff --git a/tools/chafa/conhost.h b/tools/chafa/conhost.h new file mode 100644 index 00000000..2b81989c --- /dev/null +++ b/tools/chafa/conhost.h @@ -0,0 +1,25 @@ + +#ifndef CHARACTER_CANVAS_TO_CONHOST_H +#define CHARACTER_CANVAS_TO_CONHOST_H +#include +#include +#include + +typedef WORD ConhostAttribute; +typedef struct { + gunichar2 *str; + ConhostAttribute *attributes; + size_t length; + size_t utf16_string_length; +} ConhostRow; +gboolean safe_WriteConsoleA (HANDLE chd, const gchar *data, gsize len); +gboolean safe_WriteConsoleW (HANDLE chd, const gunichar2 *data, gsize len); +gsize canvas_to_conhost (ChafaCanvas *canvas, ConhostRow **lines); +void write_image_conhost (const ConhostRow *lines, gsize s); +void destroy_lines (ConhostRow *lines, gsize s); + +/* We must determine if stdout is redirected to a file, and if so, use a + * different set of I/O functions. */ +extern gboolean win32_stdout_is_file; + +#endif \ No newline at end of file diff --git a/tools/chafa/file-mapping.c b/tools/chafa/file-mapping.c index ee73fc2d..fece81f5 100644 --- a/tools/chafa/file-mapping.c +++ b/tools/chafa/file-mapping.c @@ -26,7 +26,7 @@ #include #include #include - +#include #ifdef G_OS_WIN32 # include # include