Just a little correction at the it.po file.
[midnight-commander.git] / src / user.c
blob54df5d5a1ba97fee154683cc64e5bee3433cea76
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"
36 #include "edit/edit.h" /* BLOCK_FILE */
37 #include "edit/edit-widget.h" /* WEdit */
38 #include "../vfs/vfs.h"
40 /* For the simple listbox manager */
41 #include "dlg.h"
42 #include "widget.h"
43 #include "wtools.h"
45 #include "view.h" /* for default_* externs */
47 #define MAX_ENTRIES 16
48 #define MAX_ENTRY_LEN 60
50 static int debug_flag = 0;
51 static int debug_error = 0;
52 static char *menu = NULL;
54 /* Formats defined:
55 %% The % character
56 %f The current file (if non-local vfs, file will be copied locally and
57 %f will be full path to it).
58 %p The current file
59 %d The current working directory
60 %s "Selected files"; the tagged files if any, otherwise the current file
61 %t Tagged files
62 %u Tagged files (and they are untagged on return from expand_format)
63 %view Runs the commands and pipes standard output to the view command.
64 If %view is immediately followed by '{', recognize keywords
65 ascii, hex, nroff and unform
67 If the format letter is in uppercase, it refers to the other panel.
69 With a number followed the % character you can turn quoting on (default)
70 and off. For example:
71 %f quote expanded macro
72 %1f ditto
73 %0f don't quote expanded macro
75 expand_format returns a memory block that must be free()d.
78 /* Returns how many characters we should advance if %view was found */
79 int check_format_view (const char *p)
81 const char *q = p;
82 if (!strncmp (p, "view", 4)) {
83 q += 4;
84 if (*q == '{'){
85 for (q++;*q && *q != '}';q++) {
86 if (!strncmp (q, "ascii", 5)) {
87 default_hex_mode = 0;
88 q += 4;
89 } else if (!strncmp (q, "hex", 3)) {
90 default_hex_mode = 1;
91 q += 2;
92 } else if (!strncmp (q, "nroff", 5)) {
93 default_nroff_flag = 1;
94 q += 4;
95 } else if (!strncmp (q, "unform", 6)) {
96 default_nroff_flag = 0;
97 q += 5;
100 if (*q == '}')
101 q++;
103 return q - p;
105 return 0;
108 int check_format_cd (const char *p)
110 return (strncmp (p, "cd", 2)) ? 0 : 3;
113 /* Check if p has a "^var\{var-name\}" */
114 /* Returns the number of skipped characters (zero on not found) */
115 /* V will be set to the expanded variable name */
116 int check_format_var (const char *p, char **v)
118 const char *q = p;
119 char *var_name;
120 char *value;
121 const char *dots = 0;
123 *v = 0;
124 if (!strncmp (p, "var{", 4)){
125 for (q += 4; *q && *q != '}'; q++){
126 if (*q == ':')
127 dots = q+1;
129 if (!*q)
130 return 0;
132 if (!dots || dots == q+5){
133 message (1,
134 _(" Format error on file Extensions File "),
135 !dots ? _(" The %%var macro has no default ")
136 : _(" The %%var macro has no variable "));
137 return 0;
140 /* Copy the variable name */
141 var_name = g_malloc (dots - p);
142 strncpy (var_name, p+4, dots-2 - (p+3));
143 var_name [dots-2 - (p+3)] = 0;
145 value = getenv (var_name);
146 g_free (var_name);
147 if (value){
148 *v = g_strdup (value);
149 return q-p;
151 var_name = g_malloc (q - dots + 1);
152 strncpy (var_name, dots, q - dots + 1);
153 var_name [q-dots] = 0;
154 *v = var_name;
155 return q-p;
157 return 0;
160 /* strip file's extension */
161 static char *
162 strip_ext(char *ss)
164 register char *s = ss;
165 char *e = NULL;
166 while(*s) {
167 if(*s == '.') e = s;
168 if(*s == PATH_SEP && e) e=NULL; /* '.' in *directory* name */
169 s++;
171 if(e) *e = 0;
172 return ss;
175 char *
176 expand_format (struct WEdit *edit_widget, char c, int quote)
178 WPanel *panel;
179 char *(*quote_func) (const char *, int);
180 char *fname;
182 if (c == '%')
183 return g_strdup ("%");
185 if (islower ((unsigned) c))
186 panel = cpanel;
187 else {
188 if (get_other_type () != view_listing)
189 return g_strdup ("");
190 panel = other_panel;
192 if (!panel)
193 panel = cpanel;
195 if (quote)
196 quote_func = name_quote;
197 else
198 quote_func = fake_name_quote;
200 c = tolower (c);
201 fname = panel->dir.list[panel->selected].fname;
203 switch (c) {
204 case 'f':
205 case 'p':
206 return (*quote_func) (fname, 0);
207 case 'x':
208 return (*quote_func) (extension (fname), 0);
209 case 'd':
210 return (*quote_func) (panel->cwd, 0);
211 case 'i': /* indent equal number cursor position in line */
212 if (edit_widget)
213 return g_strnfill (edit_widget->curs_col, ' ');
214 break;
215 case 'y': /* syntax type */
216 if (edit_widget && edit_widget->syntax_type)
217 return g_strdup (edit_widget->syntax_type);
218 break;
219 case 'k': /* block file name */
220 case 'b': /* block file name / strip extension */ {
221 if (edit_widget) {
222 char *file = g_strconcat (home_dir, BLOCK_FILE, NULL);
223 fname = (*quote_func) (file, 0);
224 g_free (file);
225 return fname;
226 } else if (c == 'b') {
227 return strip_ext ((*quote_func) (fname, 0));
229 break;
231 case 'n': /* strip extension in editor */
232 if (edit_widget)
233 return strip_ext ((*quote_func) (fname, 0));
234 break;
235 case 'm': /* menu file name */
236 if (menu)
237 return (*quote_func) (menu, 0);
238 break;
239 case 's':
240 if (!panel->marked)
241 return (*quote_func) (fname, 0);
243 /* Fall through */
245 case 't':
246 case 'u':
248 int length = 2, i;
249 char *block, *tmp;
251 for (i = 0; i < panel->count; i++)
252 if (panel->dir.list[i].f.marked)
253 length += strlen (panel->dir.list[i].fname) + 1; /* for space */
255 block = g_malloc (length * 2 + 1);
256 *block = 0;
257 for (i = 0; i < panel->count; i++)
258 if (panel->dir.list[i].f.marked) {
259 strcat (block, tmp =
260 (*quote_func) (panel->dir.list[i].fname, 0));
261 g_free (tmp);
262 strcat (block, " ");
263 if (c == 'u')
264 do_file_mark (panel, i, 0);
266 return block;
267 } /* sub case block */
268 } /* switch */
269 return g_strdup ("");
273 * Check for the "shell_patterns" directive. If it's found and valid,
274 * interpret it and move the pointer past the directive. Return the
275 * current pointer.
277 static char *
278 check_patterns (char *p)
280 static const char def_name[] = "shell_patterns=";
281 char *p0 = p;
283 if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
284 return p0;
286 p += sizeof (def_name) - 1;
287 if (*p == '1')
288 easy_patterns = 1;
289 else if (*p == '0')
290 easy_patterns = 0;
291 else
292 return p0;
294 /* Skip spaces */
295 p++;
296 while (*p == '\n' || *p == '\t' || *p == ' ')
297 p++;
298 return p;
301 /* Copies a whitespace separated argument from p to arg. Returns the
302 point after argument. */
303 static char *extract_arg (char *p, char *arg)
305 while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
306 p++;
307 /* support quote space .mnu */
308 while (*p && (*p != ' ' || *(p-1) == '\\') && *p != '\t' && *p != '\n')
309 *arg++ = *p++;
310 *arg = 0;
311 if (!*p || *p == '\n')
312 p --;
313 return p;
316 /* Tests whether the selected file in the panel is of any of the types
317 specified in argument. */
318 static int test_type (WPanel *panel, char *arg)
320 int result = 0; /* False by default */
321 int st_mode = panel->dir.list [panel->selected].buf.st_mode;
323 for (;*arg != 0; arg++){
324 switch (*arg){
325 case 'n': /* Not a directory */
326 result |= !S_ISDIR (st_mode);
327 break;
328 case 'r': /* Regular file */
329 result |= S_ISREG (st_mode);
330 break;
331 case 'd': /* Directory */
332 result |= S_ISDIR (st_mode);
333 break;
334 case 'l': /* Link */
335 result |= S_ISLNK (st_mode);
336 break;
337 case 'c': /* Character special */
338 result |= S_ISCHR (st_mode);
339 break;
340 case 'b': /* Block special */
341 result |= S_ISBLK (st_mode);
342 break;
343 case 'f': /* Fifo (named pipe) */
344 result |= S_ISFIFO (st_mode);
345 break;
346 case 's': /* Socket */
347 result |= S_ISSOCK (st_mode);
348 break;
349 case 'x': /* Executable */
350 result |= (st_mode & 0111) ? 1 : 0;
351 break;
352 case 't':
353 result |= panel->marked ? 1 : 0;
354 break;
355 default:
356 debug_error = 1;
357 break;
360 return result;
363 /* Calculates the truth value of the next condition starting from
364 p. Returns the point after condition. */
365 static char *test_condition (WEdit *edit_widget, char *p, int *condition)
367 WPanel *panel;
368 char arg [256];
370 /* Handle one condition */
371 for (;*p != '\n' && *p != '&' && *p != '|'; p++){
372 /* support quote space .mnu */
373 if ((*p == ' ' && *(p-1) != '\\') || *p == '\t')
374 continue;
375 if (*p >= 'a')
376 panel = cpanel;
377 else {
378 if (get_other_type () == view_listing)
379 panel = other_panel;
380 else
381 panel = NULL;
383 *p |= 0x20;
385 switch (*p++){
386 case '!':
387 p = test_condition (edit_widget, p, condition);
388 *condition = ! *condition;
389 p--;
390 break;
391 case 'f': /* file name pattern */
392 p = extract_arg (p, arg);
393 *condition = panel && regexp_match (arg, panel->dir.list [panel->selected].fname, match_file);
394 break;
395 case 'y': /* syntax pattern */
396 if (edit_widget && edit_widget->syntax_type) {
397 p = extract_arg (p, arg);
398 *condition = panel &&
399 regexp_match (arg, edit_widget->syntax_type, match_normal);
401 break;
402 case 'd':
403 p = extract_arg (p, arg);
404 *condition = panel && regexp_match (arg, panel->cwd, match_file);
405 break;
406 case 't':
407 p = extract_arg (p, arg);
408 *condition = panel && test_type (panel, arg);
409 break;
410 case 'x': /* executable */
412 struct stat status;
414 p = extract_arg (p, arg);
415 if (stat (arg, &status) == 0)
416 *condition = is_exe (status.st_mode);
417 else
418 *condition = 0;
419 break;
421 default:
422 debug_error = 1;
423 break;
424 } /* switch */
426 } /* while */
427 return p;
430 /* General purpose condition debug output handler */
431 static void
432 debug_out (char *start, char *end, int cond)
434 static char msg [256];
435 int len;
437 if (start == NULL && end == NULL){
438 if (cond == 0){
439 /* Init */
440 msg [0] = 0;
441 } else {
442 /* Show output */
443 if (!debug_flag)
444 return;
445 len = strlen (msg);
446 if (len)
447 msg [len - 1] = 0;
448 message (0, _(" Debug "), "%s", msg);
449 debug_flag = 0;
451 } else {
452 /* Save debug info for later output */
453 if (!debug_flag)
454 return;
455 /* Save the result of the condition */
456 if (debug_error){
457 strcat (msg, _(" ERROR: "));
458 debug_error = 0;
460 else if (cond)
461 strcat (msg, _(" True: "));
462 else
463 strcat (msg, _(" False: "));
464 /* Copy condition statement */
465 len = strlen (msg);
466 if (end == NULL){
467 /* Copy one character */
468 msg [len] = *start;
469 msg [len + 1] = 0;
470 } else {
471 /* Copy many characters */
472 while (start < end){
473 msg [len++] = *start++;
475 msg [len] = 0;
477 strcat (msg, " \n");
481 /* Calculates the truth value of one lineful of conditions. Returns
482 the point just before the end of line. */
483 static char *test_line (WEdit *edit_widget, char *p, int *result)
485 int condition;
486 char operator;
487 char *debug_start, *debug_end;
489 /* Init debugger */
490 debug_out (NULL, NULL, 0);
491 /* Repeat till end of line */
492 while (*p && *p != '\n') {
493 /* support quote space .mnu */
494 while ((*p == ' ' && *(p-1) != '\\' ) || *p == '\t')
495 p++;
496 if (!*p || *p == '\n')
497 break;
498 operator = *p++;
499 if (*p == '?'){
500 debug_flag = 1;
501 p++;
503 /* support quote space .mnu */
504 while ((*p == ' ' && *(p-1) != '\\' ) || *p == '\t')
505 p++;
506 if (!*p || *p == '\n')
507 break;
508 condition = 1; /* True by default */
510 debug_start = p;
511 p = test_condition (edit_widget, p, &condition);
512 debug_end = p;
513 /* Add one debug statement */
514 debug_out (debug_start, debug_end, condition);
516 switch (operator){
517 case '+':
518 case '=':
519 /* Assignment */
520 *result = condition;
521 break;
522 case '&': /* Logical and */
523 *result &= condition;
524 break;
525 case '|': /* Logical or */
526 *result |= condition;
527 break;
528 default:
529 debug_error = 1;
530 break;
531 } /* switch */
532 /* Add one debug statement */
533 debug_out (&operator, NULL, *result);
535 } /* while (*p != '\n') */
536 /* Report debug message */
537 debug_out (NULL, NULL, 1);
539 if (!*p || *p == '\n')
540 p --;
541 return p;
544 /* FIXME: recode this routine on version 3.0, it could be cleaner */
545 static void
546 execute_menu_command (WEdit *edit_widget, char *commands)
548 FILE *cmd_file;
549 int cmd_file_fd;
550 int expand_prefix_found = 0;
551 char *parameter = 0;
552 int do_quote = 0;
553 char prompt [80];
554 int col;
555 char *file_name;
556 #ifdef NATIVE_WIN32
557 char *p;
558 #endif
559 /* Skip menu entry title line */
560 commands = strchr (commands, '\n');
561 if (!commands){
562 return;
565 cmd_file_fd = mc_mkstemps(&file_name, "mcusr", SCRIPT_SUFFIX);
567 if (cmd_file_fd == -1){
568 message (1, MSG_ERROR, _(" Cannot create temporary command file \n %s "),
569 unix_error_string (errno));
570 return;
572 cmd_file = fdopen (cmd_file_fd, "w");
573 commands++;
575 for (col = 0; *commands; commands++){
576 if (col == 0) {
577 if (*commands != ' ' && *commands != '\t')
578 break;
579 while (*commands == ' ' || *commands == '\t')
580 commands++;
582 col++;
583 if (*commands == '\n')
584 col = 0;
585 if (parameter){
586 if (*commands == '}'){
587 char *tmp;
588 *parameter = 0;
589 parameter = input_dialog (_(" Parameter "), prompt, "");
590 if (!parameter || !*parameter){
591 /* User canceled */
592 fclose (cmd_file);
593 unlink (file_name);
594 g_free (file_name);
595 return;
597 if (do_quote) {
598 fputs (tmp = name_quote (parameter, 0), cmd_file);
599 g_free (tmp);
600 } else
601 fputs (parameter, cmd_file);
602 g_free (parameter);
603 parameter = 0;
604 } else {
605 if (parameter < &prompt [sizeof (prompt) - 1]) {
606 *parameter++ = *commands;
609 } else if (expand_prefix_found){
610 expand_prefix_found = 0;
611 if (isdigit ((unsigned)*commands)) {
612 do_quote = atoi (commands);
613 while (isdigit ((unsigned)*commands))
614 commands++;
616 if (*commands == '{')
617 parameter = prompt;
618 else{
619 char *text = expand_format (edit_widget, *commands, do_quote);
620 fputs (text, cmd_file);
621 g_free (text);
623 } else {
624 if (*commands == '%') {
625 do_quote = 1; /* Default: Quote expanded macro */
626 expand_prefix_found = 1;
627 } else
628 fputc (*commands, cmd_file);
631 fclose (cmd_file);
632 chmod (file_name, S_IRWXU);
633 execute (file_name);
634 unlink (file_name);
635 g_free (file_name);
639 ** Check owner of the menu file. Using menu file is allowed, if
640 ** owner of the menu is root or the actual user. In either case
641 ** file should not be group and word-writable.
643 ** Q. Should we apply this routine to system and home menu (and .ext files)?
645 static int
646 menu_file_own(char* path)
648 struct stat st;
650 if (stat (path, &st) == 0
651 && (!st.st_uid || (st.st_uid == geteuid ()))
652 && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0)
654 return 1;
656 if (verbose)
658 message (0, _(" Warning -- ignoring file "),
659 _("File %s is not owned by root or you or is world writable.\n"
660 "Using it may compromise your security"),
661 path
664 return 0;
668 * If edit_widget is NULL then we are called from the mc menu,
669 * otherwise we are called from the mcedit menu.
671 void
672 user_menu_cmd (struct WEdit *edit_widget)
674 char *p;
675 char *data, **entries;
676 int max_cols, menu_lines, menu_limit;
677 int col, i, accept_entry = 1;
678 int selected, old_patterns;
679 Listbox *listbox;
681 if (!vfs_current_is_local ()){
682 message (1, MSG_ERROR,
683 _(" Cannot execute commands on non-local filesystems"));
684 return;
687 menu = g_strdup (edit_widget ? CEDIT_LOCAL_MENU : MC_LOCAL_MENU);
688 if (!exist_file (menu) || !menu_file_own (menu)){
689 g_free (menu);
690 menu = concat_dir_and_file \
691 (home_dir, edit_widget ? CEDIT_HOME_MENU : MC_HOME_MENU);
692 if (!exist_file (menu)){
693 g_free (menu);
694 menu = concat_dir_and_file \
695 (mc_home, edit_widget ? CEDIT_GLOBAL_MENU : MC_GLOBAL_MENU);
699 if ((data = load_file (menu)) == NULL){
700 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "),
701 menu, unix_error_string (errno));
702 g_free (menu);
703 menu = NULL;
704 return;
707 max_cols = 0;
708 selected = 0;
709 menu_limit = 0;
710 entries = 0;
712 /* Parse the menu file */
713 old_patterns = easy_patterns;
714 p = check_patterns (data);
715 for (menu_lines = col = 0; *p; p++){
716 if (menu_lines >= menu_limit){
717 char ** new_entries;
719 menu_limit += MAX_ENTRIES;
720 new_entries = g_realloc (entries, sizeof (new_entries[0]) * menu_limit);
722 if (new_entries == 0)
723 break;
725 entries = new_entries;
726 new_entries += menu_limit;
727 while (--new_entries >= &entries[menu_lines])
728 *new_entries = 0;
730 if (col == 0 && !entries [menu_lines]){
731 if (*p == '#'){
732 /* A commented menu entry */
733 accept_entry = 1;
734 } else if (*p == '+'){
735 if (*(p+1) == '='){
736 /* Combined adding and default */
737 p = test_line (edit_widget, p, &accept_entry);
738 if (selected == 0 && accept_entry)
739 selected = menu_lines;
740 } else {
741 /* A condition for adding the entry */
742 p = test_line (edit_widget, p, &accept_entry);
744 } else if (*p == '='){
745 if (*(p+1) == '+'){
746 /* Combined adding and default */
747 p = test_line (edit_widget, p, &accept_entry);
748 if (selected == 0 && accept_entry)
749 selected = menu_lines;
750 } else {
751 /* A condition for making the entry default */
752 i = 1;
753 p = test_line (edit_widget, p, &i);
754 if (selected == 0 && i)
755 selected = menu_lines;
758 else if (*p != ' ' && *p != '\t' && is_printable (*p)) {
759 /* A menu entry title line */
760 if (accept_entry)
761 entries [menu_lines] = p;
762 else
763 accept_entry = 1;
766 if (*p == '\n'){
767 if (entries [menu_lines]){
768 menu_lines++;
769 accept_entry = 1;
771 max_cols = max (max_cols, col);
772 col = 0;
773 } else {
774 if (*p == '\t')
775 *p = ' ';
776 col++;
780 if (menu_lines == 0) {
781 message (1, MSG_ERROR, _(" No suitable entries found in %s "), menu);
782 } else {
784 max_cols = min (max (max_cols, col), MAX_ENTRY_LEN);
786 /* Create listbox */
787 listbox = create_listbox_window (max_cols+2, menu_lines, _(" User menu "),
788 "[Menu File Edit]");
789 /* insert all the items found */
790 for (i = 0; i < menu_lines; i++) {
791 p = entries [i];
792 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
793 extract_line (p, p + MAX_ENTRY_LEN), p
796 /* Select the default entry */
797 listbox_select_by_number (listbox->list, selected);
799 selected = run_listbox (listbox);
800 if (selected >= 0)
801 execute_menu_command (edit_widget, entries [selected]);
803 do_refresh ();
806 easy_patterns = old_patterns;
807 g_free (menu);
808 menu = NULL;
809 if (entries)
810 g_free (entries);
811 g_free (data);