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