* view.c (view_update_bytes_per_line): Don't use vertical bars
[midnight-commander.git] / src / user.c
blob00511f241a68a03588db4db2131c9e77527604be
1 /* User Menu implementation
2 Copyright (C) 1994 Miguel de Icaza, Janne Kukonlehto
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 #include <config.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <errno.h>
24 #include "global.h"
25 #include "tty.h"
26 #include "dialog.h"
27 #include "color.h"
28 #include "dir.h"
29 #include "panel.h"
30 #include "main.h"
31 #include "subshell.h" /* for subshell_pty */
32 #include "user.h"
33 #include "layout.h"
34 #include "setup.h"
35 #include "../vfs/vfs.h"
37 /* For the simple listbox manager */
38 #include "dlg.h"
39 #include "widget.h"
40 #include "wtools.h"
42 #include "view.h" /* for default_* externs */
44 #define MAX_ENTRIES 16
45 #define MAX_ENTRY_LEN 60
47 static int debug_flag = 0;
48 static int debug_error = 0;
49 static char *menu = NULL;
51 /* Formats defined:
52 %% The % character
53 %f The current file (if non-local vfs, file will be copied locally and
54 %f will be full path to it).
55 %p The current file
56 %d The current working directory
57 %s "Selected files"; the tagged files if any, otherwise the current file
58 %t Tagged files
59 %u Tagged files (and they are untagged on return from expand_format)
60 %view Runs the commands and pipes standard output to the view command.
61 If %view is immediately followed by '{', recognize keywords
62 ascii, hex, nroff and unform
64 If the format letter is in uppercase, it refers to the other panel.
66 With a number followed the % character you can turn quoting on (default)
67 and off. For example:
68 %f quote expanded macro
69 %1f ditto
70 %0f don't quote expanded macro
72 expand_format returns a memory block that must be free()d.
75 /* Returns how many characters we should advance if %view was found */
76 int check_format_view (const char *p)
78 const char *q = p;
79 if (!strncmp (p, "view", 4)) {
80 q += 4;
81 if (*q == '{'){
82 for (q++;*q && *q != '}';q++) {
83 if (!strncmp (q, "ascii", 5)) {
84 default_hex_mode = 0;
85 q += 4;
86 } else if (!strncmp (q, "hex", 3)) {
87 default_hex_mode = 1;
88 q += 2;
89 } else if (!strncmp (q, "nroff", 5)) {
90 default_nroff_flag = 1;
91 q += 4;
92 } else if (!strncmp (q, "unform", 6)) {
93 default_nroff_flag = 0;
94 q += 5;
97 if (*q == '}')
98 q++;
100 return q - p;
102 return 0;
105 int check_format_cd (const char *p)
107 return (strncmp (p, "cd", 2)) ? 0 : 3;
110 /* Check if p has a "^var\{var-name\}" */
111 /* Returns the number of skipped characters (zero on not found) */
112 /* V will be set to the expanded variable name */
113 int check_format_var (const char *p, char **v)
115 const char *q = p;
116 char *var_name;
117 char *value;
118 const char *dots = 0;
120 *v = 0;
121 if (!strncmp (p, "var{", 4)){
122 for (q += 4; *q && *q != '}'; q++){
123 if (*q == ':')
124 dots = q+1;
126 if (!*q)
127 return 0;
129 if (!dots || dots == q+5){
130 message (1,
131 _(" Format error on file Extensions File "),
132 !dots ? _(" The %%var macro has no default ")
133 : _(" The %%var macro has no variable "));
134 return 0;
137 /* Copy the variable name */
138 var_name = g_malloc (dots - p);
139 strncpy (var_name, p+4, dots-2 - (p+3));
140 var_name [dots-2 - (p+3)] = 0;
142 value = getenv (var_name);
143 g_free (var_name);
144 if (value){
145 *v = g_strdup (value);
146 return q-p;
148 var_name = g_malloc (q - dots + 1);
149 strncpy (var_name, dots, q - dots + 1);
150 var_name [q-dots] = 0;
151 *v = var_name;
152 return q-p;
154 return 0;
157 /* strip file's extension */
158 static char *
159 strip_ext(char *ss)
161 register char *s = ss;
162 char *e = NULL;
163 while(*s) {
164 if(*s == '.') e = s;
165 if(*s == PATH_SEP && e) e=NULL; /* '.' in *directory* name */
166 s++;
168 if(e) *e = 0;
169 return ss;
172 char *
173 expand_format (WEdit * edit_widget, char c, int quote)
175 WPanel *panel;
176 char *(*quote_func) (const char *, int);
177 char *fname;
179 if (c == '%')
180 return g_strdup ("%");
182 if (islower ((unsigned) c))
183 panel = cpanel;
184 else {
185 if (get_other_type () != view_listing)
186 return g_strdup ("");
187 panel = other_panel;
189 if (!panel)
190 panel = cpanel;
192 if (quote)
193 quote_func = name_quote;
194 else
195 quote_func = fake_name_quote;
197 c = tolower (c);
198 fname = panel->dir.list[panel->selected].fname;
200 switch (c) {
201 case 'f':
202 case 'p':
203 return (*quote_func) (fname, 0);
204 case 'x':
205 return (*quote_func) (extension (fname), 0);
206 case 'd':
207 return (*quote_func) (panel->cwd, 0);
208 case 'i': /* indent equal number cursor position in line */
209 if (edit_widget)
210 return g_strnfill (edit_widget->curs_col, ' ');
211 break;
212 case 'y': /* syntax type */
213 if (edit_widget && edit_widget->syntax_type)
214 return g_strdup (edit_widget->syntax_type);
215 break;
216 case 'k': /* block file name */
217 case 'b': /* block file name / strip extension */ {
218 if (edit_widget) {
219 char *file = g_strconcat (home_dir, BLOCK_FILE, NULL);
220 fname = (*quote_func) (file, 0);
221 g_free (file);
222 return fname;
223 } else if (c == 'b') {
224 return strip_ext ((*quote_func) (fname, 0));
226 break;
228 case 'n': /* strip extension in editor */
229 if (edit_widget)
230 return strip_ext ((*quote_func) (fname, 0));
231 break;
232 case 'm': /* menu file name */
233 if (menu)
234 return (*quote_func) (menu, 0);
235 break;
236 case 's':
237 if (!panel->marked)
238 return (*quote_func) (fname, 0);
240 /* Fall through */
242 case 't':
243 case 'u':
245 int length = 2, i;
246 char *block, *tmp;
248 for (i = 0; i < panel->count; i++)
249 if (panel->dir.list[i].f.marked)
250 length += strlen (panel->dir.list[i].fname) + 1; /* for space */
252 block = g_malloc (length * 2 + 1);
253 *block = 0;
254 for (i = 0; i < panel->count; i++)
255 if (panel->dir.list[i].f.marked) {
256 strcat (block, tmp =
257 (*quote_func) (panel->dir.list[i].fname, 0));
258 g_free (tmp);
259 strcat (block, " ");
260 if (c == 'u')
261 do_file_mark (panel, i, 0);
263 return block;
264 } /* sub case block */
265 } /* switch */
266 return g_strdup ("");
270 * Check for the "shell_patterns" directive. If it's found and valid,
271 * interpret it and move the pointer past the directive. Return the
272 * current pointer.
274 static char *
275 check_patterns (char *p)
277 static const char def_name[] = "shell_patterns=";
278 char *p0 = p;
280 if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
281 return p0;
283 p += sizeof (def_name) - 1;
284 if (*p == '1')
285 easy_patterns = 1;
286 else if (*p == '0')
287 easy_patterns = 0;
288 else
289 return p0;
291 /* Skip spaces */
292 p++;
293 while (*p == '\n' || *p == '\t' || *p == ' ')
294 p++;
295 return p;
298 /* Copies a whitespace separated argument from p to arg. Returns the
299 point after argument. */
300 static char *extract_arg (char *p, char *arg)
302 while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
303 p++;
304 /* support quote space .mnu */
305 while (*p && (*p != ' ' || *(p-1) == '\\') && *p != '\t' && *p != '\n')
306 *arg++ = *p++;
307 *arg = 0;
308 if (!*p || *p == '\n')
309 p --;
310 return p;
313 /* Tests whether the selected file in the panel is of any of the types
314 specified in argument. */
315 static int test_type (WPanel *panel, char *arg)
317 int result = 0; /* False by default */
318 int st_mode = panel->dir.list [panel->selected].buf.st_mode;
320 for (;*arg != 0; arg++){
321 switch (*arg){
322 case 'n': /* Not a directory */
323 result |= !S_ISDIR (st_mode);
324 break;
325 case 'r': /* Regular file */
326 result |= S_ISREG (st_mode);
327 break;
328 case 'd': /* Directory */
329 result |= S_ISDIR (st_mode);
330 break;
331 case 'l': /* Link */
332 result |= S_ISLNK (st_mode);
333 break;
334 case 'c': /* Character special */
335 result |= S_ISCHR (st_mode);
336 break;
337 case 'b': /* Block special */
338 result |= S_ISBLK (st_mode);
339 break;
340 case 'f': /* Fifo (named pipe) */
341 result |= S_ISFIFO (st_mode);
342 break;
343 case 's': /* Socket */
344 result |= S_ISSOCK (st_mode);
345 break;
346 case 'x': /* Executable */
347 result |= (st_mode & 0111) ? 1 : 0;
348 break;
349 case 't':
350 result |= panel->marked ? 1 : 0;
351 break;
352 default:
353 debug_error = 1;
354 break;
357 return result;
360 /* Calculates the truth value of the next condition starting from
361 p. Returns the point after condition. */
362 static char *test_condition (WEdit *edit_widget, char *p, int *condition)
364 WPanel *panel;
365 char arg [256];
367 /* Handle one condition */
368 for (;*p != '\n' && *p != '&' && *p != '|'; p++){
369 /* support quote space .mnu */
370 if ((*p == ' ' && *(p-1) != '\\') || *p == '\t')
371 continue;
372 if (*p >= 'a')
373 panel = cpanel;
374 else {
375 if (get_other_type () == view_listing)
376 panel = other_panel;
377 else
378 panel = NULL;
380 *p |= 0x20;
382 switch (*p++){
383 case '!':
384 p = test_condition (edit_widget, p, condition);
385 *condition = ! *condition;
386 p--;
387 break;
388 case 'f': /* file name pattern */
389 p = extract_arg (p, arg);
390 *condition = panel && regexp_match (arg, panel->dir.list [panel->selected].fname, match_file);
391 break;
392 case 'y': /* syntax pattern */
393 if (edit_widget && edit_widget->syntax_type) {
394 p = extract_arg (p, arg);
395 *condition = panel &&
396 regexp_match (arg, edit_widget->syntax_type, match_normal);
398 break;
399 case 'd':
400 p = extract_arg (p, arg);
401 *condition = panel && regexp_match (arg, panel->cwd, match_file);
402 break;
403 case 't':
404 p = extract_arg (p, arg);
405 *condition = panel && test_type (panel, arg);
406 break;
407 case 'x': /* executable */
409 struct stat status;
411 p = extract_arg (p, arg);
412 if (stat (arg, &status) == 0)
413 *condition = is_exe (status.st_mode);
414 else
415 *condition = 0;
416 break;
418 default:
419 debug_error = 1;
420 break;
421 } /* switch */
423 } /* while */
424 return p;
427 /* General purpose condition debug output handler */
428 static void
429 debug_out (char *start, char *end, int cond)
431 static char msg [256];
432 int len;
434 if (start == NULL && end == NULL){
435 if (cond == 0){
436 /* Init */
437 msg [0] = 0;
438 } else {
439 /* Show output */
440 if (!debug_flag)
441 return;
442 len = strlen (msg);
443 if (len)
444 msg [len - 1] = 0;
445 message (0, _(" Debug "), "%s", msg);
446 debug_flag = 0;
448 } else {
449 /* Save debug info for later output */
450 if (!debug_flag)
451 return;
452 /* Save the result of the condition */
453 if (debug_error){
454 strcat (msg, _(" ERROR: "));
455 debug_error = 0;
457 else if (cond)
458 strcat (msg, _(" True: "));
459 else
460 strcat (msg, _(" False: "));
461 /* Copy condition statement */
462 len = strlen (msg);
463 if (end == NULL){
464 /* Copy one character */
465 msg [len] = *start;
466 msg [len + 1] = 0;
467 } else {
468 /* Copy many characters */
469 while (start < end){
470 msg [len++] = *start++;
472 msg [len] = 0;
474 strcat (msg, " \n");
478 /* Calculates the truth value of one lineful of conditions. Returns
479 the point just before the end of line. */
480 static char *test_line (WEdit *edit_widget, char *p, int *result)
482 int condition;
483 char operator;
484 char *debug_start, *debug_end;
486 /* Init debugger */
487 debug_out (NULL, NULL, 0);
488 /* Repeat till end of line */
489 while (*p && *p != '\n') {
490 /* support quote space .mnu */
491 while ((*p == ' ' && *(p-1) != '\\' ) || *p == '\t')
492 p++;
493 if (!*p || *p == '\n')
494 break;
495 operator = *p++;
496 if (*p == '?'){
497 debug_flag = 1;
498 p++;
500 /* support quote space .mnu */
501 while ((*p == ' ' && *(p-1) != '\\' ) || *p == '\t')
502 p++;
503 if (!*p || *p == '\n')
504 break;
505 condition = 1; /* True by default */
507 debug_start = p;
508 p = test_condition (edit_widget, p, &condition);
509 debug_end = p;
510 /* Add one debug statement */
511 debug_out (debug_start, debug_end, condition);
513 switch (operator){
514 case '+':
515 case '=':
516 /* Assignment */
517 *result = condition;
518 break;
519 case '&': /* Logical and */
520 *result &= condition;
521 break;
522 case '|': /* Logical or */
523 *result |= condition;
524 break;
525 default:
526 debug_error = 1;
527 break;
528 } /* switch */
529 /* Add one debug statement */
530 debug_out (&operator, NULL, *result);
532 } /* while (*p != '\n') */
533 /* Report debug message */
534 debug_out (NULL, NULL, 1);
536 if (!*p || *p == '\n')
537 p --;
538 return p;
541 /* FIXME: recode this routine on version 3.0, it could be cleaner */
542 static void
543 execute_menu_command (WEdit *edit_widget, char *commands)
545 FILE *cmd_file;
546 int cmd_file_fd;
547 int expand_prefix_found = 0;
548 char *parameter = 0;
549 int do_quote = 0;
550 char prompt [80];
551 int col;
552 char *file_name;
553 #ifdef NATIVE_WIN32
554 char *p;
555 #endif
556 /* Skip menu entry title line */
557 commands = strchr (commands, '\n');
558 if (!commands){
559 return;
562 cmd_file_fd = mc_mkstemps(&file_name, "mcusr", SCRIPT_SUFFIX);
564 if (cmd_file_fd == -1){
565 message (1, MSG_ERROR, _(" Cannot create temporary command file \n %s "),
566 unix_error_string (errno));
567 return;
569 cmd_file = fdopen (cmd_file_fd, "w");
570 commands++;
572 for (col = 0; *commands; commands++){
573 if (col == 0) {
574 if (*commands != ' ' && *commands != '\t')
575 break;
576 while (*commands == ' ' || *commands == '\t')
577 commands++;
579 col++;
580 if (*commands == '\n')
581 col = 0;
582 if (parameter){
583 if (*commands == '}'){
584 char *tmp;
585 *parameter = 0;
586 parameter = input_dialog (_(" Parameter "), prompt, "");
587 if (!parameter || !*parameter){
588 /* User canceled */
589 fclose (cmd_file);
590 unlink (file_name);
591 g_free (file_name);
592 return;
594 if (do_quote) {
595 fputs (tmp = name_quote (parameter, 0), cmd_file);
596 g_free (tmp);
597 } else
598 fputs (parameter, cmd_file);
599 g_free (parameter);
600 parameter = 0;
601 } else {
602 if (parameter < &prompt [sizeof (prompt) - 1]) {
603 *parameter++ = *commands;
606 } else if (expand_prefix_found){
607 expand_prefix_found = 0;
608 if (isdigit ((unsigned)*commands)) {
609 do_quote = atoi (commands);
610 while (isdigit ((unsigned)*commands))
611 commands++;
613 if (*commands == '{')
614 parameter = prompt;
615 else{
616 char *text = expand_format (edit_widget, *commands, do_quote);
617 fputs (text, cmd_file);
618 g_free (text);
620 } else {
621 if (*commands == '%') {
622 do_quote = 1; /* Default: Quote expanded macro */
623 expand_prefix_found = 1;
624 } else
625 fputc (*commands, cmd_file);
628 fclose (cmd_file);
629 chmod (file_name, S_IRWXU);
630 execute (file_name);
631 unlink (file_name);
632 g_free (file_name);
636 ** Check owner of the menu file. Using menu file is allowed, if
637 ** owner of the menu is root or the actual user. In either case
638 ** file should not be group and word-writable.
640 ** Q. Should we apply this routine to system and home menu (and .ext files)?
642 static int
643 menu_file_own(char* path)
645 struct stat st;
647 if (stat (path, &st) == 0
648 && (!st.st_uid || (st.st_uid == geteuid ()))
649 && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0)
651 return 1;
653 if (verbose)
655 message (0, _(" Warning -- ignoring file "),
656 _("File %s is not owned by root or you or is world writable.\n"
657 "Using it may compromise your security"),
658 path
661 return 0;
665 * If edit_widget is NULL then we are called from the mc menu,
666 * otherwise we are called from the mcedit menu.
668 void user_menu_cmd (WEdit *edit_widget)
670 char *p;
671 char *data, **entries;
672 int max_cols, menu_lines, menu_limit;
673 int col, i, accept_entry = 1;
674 int selected, old_patterns;
675 Listbox *listbox;
677 if (!vfs_current_is_local ()){
678 message (1, _(" Oops... "),
679 _(" I can't run programs while logged on a non local directory "));
680 return;
683 menu = g_strdup (edit_widget ? CEDIT_LOCAL_MENU : MC_LOCAL_MENU);
684 if (!exist_file (menu) || !menu_file_own (menu)){
685 g_free (menu);
686 menu = concat_dir_and_file \
687 (home_dir, edit_widget ? CEDIT_HOME_MENU : MC_HOME_MENU);
688 if (!exist_file (menu)){
689 g_free (menu);
690 menu = concat_dir_and_file \
691 (mc_home, edit_widget ? CEDIT_GLOBAL_MENU : MC_GLOBAL_MENU);
695 if ((data = load_file (menu)) == NULL){
696 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "),
697 menu, unix_error_string (errno));
698 g_free (menu);
699 menu = NULL;
700 return;
703 max_cols = 0;
704 selected = 0;
705 menu_limit = 0;
706 entries = 0;
708 /* Parse the menu file */
709 old_patterns = easy_patterns;
710 p = check_patterns (data);
711 for (menu_lines = col = 0; *p; p++){
712 if (menu_lines >= menu_limit){
713 char ** new_entries;
715 menu_limit += MAX_ENTRIES;
716 new_entries = g_realloc (entries, sizeof (new_entries[0]) * menu_limit);
718 if (new_entries == 0)
719 break;
721 entries = new_entries;
722 new_entries += menu_limit;
723 while (--new_entries >= &entries[menu_lines])
724 *new_entries = 0;
726 if (col == 0 && !entries [menu_lines]){
727 if (*p == '#'){
728 /* A commented menu entry */
729 accept_entry = 1;
730 } else if (*p == '+'){
731 if (*(p+1) == '='){
732 /* Combined adding and default */
733 p = test_line (edit_widget, p, &accept_entry);
734 if (selected == 0 && accept_entry)
735 selected = menu_lines;
736 } else {
737 /* A condition for adding the entry */
738 p = test_line (edit_widget, p, &accept_entry);
740 } else if (*p == '='){
741 if (*(p+1) == '+'){
742 /* Combined adding and default */
743 p = test_line (edit_widget, p, &accept_entry);
744 if (selected == 0 && accept_entry)
745 selected = menu_lines;
746 } else {
747 /* A condition for making the entry default */
748 i = 1;
749 p = test_line (edit_widget, p, &i);
750 if (selected == 0 && i)
751 selected = menu_lines;
754 else if (*p != ' ' && *p != '\t' && is_printable (*p)) {
755 /* A menu entry title line */
756 if (accept_entry)
757 entries [menu_lines] = p;
758 else
759 accept_entry = 1;
762 if (*p == '\n'){
763 if (entries [menu_lines]){
764 menu_lines++;
765 accept_entry = 1;
767 max_cols = max (max_cols, col);
768 col = 0;
769 } else {
770 if (*p == '\t')
771 *p = ' ';
772 col++;
776 if (menu_lines == 0) {
777 message (1, MSG_ERROR, _(" No suitable entries found in %s "), menu);
778 } else {
780 max_cols = min (max (max_cols, col), MAX_ENTRY_LEN);
782 /* Create listbox */
783 listbox = create_listbox_window (max_cols+2, menu_lines, _(" User menu "),
784 "[Menu File Edit]");
785 /* insert all the items found */
786 for (i = 0; i < menu_lines; i++) {
787 p = entries [i];
788 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
789 extract_line (p, p + MAX_ENTRY_LEN), p
792 /* Select the default entry */
793 listbox_select_by_number (listbox->list, selected);
795 selected = run_listbox (listbox);
796 if (selected >= 0)
797 execute_menu_command (edit_widget, entries [selected]);
799 do_refresh ();
802 easy_patterns = old_patterns;
803 g_free (menu);
804 menu = NULL;
805 if (entries)
806 g_free (entries);
807 g_free (data);