root/src/viewer/hex.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. mcview_hex_calculate_boldflag
  2. mcview_display_hex
  3. mcview_hexedit_save_changes
  4. mcview_toggle_hexedit_mode
  5. mcview_hexedit_free_change_list
  6. mcview_enqueue_change

   1 /*
   2    Internal file viewer for the Midnight Commander
   3    Function for hex view
   4 
   5    Copyright (C) 1994-2025
   6    Free Software Foundation, Inc.
   7 
   8    Written by:
   9    Miguel de Icaza, 1994, 1995, 1998
  10    Janne Kukonlehto, 1994, 1995
  11    Jakub Jelinek, 1995
  12    Joseph M. Hinkle, 1996
  13    Norbert Warmuth, 1997
  14    Pavel Machek, 1998
  15    Roland Illig <roland.illig@gmx.de>, 2004, 2005
  16    Slava Zanko <slavazanko@google.com>, 2009, 2013
  17    Andrew Borodin <aborodin@vmail.ru>, 2009-2022
  18    Ilia Maslakov <il.smind@gmail.com>, 2009
  19 
  20    This file is part of the Midnight Commander.
  21 
  22    The Midnight Commander is free software: you can redistribute it
  23    and/or modify it under the terms of the GNU General Public License as
  24    published by the Free Software Foundation, either version 3 of the License,
  25    or (at your option) any later version.
  26 
  27    The Midnight Commander is distributed in the hope that it will be useful,
  28    but WITHOUT ANY WARRANTY; without even the implied warranty of
  29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  30    GNU General Public License for more details.
  31 
  32    You should have received a copy of the GNU General Public License
  33    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  34  */
  35 
  36 #include <config.h>
  37 
  38 #include <errno.h>
  39 #include <inttypes.h>  // uintmax_t
  40 
  41 #include "lib/global.h"
  42 #include "lib/tty/tty.h"
  43 #include "lib/skin.h"
  44 #include "lib/vfs/vfs.h"
  45 #include "lib/lock.h"  // lock_file() and unlock_file()
  46 #include "lib/util.h"
  47 #include "lib/widget.h"
  48 #ifdef HAVE_CHARSET
  49 #    include "lib/charsets.h"
  50 #endif
  51 
  52 #include "internal.h"
  53 
  54 /*** global variables ****************************************************************************/
  55 
  56 /*** file scope macro definitions ****************************************************************/
  57 
  58 /*** file scope type declarations ****************************************************************/
  59 
  60 typedef enum
  61 {
  62     MARK_NORMAL,
  63     MARK_SELECTED,
  64     MARK_CURSOR,
  65     MARK_CHANGED
  66 } mark_t;
  67 
  68 /*** forward declarations (file scope functions) *************************************************/
  69 
  70 /*** file scope variables ************************************************************************/
  71 
  72 static const char hex_char[] = "0123456789ABCDEF";
  73 
  74 /* --------------------------------------------------------------------------------------------- */
  75 /*** file scope functions ************************************************************************/
  76 /* --------------------------------------------------------------------------------------------- */
  77 /** Determine the state of the current byte.
  78  *
  79  * @param view viewer object
  80  * @param from offset
  81  * @param curr current node
  82  */
  83 
  84 static mark_t
  85 mcview_hex_calculate_boldflag (WView *view, off_t from, struct hexedit_change_node *curr,
     /* [previous][next][first][last][top][bottom][index][help]  */
  86                                gboolean force_changed)
  87 {
  88     return (from == view->hex_cursor)                               ? MARK_CURSOR
  89         : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
  90         : (view->search_start <= from && from < view->search_end)   ? MARK_SELECTED
  91                                                                     : MARK_NORMAL;
  92 }
  93 
  94 /* --------------------------------------------------------------------------------------------- */
  95 /*** public functions ****************************************************************************/
  96 /* --------------------------------------------------------------------------------------------- */
  97 
  98 void
  99 mcview_display_hex (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 100 {
 101     const WRect *r = &view->data_area;
 102     int ngroups = view->bytes_per_line / 4;
 103     /* 8 characters are used for the file offset, and every hex group
 104      * takes 13 characters. Starting at width of 80 columns, the groups
 105      * are separated by an extra vertical line. Starting at width of 81,
 106      * there is an extra space before the text column. There is always a
 107      * mostly empty column on the right, to allow overflowing CJKs.
 108      */
 109     int text_start;
 110 
 111     int row = 0;
 112     off_t from;
 113     mark_t boldflag_byte = MARK_NORMAL;
 114     mark_t boldflag_char = MARK_NORMAL;
 115     struct hexedit_change_node *curr = view->change_list;
 116 #ifdef HAVE_CHARSET
 117     int cont_bytes = 0;          // number of continuation bytes remanining from current UTF-8
 118     gboolean cjk_right = FALSE;  // whether the second byte of a CJK is to be processed
 119 #endif
 120     gboolean utf8_changed = FALSE;  // whether any of the bytes in the UTF-8 were changed
 121 
 122     char hex_buff[10];  // A temporary buffer for sprintf and mvwaddstr
 123 
 124     text_start = 8 + 13 * ngroups;
 125     if (r->cols == 80)
 126         text_start += ngroups - 1;
 127     else if (r->cols > 80)
 128         text_start += ngroups;
 129 
 130     mcview_display_clean (view);
 131 
 132     // Find the first displayable changed byte
 133     // In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly.
 134     from = view->dpy_start;
 135 #ifdef HAVE_CHARSET
 136     if (view->utf8)
 137     {
 138         if (from >= view->bytes_per_line)
 139         {
 140             row--;
 141             from -= view->bytes_per_line;
 142         }
 143         if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
 144         {
 145             row--;
 146             from -= view->bytes_per_line;
 147         }
 148     }
 149 #endif
 150     while (curr != NULL && (curr->offset < from))
 151         curr = curr->next;
 152 
 153     for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
 154     {
 155         int col = 0;
 156         int bytes;  // Number of bytes already printed on the line
 157 
 158         // Print the hex offset
 159         if (row >= 0)
 160         {
 161             int i;
 162 
 163             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
 164             widget_gotoyx (view, r->y + row, r->x);
 165             tty_setcolor (VIEW_BOLD_COLOR);
 166             for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
 167                 tty_print_char (hex_buff[i]);
 168             tty_setcolor (VIEW_NORMAL_COLOR);
 169         }
 170 
 171         for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
 172         {
 173             int c;
 174 #ifdef HAVE_CHARSET
 175             int ch = 0;
 176 
 177             if (view->utf8)
 178             {
 179                 struct hexedit_change_node *corr = curr;
 180 
 181                 if (cont_bytes != 0)
 182                 {
 183                     // UTF-8 continuation bytes, print a space (with proper attributes)...
 184                     cont_bytes--;
 185                     ch = ' ';
 186                     if (cjk_right)
 187                     {
 188                         // ... except when it'd wipe out the right half of a CJK, then print nothing
 189                         cjk_right = FALSE;
 190                         ch = -1;
 191                     }
 192                 }
 193                 else
 194                 {
 195                     int j;
 196                     gchar utf8buf[UTF8_CHAR_LEN + 1];
 197                     int res;
 198                     int first_changed = -1;
 199 
 200                     for (j = 0; j < UTF8_CHAR_LEN; j++)
 201                     {
 202                         if (mcview_get_byte (view, from + j, &res))
 203                             utf8buf[j] = res;
 204                         else
 205                         {
 206                             utf8buf[j] = '\0';
 207                             break;
 208                         }
 209                         if (curr != NULL && from + j == curr->offset)
 210                         {
 211                             utf8buf[j] = curr->value;
 212                             if (first_changed == -1)
 213                                 first_changed = j;
 214                         }
 215                         if (curr != NULL && from + j >= curr->offset)
 216                             curr = curr->next;
 217                     }
 218                     utf8buf[UTF8_CHAR_LEN] = '\0';
 219 
 220                     // Determine the state of the current multibyte char
 221                     ch = g_utf8_get_char_validated (utf8buf, -1);
 222                     if (ch == -1 || ch == -2)
 223                         ch = '.';
 224                     else
 225                     {
 226                         gchar *next_ch;
 227 
 228                         next_ch = g_utf8_next_char (utf8buf);
 229                         cont_bytes = next_ch - utf8buf - 1;
 230                         if (g_unichar_iswide (ch))
 231                             cjk_right = TRUE;
 232                     }
 233 
 234                     utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
 235                     curr = corr;
 236                 }
 237             }
 238 #endif
 239 
 240             /* For negative rows, the only thing we care about is overflowing
 241              * UTF-8 continuation bytes which were handled above. */
 242             if (row < 0)
 243             {
 244                 if (curr != NULL && from == curr->offset)
 245                     curr = curr->next;
 246                 continue;
 247             }
 248 
 249             if (!mcview_get_byte (view, from, &c))
 250                 break;
 251 
 252             // Save the cursor position for mcview_place_cursor()
 253             if (from == view->hex_cursor && !view->hexview_in_text)
 254             {
 255                 view->cursor_row = row;
 256                 view->cursor_col = col;
 257             }
 258 
 259             // Determine the state of the current byte
 260             boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
 261             boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
 262 
 263             // Determine the value of the current byte
 264             if (curr != NULL && from == curr->offset)
 265             {
 266                 c = curr->value;
 267                 curr = curr->next;
 268             }
 269 
 270             // Select the color for the hex number
 271             tty_setcolor (boldflag_byte == MARK_NORMAL         ? VIEW_NORMAL_COLOR
 272                               : boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR
 273                               : boldflag_byte == MARK_CHANGED  ? VIEW_UNDERLINED_COLOR
 274                                                                :
 275                                                               // boldflag_byte == MARK_CURSOR
 276                               view->hexview_in_text ? VIEW_SELECTED_COLOR
 277                                                     : VIEW_UNDERLINED_COLOR);
 278 
 279             // Print the hex number
 280             widget_gotoyx (view, r->y + row, r->x + col);
 281             if (col < r->cols)
 282             {
 283                 tty_print_char (hex_char[c / 16]);
 284                 col++;
 285             }
 286             if (col < r->cols)
 287             {
 288                 tty_print_char (hex_char[c % 16]);
 289                 col++;
 290             }
 291 
 292             // Print the separator
 293             tty_setcolor (VIEW_NORMAL_COLOR);
 294             if (bytes != view->bytes_per_line - 1)
 295             {
 296                 if (col < r->cols)
 297                 {
 298                     tty_print_char (' ');
 299                     col++;
 300                 }
 301 
 302                 // After every four bytes, print a group separator
 303                 if (bytes % 4 == 3)
 304                 {
 305                     if (view->data_area.cols >= 80 && col < r->cols)
 306                     {
 307                         tty_print_one_vline (TRUE);
 308                         col++;
 309                     }
 310                     if (col < r->cols)
 311                     {
 312                         tty_print_char (' ');
 313                         col++;
 314                     }
 315                 }
 316             }
 317 
 318             /* Select the color for the character; this differs from the
 319              * hex color when boldflag == MARK_CURSOR */
 320             tty_setcolor (boldflag_char == MARK_NORMAL         ? VIEW_NORMAL_COLOR
 321                               : boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR
 322                               : boldflag_char == MARK_CHANGED  ? VIEW_UNDERLINED_COLOR
 323                                                                :
 324                                                               // boldflag_char == MARK_CURSOR
 325                               view->hexview_in_text ? VIEW_SELECTED_COLOR
 326                                                     : MARKED_SELECTED_COLOR);
 327 
 328 #ifdef HAVE_CHARSET
 329             if (mc_global.utf8_display)
 330             {
 331                 if (!view->utf8)
 332                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 333                 if (!g_unichar_isprint (c))
 334                     c = '.';
 335             }
 336             else if (view->utf8)
 337                 ch = convert_from_utf_to_current_c (ch, view->converter);
 338             else
 339 #endif
 340             {
 341 #ifdef HAVE_CHARSET
 342                 c = convert_to_display_c (c);
 343 #endif
 344 
 345                 if (!is_printable (c))
 346                     c = '.';
 347             }
 348 
 349             // Print corresponding character on the text side
 350             if (text_start + bytes < r->cols)
 351             {
 352                 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
 353 #ifdef HAVE_CHARSET
 354                 if (view->utf8)
 355                     tty_print_anychar (ch);
 356                 else
 357 #endif
 358                     tty_print_char (c);
 359             }
 360 
 361             // Save the cursor position for mcview_place_cursor()
 362             if (from == view->hex_cursor && view->hexview_in_text)
 363             {
 364                 view->cursor_row = row;
 365                 view->cursor_col = text_start + bytes;
 366             }
 367         }
 368     }
 369 
 370     // Be polite to the other functions
 371     tty_setcolor (VIEW_NORMAL_COLOR);
 372 
 373     mcview_place_cursor (view);
 374     view->dpy_end = from;
 375 }
 376 
 377 /* --------------------------------------------------------------------------------------------- */
 378 
 379 gboolean
 380 mcview_hexedit_save_changes (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 381 {
 382     int answer = 0;
 383 
 384     if (view->change_list == NULL)
 385         return TRUE;
 386 
 387     while (answer == 0)
 388     {
 389         int fp;
 390         char *text;
 391         struct hexedit_change_node *curr, *next;
 392 
 393         g_assert (view->filename_vpath != NULL);
 394 
 395         fp = mc_open (view->filename_vpath, O_WRONLY);
 396         if (fp != -1)
 397         {
 398             for (curr = view->change_list; curr != NULL; curr = next)
 399             {
 400                 next = curr->next;
 401 
 402                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 403                     || mc_write (fp, &(curr->value), 1) != 1)
 404                     goto save_error;
 405 
 406                 // delete the saved item from the change list
 407                 view->change_list = next;
 408                 view->dirty++;
 409                 mcview_set_byte (view, curr->offset, curr->value);
 410                 g_free (curr);
 411             }
 412 
 413             view->change_list = NULL;
 414 
 415             if (view->locked)
 416                 view->locked = unlock_file (view->filename_vpath) != 0;
 417 
 418             if (mc_close (fp) == -1)
 419                 message (D_ERROR, _ ("Save file"),
 420                          _ ("Error while closing the file:\n%s\n"
 421                             "Data may have been written or not"),
 422                          unix_error_string (errno));
 423 
 424             view->dirty++;
 425             return TRUE;
 426         }
 427 
 428     save_error:
 429         text = g_strdup_printf (_ ("Cannot save file:\n%s"), unix_error_string (errno));
 430         (void) mc_close (fp);
 431 
 432         answer = query_dialog (_ ("Save file"), text, D_ERROR, 2, _ ("&Retry"), _ ("&Cancel"));
 433         g_free (text);
 434     }
 435 
 436     return FALSE;
 437 }
 438 
 439 /* --------------------------------------------------------------------------------------------- */
 440 
 441 void
 442 mcview_toggle_hexedit_mode (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 443 {
 444     view->hexedit_mode = !view->hexedit_mode;
 445     view->dpy_bbar_dirty = TRUE;
 446     view->dirty++;
 447 }
 448 
 449 /* --------------------------------------------------------------------------------------------- */
 450 
 451 void
 452 mcview_hexedit_free_change_list (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 453 {
 454     struct hexedit_change_node *curr, *next;
 455 
 456     for (curr = view->change_list; curr != NULL; curr = next)
 457     {
 458         next = curr->next;
 459         g_free (curr);
 460     }
 461     view->change_list = NULL;
 462 
 463     if (view->locked)
 464         view->locked = unlock_file (view->filename_vpath) != 0;
 465 
 466     view->dirty++;
 467 }
 468 
 469 /* --------------------------------------------------------------------------------------------- */
 470 
 471 void
 472 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 473 {
 474     /* chnode always either points to the head of the list or
 475      * to one of the ->next fields in the list. The value at
 476      * this location will be overwritten with the new node.   */
 477     struct hexedit_change_node **chnode = head;
 478 
 479     while (*chnode != NULL && (*chnode)->offset < node->offset)
 480         chnode = &((*chnode)->next);
 481 
 482     node->next = *chnode;
 483     *chnode = node;
 484 }
 485 
 486 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */