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 (VIEW_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 (VIEW_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 ? VIEW_NORMAL_COLOR
265 : boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR
266 : boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR
267 :
268 // boldflag_byte == MARK_CURSOR
269 view->hexview_in_text ? VIEW_SELECTED_COLOR
270 : VIEW_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 (VIEW_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_print_one_vline (TRUE);
301 col++;
302 }
303 if (col < r->cols)
304 {
305 tty_print_char (' ');
306 col++;
307 }
308 }
309 }
310
311 /* Select the color for the character; this differs from the
312 * hex color when boldflag == MARK_CURSOR */
313 tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR
314 : boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR
315 : boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR
316 :
317 // boldflag_char == MARK_CURSOR
318 view->hexview_in_text ? VIEW_SELECTED_COLOR
319 : MARKED_SELECTED_COLOR);
320
321 if (mc_global.utf8_display)
322 {
323 if (!view->utf8)
324 c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
325 if (!g_unichar_isprint (c))
326 c = '.';
327 }
328 else if (view->utf8)
329 ch = convert_from_utf_to_current_c (ch, view->converter);
330 else
331 {
332 c = convert_to_display_c (c);
333
334 if (!is_printable (c))
335 c = '.';
336 }
337
338 // Print corresponding character on the text side
339 if (text_start + bytes < r->cols)
340 {
341 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
342 if (view->utf8)
343 tty_print_anychar (ch);
344 else
345 tty_print_char (c);
346 }
347
348 // Save the cursor position for mcview_place_cursor()
349 if (from == view->hex_cursor && view->hexview_in_text)
350 {
351 view->cursor_row = row;
352 view->cursor_col = text_start + bytes;
353 }
354 }
355 }
356
357 // Be polite to the other functions
358 tty_setcolor (VIEW_NORMAL_COLOR);
359
360 mcview_place_cursor (view);
361 view->dpy_end = from;
362 }
363
364 /* --------------------------------------------------------------------------------------------- */
365
366 gboolean
367 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)
*/
368 {
369 int answer = 0;
370
371 if (view->change_list == NULL)
372 return TRUE;
373
374 while (answer == 0)
375 {
376 int fp;
377 char *text;
378 struct hexedit_change_node *curr, *next;
379
380 g_assert (view->filename_vpath != NULL);
381
382 fp = mc_open (view->filename_vpath, O_WRONLY);
383 if (fp != -1)
384 {
385 for (curr = view->change_list; curr != NULL; curr = next)
386 {
387 next = curr->next;
388
389 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
390 || mc_write (fp, &(curr->value), 1) != 1)
391 goto save_error;
392
393 // delete the saved item from the change list
394 view->change_list = next;
395 view->dirty++;
396 mcview_set_byte (view, curr->offset, curr->value);
397 g_free (curr);
398 }
399
400 view->change_list = NULL;
401
402 if (view->locked)
403 view->locked = unlock_file (view->filename_vpath) != 0;
404
405 if (mc_close (fp) == -1)
406 message (D_ERROR, _ ("Save file"),
407 _ ("Error while closing the file:\n%s\n"
408 "Data may have been written or not"),
409 unix_error_string (errno));
410
411 view->dirty++;
412 return TRUE;
413 }
414
415 save_error:
416 text = g_strdup_printf (_ ("Cannot save file:\n%s"), unix_error_string (errno));
417 (void) mc_close (fp);
418
419 answer = query_dialog (_ ("Save file"), text, D_ERROR, 2, _ ("&Retry"), _ ("&Cancel"));
420 g_free (text);
421 }
422
423 return FALSE;
424 }
425
426 /* --------------------------------------------------------------------------------------------- */
427
428 void
429 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)
*/
430 {
431 view->hexedit_mode = !view->hexedit_mode;
432 view->dpy_bbar_dirty = TRUE;
433 view->dirty++;
434 }
435
436 /* --------------------------------------------------------------------------------------------- */
437
438 void
439 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)
*/
440 {
441 struct hexedit_change_node *curr, *next;
442
443 for (curr = view->change_list; curr != NULL; curr = next)
444 {
445 next = curr->next;
446 g_free (curr);
447 }
448 view->change_list = NULL;
449
450 if (view->locked)
451 view->locked = unlock_file (view->filename_vpath) != 0;
452
453 view->dirty++;
454 }
455
456 /* --------------------------------------------------------------------------------------------- */
457
458 void
459 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)
*/
460 {
461 /* chnode always either points to the head of the list or
462 * to one of the ->next fields in the list. The value at
463 * this location will be overwritten with the new node. */
464 struct hexedit_change_node **chnode = head;
465
466 while (*chnode != NULL && (*chnode)->offset < node->offset)
467 chnode = &((*chnode)->next);
468
469 node->next = *chnode;
470 *chnode = node;
471 }
472
473 /* --------------------------------------------------------------------------------------------- */