1 /*
2 Internal file viewer for the Midnight Commander
3 Function for hex view
4
5 Copyright (C) 1994-2021
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
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 <http://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 /*** file scope variables ************************************************************************/
69
70 static const char hex_char[] = "0123456789ABCDEF";
71
72 /*** file scope functions ************************************************************************/
73 /* --------------------------------------------------------------------------------------------- */
74
75 /* --------------------------------------------------------------------------------------------- */
76 /** Determine the state of the current byte.
77 *
78 * @param view viewer object
79 * @param from offset
80 * @param curr current node
81 */
82
83 static mark_t
mcview_hex_calculate_boldflag(WView * view,off_t from,struct hexedit_change_node * curr,gboolean force_changed)84 mcview_hex_calculate_boldflag (WView * view, off_t from, struct hexedit_change_node *curr,
85 gboolean force_changed)
86 {
87 return (from == view->hex_cursor) ? MARK_CURSOR
88 : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
89 : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
90 }
91
92 /* --------------------------------------------------------------------------------------------- */
93 /*** public functions ****************************************************************************/
94 /* --------------------------------------------------------------------------------------------- */
95
96 void
mcview_display_hex(WView * view)97 mcview_display_hex (WView * view)
98 {
99 const screen_dimen top = view->data_area.top;
100 const screen_dimen left = view->data_area.left;
101 const screen_dimen height = view->data_area.height;
102 const screen_dimen width = view->data_area.width;
103 const int ngroups = view->bytes_per_line / 4;
104 /* 8 characters are used for the file offset, and every hex group
105 * takes 13 characters. Starting at width of 80 columns, the groups
106 * are separated by an extra vertical line. Starting at width of 81,
107 * there is an extra space before the text column. There is always a
108 * mostly empty column on the right, to allow overflowing CJKs.
109 */
110 const screen_dimen text_start = 8 + 13 * ngroups +
111 ((width < 80) ? 0 : (width == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
112
113 int row;
114 off_t from;
115 mark_t boldflag_byte = MARK_NORMAL;
116 mark_t boldflag_char = MARK_NORMAL;
117 struct hexedit_change_node *curr = view->change_list;
118 #ifdef HAVE_CHARSET
119 int cont_bytes = 0; /* number of continuation bytes remanining from current UTF-8 */
120 gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
121 #endif /* HAVE_CHARSET */
122 gboolean utf8_changed = FALSE; /* whether any of the bytes in the UTF-8 were changed */
123
124 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
125
126 mcview_display_clean (view);
127
128 /* Find the first displayable changed byte */
129 /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
130 from = view->dpy_start;
131 row = 0;
132 #ifdef HAVE_CHARSET
133 if (view->utf8)
134 {
135 if (from >= view->bytes_per_line)
136 {
137 row--;
138 from -= view->bytes_per_line;
139 }
140 if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
141 {
142 row--;
143 from -= view->bytes_per_line;
144 }
145 }
146 #endif /* HAVE_CHARSET */
147 while (curr && (curr->offset < from))
148 {
149 curr = curr->next;
150 }
151
152 for (; mcview_get_byte (view, from, NULL) && row < (int) height; row++)
153 {
154 screen_dimen col = 0;
155 int bytes; /* Number of bytes already printed on the line */
156
157 /* Print the hex offset */
158 if (row >= 0)
159 {
160 size_t i;
161
162 g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
163 widget_gotoyx (view, top + row, left);
164 tty_setcolor (VIEW_BOLD_COLOR);
165 for (i = 0; col < width && hex_buff[i] != '\0'; col++, i++)
166 tty_print_char (hex_buff[i]);
167 tty_setcolor (VIEW_NORMAL_COLOR);
168 }
169
170 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
171 {
172 int c;
173 #ifdef HAVE_CHARSET
174 int ch = 0;
175
176 if (view->utf8)
177 {
178 struct hexedit_change_node *corr = curr;
179
180 if (cont_bytes != 0)
181 {
182 /* UTF-8 continuation bytes, print a space (with proper attributes)... */
183 cont_bytes--;
184 ch = ' ';
185 if (cjk_right)
186 {
187 /* ... except when it'd wipe out the right half of a CJK, then print nothing */
188 cjk_right = FALSE;
189 ch = -1;
190 }
191 }
192 else
193 {
194 int j;
195 gchar utf8buf[UTF8_CHAR_LEN + 1];
196 int res;
197 int first_changed = -1;
198
199 for (j = 0; j < UTF8_CHAR_LEN; j++)
200 {
201 if (mcview_get_byte (view, from + j, &res))
202 utf8buf[j] = res;
203 else
204 {
205 utf8buf[j] = '\0';
206 break;
207 }
208 if (curr != NULL && from + j == curr->offset)
209 {
210 utf8buf[j] = curr->value;
211 if (first_changed == -1)
212 first_changed = j;
213 }
214 if (curr != NULL && from + j >= curr->offset)
215 curr = curr->next;
216 }
217 utf8buf[UTF8_CHAR_LEN] = '\0';
218
219 /* Determine the state of the current multibyte char */
220 ch = g_utf8_get_char_validated (utf8buf, -1);
221 if (ch == -1 || ch == -2)
222 {
223 ch = '.';
224 }
225 else
226 {
227 gchar *next_ch;
228
229 next_ch = g_utf8_next_char (utf8buf);
230 cont_bytes = next_ch - utf8buf - 1;
231 if (g_unichar_iswide (ch))
232 cjk_right = TRUE;
233 }
234
235 utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
236 curr = corr;
237 }
238 }
239 #endif /* HAVE_CHARSET */
240
241 /* For negative rows, the only thing we care about is overflowing
242 * UTF-8 continuation bytes which were handled above. */
243 if (row < 0)
244 {
245 if (curr != NULL && from == curr->offset)
246 curr = curr->next;
247 continue;
248 }
249
250 if (!mcview_get_byte (view, from, &c))
251 break;
252
253 /* Save the cursor position for mcview_place_cursor() */
254 if (from == view->hex_cursor && !view->hexview_in_text)
255 {
256 view->cursor_row = row;
257 view->cursor_col = col;
258 }
259
260 /* Determine the state of the current byte */
261 boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
262 boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
263
264 /* Determine the value of the current byte */
265 if (curr != NULL && from == curr->offset)
266 {
267 c = curr->value;
268 curr = curr->next;
269 }
270
271 /* Select the color for the hex number */
272 tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
273 boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
274 boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
275 /* boldflag_byte == MARK_CURSOR */
276 view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
277
278 /* Print the hex number */
279 widget_gotoyx (view, top + row, left + col);
280 if (col < width)
281 {
282 tty_print_char (hex_char[c / 16]);
283 col += 1;
284 }
285 if (col < width)
286 {
287 tty_print_char (hex_char[c % 16]);
288 col += 1;
289 }
290
291 /* Print the separator */
292 tty_setcolor (VIEW_NORMAL_COLOR);
293 if (bytes != view->bytes_per_line - 1)
294 {
295 if (col < width)
296 {
297 tty_print_char (' ');
298 col += 1;
299 }
300
301 /* After every four bytes, print a group separator */
302 if (bytes % 4 == 3)
303 {
304 if (view->data_area.width >= 80 && col < width)
305 {
306 tty_print_one_vline (TRUE);
307 col += 1;
308 }
309 if (col < width)
310 {
311 tty_print_char (' ');
312 col += 1;
313 }
314 }
315 }
316
317 /* Select the color for the character; this differs from the
318 * hex color when boldflag == MARK_CURSOR */
319 tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
320 boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
321 boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
322 /* boldflag_char == MARK_CURSOR */
323 view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
324
325
326 #ifdef HAVE_CHARSET
327 if (mc_global.utf8_display)
328 {
329 if (!view->utf8)
330 {
331 c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
332 }
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 < width)
351 {
352 widget_gotoyx (view, top + row, left + 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
mcview_hexedit_save_changes(WView * view)380 mcview_hexedit_save_changes (WView * view)
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);
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"), unix_error_string (errno));
422
423 view->dirty++;
424 return TRUE;
425 }
426
427 save_error:
428 text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
429 (void) mc_close (fp);
430
431 answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
432 g_free (text);
433 }
434
435 return FALSE;
436 }
437
438 /* --------------------------------------------------------------------------------------------- */
439
440 void
mcview_toggle_hexedit_mode(WView * view)441 mcview_toggle_hexedit_mode (WView * view)
442 {
443 view->hexedit_mode = !view->hexedit_mode;
444 view->dpy_bbar_dirty = TRUE;
445 view->dirty++;
446 }
447
448 /* --------------------------------------------------------------------------------------------- */
449
450 void
mcview_hexedit_free_change_list(WView * view)451 mcview_hexedit_free_change_list (WView * view)
452 {
453 struct hexedit_change_node *curr, *next;
454
455 for (curr = view->change_list; curr != NULL; curr = next)
456 {
457 next = curr->next;
458 g_free (curr);
459 }
460 view->change_list = NULL;
461
462 if (view->locked)
463 view->locked = unlock_file (view->filename_vpath);
464
465 view->dirty++;
466 }
467
468 /* --------------------------------------------------------------------------------------------- */
469
470 void
mcview_enqueue_change(struct hexedit_change_node ** head,struct hexedit_change_node * node)471 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
472 {
473 /* chnode always either points to the head of the list or
474 * to one of the ->next fields in the list. The value at
475 * this location will be overwritten with the new node. */
476 struct hexedit_change_node **chnode = head;
477
478 while (*chnode != NULL && (*chnode)->offset < node->offset)
479 chnode = &((*chnode)->next);
480
481 node->next = *chnode;
482 *chnode = node;
483 }
484
485 /* --------------------------------------------------------------------------------------------- */
486