1 /*
2 Internal file viewer for the Midnight Commander
3 Interface functions
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, 2013
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 #include <errno.h>
38
39 #include "lib/global.h"
40 #include "lib/tty/tty.h"
41 #include "lib/vfs/vfs.h"
42 #include "lib/strutil.h"
43 #include "lib/util.h" /* load_file_position() */
44 #include "lib/widget.h"
45
46 #include "src/filemanager/layout.h"
47 #include "src/filemanager/filemanager.h" /* the_menubar */
48
49 #include "internal.h"
50
51 /*** global variables ****************************************************************************/
52
53 mcview_mode_flags_t mcview_global_flags = {
54 .wrap = TRUE,
55 .hex = FALSE,
56 .magic = TRUE,
57 .nroff = FALSE
58 };
59
60 mcview_mode_flags_t mcview_altered_flags = {
61 .wrap = FALSE,
62 .hex = FALSE,
63 .magic = FALSE,
64 .nroff = FALSE
65 };
66
67 gboolean mcview_remember_file_position = FALSE;
68
69 /* Maxlimit for skipping updates */
70 int mcview_max_dirt_limit = 10;
71
72 /* Scrolling is done in pages or line increments */
73 gboolean mcview_mouse_move_pages = TRUE;
74
75 /* end of file will be showen from mcview_show_eof */
76 char *mcview_show_eof = NULL;
77
78 /*** file scope macro definitions ****************************************************************/
79
80 /*** file scope type declarations ****************************************************************/
81
82 /*** file scope variables ************************************************************************/
83
84 /* --------------------------------------------------------------------------------------------- */
85 /*** file scope functions ************************************************************************/
86 /* --------------------------------------------------------------------------------------------- */
87
88 static void
mcview_mouse_callback(Widget * w,mouse_msg_t msg,mouse_event_t * event)89 mcview_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
90 {
91 WView *view = (WView *) w;
92 gboolean ok = TRUE;
93
94 switch (msg)
95 {
96 case MSG_MOUSE_DOWN:
97 if (mcview_is_in_panel (view))
98 {
99 if (event->y == WIDGET (w->owner)->y)
100 {
101 /* return MOU_UNHANDLED */
102 event->result.abort = TRUE;
103 /* don't draw viewer over menu */
104 ok = FALSE;
105 break;
106 }
107
108 if (!widget_get_state (w, WST_FOCUSED))
109 {
110 /* Grab focus */
111 (void) change_panel ();
112 }
113 }
114 MC_FALLTHROUGH;
115
116 case MSG_MOUSE_CLICK:
117 if (!view->mode_flags.wrap)
118 {
119 /* Scrolling left and right */
120 screen_dimen x;
121
122 x = event->x + 1; /* FIXME */
123
124 if (x < view->data_area.width * 1 / 4)
125 {
126 mcview_move_left (view, 1);
127 event->result.repeat = msg == MSG_MOUSE_DOWN;
128 }
129 else if (x < view->data_area.width * 3 / 4)
130 {
131 /* ignore the click */
132 ok = FALSE;
133 }
134 else
135 {
136 mcview_move_right (view, 1);
137 event->result.repeat = msg == MSG_MOUSE_DOWN;
138 }
139 }
140 else
141 {
142 /* Scrolling up and down */
143 screen_dimen y;
144
145 y = event->y + 1; /* FIXME */
146
147 if (y < view->data_area.top + view->data_area.height * 1 / 3)
148 {
149 if (mcview_mouse_move_pages)
150 mcview_move_up (view, view->data_area.height / 2);
151 else
152 mcview_move_up (view, 1);
153
154 event->result.repeat = msg == MSG_MOUSE_DOWN;
155 }
156 else if (y < view->data_area.top + view->data_area.height * 2 / 3)
157 {
158 /* ignore the click */
159 ok = FALSE;
160 }
161 else
162 {
163 if (mcview_mouse_move_pages)
164 mcview_move_down (view, view->data_area.height / 2);
165 else
166 mcview_move_down (view, 1);
167
168 event->result.repeat = msg == MSG_MOUSE_DOWN;
169 }
170 }
171 break;
172
173 case MSG_MOUSE_SCROLL_UP:
174 mcview_move_up (view, 2);
175 break;
176
177 case MSG_MOUSE_SCROLL_DOWN:
178 mcview_move_down (view, 2);
179 break;
180
181 default:
182 ok = FALSE;
183 break;
184 }
185
186 if (ok)
187 mcview_update (view);
188 }
189
190 /* --------------------------------------------------------------------------------------------- */
191 /*** public functions ****************************************************************************/
192 /* --------------------------------------------------------------------------------------------- */
193
194 WView *
mcview_new(int y,int x,int lines,int cols,gboolean is_panel)195 mcview_new (int y, int x, int lines, int cols, gboolean is_panel)
196 {
197 WView *view;
198 Widget *w;
199
200 view = g_new0 (WView, 1);
201 w = WIDGET (view);
202 widget_init (w, y, x, lines, cols, mcview_callback, mcview_mouse_callback);
203 w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
204 w->keymap = viewer_map;
205
206 mcview_clear_mode_flags (&view->mode_flags);
207 view->hexedit_mode = FALSE;
208 view->hex_keymap = viewer_hex_map;
209 view->hexview_in_text = FALSE;
210 view->locked = FALSE;
211
212 view->dpy_frame_size = is_panel ? 1 : 0;
213 view->converter = str_cnv_from_term;
214
215 mcview_init (view);
216
217 if (mcview_global_flags.hex)
218 mcview_toggle_hex_mode (view);
219 if (mcview_global_flags.nroff)
220 mcview_toggle_nroff_mode (view);
221 if (mcview_global_flags.wrap)
222 mcview_toggle_wrap_mode (view);
223 if (mcview_global_flags.magic)
224 mcview_toggle_magic_mode (view);
225
226 return view;
227 }
228
229 /* --------------------------------------------------------------------------------------------- */
230 /** Real view only */
231
232 gboolean
mcview_viewer(const char * command,const vfs_path_t * file_vpath,int start_line,off_t search_start,off_t search_end)233 mcview_viewer (const char *command, const vfs_path_t * file_vpath, int start_line,
234 off_t search_start, off_t search_end)
235 {
236 gboolean succeeded;
237 WView *lc_mcview;
238 WDialog *view_dlg;
239 Widget *vw, *b;
240 WGroup *g;
241
242 /* Create dialog and widgets, put them on the dialog */
243 view_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, mcview_dialog_callback,
244 NULL, "[Internal File Viewer]", NULL);
245 vw = WIDGET (view_dlg);
246 widget_want_tab (vw, TRUE);
247
248 g = GROUP (view_dlg);
249
250 lc_mcview = mcview_new (vw->y, vw->x, vw->lines - 1, vw->cols, FALSE);
251 group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
252
253 b = WIDGET (buttonbar_new ());
254 group_add_widget_autopos (g, b, b->pos_flags, NULL);
255
256 view_dlg->get_title = mcview_get_title;
257
258 succeeded =
259 mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line, search_start,
260 search_end);
261
262 if (succeeded)
263 dlg_run (view_dlg);
264 else
265 dlg_stop (view_dlg);
266
267 if (widget_get_state (vw, WST_CLOSED))
268 widget_destroy (vw);
269
270 return succeeded;
271 }
272
273 /* {{{ Miscellaneous functions }}} */
274
275 /* --------------------------------------------------------------------------------------------- */
276
277 gboolean
mcview_load(WView * view,const char * command,const char * file,int start_line,off_t search_start,off_t search_end)278 mcview_load (WView * view, const char *command, const char *file, int start_line,
279 off_t search_start, off_t search_end)
280 {
281 gboolean retval = FALSE;
282 vfs_path_t *vpath = NULL;
283
284 g_assert (view->bytes_per_line != 0);
285
286 view->filename_vpath = vfs_path_from_str (file);
287
288 /* get working dir */
289 if (file != NULL && file[0] != '\0')
290 {
291 vfs_path_free (view->workdir_vpath, TRUE);
292
293 if (!g_path_is_absolute (file))
294 {
295 vfs_path_t *p;
296
297 p = vfs_path_clone (vfs_get_raw_current_dir ());
298 view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
299 vfs_path_free (p, TRUE);
300 }
301 else
302 {
303 /* try extract path from filename */
304 const char *fname;
305 char *dir;
306
307 fname = x_basename (file);
308 dir = g_strndup (file, (size_t) (fname - file));
309 view->workdir_vpath = vfs_path_from_str (dir);
310 g_free (dir);
311 }
312 }
313
314 if (!mcview_is_in_panel (view))
315 view->dpy_text_column = 0;
316
317 #ifdef HAVE_CHARSET
318 mcview_set_codeset (view);
319 #endif
320
321 if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
322 retval = mcview_load_command_output (view, command);
323 else if (file != NULL && file[0] != '\0')
324 {
325 int fd;
326 char tmp[BUF_MEDIUM];
327 struct stat st;
328
329 /* Open the file */
330 vpath = vfs_path_from_str (file);
331 fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
332 if (fd == -1)
333 {
334 g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\"\n%s"),
335 file, unix_error_string (errno));
336 mcview_close_datasource (view);
337 mcview_show_error (view, tmp);
338 vfs_path_free (view->filename_vpath, TRUE);
339 view->filename_vpath = NULL;
340 vfs_path_free (view->workdir_vpath, TRUE);
341 view->workdir_vpath = NULL;
342 goto finish;
343 }
344
345 /* Make sure we are working with a regular file */
346 if (mc_fstat (fd, &st) == -1)
347 {
348 mc_close (fd);
349 g_snprintf (tmp, sizeof (tmp), _("Cannot stat \"%s\"\n%s"),
350 file, unix_error_string (errno));
351 mcview_close_datasource (view);
352 mcview_show_error (view, tmp);
353 vfs_path_free (view->filename_vpath, TRUE);
354 view->filename_vpath = NULL;
355 vfs_path_free (view->workdir_vpath, TRUE);
356 view->workdir_vpath = NULL;
357 goto finish;
358 }
359
360 if (!S_ISREG (st.st_mode))
361 {
362 mc_close (fd);
363 mcview_close_datasource (view);
364 mcview_show_error (view, _("Cannot view: not a regular file"));
365 vfs_path_free (view->filename_vpath, TRUE);
366 view->filename_vpath = NULL;
367 vfs_path_free (view->workdir_vpath, TRUE);
368 view->workdir_vpath = NULL;
369 goto finish;
370 }
371
372 if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
373 {
374 /* Must be one of those nice files that grow (/proc) */
375 mcview_set_datasource_vfs_pipe (view, fd);
376 }
377 else
378 {
379 if (view->mode_flags.magic)
380 {
381 int type;
382
383 type = get_compression_type (fd, file);
384
385 if (type != COMPRESSION_NONE)
386 {
387 char *tmp_filename;
388 vfs_path_t *vpath1;
389 int fd1;
390
391 tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
392 vpath1 = vfs_path_from_str (tmp_filename);
393 g_free (tmp_filename);
394 fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
395 vfs_path_free (vpath1, TRUE);
396
397 if (fd1 == -1)
398 {
399 g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\" in parse mode\n%s"),
400 file, unix_error_string (errno));
401 mcview_close_datasource (view);
402 mcview_show_error (view, tmp);
403 }
404 else
405 {
406 mc_close (fd);
407 fd = fd1;
408 mc_fstat (fd, &st);
409 }
410 }
411 }
412
413 mcview_set_datasource_file (view, fd, &st);
414 }
415 retval = TRUE;
416 }
417
418 finish:
419 view->command = g_strdup (command);
420 view->dpy_start = 0;
421 view->dpy_paragraph_skip_lines = 0;
422 mcview_state_machine_init (&view->dpy_state_top, 0);
423 view->dpy_wrap_dirty = FALSE;
424 view->force_max = -1;
425 view->dpy_text_column = 0;
426
427 mcview_compute_areas (view);
428 mcview_update_bytes_per_line (view);
429
430 if (mcview_remember_file_position && view->filename_vpath != NULL && start_line == 0)
431 {
432 long line, col;
433 off_t new_offset, max_offset;
434
435 load_file_position (view->filename_vpath, &line, &col, &new_offset, &view->saved_bookmarks);
436 max_offset = mcview_get_filesize (view) - 1;
437 if (max_offset < 0)
438 new_offset = 0;
439 else
440 new_offset = MIN (new_offset, max_offset);
441 if (!view->mode_flags.hex)
442 {
443 view->dpy_start = mcview_bol (view, new_offset, 0);
444 view->dpy_wrap_dirty = TRUE;
445 }
446 else
447 {
448 view->dpy_start = new_offset - new_offset % view->bytes_per_line;
449 view->hex_cursor = new_offset;
450 }
451 }
452 else if (start_line > 0)
453 mcview_moveto (view, start_line - 1, 0);
454
455 view->search_start = search_start;
456 view->search_end = search_end;
457 view->hexedit_lownibble = FALSE;
458 view->hexview_in_text = FALSE;
459 view->change_list = NULL;
460 vfs_path_free (vpath, TRUE);
461 return retval;
462 }
463
464 /* --------------------------------------------------------------------------------------------- */
465