hex.c 16 KB

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