From 00d6c5ede909c45d44c904a02f658256130b84ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Sat, 17 Jun 2023 15:50:56 +0200 Subject: Bump liberty, move the UI to liberty-xui.c This deduplicates code between nncmpp and hex, while adding functionality. --- CMakeLists.txt | 20 +- LICENSE | 2 +- README.adoc | 3 +- config.h.in | 3 +- hex.adoc | 4 + hex.c | 738 ++++++++++++++++++++++++++++----------------------------- hex.desktop | 9 + liberty | 2 +- 8 files changed, 399 insertions(+), 382 deletions(-) create mode 100644 hex.desktop diff --git a/CMakeLists.txt b/CMakeLists.txt index 732fb15..b54b21d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ include (AddThreads) find_package (Termo QUIET NO_MODULE) option (USE_SYSTEM_TERMO "Don't compile our own termo library, use the system one" ${Termo_FOUND}) - if (USE_SYSTEM_TERMO) if (NOT Termo_FOUND) message (FATAL_ERROR "System termo library not found") @@ -45,7 +44,6 @@ set (project_libraries ${Unistring_LIBRARIES} pkg_search_module (lua lua53 lua5.3 lua-5.3 lua>=5.3) option (WITH_LUA "Enable support for Lua plugins" ${lua_FOUND}) - if (WITH_LUA) if (NOT lua_FOUND) message (FATAL_ERROR "Lua library not found") @@ -65,6 +63,18 @@ if (WITH_LUA) endif () endif () +pkg_check_modules (x11 x11 xrender xft fontconfig) +option (WITH_X11 "Build with X11 support" ${x11_FOUND}) +if (WITH_X11) + if (NOT x11_FOUND) + message (FATAL_ERROR "Some X11 libraries were not found") + endif () + + list (APPEND project_libraries ${x11_LIBRARIES}) + include_directories (${x11_INCLUDE_DIRS}) + link_directories (${x11_LIBRARY_DIRS}) +endif () + include_directories (${Unistring_INCLUDE_DIRS} ${Ncursesw_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS}) @@ -74,8 +84,6 @@ set (CMAKE_REQUIRED_LIBRARIES ${Ncursesw_LIBRARIES}) CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM) # Generate a configuration file -set (HAVE_LUA "${WITH_LUA}") - configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h) include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) @@ -89,6 +97,10 @@ add_threads (${PROJECT_NAME}) include (GNUInstallDirs) install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) +if (WITH_X11) + install (FILES ${PROJECT_NAME}.desktop + DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) +endif () # Generate documentation from text markup find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor) diff --git a/LICENSE b/LICENSE index ddfaee0..7b6617a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 - 2017, Přemysl Eric Janouch +Copyright (c) 2016 - 2023, Přemysl Eric Janouch Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. diff --git a/README.adoc b/README.adoc index f2b23d1..32436c6 100644 --- a/README.adoc +++ b/README.adoc @@ -3,7 +3,7 @@ hex 'hex' is yet another hex viewer. It automatically interprets fields within files using a set of Lua scripts, colorizing them and showing descriptions on -the side. +the side. It also runs equally well in the terminal, or as an X11 client. At the moment there aren't that many features and we only have a few decoders. @@ -24,6 +24,7 @@ Building and Running Build dependencies: CMake, pkg-config, awk, liberty (included), termo (included), asciidoctor or asciidoc (recommended but optional) + Runtime dependencies: ncursesw, libunistring, Lua >= 5.3 (for highlighting) +Optional runtime dependencies: x11, xft $ git clone --recursive https://git.janouch.name/p/hex.git $ mkdir hex/build diff --git a/config.h.in b/config.h.in index ba7991d..2551a7f 100644 --- a/config.h.in +++ b/config.h.in @@ -5,7 +5,8 @@ #define PROGRAM_VERSION "${PROJECT_VERSION}" #cmakedefine HAVE_RESIZETERM -#cmakedefine HAVE_LUA +#cmakedefine WITH_LUA +#cmakedefine WITH_X11 #endif // ! CONFIG_H diff --git a/hex.adoc b/hex.adoc index d422283..eb5f55d 100644 --- a/hex.adoc +++ b/hex.adoc @@ -38,6 +38,10 @@ _G_=1024M, and _GB_=1000M. The default value is 1G. *-d*, *--debug*:: Run in debug mode. +*-x*, *--x11*:: + Use an X11 interface even when run from a terminal. + Note that the application may be built with this feature disabled. + *-h*, *--help*:: Display a help message and exit. diff --git a/hex.c b/hex.c index 0c57aff..3b6d4a9 100644 --- a/hex.c +++ b/hex.c @@ -1,7 +1,7 @@ /* * hex -- hex viewer * - * Copyright (c) 2016 - 2017, Přemysl Eric Janouch + * Copyright (c) 2016 - 2023, Přemysl Eric Janouch * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. @@ -20,6 +20,8 @@ // We "need" to have an enum for attributes before including liberty. // Avoiding colours in the defaults here in order to support dumb terminals. +// FIXME: we need a 256color default palette that fails gracefully +// to something like underlined fields #define ATTRIBUTE_TABLE(XX) \ XX( FOOTER, "footer", -1, -1, 0 ) \ XX( FOOTER_HL, "footer_hl", -1, -1, A_BOLD ) \ @@ -55,49 +57,24 @@ enum #define LIBERTY_WANT_POLLER #define LIBERTY_WANT_ASYNC #include "liberty/liberty.c" -#include "liberty/liberty-tui.c" + +#ifdef WITH_X11 +#define LIBERTY_XUI_WANT_X11 +#endif // WITH_X11 +#include "liberty/liberty-xui.c" #include -#include -#ifndef TIOCGWINSZ -#include -#endif // ! TIOCGWINSZ -#include "termo.h" +#ifdef WITH_LUA +#include -#ifdef HAVE_LUA #include #include #include - -#include -#endif // HAVE_LUA +#endif // WITH_LUA #define APP_TITLE PROGRAM_NAME ///< Left top corner -// --- Utilities --------------------------------------------------------------- - -// The standard endwin/refresh sequence makes the terminal flicker -static void -update_curses_terminal_size (void) -{ -#if defined HAVE_RESIZETERM && defined TIOCGWINSZ - struct winsize size; - if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size)) - { - char *row = getenv ("LINES"); - char *col = getenv ("COLUMNS"); - unsigned long tmp; - resizeterm ( - (row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row, - (col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col); - } -#else // HAVE_RESIZETERM && TIOCGWINSZ - endwin (); - refresh (); -#endif // HAVE_RESIZETERM && TIOCGWINSZ -} - // --- Application ------------------------------------------------------------- enum @@ -136,23 +113,18 @@ static struct app_context // Event loop: struct poller poller; ///< Poller - bool quitting; ///< Quit signal for the event loop bool polling; ///< The event loop is running - struct poller_fd tty_event; ///< Terminal input event struct poller_fd signal_event; ///< Signal FD event -#ifdef HAVE_LUA +#ifdef WITH_LUA lua_State *L; ///< Lua state int ref_format; ///< Reference to "string.format" struct str_map coders; ///< Map of coders by name -#endif // HAVE_LUA +#endif // WITH_LUA // Data: - char *message; ///< Last logged message - int message_attr; ///< Attributes for the logged message - struct config config; ///< Program configuration char *filename; ///< Target filename @@ -176,15 +148,12 @@ static struct app_context enum endianity endianity; ///< Endianity - // Emulated widgets: - - struct poller_idle refresh_event; ///< Refresh the screen + // User interface: - // Terminal: + struct poller_timer message_timer; ///< Message timeout + char *message; ///< Last logged message - termo_t *tk; ///< termo handle - struct poller_timer tk_timer; ///< termo timeout timer - bool locale_is_utf8; ///< The locale is Unicode + int digitw; ///< Width of a single digit struct attrs attrs[ATTRIBUTE_COUNT]; } @@ -277,6 +246,13 @@ app_init_attributes (void) #undef XX } +static bool +app_on_insufficient_color (void) +{ + app_init_attributes (); + return true; +} + static void app_init_context (void) { @@ -288,46 +264,9 @@ app_init_context (void) ARRAY_INIT (g_ctx.marks_by_offset); ARRAY_INIT (g_ctx.offset_entries); - // This is also approximately what libunistring does internally, - // since the locale name is canonicalized by locale_charset(). - // Note that non-Unicode locales are handled pretty inefficiently. - g_ctx.locale_is_utf8 = !strcasecmp_ascii (locale_charset (), "UTF-8"); - app_init_attributes (); } -static void -app_init_terminal (void) -{ - TERMO_CHECK_VERSION; - if (!(g_ctx.tk = termo_new (STDIN_FILENO, NULL, 0))) - abort (); - if (!initscr () || nonl () == ERR) - abort (); - - // By default we don't use any colors so they're not required... - if (start_color () == ERR - || use_default_colors () == ERR - || COLOR_PAIRS <= ATTRIBUTE_COUNT) - return; - - for (int a = 0; a < ATTRIBUTE_COUNT; a++) - { - // ...thus we can reset back to defaults even after initializing some - if (g_ctx.attrs[a].fg >= COLORS || g_ctx.attrs[a].fg < -1 - || g_ctx.attrs[a].bg >= COLORS || g_ctx.attrs[a].bg < -1) - { - // FIXME: we need a 256color default palette that fails gracefully - // to something like underlined fields - app_init_attributes (); - return; - } - - init_pair (a + 1, g_ctx.attrs[a].fg, g_ctx.attrs[a].bg); - g_ctx.attrs[a].attrs |= COLOR_PAIR (a + 1); - } -} - static void app_free_context (void) { @@ -343,36 +282,14 @@ app_free_context (void) cstr_set (&g_ctx.filename, NULL); free (g_ctx.data); - - if (g_ctx.tk) - termo_destroy (g_ctx.tk); } static void app_quit (void) { - g_ctx.quitting = true; g_ctx.polling = false; } -static bool -app_is_character_in_locale (ucs4_t ch) -{ - // Avoid the overhead joined with calling iconv() for all characters. - if (g_ctx.locale_is_utf8) - return true; - - // The library really creates a new conversion object every single time - // and doesn't provide any smarter APIs. Luckily, most users use UTF-8. - size_t len; - char *tmp = u32_conv_to_encoding (locale_charset (), iconveh_error, - &ch, 1, NULL, NULL, &len); - if (!tmp) - return false; - free (tmp); - return true; -} - // --- Field marking ----------------------------------------------------------- /// Find the "marks_by_offset" span covering the offset (if any) @@ -495,30 +412,58 @@ app_flatten_marks (void) free (current); } -// --- Rendering --------------------------------------------------------------- +// --- Layouting --------------------------------------------------------------- -static void -app_invalidate (void) +enum +{ + WIDGET_NONE = 0, WIDGET_HEX, WIDGET_ASCII, WIDGET_ENDIANITY, +}; + +struct layout +{ + struct widget *head; + struct widget *tail; +}; + +static struct widget * +app_label (chtype attrs, const char *label) { - poller_idle_set (&g_ctx.refresh_event); + return g_xui.ui->label (attrs, 0, label); } -static void -app_flush_buffer (struct row_buffer *buf, int width, chtype attrs) +static struct widget * +app_mono_label (chtype attrs, const char *label) { - row_buffer_align (buf, width, attrs); - row_buffer_flush (buf); - row_buffer_free (buf); + return g_xui.ui->label (attrs, XUI_ATTR_MONOSPACE, label); } -/// Write the given UTF-8 string padded with spaces. -/// @param[in] attrs Text attributes for the text, including padding. -static void -app_write_line (const char *str, chtype attrs) +static struct widget * +app_mono_padding (chtype attrs, float width, float height) +{ + struct widget *w = g_xui.ui->padding (attrs, width, height); + w->width = width * g_ctx.digitw; + return w; +} + +static struct widget * +app_push (struct layout *l, struct widget *w) +{ + LIST_APPEND_WITH_TAIL (l->head, l->tail, w); + return w; +} + +static struct widget * +app_push_hfill (struct layout *l, struct widget *w) { - struct row_buffer buf = row_buffer_make (); - row_buffer_append (&buf, str, attrs); - app_flush_buffer (&buf, COLS, attrs); + w->width = -1; + return app_push (l, w); +} + +static struct widget * +app_push_vfill (struct layout *l, struct widget *w) +{ + w->height = -1; + return app_push (l, w); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -526,104 +471,130 @@ app_write_line (const char *str, chtype attrs) static int app_visible_rows (void) { - return MAX (0, LINES - 1 /* bar */ - 3 /* decoder */ - !!g_ctx.message); + int occupied = 1 /* bar */ + 3 /* decoder */; + return MAX (0, g_xui.height - occupied * g_xui.vunit) / g_xui.vunit; } -static void -app_make_row (struct row_buffer *buf, int64_t addr, int attrs) +static inline void +app_layout_cell (struct layout *hex, struct layout *ascii, int attrs, + int64_t addr) { + const char *hexa = "0123456789abcdef"; + + struct marks_by_offset *marks = app_marks_at_offset (addr); + int attrs_mark = attrs; + if (marks && marks->color >= 0) + attrs_mark = g_ctx.attrs[marks->color].attrs; + + if (addr >= g_ctx.view_cursor + && addr < g_ctx.view_cursor + 8) + { + attrs |= A_UNDERLINE; + attrs_mark |= A_UNDERLINE; + } + + // TODO: leave it up to the user to decide what should be colored + uint8_t cell = g_ctx.data[addr - g_ctx.data_offset]; + if (addr != g_ctx.view_cursor) + { + char s[] = { hexa[cell >> 4], hexa[cell & 0xf], 0 }; + app_push (hex, app_mono_label (attrs, s)); + } + else if (g_ctx.view_skip_nibble) + { + char s1[] = { hexa[cell >> 4], 0 }, s2[] = { hexa[cell & 0xf], 0 }; + app_push (hex, app_mono_label (attrs, s1)); + app_push (hex, app_mono_label (attrs ^ A_REVERSE, s2)); + } + else + { + char s1[] = { hexa[cell >> 4], 0 }, s2[] = { hexa[cell & 0xf], 0 }; + app_push (hex, app_mono_label (attrs ^ A_REVERSE, s1)); + app_push (hex, app_mono_label (attrs, s2)); + } + + char s[] = { (cell >= 32 && cell < 127) ? cell : '.', 0 }; + app_push (ascii, app_mono_label (attrs_mark, s)); +} + +// XXX: This per-character layouting is very inefficient, but not extremely so. +static struct widget * +app_layout_row (int64_t addr, int y, int attrs) +{ + struct layout l = {}; char *row_addr_str = xstrdup_printf ("%08" PRIx64, addr); - row_buffer_append (buf, row_addr_str, attrs); + app_push (&l, app_mono_label (attrs, row_addr_str)); free (row_addr_str); - struct row_buffer ascii = row_buffer_make (); - row_buffer_append (&ascii, " ", attrs); + struct layout hex = {}; + struct layout ascii = {}; + app_push (&ascii, app_mono_padding (attrs, 2, 1)); int64_t end_addr = g_ctx.data_offset + g_ctx.data_len; - const char *hexa = "0123456789abcdef"; for (int x = 0; x < ROW_SIZE; x++) { - if (x % 8 == 0) row_buffer_append (buf, " ", attrs); - if (x % 2 == 0) row_buffer_append (buf, " ", attrs); + if (x % 8 == 0) app_push (&hex, app_mono_padding (attrs, 1, 1)); + if (x % 2 == 0) app_push (&hex, app_mono_padding (attrs, 1, 1)); int64_t cell_addr = addr + x; if (cell_addr < g_ctx.data_offset || cell_addr >= end_addr) { - row_buffer_append (buf, " ", attrs); - row_buffer_append (&ascii, " ", attrs); + app_push (&hex, app_mono_padding (attrs, 2, 1)); + app_push (&ascii, app_mono_padding (attrs, 1, 1)); } else - { - int attrs_mark = attrs; - struct marks_by_offset *marks = app_marks_at_offset (cell_addr); - if (marks && marks->color >= 0) - attrs_mark = g_ctx.attrs[marks->color].attrs; - - int highlight = 0; - if (cell_addr >= g_ctx.view_cursor - && cell_addr < g_ctx.view_cursor + 8) - highlight = A_UNDERLINE; - - // TODO: leave it up to the user to decide what should be colored - uint8_t cell = g_ctx.data[cell_addr - g_ctx.data_offset]; - row_buffer_append (buf, - (char[3]) { hexa[cell >> 4], hexa[cell & 0xf], 0 }, - attrs | highlight); - - char s[2] = { (cell >= 32 && cell < 127) ? cell : '.', 0 }; - row_buffer_append (&ascii, s, attrs_mark | highlight); - } + app_layout_cell (&hex, &ascii, attrs, cell_addr); } - row_buffer_append_buffer (buf, &ascii); - row_buffer_free (&ascii); + + struct widget *w = NULL; + app_push (&l, (w = xui_hbox (hex.head)))->id = WIDGET_HEX; + w->userdata = y; + app_push (&l, (w = xui_hbox (ascii.head)))->id = WIDGET_ASCII; + w->userdata = y; + return xui_hbox (l.head); } -static void -app_draw_view (void) +static struct widget * +app_layout_view (void) { - move (0, 0); - + struct layout l = {}; int64_t end_addr = g_ctx.data_offset + g_ctx.data_len; - for (int y = 0; y < app_visible_rows (); y++) + for (int y = 0; y <= app_visible_rows (); y++) { int64_t addr = g_ctx.view_top + y * ROW_SIZE; if (addr >= end_addr) break; int attrs = (addr / ROW_SIZE & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN); - - struct row_buffer buf = row_buffer_make (); - app_make_row (&buf, addr, attrs); - app_flush_buffer (&buf, COLS, attrs); + app_push (&l, app_layout_row (addr, y, attrs)); } + return xui_vbox (l.head); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -app_draw_info (void) +static struct widget * +app_layout_info (void) { const struct marks_by_offset *marks; + struct layout l = {}; if (!(marks = app_marks_at_offset (g_ctx.view_cursor))) - return; + goto out; - int x_offset = 70; struct mark *mark, **iter = g_ctx.offset_entries + marks->marks; - for (int y = 0; y < app_visible_rows (); y++) + for (int y = 0; y <= app_visible_rows (); y++) { // TODO: we can use the field background // TODO: we can keep going through subsequent fields to fill the column if (!(mark = *iter++)) break; - struct row_buffer buf = row_buffer_make (); - row_buffer_append (&buf, - g_ctx.mark_strings.str + mark->description, 0); - - move (y, x_offset); - app_flush_buffer (&buf, COLS - x_offset, 0); + const char *description = g_ctx.mark_strings.str + mark->description; + app_push (&l, app_label (0, description)); } +out: + return xui_vbox (l.head); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -641,64 +612,93 @@ app_decode (const uint8_t *p, size_t len, enum endianity endianity) return val; } -static void -app_write_footer (struct row_buffer *b, char id, int len, const char *fmt, ...) - ATTRIBUTE_PRINTF (4, 5); +static struct widget * +app_footer_field (char id, int size, const char *fmt, ...) + ATTRIBUTE_PRINTF (3, 4); -static void -app_footer_field (struct row_buffer *b, char id, int len, const char *fmt, ...) +static struct widget * +app_footer_field (char id, int size, const char *fmt, ...) { const char *coding = ""; - if (len <= 1) + if (size <= 1) ; else if (g_ctx.endianity == ENDIANITY_LE) coding = "le"; else if (g_ctx.endianity == ENDIANITY_BE) coding = "be"; - char *key = xstrdup_printf ("%c%d%s", id, len * 8, coding); - row_buffer_append (b, key, APP_ATTR (FOOTER_HL)); + struct layout l = {}; + char *key = xstrdup_printf ("%c%d%s", id, size * 8, coding); + app_push (&l, app_mono_label (APP_ATTR (FOOTER_HL), key)); free (key); + app_push (&l, app_mono_padding (0, 1, 1)); + va_list ap; va_start (ap, fmt); struct str value = str_make (); str_append_vprintf (&value, fmt, ap); va_end (ap); - row_buffer_append (b, value.str, APP_ATTR (FOOTER)); + // Right-aligned + app_push_hfill (&l, g_xui.ui->padding (0, 1, 1)); + app_push (&l, app_mono_label (APP_ATTR (FOOTER), value.str)); str_free (&value); + return xui_hbox (l.head); } static void -app_draw_footer (void) +app_footer_group (struct layout *out, int size, int64_t u, int64_t s, int align) { - move (app_visible_rows (), 0); + struct layout l = {}; + app_push (&l, app_footer_field ('x', size, "%0*" PRIx64, size * 2, u)); + app_push (&l, app_footer_field ('u', size, "%" PRIu64, u)); + app_push (&l, app_footer_field ('s', size, "%" PRId64, s)); + l.head->width = MAX (l.head->width, + g_ctx.digitw * align /* sign + ceil(log10(U/INT*_MAX)) */); + if (out->head) + app_push (out, app_mono_padding (APP_ATTR (FOOTER), 2, 1)); + app_push (out, xui_vbox (l.head)); +} - struct row_buffer buf = row_buffer_make (); - row_buffer_append (&buf, APP_TITLE, APP_ATTR (BAR)); +static struct widget * +app_layout_footer (void) +{ + struct layout statusl = {}; + app_push (&statusl, app_label (APP_ATTR (BAR), APP_TITLE)); + app_push (&statusl, g_xui.ui->padding (APP_ATTR (BAR), 1, 1)); - if (g_ctx.filename) + if (g_ctx.message) + app_push (&statusl, app_label (APP_ATTR (BAR_HL), g_ctx.message)); + else if (g_ctx.filename) { - row_buffer_append (&buf, " ", APP_ATTR (BAR)); char *filename = (char *) u8_strconv_from_locale (g_ctx.filename); - row_buffer_append (&buf, filename, APP_ATTR (BAR_HL)); + app_push (&statusl, app_label (APP_ATTR (BAR_HL), filename)); free (filename); + app_push (&statusl, g_xui.ui->padding (APP_ATTR (BAR), 1, 1)); } - struct str right = str_make (); - str_append_printf (&right, " %08" PRIx64, g_ctx.view_cursor); - str_append (&right, g_ctx.endianity == ENDIANITY_LE ? " LE " : " BE "); + app_push_hfill (&statusl, g_xui.ui->padding (APP_ATTR (BAR), 1, 1)); + + char *address = xstrdup_printf ("%08" PRIx64, g_ctx.view_cursor); + app_push (&statusl, app_mono_label (APP_ATTR (BAR), address)); + free (address); + app_push (&statusl, g_xui.ui->padding (APP_ATTR (BAR), 1, 1)); + + app_push (&statusl, app_mono_label (APP_ATTR (BAR), + g_ctx.endianity == ENDIANITY_LE ? "LE" : "BE"))->id = WIDGET_ENDIANITY; + app_push (&statusl, g_xui.ui->padding (APP_ATTR (BAR), 1, 1)); int64_t top = g_ctx.view_top; int64_t bot = g_ctx.view_top + app_visible_rows () * ROW_SIZE; + struct str where = str_make (); if (top <= g_ctx.data_offset && bot >= g_ctx.data_offset + g_ctx.data_len) - str_append (&right, "All"); + str_append (&where, "All"); else if (top <= g_ctx.data_offset) - str_append (&right, "Top"); + str_append (&where, "Top"); else if (bot >= g_ctx.data_offset + g_ctx.data_len) - str_append (&right, "Bot"); + str_append (&where, "Bot"); else { int64_t end_addr = g_ctx.data_offset + g_ctx.data_len; @@ -707,90 +707,67 @@ app_draw_footer (void) cur -= g_ctx.data_offset / ROW_SIZE; max -= g_ctx.data_offset / ROW_SIZE; - str_append_printf (&right, "%2d%%", (int) (100 * cur / max)); + str_append_printf (&where, "%2d%%", (int) (100 * cur / max)); } - row_buffer_align (&buf, COLS - right.len, APP_ATTR (BAR)); - row_buffer_append (&buf, right.str, APP_ATTR (BAR)); - app_flush_buffer (&buf, COLS, APP_ATTR (BAR)); - str_free (&right); + app_push (&statusl, app_mono_label (APP_ATTR (BAR), where.str)); + str_free (&where); int64_t end_addr = g_ctx.data_offset + g_ctx.data_len; if (g_ctx.view_cursor < g_ctx.data_offset || g_ctx.view_cursor >= end_addr) - return; + return xui_hbox (statusl.head); int64_t len = end_addr - g_ctx.view_cursor; uint8_t *p = g_ctx.data + (g_ctx.view_cursor - g_ctx.data_offset); - struct row_buffer x = row_buffer_make (); - struct row_buffer u = row_buffer_make (); - struct row_buffer s = row_buffer_make (); - + // TODO: The entire bottom part perhaps should be pre-painted + // with APP_ATTR (FOOTER). + struct layout groupl = {}; if (len >= 1) - { - app_footer_field (&x, 'x', 1, " %02x ", p[0]); - app_footer_field (&u, 'u', 1, " %4u ", p[0]); - app_footer_field (&s, 's', 1, " %4d ", (int8_t) p[0]); - } + app_footer_group (&groupl, 1, p[0], (int8_t) p[0], 3 + 4); if (len >= 2) { - uint16_t val = app_decode (p, 2, g_ctx.endianity); - app_footer_field (&x, 'x', 2, " %04x ", val); - app_footer_field (&u, 'u', 2, " %6u ", val); - app_footer_field (&s, 's', 2, " %6d ", (int16_t) val); + uint16_t value = app_decode (p, 2, g_ctx.endianity); + app_footer_group (&groupl, 2, value, (int16_t) value, 6 + 6); } if (len >= 4) { - uint32_t val = app_decode (p, 4, g_ctx.endianity); - app_footer_field (&x, 'x', 4, " %08x ", val); - app_footer_field (&u, 'u', 4, " %11u ", val); - app_footer_field (&s, 's', 4, " %11d ", (int32_t) val); + uint32_t value = app_decode (p, 4, g_ctx.endianity); + app_footer_group (&groupl, 4, value, (int32_t) value, 6 + 11); } if (len >= 8) { - uint64_t val = app_decode (p, 8, g_ctx.endianity); - app_footer_field (&x, 'x', 8, " %016" PRIx64, val); - app_footer_field (&u, 'u', 8, " %20" PRIu64, val); - app_footer_field (&s, 's', 8, " %20" PRId64, (int64_t) val); + uint64_t value = app_decode (p, 8, g_ctx.endianity); + app_footer_group (&groupl, 8, value, (int64_t) value, 6 + 20); } - app_flush_buffer (&x, COLS, APP_ATTR (FOOTER)); - app_flush_buffer (&u, COLS, APP_ATTR (FOOTER)); - app_flush_buffer (&s, COLS, APP_ATTR (FOOTER)); - - if (g_ctx.message) - app_write_line (g_ctx.message, g_ctx.attrs[g_ctx.message_attr].attrs); + struct layout lll = {}; + app_push (&lll, xui_hbox (statusl.head)); + app_push (&lll, xui_hbox (groupl.head)); + return xui_vbox (lll.head); } static void -app_on_refresh (void *user_data) +app_layout (void) { - (void) user_data; - poller_idle_reset (&g_ctx.refresh_event); - - erase (); - app_draw_view (); - app_draw_info (); - app_draw_footer (); + struct layout topl = {}; + app_push (&topl, app_layout_view ()); + app_push (&topl, g_xui.ui->padding (0, 1, 1)); + app_push_hfill (&topl, app_layout_info ()); - int64_t diff = g_ctx.view_cursor - g_ctx.view_top; - int64_t y = diff / ROW_SIZE; - int64_t x = diff % ROW_SIZE; - if (diff >= 0 && y < app_visible_rows ()) - { - curs_set (1); - move (y, 10 + x*2 + g_ctx.view_skip_nibble + x/8 + x/2); - } - else - curs_set (0); + struct layout l = {}; + app_push_vfill (&l, xui_hbox (topl.head)); + app_push (&l, app_layout_footer ()); - refresh (); + struct widget *root = g_xui.widgets = xui_vbox (l.head); + root->width = g_xui.width; + root->height = g_xui.height; } // --- Lua --------------------------------------------------------------------- -#ifdef HAVE_LUA +#ifdef WITH_LUA static void * app_lua_alloc (void *ud, void *ptr, size_t o_size, size_t n_size) @@ -1284,7 +1261,7 @@ app_lua_init (void) strv_free (&v); } -#endif // HAVE_LUA +#endif // WITH_LUA // --- Actions ----------------------------------------------------------------- @@ -1296,7 +1273,7 @@ app_fix_view_range (void) if (g_ctx.view_top < data_view_start) { g_ctx.view_top = data_view_start; - app_invalidate (); + xui_invalidate (); return false; } @@ -1310,7 +1287,7 @@ app_fix_view_range (void) if (g_ctx.view_top > max_view_top) { g_ctx.view_top = max_view_top; - app_invalidate (); + xui_invalidate (); return false; } return true; @@ -1321,7 +1298,7 @@ static bool app_scroll (int n) { g_ctx.view_top += n * ROW_SIZE; - app_invalidate (); + xui_invalidate (); return app_fix_view_range (); } @@ -1348,7 +1325,7 @@ app_move_cursor_by_rows (int diff) bool result = g_ctx.view_cursor == fixed; g_ctx.view_cursor = fixed; - app_invalidate (); + xui_invalidate (); app_ensure_selection_visible (); return result; @@ -1362,7 +1339,7 @@ app_jump_to_marks (ssize_t i) g_ctx.view_cursor = g_ctx.marks_by_offset[i].offset; g_ctx.view_skip_nibble = false; - app_invalidate (); + xui_invalidate (); app_ensure_selection_visible (); return true; } @@ -1398,7 +1375,7 @@ app_process_action (enum action action) g_ctx.view_cursor = g_ctx.data_offset; g_ctx.view_skip_nibble = false; app_ensure_selection_visible (); - app_invalidate (); + xui_invalidate (); break; case ACTION_GOTO_BOTTOM: if (!g_ctx.data_len) @@ -1407,7 +1384,7 @@ app_process_action (enum action action) g_ctx.view_cursor = g_ctx.data_offset + g_ctx.data_len - 1; g_ctx.view_skip_nibble = false; app_ensure_selection_visible (); - app_invalidate (); + xui_invalidate (); break; case ACTION_GOTO_PAGE_PREVIOUS: @@ -1434,7 +1411,7 @@ app_process_action (enum action action) g_ctx.view_cursor--; app_ensure_selection_visible (); } - app_invalidate (); + xui_invalidate (); break; case ACTION_RIGHT: if (!g_ctx.view_skip_nibble) @@ -1448,7 +1425,7 @@ app_process_action (enum action action) g_ctx.view_cursor++; app_ensure_selection_visible (); } - app_invalidate (); + xui_invalidate (); break; case ACTION_ROW_START: @@ -1459,7 +1436,7 @@ app_process_action (enum action action) g_ctx.view_cursor = new; g_ctx.view_skip_nibble = false; - app_invalidate (); + xui_invalidate (); break; } case ACTION_ROW_END: @@ -1470,7 +1447,7 @@ app_process_action (enum action action) g_ctx.view_cursor = new; g_ctx.view_skip_nibble = false; - app_invalidate (); + xui_invalidate (); break; } @@ -1491,13 +1468,13 @@ app_process_action (enum action action) break; case ACTION_REDRAW: clear (); - app_invalidate (); + xui_invalidate (); break; case ACTION_TOGGLE_ENDIANITY: g_ctx.endianity = (g_ctx.endianity == ENDIANITY_LE) ? ENDIANITY_BE : ENDIANITY_LE; - app_invalidate (); + xui_invalidate (); break; default: return false; @@ -1508,59 +1485,76 @@ app_process_action (enum action action) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool -app_process_left_mouse_click (int line, int column) +app_process_left_mouse_click (struct widget *w, int x, int y) { - if (line < 0) - return false; + if (w->id == WIDGET_ENDIANITY) + return app_process_action (ACTION_TOGGLE_ENDIANITY); - if (line == app_visible_rows ()) + // XXX: This is really ugly. + x = x / g_ctx.digitw - 2; + y = w->userdata; + switch (w->id) { - if (column < COLS - 7 - || column >= COLS - 5) - return false; - return app_process_action (ACTION_TOGGLE_ENDIANITY); + case WIDGET_HEX: + x -= x/5 + x/21; + g_ctx.view_skip_nibble = x % 2; + x /= 2; + break; + case WIDGET_ASCII: + g_ctx.view_skip_nibble = false; + break; + default: + return true; } - else if (line < app_visible_rows ()) - { - // TODO: when holding a mouse button over a mark string, - // go to a locked mode that highlights that entire mark - // (probably by inverting colors) - // TODO: employ strict checking here before the autofix - int offset; - if (column >= 10 && column < 50) - { - offset = column - 10; - offset -= offset/5 + offset/21; - g_ctx.view_skip_nibble = offset % 2; - offset /= 2; - } - else if (column >= 52 && column < 68) - { - offset = column - 52; - g_ctx.view_skip_nibble = false; - } - else - return false; + g_ctx.view_cursor = g_ctx.view_top + y * ROW_SIZE + x; + return app_move_cursor_by_rows (0); +} + +/// Returns the deepest child at the cursor that has a non-zero ID, if any. +static struct widget * +app_find_widget (struct widget *list, int x, int y) +{ + struct widget *target = NULL; + LIST_FOR_EACH (struct widget, w, list) + { + if (x < w->x || x >= w->x + w->width + || y < w->y || y >= w->y + w->height) + continue; - g_ctx.view_cursor = g_ctx.view_top + line * ROW_SIZE + offset; - return app_move_cursor_by_rows (0); + struct widget *child = app_find_widget (w->children, x, y); + if (child) + target = child; + else if (w->id) + target = w; } - return true; + return target; } static bool -app_process_mouse (termo_mouse_event_t type, int line, int column, int button) +app_process_mouse (termo_mouse_event_t type, int x, int y, int button, + int modifiers) { + (void) modifiers; + + // TODO: when holding a mouse button over a mark string, + // go to a locked mode that highlights that entire mark + // (probably by inverting colors) if (type != TERMO_MOUSE_PRESS) return true; - - if (button == 1) - return app_process_left_mouse_click (line, column); - else if (button == 4) + if (button == 4) return app_process_action (ACTION_SCROLL_UP); - else if (button == 5) + if (button == 5) return app_process_action (ACTION_SCROLL_DOWN); + + struct widget *target = app_find_widget (g_xui.widgets, x, y); + if (!target) + return false; + + x -= target->x; + y -= target->y; + if (button == 1) + return app_process_left_mouse_click (target, x, y); return false; } @@ -1615,7 +1609,7 @@ g_default_bindings[] = static int app_binding_cmp (const void *a, const void *b) { - return termo_keycmp (g_ctx.tk, + return termo_keycmp (g_xui.tk, &((struct binding *) a)->decoded, &((struct binding *) b)->decoded); } @@ -1625,7 +1619,7 @@ app_init_bindings (void) for (size_t i = 0; i < N_ELEMENTS (g_default_bindings); i++) { struct binding *binding = &g_default_bindings[i]; - hard_assert (!*termo_strpkey_utf8 (g_ctx.tk, + hard_assert (!*termo_strpkey_utf8 (g_xui.tk, binding->key, &binding->decoded, TERMO_FORMAT_ALTISMETA)); } qsort (g_default_bindings, N_ELEMENTS (g_default_bindings), @@ -1713,72 +1707,58 @@ signals_setup_handlers (void) // --- Initialisation, event handling ------------------------------------------ static void -app_on_tty_readable (const struct pollfd *fd, void *user_data) +app_on_signal_pipe_readable (const struct pollfd *fd, void *user_data) { (void) user_data; - if (fd->revents & ~(POLLIN | POLLHUP | POLLERR)) - print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents); - poller_timer_reset (&g_ctx.tk_timer); - termo_advisereadable (g_ctx.tk); + char id = 0; + (void) read (fd->fd, &id, 1); - termo_key_t event; - termo_result_t res; - while ((res = termo_getkey (g_ctx.tk, &event)) == TERMO_RES_KEY) - { - int y, x, button; - termo_mouse_event_t type; - bool success; - if (termo_interpret_mouse (g_ctx.tk, &event, &type, &button, &y, &x)) - success = app_process_mouse (type, y, x, button); - else - success = app_process_termo_event (&event); + if (g_termination_requested) + app_quit (); - if (!success) - beep (); + if (g_winch_received) + { + g_winch_received = false; + if (g_xui.ui->winch) + g_xui.ui->winch (); + app_fix_view_range (); } +} - if (res == TERMO_RES_AGAIN) - poller_timer_set (&g_ctx.tk_timer, termo_get_waittime (g_ctx.tk)); - else if (res == TERMO_RES_ERROR || res == TERMO_RES_EOF) - app_quit (); +static void +app_show_message (char *message) +{ + cstr_set (&g_ctx.message, message); + poller_timer_set (&g_ctx.message_timer, 5000); + xui_invalidate (); } static void -app_on_key_timer (void *user_data) +app_hide_message (void) { - (void) user_data; + if (!g_ctx.message) + return; - termo_key_t event; - if (termo_getkey_force (g_ctx.tk, &event) == TERMO_RES_KEY) - if (!app_process_termo_event (&event)) - app_quit (); + cstr_set (&g_ctx.message, NULL); + poller_timer_reset (&g_ctx.message_timer); + xui_invalidate (); } static void -app_on_signal_pipe_readable (const struct pollfd *fd, void *user_data) +app_on_message_timer (void *user_data) { (void) user_data; - char id = 0; - (void) read (fd->fd, &id, 1); - - if (g_termination_requested && !g_ctx.quitting) - app_quit (); - - if (g_winch_received) - { - g_winch_received = false; - update_curses_terminal_size (); - app_fix_view_range (); - app_invalidate (); - } + app_hide_message (); } static void app_log_handler (void *user_data, const char *quote, const char *fmt, va_list ap) { + (void) user_data; + // We certainly don't want to end up in a possibly infinite recursion static bool in_processing; if (in_processing) @@ -1790,21 +1770,24 @@ app_log_handler (void *user_data, const char *quote, const char *fmt, str_append (&message, quote); str_append_vprintf (&message, fmt, ap); + app_show_message (xstrdup (message.str)); + // If the standard error output isn't redirected, try our best at showing // the message to the user - if (!isatty (STDERR_FILENO)) + if (g_debug_mode && !isatty (STDERR_FILENO)) fprintf (stderr, "%s\n", message.str); - else - { - cstr_set (&g_ctx.message, xstrdup (message.str)); - g_ctx.message_attr = (intptr_t) user_data; - app_invalidate (); - } str_free (&message); in_processing = false; } +static void +app_on_clipboard_copy (const char *text) +{ + // TODO: Resolve encoding. + print_status ("Text copied to clipboard: %s", text); +} + static void app_init_poller_events (void) { @@ -1812,15 +1795,8 @@ app_init_poller_events (void) g_ctx.signal_event.dispatcher = app_on_signal_pipe_readable; poller_fd_set (&g_ctx.signal_event, POLLIN); - g_ctx.tty_event = poller_fd_make (&g_ctx.poller, STDIN_FILENO); - g_ctx.tty_event.dispatcher = app_on_tty_readable; - poller_fd_set (&g_ctx.tty_event, POLLIN); - - g_ctx.tk_timer = poller_timer_make (&g_ctx.poller); - g_ctx.tk_timer.dispatcher = app_on_key_timer; - - g_ctx.refresh_event = poller_idle_make (&g_ctx.poller); - g_ctx.refresh_event.dispatcher = app_on_refresh; + g_ctx.message_timer = poller_timer_make (&g_ctx.poller); + g_ctx.message_timer.dispatcher = app_on_message_timer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1860,17 +1836,21 @@ main (int argc, char *argv[]) static const struct opt opts[] = { { 'd', "debug", NULL, 0, "run in debug mode" }, +#ifdef WITH_X11 + { 'x', "x11", NULL, 0, "use X11 even when run from a terminal" }, +#endif // WITH_X11 { 'h', "help", NULL, 0, "display this help and exit" }, { 'V', "version", NULL, 0, "output version information and exit" }, { 'o', "offset", "OFFSET", 0, "offset within the file" }, { 's', "size", "SIZE", 0, "size limit (1G by default)" }, -#ifdef HAVE_LUA +#ifdef WITH_LUA { 't', "type", "TYPE", 0, "force interpretation as the given type" }, -#endif // HAVE_LUA +#endif // WITH_LUA { 0, NULL, NULL, 0, NULL } }; + bool requested_x11 = false; struct opt_handler oh = opt_handler_make (argc, argv, opts, "[FILE]", "Interpreting hex viewer."); int64_t size_limit = 1 << 30; @@ -1883,6 +1863,9 @@ main (int argc, char *argv[]) case 'd': g_debug_mode = true; break; + case 'x': + requested_x11 = true; + break; case 'h': opt_handler_usage (&oh, stdout); exit (EXIT_SUCCESS); @@ -1910,7 +1893,7 @@ main (int argc, char *argv[]) argc -= optind; argv += optind; -#ifdef HAVE_LUA +#ifdef WITH_LUA // We do it at this questionable location to catch plugin failure before // we read potentially hundreds of megabytes of data in app_lua_init (); @@ -1922,7 +1905,7 @@ main (int argc, char *argv[]) puts (iter.link->key); exit (EXIT_SUCCESS); } -#endif // HAVE_LUA +#endif // WITH_LUA // When no filename is given, read from stdin and replace it with the tty int input_fd; @@ -1984,7 +1967,7 @@ main (int argc, char *argv[]) print_warning ("failed to set the locale"); app_init_context (); -#ifdef HAVE_LUA +#ifdef WITH_LUA // TODO: eventually we should do this in a separate thread after load // as it may take a long time (-> responsivity) and once we allow the user // to edit the file, each change will need a background rescan @@ -2003,15 +1986,22 @@ main (int argc, char *argv[]) exit_fatal ("Lua: decoding failed: %s", lua_tostring (g_ctx.L, -1)); lua_pop (g_ctx.L, 1); -#endif // HAVE_LUA +#endif // WITH_LUA app_flatten_marks (); app_load_configuration (); - app_init_terminal (); signals_setup_handlers (); app_init_poller_events (); - app_invalidate (); + + xui_preinit (); app_init_bindings (); + xui_start (&g_ctx.poller, + requested_x11, g_ctx.attrs, N_ELEMENTS (g_ctx.attrs)); + xui_invalidate (); + + struct widget *w = g_xui.ui->label (0, XUI_ATTR_MONOSPACE, "8"); + g_ctx.digitw = w->width; + widget_destroy (w); // Redirect all messages from liberty so that they don't disrupt display g_log_message_real = app_log_handler; @@ -2020,14 +2010,14 @@ main (int argc, char *argv[]) while (g_ctx.polling) poller_run (&g_ctx.poller); - endwin (); + xui_stop (); g_log_message_real = log_message_stdio; app_free_context (); -#ifdef HAVE_LUA +#ifdef WITH_LUA str_map_free (&g_ctx.coders); lua_close (g_ctx.L); -#endif // HAVE_LUA +#endif // WITH_LUA return 0; } diff --git a/hex.desktop b/hex.desktop new file mode 100644 index 0000000..d4351a6 --- /dev/null +++ b/hex.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=hex +GenericName=Interpreting hex viewer +Icon=hex +Exec=hex %f +NoDisplay=true +StartupNotify=false +Categories=Utility; diff --git a/liberty b/liberty index 34f8665..d01a1ff 160000 --- a/liberty +++ b/liberty @@ -1 +1 @@ -Subproject commit 34f86651f6220038c0ee07d3f422a52d9b081f02 +Subproject commit d01a1ff0348174f91bb2d3ba53145cc2c9f50a7f -- cgit v1.2.3