Codepage messages related translated & other stuff...
[midnight-commander.git] / src / user.c
blob514fdef103afdb64db908dad3dd103fb8c9047f0
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 #ifdef NEEDS_IO_H
21 # include <io.h>
22 #endif
23 #include "tty.h"
24 #include <string.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include "global.h"
29 #include "dialog.h"
30 #include "color.h"
31 #include "dir.h"
32 #include "panel.h"
33 #include "main.h"
34 #include "subshell.h" /* for subshell_pty */
35 #include "user.h"
36 #include "layout.h"
37 #include "setup.h"
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 /* "$Id$" */
49 #define MAX_ENTRIES 16
50 #define MAX_ENTRY_LEN 60
52 static int debug_flag = 0;
53 static int debug_error = 0;
54 static WEdit *s_editwidget;
55 static char *menu = NULL;
57 /* Formats defined:
58 %% The % character
59 %f The current file (if non-local vfs, file will be copied locally and
60 %f will be full path to it).
61 %p The current file
62 %d The current working directory
63 %s "Selected files"; the tagged files if any, otherwise the current file
64 %t Tagged files
65 %u Tagged files (and they are untagged on return from expand_format)
66 %view Runs the commands and pipes standard output to the view command
67 if %view is directly followed by '{', a list of keywords
68 ascii, hex, nroff, unformated and
70 If the format letter is in uppercase, it refers to the other panel.
72 With a number followed the % character you can turn quoting on (default)
73 and off. For example:
74 %f quote expanded macro
75 %1f ditto
76 %0f don't quote expanded macro
78 expand_format returns a memory block that must be free()d.
81 /* Returns how many characters we should advance if %view was found */
82 int check_format_view (const char *p)
84 const char *q = p;
85 if (!strncmp (p, "view", 4)){
86 q += 4;
87 if (*q == '{'){
88 for (q++;*q && *q != '}';q++){
89 if (!strncmp (q, "ascii", 5)){
90 default_hex_mode = 0;
91 q += 4;
92 } else if (!strncmp (q, "hex", 3)){
93 default_hex_mode = 1;
94 q += 2;
95 } else if (!strncmp (q, "nroff", 5)){
96 default_nroff_flag = 1;
97 q += 4;
98 } else if (!strncmp (q, "unformated", 10)){
99 default_nroff_flag = 0;
100 q += 9;
103 if (*q == '}')
104 q++;
106 return q - p;
108 return 0;
111 int check_format_cd (const char *p)
113 return (strncmp (p, "cd", 2)) ? 0 : 3;
116 /* Check if p has a "^var\{var-name\}" */
117 /* Returns the number of skipped characters (zero on not found) */
118 /* V will be set to the expanded variable name */
119 int check_format_var (const char *p, char **v)
121 const char *q = p;
122 char *var_name;
123 char *value;
124 const char *dots = 0;
126 *v = 0;
127 if (!strncmp (p, "var{", 4)){
128 for (q += 4; *q && *q != '}'; q++){
129 if (*q == ':')
130 dots = q+1;
132 if (!*q)
133 return 0;
135 if (!dots || dots == q+5){
136 message (1,
137 _(" Format error on file Extensions File "),
138 _(!dots ? N_(" The %%var macro does not have a default ")
139 : N_(" The %%var macros does not have a variable ")));
140 return 0;
143 /* Copy the variable name */
144 var_name = g_malloc (dots - p);
145 strncpy (var_name, p+4, dots-2 - (p+3));
146 var_name [dots-2 - (p+3)] = 0;
148 value = getenv (var_name);
149 g_free (var_name);
150 if (value){
151 *v = g_strdup (value);
152 return q-p;
154 var_name = g_malloc (q - dots + 1);
155 strncpy (var_name, dots, q - dots + 1);
156 var_name [q-dots] = 0;
157 *v = var_name;
158 return q-p;
160 return 0;
163 /* strip file's extension */
164 static char *
165 strip_ext(char *ss)
167 register char *s = ss;
168 char *e = NULL;
169 while(*s) {
170 if(*s == '.') e = s;
171 if(*s == PATH_SEP && e) e=NULL; /* '.' in *directory* name */
172 s++;
174 if(e) *e = 0;
175 return ss;
178 char *expand_format (char c, int quote)
180 WPanel *panel;
181 char *(*quote_func)(const char *, int);
182 char *fname;
184 if (c == '%')
185 return g_strdup ("%");
187 if (islower (c))
188 panel = cpanel;
189 else {
190 if (get_other_type () != view_listing)
191 return g_strdup ("");
192 panel = other_panel;
194 if (!panel)
195 panel = cpanel;
197 if (quote)
198 quote_func = name_quote;
199 else
200 quote_func = fake_name_quote;
202 c = tolower (c);
203 fname = panel->dir.list [panel->selected].fname;
205 switch (c){
206 case 'f':
207 case 'p': return (*quote_func) (fname, 0);
208 case 'b': return strip_ext((*quote_func) (fname, 0));
209 case 'x': return (*quote_func) (extension(fname), 0);
210 case 'd': return (*quote_func) (panel->cwd, 0);
211 case 'i': /* indent equal number cursor position in line */
212 if (s_editwidget)
213 return g_strnfill (s_editwidget->curs_col, ' ');
214 break;
215 case 'y': /* syntax type */
216 if (s_editwidget && s_editwidget->syntax_type)
217 return g_strdup (s_editwidget->syntax_type);
218 break;
219 case 'e': /* error file name */
220 case 'k': /* block file name */ {
221 char *file = g_strconcat (home_dir, (c == 'e') ? ERROR_FILE : BLOCK_FILE, NULL);
222 fname = (*quote_func) (file, 0);
223 g_free (file);
224 return fname;
226 case 'm': /* menu file name */
227 if (menu)
228 return (*quote_func) (menu, 0);
229 break;
230 case 's': if (!panel->marked)
231 return (*quote_func) (fname, 0);
233 /* Fall through */
235 case 't':
236 case 'u':
238 int length = 2, i;
239 char *block, *tmp;
241 for (i = 0; i < panel->count; i++)
242 if (panel->dir.list [i].f.marked)
243 length += strlen (panel->dir.list [i].fname) + 1; /* for space */
245 block = g_malloc (length*2+1);
246 *block = 0;
247 for (i = 0; i < panel->count; i++)
248 if (panel->dir.list [i].f.marked){
249 strcat (block, tmp = (*quote_func) (panel->dir.list [i].fname, 0));
250 g_free (tmp);
251 strcat (block, " ");
252 if (c == 'u')
253 do_file_mark (panel, i, 0);
255 return block;
256 } /* sub case block */
257 } /* switch */
258 return g_strdup ("");
261 /* Checks for shell patterns definition */
262 char *check_patterns (char *p)
264 static const char def_name [] = "shell_patterns=";
265 int value;
267 if (strncmp (p, def_name, sizeof (def_name) - 1) == 0){
268 p += sizeof (def_name) - 1;
269 value = *p++ - '0';
270 if (value == 0 || value == 1)
271 easy_patterns = value;
272 else
273 message (1, MSG_ERROR, _(" Invalid shell pattern definition \"%c\". "), value + '0');
275 while (*p == '\n' || *p == '\t' || *p == ' ') p++;
276 return p;
279 /* Copies a whitespace separated argument from p to arg. Returns the
280 point after argument. */
281 static char *extract_arg (char *p, char *arg)
283 while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
284 p++;
285 /* support quote space .mnu */
286 while (*p && (*p != ' ' || *(p-1) == '\\') && *p != '\t' && *p != '\n')
287 *arg++ = *p++;
288 *arg = 0;
289 if (!*p || *p == '\n')
290 p --;
291 return p;
294 /* Tests whether the selected file in the panel is of any of the types
295 specified in argument. */
296 static int test_type (WPanel *panel, char *arg)
298 int result = 0; /* False by default */
299 int st_mode = panel->dir.list [panel->selected].buf.st_mode;
301 for (;*arg != 0; arg++){
302 switch (*arg){
303 case 'n': /* Not a directory */
304 result |= !S_ISDIR (st_mode);
305 break;
306 case 'r': /* Regular file */
307 result |= S_ISREG (st_mode);
308 break;
309 case 'd': /* Directory */
310 result |= S_ISDIR (st_mode);
311 break;
312 case 'l': /* Link */
313 result |= S_ISLNK (st_mode);
314 break;
315 case 'c': /* Character special */
316 result |= S_ISCHR (st_mode);
317 break;
318 case 'b': /* Block special */
319 result |= S_ISBLK (st_mode);
320 break;
321 case 'f': /* Fifo (named pipe) */
322 result |= S_ISFIFO (st_mode);
323 break;
324 case 's': /* Socket */
325 result |= S_ISSOCK (st_mode);
326 break;
327 case 'x': /* Executable */
328 result |= (st_mode & 0111) ? 1 : 0;
329 break;
330 case 't':
331 result |= panel->marked ? 1 : 0;
332 break;
333 default:
334 debug_error = 1;
335 break;
338 return result;
341 /* Calculates the truth value of the next condition starting from
342 p. Returns the point after condition. */
343 static char *test_condition (char *p, int *condition)
345 WPanel *panel;
346 char arg [256];
348 /* Handle one condition */
349 for (;*p != '\n' && *p != '&' && *p != '|'; p++){
350 /* support quote space .mnu */
351 if ((*p == ' ' && *(p-1) != '\\') || *p == '\t')
352 continue;
353 if (*p >= 'a')
354 panel = cpanel;
355 else {
356 if (get_other_type () == view_listing)
357 panel = other_panel;
358 else
359 panel = NULL;
361 *p |= 0x20;
363 switch (*p++){
364 case '!':
365 p = test_condition (p, condition);
366 *condition = ! *condition;
367 p--;
368 break;
369 case 'f': /* file name pattern */
370 p = extract_arg (p, arg);
371 *condition = panel && regexp_match (arg, panel->dir.list [panel->selected].fname, match_file);
372 break;
373 case 'y': /* syntax pattern */
374 if (s_editwidget && s_editwidget->syntax_type) {
375 p = extract_arg (p, arg);
376 *condition = panel &&
377 regexp_match (arg, s_editwidget->syntax_type, match_normal);
379 break;
380 case 'd':
381 p = extract_arg (p, arg);
382 *condition = panel && regexp_match (arg, panel->cwd, match_file);
383 break;
384 case 't':
385 p = extract_arg (p, arg);
386 *condition = panel && test_type (panel, arg);
387 break;
388 case 'x': /* executable */
390 struct stat status;
392 p = extract_arg (p, arg);
393 if (stat (arg, &status) == 0)
394 *condition = is_exe (status.st_mode);
395 else
396 *condition = 0;
397 break;
399 default:
400 debug_error = 1;
401 break;
402 } /* switch */
404 } /* while */
405 return p;
408 /* General purpose condition debug output handler */
409 static void
410 debug_out (char *start, char *end, int cond)
412 static char msg [256];
413 int len;
415 if (start == NULL && end == NULL){
416 if (cond == 0){
417 /* Init */
418 msg [0] = 0;
419 } else {
420 /* Show output */
421 if (!debug_flag)
422 return;
423 len = strlen (msg);
424 if (len)
425 msg [len - 1] = 0;
426 message (0, _(" Debug "), msg);
427 debug_flag = 0;
429 } else {
430 /* Save debug info for later output */
431 if (!debug_flag)
432 return;
433 /* Save the result of the condition */
434 if (debug_error){
435 strcat (msg, _(" ERROR: "));
436 debug_error = 0;
438 else if (cond)
439 strcat (msg, _(" True: "));
440 else
441 strcat (msg, _(" False: "));
442 /* Copy condition statement */
443 len = strlen (msg);
444 if (end == NULL){
445 /* Copy one character */
446 msg [len] = *start;
447 msg [len + 1] = 0;
448 } else {
449 /* Copy many characters */
450 while (start < end){
451 msg [len++] = *start++;
453 msg [len] = 0;
455 strcat (msg, " \n");
459 /* Calculates the truth value of one lineful of conditions. Returns
460 the point just before the end of line. */
461 static char *test_line (char *p, int *result)
463 int condition;
464 char operator;
465 char *debug_start, *debug_end;
467 /* Init debugger */
468 debug_out (NULL, NULL, 0);
469 /* Repeat till end of line */
470 while (*p && *p != '\n') {
471 /* support quote space .mnu */
472 while ((*p == ' ' && *(p-1) != '\\' ) || *p == '\t')
473 p++;
474 if (!*p || *p == '\n')
475 break;
476 operator = *p++;
477 if (*p == '?'){
478 debug_flag = 1;
479 p++;
481 /* support quote space .mnu */
482 while ((*p == ' ' && *(p-1) != '\\' ) || *p == '\t')
483 p++;
484 if (!*p || *p == '\n')
485 break;
486 condition = 1; /* True by default */
488 debug_start = p;
489 p = test_condition (p, &condition);
490 debug_end = p;
491 /* Add one debug statement */
492 debug_out (debug_start, debug_end, condition);
494 switch (operator){
495 case '+':
496 case '=':
497 /* Assignment */
498 *result = condition;
499 break;
500 case '&': /* Logical and */
501 *result &= condition;
502 break;
503 case '|': /* Logical or */
504 *result |= condition;
505 break;
506 default:
507 debug_error = 1;
508 break;
509 } /* switch */
510 /* Add one debug statement */
511 debug_out (&operator, NULL, *result);
513 } /* while (*p != '\n') */
514 /* Report debug message */
515 debug_out (NULL, NULL, 1);
517 if (!*p || *p == '\n')
518 p --;
519 return p;
522 /* FIXME: recode this routine on version 3.0, it could be cleaner */
523 static void
524 execute_menu_command (char *commands)
526 FILE *cmd_file;
527 int cmd_file_fd;
528 int expand_prefix_found = 0;
529 char *parameter = 0;
530 int do_quote = 0;
531 char prompt [80];
532 int col;
533 char *file_name;
534 #ifdef OS2_NT
535 char *p;
536 #endif
537 /* Skip menu entry title line */
538 commands = strchr (commands, '\n');
539 if (!commands){
540 return;
543 cmd_file_fd = mc_mkstemps(&file_name, "mcusr", SCRIPT_SUFFIX);
545 if (cmd_file_fd == -1){
546 message (1, MSG_ERROR, _(" Can't create temporary command file \n %s "),
547 unix_error_string (errno));
548 return;
550 cmd_file = fdopen (cmd_file_fd, "w");
551 commands++;
553 for (col = 0; *commands; commands++){
554 if (col == 0) {
555 if (*commands != ' ' && *commands != '\t')
556 break;
557 while (*commands == ' ' || *commands == '\t')
558 commands++;
560 col++;
561 if (*commands == '\n')
562 col = 0;
563 if (parameter){
564 if (*commands == '}'){
565 char *tmp;
566 *parameter = 0;
567 parameter = input_dialog (_(" Parameter "), prompt, "");
568 if (!parameter || !*parameter){
569 /* User canceled */
570 fclose (cmd_file);
571 unlink (file_name);
572 g_free (file_name);
573 return;
575 if (do_quote) {
576 fputs (tmp = name_quote (parameter, 0), cmd_file);
577 g_free (tmp);
578 } else
579 fputs (parameter, cmd_file);
580 g_free (parameter);
581 parameter = 0;
582 } else {
583 if (parameter < &prompt [sizeof (prompt) - 1]) {
584 *parameter++ = *commands;
587 } else if (expand_prefix_found){
588 expand_prefix_found = 0;
589 if (isdigit (*commands)) {
590 do_quote = atoi (commands);
591 while (isdigit (*commands))
592 commands++;
594 if (*commands == '{')
595 parameter = prompt;
596 else{
597 char *text = expand_format (*commands, do_quote);
598 fputs (text, cmd_file);
599 g_free (text);
601 } else {
602 if (*commands == '%') {
603 do_quote = 1; /* Default: Quote expanded macro */
604 expand_prefix_found = 1;
605 } else
606 fputc (*commands, cmd_file);
609 fclose (cmd_file);
610 chmod (file_name, S_IRWXU);
611 execute (file_name);
612 unlink (file_name);
613 g_free (file_name);
617 ** Check owner of the menu file. Using menu file is allowed, if
618 ** owner of the menu is root or the actual user. In either case
619 ** file should not be group and word-writable.
621 ** Q. Should we apply this routine to system and home menu (and .ext files)?
623 static int
624 menu_file_own(char* path)
626 struct stat st;
628 if (stat (path, &st) == 0
629 && (!st.st_uid || (st.st_uid == geteuid ()))
630 && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0)
632 return 1;
634 if (verbose)
636 message (0, _(" Warning -- ignoring file "),
637 _("File %s is not owned by root or you or is world writable.\n"
638 "Using it may compromise your security"),
639 path
642 return 0;
646 if edit_widget = pointer then it is file menu from cool edit
647 if edit_widget = NULL then routine is invoke from file menu of mc.
649 void user_menu_cmd (WEdit *edit_widget)
651 char *p;
652 char *data, **entries;
653 int max_cols, menu_lines, menu_limit;
654 int col, i, accept_entry = 1;
655 int selected, old_patterns;
656 Listbox *listbox;
657 s_editwidget = edit_widget;
659 if (!vfs_current_is_local ()){
660 message (1, _(" Oops... "),
661 _(" I can't run programs while logged on a non local directory "));
662 return;
665 menu = g_strdup (edit_widget ? CEDIT_LOCAL_MENU : MC_LOCAL_MENU);
666 if (!exist_file (menu) || !menu_file_own (menu)){
667 g_free (menu);
668 menu = concat_dir_and_file \
669 (home_dir, edit_widget ? CEDIT_HOME_MENU : MC_HOME_MENU);
670 if (!exist_file (menu)){
671 g_free (menu);
672 menu = concat_dir_and_file \
673 (mc_home, edit_widget ? CEDIT_GLOBAL_MENU : MC_GLOBAL_MENU);
677 if ((data = load_file (menu)) == NULL){
678 message (1, MSG_ERROR, _(" Can't open file %s \n %s "),
679 menu, unix_error_string (errno));
680 g_free (menu);
681 menu = NULL;
682 return;
685 max_cols = 0;
686 selected = 0;
687 menu_limit = 0;
688 entries = 0;
690 /* Parse the menu file */
691 old_patterns = easy_patterns;
692 p = check_patterns (data);
693 for (menu_lines = col = 0; *p; p++){
694 if (menu_lines >= menu_limit){
695 char ** new_entries;
697 menu_limit += MAX_ENTRIES;
698 new_entries = g_realloc (entries, sizeof (new_entries[0]) * menu_limit);
700 if (new_entries == 0)
701 break;
703 entries = new_entries;
704 new_entries += menu_limit;
705 while (--new_entries >= &entries[menu_lines])
706 *new_entries = 0;
708 if (col == 0 && !entries [menu_lines]){
709 if (*p == '#'){
710 /* A commented menu entry */
711 accept_entry = 1;
712 } else if (*p == '+'){
713 if (*(p+1) == '='){
714 /* Combined adding and default */
715 p = test_line (p, &accept_entry);
716 if (selected == 0 && accept_entry)
717 selected = menu_lines;
718 } else {
719 /* A condition for adding the entry */
720 p = test_line (p, &accept_entry);
722 } else if (*p == '='){
723 if (*(p+1) == '+'){
724 /* Combined adding and default */
725 p = test_line (p, &accept_entry);
726 if (selected == 0 && accept_entry)
727 selected = menu_lines;
728 } else {
729 /* A condition for making the entry default */
730 i = 1;
731 p = test_line (p, &i);
732 if (selected == 0 && i)
733 selected = menu_lines;
736 else if (*p != ' ' && *p != '\t' && is_printable (*p)) {
737 /* A menu entry title line */
738 if (accept_entry)
739 entries [menu_lines] = p;
740 else
741 accept_entry = 1;
744 if (*p == '\n'){
745 if (entries [menu_lines]){
746 menu_lines++;
747 accept_entry = 1;
749 max_cols = max (max_cols, col);
750 col = 0;
751 } else {
752 if (*p == '\t')
753 *p = ' ';
754 col++;
758 if (menu_lines == 0) {
759 message (1, MSG_ERROR, _(" No appropriative entries found in %s "), menu);
760 } else {
762 max_cols = min (max (max_cols, col), MAX_ENTRY_LEN);
764 /* Create listbox */
765 listbox = create_listbox_window (max_cols+2, menu_lines, _(" User menu "),
766 "[Menu File Edit]");
767 /* insert all the items found */
768 for (i = 0; i < menu_lines; i++) {
769 p = entries [i];
770 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
771 extract_line (p, p + MAX_ENTRY_LEN), p
774 /* Select the default entry */
775 listbox_select_by_number (listbox->list, selected);
777 selected = run_listbox (listbox);
778 if (selected >= 0)
779 execute_menu_command (entries [selected]);
781 do_refresh ();
784 easy_patterns = old_patterns;
785 g_free (menu);
786 menu = NULL;
787 if (entries)
788 g_free (entries);
789 g_free (data);