Take care of one BSOD in NtGdiDdCreateDirectDrawObject, it is not correct fix, it...
[reactos.git] / rosapps / mc / src / user.c
1 /* User Menu implementation
2 Copyright (C) 1994 Miguel de Icaza, Janne Kukonlehto
3
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.
8
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.
13
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
17
18 #include <config.h>
19 #include <stdio.h>
20 #ifdef OS2_NT
21 # include <io.h>
22 #endif
23 #include "tty.h"
24 #include <stdlib.h> /* For free() */
25 #include "fs.h"
26 #include <string.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include "mad.h"
31 #include "util.h"
32 #include "global.h"
33 #include "dialog.h"
34 #include "color.h"
35 #include "dir.h"
36 #include "panel.h"
37 #include "main.h"
38 #include "user.h"
39 #include "layout.h"
40 #include "../vfs/vfs.h"
41
42 /* For the simple listbox manager */
43 #include "dlg.h"
44 #include "widget.h"
45 #include "wtools.h"
46
47 #include "view.h" /* for default_* externs */
48
49 /* "$Id$" */
50
51 #define MAX_ENTRIES 40
52 #define MAX_ENTRY_LEN 60
53
54 static char *data;
55 static char *entries [MAX_ENTRIES];
56 static int max_cols;
57 static int menu_lines;
58 static int debug_flag = 0;
59 static int debug_error = 0;
60 extern char *search_string (char *, char *);
61
62 /* Formats defined:
63 %% The % character
64 %f The current file (if non-local vfs, file will be copied locally and
65 %f will be full path to it).
66 %p The current file
67 %d The current working directory
68 %s "Selected files"; the tagged files if any, otherwise the current file
69 %t Tagged files
70 %u Tagged files (and they are untagged on return from expand_format)
71 %view Runs the commands and pipes standard output to the view command
72 if %view is directly followed by '{', a list of keywords
73 ascii, hex, nroff, unformated and
74
75 If the format letter is in uppercase, it refers to the other panel.
76
77 With a number followed the % character you can turn quoting on (default)
78 and off. For example:
79 %f quote expanded macro
80 %1f ditto
81 %0f don't quote expanded macro
82
83 expand_format returns a memory block that must be free()d.
84 */
85
86 /* Returns how many characters we should advance if %view was found */
87 int check_format_view (char *p)
88 {
89 char *q = p;
90 if (!strncmp (p, "view", 4)){
91 q += 4;
92 if (*q == '{'){
93 for (q++;*q && *q != '}';q++){
94 if (!strncmp (q, "ascii", 5)){
95 default_hex_mode = 0;
96 q += 4;
97 } else if (!strncmp (q, "hex", 3)){
98 default_hex_mode = 1;
99 q += 2;
100 } else if (!strncmp (q, "nroff", 5)){
101 default_nroff_flag = 1;
102 q += 4;
103 } else if (!strncmp (q, "unformated", 10)){
104 default_nroff_flag = 0;
105 q += 9;
106 }
107 }
108 if (*q == '}')
109 q++;
110 }
111 return q - p;
112 } else
113 return 0;
114 }
115
116 int check_format_cd (char *p)
117 {
118 if (!strncmp (p, "cd", 2))
119 return 3;
120 else
121 return 0;
122 }
123
124 /* Check if p has a "^var\{var-name\}" */
125 /* Returns the number of skipped characters (zero on not found) */
126 /* V will be set to the expanded variable name */
127 int check_format_var (char *p, char **v)
128 {
129 char *q = p;
130 char *var_name;
131 char *value;
132 char *dots;
133
134 *v = 0;
135 dots = 0;
136 if (!strncmp (p, "var{", 4)){
137 for (q += 4; *q && *q != '}'; q++){
138 if (*q == ':')
139 dots = q+1;
140 ;
141 }
142 if (!*q)
143 return 0;
144
145 if (!dots || dots == q+5){
146 message (1,
147 " Format error on file Extensions File ",
148 !dots ? " The %%var macro does not have a default "
149 : " The %%var macros does not have a variable " );
150 return 0;
151 }
152
153 /* Copy the variable name */
154 var_name = xmalloc (dots - p, "check_format_var");
155 strncpy (var_name, p+4, dots-2 - (p+3));
156 var_name [dots-2 - (p+3)] = 0;
157
158 value = getenv (var_name);
159 if (value){
160 *v = strdup (value);
161 return q-p;
162 }
163 free (var_name);
164 var_name = xmalloc (q - dots + 1, "check_format_var_2");
165 strncpy (var_name, dots, q - dots + 1);
166 var_name [q-dots] = 0;
167 *v = var_name;
168 return q-p;
169 }
170 return 0;
171 }
172
173 /* strip file's extension */
174 char *strip_ext(char *ss)
175 {
176 register char *s = ss;
177 char *e = NULL;
178 while(*s) {
179 if(*s == '.') e = s;
180 if(*s == PATH_SEP && e) e=NULL; /* '.' in *directory* name */
181 s++;
182 }
183 if(e) *e = 0;
184 return ss;
185 }
186
187 char *expand_format (char c, int quote)
188 {
189 WPanel *panel;
190 char *(*quote_func)(const char *, int);
191
192 if (quote)
193 quote_func = name_quote;
194 else
195 quote_func = fake_name_quote;
196
197 if (c == '%')
198 return strdup ("%");
199
200 if (islower (c))
201 panel = cpanel;
202 else {
203 if (get_other_type () == view_listing){
204 panel = other_panel;
205 } else
206 return strdup ("");
207 }
208 if (!panel)
209 panel = cpanel;
210
211 c = tolower (c);
212
213 switch (c){
214 case 'f':
215 case 'p': return (*quote_func) (panel->dir.list [panel->selected].fname, 0);
216 case 'b':
217 return strip_ext((*quote_func) (panel->dir.list [panel->selected].fname, 0));
218 case 'd': return (*quote_func) (panel->cwd, 0);
219 case 's':
220 if (!panel->marked)
221 return (*quote_func) (panel->dir.list [panel->selected].fname, 0);
222
223 /* Fall through */
224
225 case 't':
226 case 'u':
227 {
228 int length = 2, i;
229 char *block, *tmp;
230
231 for (i = 0; i < panel->count; i++)
232 if (panel->dir.list [i].f.marked)
233 length += strlen (panel->dir.list [i].fname) + 1;
234
235 block = xmalloc (length*2+1, "expand_format");
236 *block = 0;
237 for (i = 0; i < panel->count; i++)
238 if (panel->dir.list [i].f.marked){
239 strcat (block, tmp = (*quote_func) (panel->dir.list [i].fname, 0));
240 free (tmp);
241 strcat (block, " ");
242 if (c == 'u')
243 do_file_mark (panel, i, 0);
244 }
245 return block;
246 } /* sub case block */
247 } /* switch */
248 return strdup ("");
249 }
250
251 /* Checks for shell patterns defination */
252 char *check_patterns (char *p)
253 {
254 const char *def_name = "shell_patterns=";
255 int value;
256
257 if (strncmp (p, def_name, sizeof (def_name)) == 0){
258 p += strlen (def_name);
259 value = *p++ - '0';
260 if (value == 0 || value == 1)
261 easy_patterns = value;
262 else
263 message (1, MSG_ERROR, _(" Invalid shell pattern defination \"%c\". "), value + '0');
264 }
265 while (*p == '\n' || *p == '\t' || *p == ' ') p++;
266 return p;
267 }
268
269 /* Copies a whitespace separated argument from p to arg. Returns the
270 point after argument. */
271 static char *extract_arg (char *p, char *arg)
272 {
273 while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
274 p++;
275 while (*p && *p != ' ' && *p != '\t' && *p != '\n')
276 *arg++ = *p++;
277 *arg = 0;
278 if (!*p || *p == '\n')
279 p --;
280 return p;
281 }
282
283 /* Tests whether the selected file in the panel is of any of the types
284 specified in argument. */
285 static int test_type (WPanel *panel, char *arg)
286 {
287 int result = 0; /* False by default */
288 int st_mode = panel->dir.list [panel->selected].buf.st_mode;
289
290 for (;*arg != 0; arg++){
291 switch (*arg){
292 case 'n': /* Not a directory */
293 result |= !S_ISDIR (st_mode);
294 break;
295 case 'r': /* Regular file */
296 result |= S_ISREG (st_mode);
297 break;
298 case 'd': /* Directory */
299 result |= S_ISDIR (st_mode);
300 break;
301 case 'l': /* Link */
302 result |= S_ISLNK (st_mode);
303 break;
304 case 'c': /* Character special */
305 result |= S_ISCHR (st_mode);
306 break;
307 case 'b': /* Block special */
308 result |= S_ISBLK (st_mode);
309 break;
310 case 'f': /* Fifo (named pipe) */
311 result |= S_ISFIFO (st_mode);
312 break;
313 case 's': /* Socket */
314 result |= S_ISSOCK (st_mode);
315 break;
316 case 'x': /* Executable */
317 result |= (st_mode & 0111) ? 1 : 0;
318 break;
319 case 't':
320 result |= panel->marked ? 1 : 0;
321 break;
322 default:
323 debug_error = 1;
324 break;
325 }
326 }
327 return result;
328 }
329
330 /* Calculates the truth value of the next condition starting from
331 p. Returns the point after condition. */
332 static char *test_condition (char *p, int *condition)
333 {
334 WPanel *panel;
335 char arg [256];
336
337 /* Handle one condition */
338 for (;*p != '\n' && *p != '&' && *p != '|'; p++){
339 if (*p == ' ' || *p == '\t')
340 continue;
341 if (*p >= 'a')
342 panel = cpanel;
343 else {
344 if (get_other_type () == view_listing)
345 panel = other_panel;
346 else
347 panel = NULL;
348 }
349 *p |= 0x20;
350
351 switch (*p++){
352 case '!':
353 p = test_condition (p, condition);
354 *condition = ! *condition;
355 p--;
356 break;
357 case 'f':
358 p = extract_arg (p, arg);
359 *condition = panel && regexp_match (arg, panel->dir.list [panel->selected].fname, match_file);
360 break;
361 case 'd':
362 p = extract_arg (p, arg);
363 *condition = panel && regexp_match (arg, panel->cwd, match_file);
364 break;
365 case 't':
366 p = extract_arg (p, arg);
367 *condition = panel && test_type (panel, arg);
368 break;
369 default:
370 debug_error = 1;
371 break;
372 } /* switch */
373
374 } /* while */
375 return p;
376 }
377
378 /* General purpose condition debug output handler */
379 void debug_out (char *start, char *end, int cond)
380 {
381 static char msg [256];
382 int len;
383
384 if (start == NULL && end == NULL){
385 if (cond == 0){
386 /* Init */
387 msg [0] = 0;
388 } else {
389 /* Show output */
390 if (!debug_flag)
391 return;
392 len = strlen (msg);
393 if (len)
394 msg [len - 1] = 0;
395 message (0, _(" Debug "), msg);
396 debug_flag = 0;
397 }
398 } else {
399 /* Save debug info for later output */
400 if (!debug_flag)
401 return;
402 /* Save the result of the condition */
403 if (debug_error){
404 strcat (msg, _(" ERROR: "));
405 debug_error = 0;
406 }
407 else if (cond)
408 strcat (msg, _(" True: "));
409 else
410 strcat (msg, _(" False: "));
411 /* Copy condition statement */
412 len = strlen (msg);
413 if (end == NULL){
414 /* Copy one character */
415 msg [len] = *start;
416 msg [len + 1] = 0;
417 } else {
418 /* Copy many characters */
419 while (start < end){
420 msg [len++] = *start++;
421 }
422 msg [len] = 0;
423 }
424 strcat (msg, " \n");
425 }
426 }
427
428 /* Calculates the truth value of one lineful of conditions. Returns
429 the point just before the end of line. */
430 static char *test_line (char *p, int *result)
431 {
432 int condition;
433 char operator;
434 char *debug_start, *debug_end;
435
436 /* Init debugger */
437 debug_out (NULL, NULL, 0);
438 /* Repeat till end of line */
439 while (*p && *p != '\n'){
440 while (*p == ' ' || *p == '\t')
441 p++;
442 if (!*p || *p == '\n')
443 break;
444 operator = *p++;
445 if (*p == '?'){
446 debug_flag = 1;
447 p++;
448 }
449 while (*p == ' ' || *p == '\t')
450 p++;
451 if (!*p || *p == '\n')
452 break;
453 condition = 1; /* True by default */
454
455 debug_start = p;
456 p = test_condition (p, &condition);
457 debug_end = p;
458 /* Add one debug statement */
459 debug_out (debug_start, debug_end, condition);
460
461 switch (operator){
462 case '+':
463 case '=':
464 /* Assignment */
465 *result = condition;
466 break;
467 case '&': /* Logical and */
468 *result &= condition;
469 break;
470 case '|': /* Logical or */
471 *result |= condition;
472 break;
473 default:
474 debug_error = 1;
475 break;
476 } /* switch */
477 /* Add one debug statement */
478 debug_out (&operator, NULL, *result);
479
480 } /* while (*p != '\n') */
481 /* Report debug message */
482 debug_out (NULL, NULL, 1);
483
484 if (!*p || *p == '\n')
485 p --;
486 return p;
487 }
488
489 /* FIXME: recode this routine on version 3.0, it could be cleaner */
490 void execute_menu_command (char *s)
491 {
492 char *commands;
493 FILE *cmd_file;
494 int cmd_file_fd;
495 int expand_prefix_found = 0;
496 int parameter_found = 0;
497 int do_quote;
498 char prompt [80] = "";
499 int col;
500 char *file_name = tmpnam (0);
501
502 #ifdef OS2_NT
503 /* OS/2 and NT requires the command to end in .cmd */
504 file_name = copy_strings (file_name, ".cmd", NULL);
505 #endif
506 if ((cmd_file_fd = open (file_name, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0600)) == -1){
507 message (1, MSG_ERROR, _(" Can't create temporary command file \n %s "),
508 unix_error_string (errno));
509 return;
510 }
511 cmd_file = fdopen (cmd_file_fd, "w");
512 commands = strchr (s, '\n');
513 if (!commands){
514 fclose (cmd_file);
515 unlink (file_name);
516 return;
517 }
518 commands++;
519
520 for (col = 0; *commands; commands++){
521 if (col == 0 && (*commands != ' ' && *commands != '\t'))
522 break;
523 else if (col == 0)
524 while (*commands == ' ' || *commands == '\t')
525 commands++;
526 col++;
527 if (*commands == '\n')
528 col = 0;
529 if (parameter_found){
530 if (*commands == '}'){
531 char *parameter;
532 char *tmp;
533 parameter_found = 0;
534 parameter = input_dialog (_(" Parameter "), prompt, "");
535 if (!parameter || !*parameter){
536 /* User canceled */
537 fclose (cmd_file);
538 unlink (file_name);
539 return;
540 }
541 if (do_quote) {
542 fputs (tmp = name_quote (parameter, 0), cmd_file);
543 free (tmp);
544 } else
545 fputs (parameter, cmd_file);
546 free (parameter);
547 } else {
548 int len = strlen (prompt);
549
550 if (len+1 < sizeof (prompt)){
551 prompt [len] = *commands;
552 prompt [len+1] = 0;
553 } else
554 prompt [sizeof (prompt)-1] = 0;
555 }
556 } else if (expand_prefix_found){
557 expand_prefix_found = 0;
558 if (isdigit (*commands)) {
559 do_quote = atoi (commands);
560 for ( ; isdigit (*commands); commands++)
561 ;
562 }
563 if (*commands == '{')
564 parameter_found = 1;
565 else{
566 char *text = expand_format (*commands, do_quote);
567 fputs (text, cmd_file);
568 free (text);
569 }
570 } else {
571 if (*commands == '%') {
572 do_quote = 1; /* Default: Quote expanded macro */
573 expand_prefix_found = 1;
574 } else
575 fputc (*commands, cmd_file);
576 }
577 }
578 fclose (cmd_file);
579 chmod (file_name, S_IRWXU);
580 execute (file_name);
581 unlink (file_name);
582 }
583
584 /*
585 ** Check owner of the menu file. Using menu file is allowed, if
586 ** owner of the menu is root or the actual user. In either case
587 ** file should not be group and word-writable.
588 **
589 ** Q. Should we apply this routine to system and home menu (and .ext files)?
590 */
591 static int
592 menu_file_own(char* path)
593 {
594 struct stat st;
595
596 if (stat (path, &st) == 0
597 && (!st.st_uid || (st.st_uid == geteuid ()))
598 && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0)
599 ) {
600 return 1;
601 }
602 else
603 {
604 extern int verbose;
605 if (verbose)
606 {
607 message (0, _(" Warning -- ignoring file "),
608 _("File %s is not owned by root or you or is world writable.\n"
609 "Using it may compromise your security"),
610 path
611 );
612 }
613 return 0;
614 }
615 }
616
617 void user_menu_cmd (void)
618 {
619 char *menu, *p;
620 int col, i, accept_entry = 1;
621 int selected, old_patterns;
622 Listbox *listbox;
623
624 if (!vfs_current_is_local ()){
625 message (1, _(" Oops... "),
626 _(" I can't run programs while logged on a non local directory "));
627 return;
628 }
629
630 menu = strdup (MC_LOCAL_MENU);
631 if (!exist_file (menu) || !menu_file_own (menu)){
632 free (menu);
633 menu = concat_dir_and_file (home_dir, MC_HOME_MENU);
634 if (!exist_file (menu)){
635 free (menu);
636 menu = concat_dir_and_file (mc_home, MC_GLOBAL_MENU);
637 }
638 }
639
640 if ((data = load_file (menu)) == NULL){
641 message (1, MSG_ERROR, _(" Can't open file %s \n %s "),
642 menu, unix_error_string (errno));
643 free (menu);
644 return;
645 }
646 free (menu);
647
648 max_cols = 0;
649 for (i = 0; i < MAX_ENTRIES; i++)
650 entries [i] = 0;
651 selected = 0;
652
653 /* Parse the menu file */
654 old_patterns = easy_patterns;
655 p = check_patterns (data);
656 for (menu_lines = col = 0; *p; p++){
657 if (col == 0 && !entries [menu_lines]){
658 if (*p == '#'){
659 /* A commented menu entry */
660 accept_entry = 1;
661 } else if (*p == '+'){
662 if (*(p+1) == '='){
663 /* Combined adding and default */
664 char *q = p++;
665
666 p = test_line (q, &accept_entry);
667 if (selected == 0 && accept_entry)
668 selected = menu_lines;
669 } else {
670 /* A condition for adding the entry */
671 p = test_line (p, &accept_entry);
672 }
673 } else if (*p == '='){
674 if (*(p+1) == '+'){
675 char *q = p++;
676 /* Combined adding and default */
677 p = test_line (q, &accept_entry);
678 if (selected == 0 && accept_entry)
679 selected = menu_lines;
680 } else {
681 /* A condition for making the entry default */
682 i = 1;
683 p = test_line (p, &i);
684 if (selected == 0 && i)
685 selected = menu_lines;
686 }
687 }
688 else if (*p > ' ' && *p < 127){
689 /* A menu entry title line */
690 if (accept_entry)
691 entries [menu_lines] = p;
692 else
693 accept_entry = 1;
694 }
695 }
696 if (menu_lines == MAX_ENTRIES)
697 break;
698 if (*p == '\t')
699 *p = ' ';
700 col++;
701 if (*p == '\n'){
702 if (entries [menu_lines]){
703 menu_lines++;
704 accept_entry = 1;
705 }
706 max_cols = max (max_cols, col);
707 col = 0;
708 }
709 }
710 max_cols = min (max (max_cols, col), MAX_ENTRY_LEN);
711
712 /* Create listbox */
713 listbox = create_listbox_window (max_cols+2, menu_lines, _(" User menu "),
714 "[Menu File Edit]");
715
716 /* insert all the items found */
717 for (i = 0; i < menu_lines; i++)
718 LISTBOX_APPEND_TEXT (listbox, entries [i][0],
719 extract_line (entries [i],
720 entries [i]+MAX_ENTRY_LEN),
721 entries [i]);
722
723 /* Select the default entry */
724 listbox_select_by_number (listbox->list, selected);
725
726 selected = run_listbox (listbox);
727 if (selected >= 0)
728 execute_menu_command (entries [selected]);
729
730 easy_patterns = old_patterns;
731 do_refresh ();
732 free (data);
733 }