summaryrefslogtreecommitdiffstats
path: root/src/viewer/hex.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/viewer/hex.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/viewer/hex.c b/src/viewer/hex.c
new file mode 100644
index 0000000..c0cf7d0
--- /dev/null
+++ b/src/viewer/hex.c
@@ -0,0 +1,484 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for hex view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/lock.h" /* lock_file() and unlock_file() */
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ MARK_NORMAL,
+ MARK_SELECTED,
+ MARK_CURSOR,
+ MARK_CHANGED
+} mark_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char hex_char[] = "0123456789ABCDEF";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Determine the state of the current byte.
+ *
+ * @param view viewer object
+ * @param from offset
+ * @param curr current node
+ */
+
+static mark_t
+mcview_hex_calculate_boldflag (WView * view, off_t from, struct hexedit_change_node *curr,
+ gboolean force_changed)
+{
+ return (from == view->hex_cursor) ? MARK_CURSOR
+ : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
+ : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_hex (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int ngroups = view->bytes_per_line / 4;
+ /* 8 characters are used for the file offset, and every hex group
+ * takes 13 characters. Starting at width of 80 columns, the groups
+ * are separated by an extra vertical line. Starting at width of 81,
+ * there is an extra space before the text column. There is always a
+ * mostly empty column on the right, to allow overflowing CJKs.
+ */
+ int text_start;
+
+ int row = 0;
+ off_t from;
+ mark_t boldflag_byte = MARK_NORMAL;
+ mark_t boldflag_char = MARK_NORMAL;
+ struct hexedit_change_node *curr = view->change_list;
+#ifdef HAVE_CHARSET
+ int cont_bytes = 0; /* number of continuation bytes remanining from current UTF-8 */
+ gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
+#endif /* HAVE_CHARSET */
+ gboolean utf8_changed = FALSE; /* whether any of the bytes in the UTF-8 were changed */
+
+ char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
+
+ text_start = 8 + 13 * ngroups +
+ ((r->cols < 80) ? 0 : (r->cols == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
+
+ mcview_display_clean (view);
+
+ /* Find the first displayable changed byte */
+ /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
+ from = view->dpy_start;
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ if (from >= view->bytes_per_line)
+ {
+ row--;
+ from -= view->bytes_per_line;
+ }
+ if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
+ {
+ row--;
+ from -= view->bytes_per_line;
+ }
+ }
+#endif /* HAVE_CHARSET */
+ while (curr && (curr->offset < from))
+ {
+ curr = curr->next;
+ }
+
+ for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
+ {
+ int col = 0;
+ int bytes; /* Number of bytes already printed on the line */
+
+ /* Print the hex offset */
+ if (row >= 0)
+ {
+ int i;
+
+ g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
+ widget_gotoyx (view, r->y + row, r->x);
+ tty_setcolor (VIEW_BOLD_COLOR);
+ for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
+ tty_print_char (hex_buff[i]);
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ }
+
+ for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
+ {
+ int c;
+#ifdef HAVE_CHARSET
+ int ch = 0;
+
+ if (view->utf8)
+ {
+ struct hexedit_change_node *corr = curr;
+
+ if (cont_bytes != 0)
+ {
+ /* UTF-8 continuation bytes, print a space (with proper attributes)... */
+ cont_bytes--;
+ ch = ' ';
+ if (cjk_right)
+ {
+ /* ... except when it'd wipe out the right half of a CJK, then print nothing */
+ cjk_right = FALSE;
+ ch = -1;
+ }
+ }
+ else
+ {
+ int j;
+ gchar utf8buf[UTF8_CHAR_LEN + 1];
+ int res;
+ int first_changed = -1;
+
+ for (j = 0; j < UTF8_CHAR_LEN; j++)
+ {
+ if (mcview_get_byte (view, from + j, &res))
+ utf8buf[j] = res;
+ else
+ {
+ utf8buf[j] = '\0';
+ break;
+ }
+ if (curr != NULL && from + j == curr->offset)
+ {
+ utf8buf[j] = curr->value;
+ if (first_changed == -1)
+ first_changed = j;
+ }
+ if (curr != NULL && from + j >= curr->offset)
+ curr = curr->next;
+ }
+ utf8buf[UTF8_CHAR_LEN] = '\0';
+
+ /* Determine the state of the current multibyte char */
+ ch = g_utf8_get_char_validated (utf8buf, -1);
+ if (ch == -1 || ch == -2)
+ {
+ ch = '.';
+ }
+ else
+ {
+ gchar *next_ch;
+
+ next_ch = g_utf8_next_char (utf8buf);
+ cont_bytes = next_ch - utf8buf - 1;
+ if (g_unichar_iswide (ch))
+ cjk_right = TRUE;
+ }
+
+ utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
+ curr = corr;
+ }
+ }
+#endif /* HAVE_CHARSET */
+
+ /* For negative rows, the only thing we care about is overflowing
+ * UTF-8 continuation bytes which were handled above. */
+ if (row < 0)
+ {
+ if (curr != NULL && from == curr->offset)
+ curr = curr->next;
+ continue;
+ }
+
+ if (!mcview_get_byte (view, from, &c))
+ break;
+
+ /* Save the cursor position for mcview_place_cursor() */
+ if (from == view->hex_cursor && !view->hexview_in_text)
+ {
+ view->cursor_row = row;
+ view->cursor_col = col;
+ }
+
+ /* Determine the state of the current byte */
+ boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
+ boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
+
+ /* Determine the value of the current byte */
+ if (curr != NULL && from == curr->offset)
+ {
+ c = curr->value;
+ curr = curr->next;
+ }
+
+ /* Select the color for the hex number */
+ tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
+ boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
+ boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
+ /* boldflag_byte == MARK_CURSOR */
+ view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
+
+ /* Print the hex number */
+ widget_gotoyx (view, r->y + row, r->x + col);
+ if (col < r->cols)
+ {
+ tty_print_char (hex_char[c / 16]);
+ col += 1;
+ }
+ if (col < r->cols)
+ {
+ tty_print_char (hex_char[c % 16]);
+ col += 1;
+ }
+
+ /* Print the separator */
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ if (bytes != view->bytes_per_line - 1)
+ {
+ if (col < r->cols)
+ {
+ tty_print_char (' ');
+ col += 1;
+ }
+
+ /* After every four bytes, print a group separator */
+ if (bytes % 4 == 3)
+ {
+ if (view->data_area.cols >= 80 && col < r->cols)
+ {
+ tty_print_one_vline (TRUE);
+ col += 1;
+ }
+ if (col < r->cols)
+ {
+ tty_print_char (' ');
+ col += 1;
+ }
+ }
+ }
+
+ /* Select the color for the character; this differs from the
+ * hex color when boldflag == MARK_CURSOR */
+ tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
+ boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
+ boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
+ /* boldflag_char == MARK_CURSOR */
+ view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
+
+
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!view->utf8)
+ {
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ }
+ if (!g_unichar_isprint (c))
+ c = '.';
+ }
+ else if (view->utf8)
+ ch = convert_from_utf_to_current_c (ch, view->converter);
+ else
+#endif
+ {
+#ifdef HAVE_CHARSET
+ c = convert_to_display_c (c);
+#endif
+
+ if (!is_printable (c))
+ c = '.';
+ }
+
+ /* Print corresponding character on the text side */
+ if (text_start + bytes < r->cols)
+ {
+ widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ tty_print_anychar (ch);
+ else
+#endif
+ tty_print_char (c);
+ }
+
+ /* Save the cursor position for mcview_place_cursor() */
+ if (from == view->hex_cursor && view->hexview_in_text)
+ {
+ view->cursor_row = row;
+ view->cursor_col = text_start + bytes;
+ }
+ }
+ }
+
+ /* Be polite to the other functions */
+ tty_setcolor (VIEW_NORMAL_COLOR);
+
+ mcview_place_cursor (view);
+ view->dpy_end = from;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_hexedit_save_changes (WView * view)
+{
+ int answer = 0;
+
+ if (view->change_list == NULL)
+ return TRUE;
+
+ while (answer == 0)
+ {
+ int fp;
+ char *text;
+ struct hexedit_change_node *curr, *next;
+
+ g_assert (view->filename_vpath != NULL);
+
+ fp = mc_open (view->filename_vpath, O_WRONLY);
+ if (fp != -1)
+ {
+ for (curr = view->change_list; curr != NULL; curr = next)
+ {
+ next = curr->next;
+
+ if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
+ || mc_write (fp, &(curr->value), 1) != 1)
+ goto save_error;
+
+ /* delete the saved item from the change list */
+ view->change_list = next;
+ view->dirty++;
+ mcview_set_byte (view, curr->offset, curr->value);
+ g_free (curr);
+ }
+
+ view->change_list = NULL;
+
+ if (view->locked)
+ view->locked = unlock_file (view->filename_vpath);
+
+ if (mc_close (fp) == -1)
+ message (D_ERROR, _("Save file"),
+ _("Error while closing the file:\n%s\n"
+ "Data may have been written or not"), unix_error_string (errno));
+
+ view->dirty++;
+ return TRUE;
+ }
+
+ save_error:
+ text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
+ (void) mc_close (fp);
+
+ answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
+ g_free (text);
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_hexedit_mode (WView * view)
+{
+ view->hexedit_mode = !view->hexedit_mode;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_hexedit_free_change_list (WView * view)
+{
+ struct hexedit_change_node *curr, *next;
+
+ for (curr = view->change_list; curr != NULL; curr = next)
+ {
+ next = curr->next;
+ g_free (curr);
+ }
+ view->change_list = NULL;
+
+ if (view->locked)
+ view->locked = unlock_file (view->filename_vpath);
+
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
+{
+ /* chnode always either points to the head of the list or
+ * to one of the ->next fields in the list. The value at
+ * this location will be overwritten with the new node. */
+ struct hexedit_change_node **chnode = head;
+
+ while (*chnode != NULL && (*chnode)->offset < node->offset)
+ chnode = &((*chnode)->next);
+
+ node->next = *chnode;
+ *chnode = node;
+}
+
+/* --------------------------------------------------------------------------------------------- */