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 #include "lib/charsets.h"
49
50 #include "internal.h"
51
52 /*** global variables ****************************************************************************/
53
54 /*** file scope macro definitions ****************************************************************/
55
56 /*** file scope type declarations ****************************************************************/
57
58 typedef enum
59 {
60 MARK_NORMAL,
61 MARK_SELECTED,
62 MARK_CURSOR,
63 MARK_CHANGED
64 } mark_t;
65
66 /*** forward declarations (file scope functions) *************************************************/
67
68 /*** file scope variables ************************************************************************/
69
70 static const char hex_char[] = "0123456789ABCDEF";
71
72 /* --------------------------------------------------------------------------------------------- */
73 /*** file scope functions ************************************************************************/
74 /* --------------------------------------------------------------------------------------------- */
75 /** Determine the state of the current byte.
76 *
77 * @param view viewer object
78 * @param from offset
79 * @param curr current node
80 */
81
82 static mark_t
83 mcview_hex_calculate_boldflag (WView *view, off_t from, struct hexedit_change_node *curr,
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
84 gboolean force_changed)
85 {
86 return (from == view->hex_cursor) ? MARK_CURSOR
87 : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
88 : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED
89 : MARK_NORMAL;
90 }
91
92 /* --------------------------------------------------------------------------------------------- */
93 /*** public functions ****************************************************************************/
94 /* --------------------------------------------------------------------------------------------- */
95
96 void
97 mcview_display_hex (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
98 {
99 const WRect *r = &view->data_area;
100 int ngroups = view->bytes_per_line / 4;
101 /* 8 characters are used for the file offset, and every hex group
102 * takes 13 characters. Starting at width of 80 columns, the groups
103 * are separated by an extra vertical line. Starting at width of 81,
104 * there is an extra space before the text column. There is always a
105 * mostly empty column on the right, to allow overflowing CJKs.
106 */
107 int text_start;
108
109 int row = 0;
110 off_t from;
111 mark_t boldflag_byte = MARK_NORMAL;
112 mark_t boldflag_char = MARK_NORMAL;
113 struct hexedit_change_node *curr = view->change_list;
114 int cont_bytes = 0; // number of continuation bytes remanining from current UTF-8
115 gboolean cjk_right = FALSE; // whether the second byte of a CJK is to be processed
116 gboolean utf8_changed = FALSE; // whether any of the bytes in the UTF-8 were changed
117
118 char hex_buff[10]; // A temporary buffer for sprintf and mvwaddstr
119
120 text_start = 8 + 13 * ngroups;
121 if (r->cols == 80)
122 text_start += ngroups - 1;
123 else if (r->cols > 80)
124 text_start += ngroups;
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 if (view->utf8)
132 {
133 if (from >= view->bytes_per_line)
134 {
135 row--;
136 from -= view->bytes_per_line;
137 }
138 if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
139 {
140 row--;
141 from -= view->bytes_per_line;
142 }
143 }
144
145 while (curr != NULL && (curr->offset < from))
146 curr = curr->next;
147
148 for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
149 {
150 int col = 0;
151 int bytes; // Number of bytes already printed on the line
152
153 // Print the hex offset
154 if (row >= 0)
155 {
156 int i;
157
158 g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
159 widget_gotoyx (view, r->y + row, r->x);
160 tty_setcolor (VIEWER_BOLD_COLOR);
161 for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
162 tty_print_char (hex_buff[i]);
163 tty_setcolor (VIEWER_NORMAL_COLOR);
164 }
165
166 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
167 {
168 int c;
169 int ch = 0;
170
171 if (view->utf8)
172 {
173 struct hexedit_change_node *corr = curr;
174
175 if (cont_bytes != 0)
176 {
177 // UTF-8 continuation bytes, print a space (with proper attributes)...
178 cont_bytes--;
179 ch = ' ';
180 if (cjk_right)
181 {
182 // ... except when it'd wipe out the right half of a CJK, then print nothing
183 cjk_right = FALSE;
184 ch = -1;
185 }
186 }
187 else
188 {
189 int j;
190 gchar utf8buf[MB_LEN_MAX + 1];
191 int res;
192 int first_changed = -1;
193
194 for (j = 0; j < MB_LEN_MAX; j++)
195 {
196 if (mcview_get_byte (view, from + j, &res))
197 utf8buf[j] = res;
198 else
199 {
200 utf8buf[j] = '\0';
201 break;
202 }
203 if (curr != NULL && from + j == curr->offset)
204 {
205 utf8buf[j] = curr->value;
206 if (first_changed == -1)
207 first_changed = j;
208 }
209 if (curr != NULL && from + j >= curr->offset)
210 curr = curr->next;
211 }
212 utf8buf[MB_LEN_MAX] = '\0';
213
214 // Determine the state of the current multibyte char
215 ch = g_utf8_get_char_validated (utf8buf, -1);
216 if (ch == -1 || ch == -2)
217 ch = '.';
218 else
219 {
220 gchar *next_ch;
221
222 next_ch = g_utf8_next_char (utf8buf);
223 cont_bytes = next_ch - utf8buf - 1;
224 if (g_unichar_iswide (ch))
225 cjk_right = TRUE;
226 }
227
228 utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
229 curr = corr;
230 }
231 }
232
233 /* For negative rows, the only thing we care about is overflowing
234 * UTF-8 continuation bytes which were handled above. */
235 if (row < 0)
236 {
237 if (curr != NULL && from == curr->offset)
238 curr = curr->next;
239 continue;
240 }
241
242 if (!mcview_get_byte (view, from, &c))
243 break;
244
245 // Save the cursor position for mcview_place_cursor()
246 if (from == view->hex_cursor && !view->hexview_in_text)
247 {
248 view->cursor_row = row;
249 view->cursor_col = col;
250 }
251
252 // Determine the state of the current byte
253 boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
254 boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
255
256 // Determine the value of the current byte
257 if (curr != NULL && from == curr->offset)
258 {
259 c = curr->value;
260 curr = curr->next;
261 }
262
263 // Select the color for the hex number
264 tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEWER_NORMAL_COLOR
265 : boldflag_byte == MARK_SELECTED ? VIEWER_BOLD_COLOR
266 : boldflag_byte == MARK_CHANGED ? VIEWER_UNDERLINED_COLOR
267 :
268 // boldflag_byte == MARK_CURSOR
269 view->hexview_in_text ? VIEWER_SELECTED_COLOR
270 : VIEWER_UNDERLINED_COLOR);
271
272 // Print the hex number
273 widget_gotoyx (view, r->y + row, r->x + col);
274 if (col < r->cols)
275 {
276 tty_print_char (hex_char[c / 16]);
277 col++;
278 }
279 if (col < r->cols)
280 {
281 tty_print_char (hex_char[c % 16]);
282 col++;
283 }
284
285 // Print the separator
286 tty_setcolor (VIEWER_NORMAL_COLOR);
287 if (bytes != view->bytes_per_line - 1)
288 {
289 if (col < r->cols)
290 {
291 tty_print_char (' ');
292 col++;
293 }
294
295 // After every four bytes, print a group separator
296 if (bytes % 4 == 3)
297 {
298 if (view->data_area.cols >= 80 && col < r->cols)
299 {
300 tty_setcolor (VIEWER_FRAME_COLOR);
301 tty_print_one_vline (TRUE);
302 tty_setcolor (VIEWER_NORMAL_COLOR);
303 col++;
304 }
305 if (col < r->cols)
306 {
307 tty_print_char (' ');
308 col++;
309 }
310 }
311 }
312
313 /* Select the color for the character; this differs from the
314 * hex color when boldflag == MARK_CURSOR */
315 tty_setcolor (boldflag_char == MARK_NORMAL ? VIEWER_NORMAL_COLOR
316 : boldflag_char == MARK_SELECTED ? VIEWER_BOLD_COLOR
317 : boldflag_char == MARK_CHANGED ? VIEWER_UNDERLINED_COLOR
318 :
319 // boldflag_char == MARK_CURSOR
320 view->hexview_in_text ? VIEWER_SELECTED_COLOR
321 : CORE_MARKED_SELECTED_COLOR);
322
323 if (mc_global.utf8_display)
324 {
325 if (!view->utf8)
326 c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
327 if (!g_unichar_isprint (c))
328 c = '.';
329 }
330 else if (view->utf8)
331 ch = convert_from_utf_to_current_c (ch, view->converter);
332 else
333 {
334 c = convert_to_display_c (c);
335
336 if (!is_printable (c))
337 c = '.';
338 }
339
340 // Print corresponding character on the text side
341 if (text_start + bytes < r->cols)
342 {
343 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
344 if (view->utf8)
345 tty_print_anychar (ch);
346 else
347 tty_print_char (c);
348 }
349
350 // Save the cursor position for mcview_place_cursor()
351 if (from == view->hex_cursor && view->hexview_in_text)
352 {
353 view->cursor_row = row;
354 view->cursor_col = text_start + bytes;
355 }
356 }
357 }
358
359 // Be polite to the other functions
360 tty_setcolor (VIEWER_NORMAL_COLOR);
361
362 mcview_place_cursor (view);
363 view->dpy_end = from;
364 }
365
366 /* --------------------------------------------------------------------------------------------- */
367
368 gboolean
369 mcview_hexedit_save_changes (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
370 {
371 int answer = 0;
372
373 if (view->change_list == NULL)
374 return TRUE;
375
376 while (answer == 0)
377 {
378 int fp;
379 char *text;
380 struct hexedit_change_node *curr, *next;
381
382 g_assert (view->filename_vpath != NULL);
383
384 fp = mc_open (view->filename_vpath, O_WRONLY);
385 if (fp != -1)
386 {
387 for (curr = view->change_list; curr != NULL; curr = next)
388 {
389 next = curr->next;
390
391 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
392 || mc_write (fp, &(curr->value), 1) != 1)
393 goto save_error;
394
395 // delete the saved item from the change list
396 view->change_list = next;
397 view->dirty++;
398 mcview_set_byte (view, curr->offset, curr->value);
399 g_free (curr);
400 }
401
402 view->change_list = NULL;
403
404 if (view->locked)
405 view->locked = unlock_file (view->filename_vpath) != 0;
406
407 if (mc_close (fp) == -1)
408 message (D_ERROR, _ ("Save file"),
409 _ ("Error while closing the file:\n%s\n"
410 "Data may have been written or not"),
411 unix_error_string (errno));
412
413 view->dirty++;
414 return TRUE;
415 }
416
417 save_error:
418 text = g_strdup_printf (_ ("Cannot save file:\n%s"), unix_error_string (errno));
419 (void) mc_close (fp);
420
421 answer = query_dialog (_ ("Save file"), text, D_ERROR, 2, _ ("&Retry"), _ ("&Cancel"));
422 g_free (text);
423 }
424
425 return FALSE;
426 }
427
428 /* --------------------------------------------------------------------------------------------- */
429
430 void
431 mcview_toggle_hexedit_mode (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
432 {
433 view->hexedit_mode = !view->hexedit_mode;
434 view->dpy_bbar_dirty = TRUE;
435 view->dirty++;
436 }
437
438 /* --------------------------------------------------------------------------------------------- */
439
440 void
441 mcview_hexedit_free_change_list (WView *view)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
442 {
443 struct hexedit_change_node *curr, *next;
444
445 for (curr = view->change_list; curr != NULL; curr = next)
446 {
447 next = curr->next;
448 g_free (curr);
449 }
450 view->change_list = NULL;
451
452 if (view->locked)
453 view->locked = unlock_file (view->filename_vpath) != 0;
454
455 view->dirty++;
456 }
457
458 /* --------------------------------------------------------------------------------------------- */
459
460 void
461 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
462 {
463 /* chnode always either points to the head of the list or
464 * to one of the ->next fields in the list. The value at
465 * this location will be overwritten with the new node. */
466 struct hexedit_change_node **chnode = head;
467
468 while (*chnode != NULL && (*chnode)->offset < node->offset)
469 chnode = &((*chnode)->next);
470
471 node->next = *chnode;
472 *chnode = node;
473 }
474
475 /* --------------------------------------------------------------------------------------------- */