Merge branch '4524_cleanup'
[midnight-commander.git] / src / usermenu.c
blobaccd9f224b5a4ff7ffeaf3dca98b8fcffc3ceeaf
1 /*
2 User Menu implementation
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2013
11 This file is part of the Midnight Commander.
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 /** \file usermenu.c
28 * \brief Source: user menu implementation
31 #include <config.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
39 #include "lib/global.h"
40 #include "lib/fileloc.h"
41 #include "lib/tty/tty.h"
42 #include "lib/skin.h"
43 #include "lib/search.h"
44 #include "lib/vfs/vfs.h"
45 #include "lib/strutil.h"
46 #include "lib/util.h"
48 #ifdef USE_INTERNAL_EDIT
49 #include "src/editor/edit.h" /* WEdit */
50 #endif
51 #include "src/viewer/mcviewer.h" /* for default_* externs */
53 #include "src/args.h" /* mc_run_param0 */
54 #include "src/execute.h"
55 #include "src/setup.h"
56 #include "src/history.h"
58 #include "src/filemanager/dir.h"
59 #include "src/filemanager/filemanager.h"
60 #include "src/filemanager/layout.h"
62 #include "usermenu.h"
64 /*** global variables ****************************************************************************/
66 /*** file scope macro definitions ****************************************************************/
68 #define MAX_ENTRIES 16
69 #define MAX_ENTRY_LEN 60
71 /*** file scope type declarations ****************************************************************/
73 /*** forward declarations (file scope functions) *************************************************/
75 /*** file scope variables ************************************************************************/
77 static gboolean debug_flag = FALSE;
78 static gboolean debug_error = FALSE;
79 static char *menu = NULL;
81 /* --------------------------------------------------------------------------------------------- */
82 /*** file scope functions ************************************************************************/
83 /* --------------------------------------------------------------------------------------------- */
85 /** strip file's extension */
86 static char *
87 strip_ext (char *ss)
89 char *s;
90 char *e = NULL;
92 if (ss == NULL)
93 return NULL;
95 for (s = ss; *s != '\0'; s++)
97 if (*s == '.')
98 e = s;
99 if (IS_PATH_SEP (*s) && e != NULL)
100 e = NULL; /* '.' in *directory* name */
103 if (e != NULL)
104 *e = '\0';
106 return (*ss == '\0' ? NULL : ss);
109 /* --------------------------------------------------------------------------------------------- */
111 * Check for the "shell_patterns" directive. If it's found and valid,
112 * interpret it and move the pointer past the directive. Return the
113 * current pointer.
116 static char *
117 check_patterns (char *p)
119 static const char def_name[] = "shell_patterns=";
120 char *p0 = p;
122 if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
123 return p0;
125 p += sizeof (def_name) - 1;
126 if (*p == '1')
127 easy_patterns = TRUE;
128 else if (*p == '0')
129 easy_patterns = FALSE;
130 else
131 return p0;
133 /* Skip spaces */
134 p++;
135 while (whiteness (*p))
136 p++;
137 return p;
140 /* --------------------------------------------------------------------------------------------- */
141 /** Copies a whitespace separated argument from p to arg. Returns the
142 point after argument. */
144 static char *
145 extract_arg (char *p, char *arg, int size)
147 while (*p != '\0' && whiteness (*p))
148 p++;
150 /* support quote space .mnu */
151 while (*p != '\0' && (*p != ' ' || *(p - 1) == '\\') && *p != '\t' && *p != '\n')
153 char *np;
155 np = str_get_next_char (p);
156 if (np - p >= size)
157 break;
158 memcpy (arg, p, np - p);
159 arg += np - p;
160 size -= np - p;
161 p = np;
163 *arg = '\0';
164 if (*p == '\0' || *p == '\n')
165 str_prev_char (&p);
166 return p;
169 /* --------------------------------------------------------------------------------------------- */
170 /* Tests whether the selected file in the panel is of any of the types
171 specified in argument. */
173 static gboolean
174 test_type (WPanel * panel, char *arg)
176 int result = 0; /* False by default */
177 mode_t st_mode;
179 st_mode = panel_current_entry (panel)->st.st_mode;
181 for (; *arg != '\0'; arg++)
183 switch (*arg)
185 case 'n': /* Not a directory */
186 result |= !S_ISDIR (st_mode);
187 break;
188 case 'r': /* Regular file */
189 result |= S_ISREG (st_mode);
190 break;
191 case 'd': /* Directory */
192 result |= S_ISDIR (st_mode);
193 break;
194 case 'l': /* Link */
195 result |= S_ISLNK (st_mode);
196 break;
197 case 'c': /* Character special */
198 result |= S_ISCHR (st_mode);
199 break;
200 case 'b': /* Block special */
201 result |= S_ISBLK (st_mode);
202 break;
203 case 'f': /* Fifo (named pipe) */
204 result |= S_ISFIFO (st_mode);
205 break;
206 case 's': /* Socket */
207 result |= S_ISSOCK (st_mode);
208 break;
209 case 'x': /* Executable */
210 result |= (st_mode & 0111) != 0 ? 1 : 0;
211 break;
212 case 't':
213 result |= panel->marked != 0 ? 1 : 0;
214 break;
215 default:
216 debug_error = TRUE;
217 break;
221 return (result != 0);
224 /* --------------------------------------------------------------------------------------------- */
225 /** Calculates the truth value of the next condition starting from
226 p. Returns the point after condition. */
228 static char *
229 test_condition (const Widget * edit_widget, char *p, gboolean * condition)
231 char arg[256];
232 const mc_search_type_t search_type = easy_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
233 #ifdef USE_INTERNAL_EDIT
234 const WEdit *e = CONST_EDIT (edit_widget);
235 #endif
237 /* Handle one condition */
238 for (; *p != '\n' && *p != '&' && *p != '|'; p++)
240 WPanel *panel = NULL;
242 /* support quote space .mnu */
243 if ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
244 continue;
245 if (*p >= 'a')
246 panel = current_panel;
247 else if (get_other_type () == view_listing)
248 panel = other_panel;
250 *p |= 0x20;
252 switch (*p++)
254 case '!':
255 p = test_condition (edit_widget, p, condition);
256 *condition = !*condition;
257 str_prev_char (&p);
258 break;
259 case 'f': /* file name pattern */
260 p = extract_arg (p, arg, sizeof (arg));
261 #ifdef USE_INTERNAL_EDIT
262 if (e != NULL)
264 const char *edit_filename;
266 edit_filename = edit_get_file_name (e);
267 *condition = mc_search (arg, DEFAULT_CHARSET, edit_filename, search_type);
269 else
270 #endif
271 *condition = panel != NULL &&
272 mc_search (arg, DEFAULT_CHARSET, panel_current_entry (panel)->fname->str,
273 search_type);
274 break;
275 case 'y': /* syntax pattern */
276 #ifdef USE_INTERNAL_EDIT
277 if (e != NULL)
279 const char *syntax_type;
281 syntax_type = edit_get_syntax_type (e);
282 if (syntax_type != NULL)
284 p = extract_arg (p, arg, sizeof (arg));
285 *condition = mc_search (arg, DEFAULT_CHARSET, syntax_type, MC_SEARCH_T_NORMAL);
288 #endif
289 break;
290 case 'd':
291 p = extract_arg (p, arg, sizeof (arg));
292 *condition = panel != NULL
293 && mc_search (arg, DEFAULT_CHARSET, vfs_path_as_str (panel->cwd_vpath),
294 search_type);
295 break;
296 case 't':
297 p = extract_arg (p, arg, sizeof (arg));
298 *condition = panel != NULL && test_type (panel, arg);
299 break;
300 case 'x': /* executable */
302 struct stat status;
304 p = extract_arg (p, arg, sizeof (arg));
305 *condition = stat (arg, &status) == 0 && is_exe (status.st_mode);
306 break;
308 default:
309 debug_error = TRUE;
310 break;
311 } /* switch */
312 } /* while */
313 return p;
316 /* --------------------------------------------------------------------------------------------- */
317 /** General purpose condition debug output handler */
319 static void
320 debug_out (char *start, char *end, gboolean condition)
322 static char *msg = NULL;
324 if (start == NULL && end == NULL)
326 /* Show output */
327 if (debug_flag && msg != NULL)
329 size_t len;
331 len = strlen (msg);
332 if (len != 0)
333 msg[len - 1] = '\0';
334 message (D_NORMAL, _("Debug"), "%s", msg);
337 debug_flag = FALSE;
338 MC_PTR_FREE (msg);
340 else
342 const char *type;
343 char *p;
345 /* Save debug info for later output */
346 if (!debug_flag)
347 return;
348 /* Save the result of the condition */
349 if (debug_error)
351 type = _("ERROR:");
352 debug_error = FALSE;
354 else if (condition)
355 type = _("True:");
356 else
357 type = _("False:");
358 /* This is for debugging, don't need to be super efficient. */
359 if (end == NULL)
360 p = g_strdup_printf ("%s %s %c \n", msg ? msg : "", type, *start);
361 else
362 p = g_strdup_printf ("%s %s %.*s \n", msg ? msg : "", type, (int) (end - start), start);
363 g_free (msg);
364 msg = p;
368 /* --------------------------------------------------------------------------------------------- */
369 /** Calculates the truth value of one lineful of conditions. Returns
370 the point just before the end of line. */
372 static char *
373 test_line (const Widget * edit_widget, char *p, gboolean * result)
375 char operator;
377 /* Repeat till end of line */
378 while (*p != '\0' && *p != '\n')
380 char *debug_start, *debug_end;
381 gboolean condition = TRUE;
383 /* support quote space .mnu */
384 while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
385 p++;
386 if (*p == '\0' || *p == '\n')
387 break;
388 operator = *p++;
389 if (*p == '?')
391 debug_flag = TRUE;
392 p++;
394 /* support quote space .mnu */
395 while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
396 p++;
397 if (*p == '\0' || *p == '\n')
398 break;
400 debug_start = p;
401 p = test_condition (edit_widget, p, &condition);
402 debug_end = p;
403 /* Add one debug statement */
404 debug_out (debug_start, debug_end, condition);
406 switch (operator)
408 case '+':
409 case '=':
410 /* Assignment */
411 *result = condition;
412 break;
413 case '&': /* Logical and */
414 *result = *result && condition;
415 break;
416 case '|': /* Logical or */
417 *result = *result || condition;
418 break;
419 default:
420 debug_error = TRUE;
421 break;
422 } /* switch */
423 /* Add one debug statement */
424 debug_out (&operator, NULL, *result);
426 } /* while (*p != '\n') */
427 /* Report debug message */
428 debug_out (NULL, NULL, TRUE);
430 if (*p == '\0' || *p == '\n')
431 str_prev_char (&p);
432 return p;
435 /* --------------------------------------------------------------------------------------------- */
436 /** FIXME: recode this routine on version 3.0, it could be cleaner */
438 static void
439 execute_menu_command (const Widget * edit_widget, const char *commands, gboolean show_prompt)
441 FILE *cmd_file;
442 int cmd_file_fd;
443 gboolean expand_prefix_found = FALSE;
444 char *parameter = NULL;
445 gboolean do_quote = FALSE;
446 char lc_prompt[80];
447 int col;
448 vfs_path_t *file_name_vpath;
449 gboolean run_view = FALSE;
450 char *cmd;
452 /* Skip menu entry title line */
453 commands = strchr (commands, '\n');
454 if (commands == NULL)
455 return;
457 cmd_file_fd = mc_mkstemps (&file_name_vpath, "mcusr", SCRIPT_SUFFIX);
459 if (cmd_file_fd == -1)
461 message (D_ERROR, MSG_ERROR, _("Cannot create temporary command file\n%s"),
462 unix_error_string (errno));
463 return;
466 cmd_file = fdopen (cmd_file_fd, "w");
467 fputs ("#! /bin/sh\n", cmd_file);
468 commands++;
470 for (col = 0; *commands != '\0'; commands++)
472 if (col == 0)
474 if (!whitespace (*commands))
475 break;
476 while (whitespace (*commands))
477 commands++;
478 if (*commands == '\0')
479 break;
481 col++;
482 if (*commands == '\n')
483 col = 0;
484 if (parameter != NULL)
486 if (*commands == '}')
488 *parameter = '\0';
489 parameter =
490 input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_FM_MENU_EXEC_PARAM, "",
491 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD |
492 INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_VARIABLES |
493 INPUT_COMPLETE_USERNAMES);
494 if (parameter == NULL || *parameter == '\0')
496 /* User canceled */
497 g_free (parameter);
498 fclose (cmd_file);
499 mc_unlink (file_name_vpath);
500 vfs_path_free (file_name_vpath, TRUE);
501 return;
503 if (do_quote)
505 char *tmp;
507 tmp = name_quote (parameter, FALSE);
508 if (tmp != NULL)
510 fputs (tmp, cmd_file);
511 g_free (tmp);
514 else
515 fputs (parameter, cmd_file);
517 MC_PTR_FREE (parameter);
519 else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
520 *parameter++ = *commands;
522 else if (expand_prefix_found)
524 expand_prefix_found = FALSE;
525 if (g_ascii_isdigit ((gchar) * commands))
527 do_quote = (atoi (commands) != 0);
528 while (g_ascii_isdigit ((gchar) * commands))
529 commands++;
531 if (*commands == '{')
532 parameter = lc_prompt;
533 else
535 char *text;
537 text = expand_format (edit_widget, *commands, do_quote);
538 if (text != NULL)
540 fputs (text, cmd_file);
541 g_free (text);
545 else if (*commands == '%')
547 int i;
549 i = check_format_view (commands + 1);
550 if (i != 0)
552 commands += i;
553 run_view = TRUE;
555 else
557 do_quote = TRUE; /* Default: Quote expanded macro */
558 expand_prefix_found = TRUE;
561 else
562 fputc (*commands, cmd_file);
565 fclose (cmd_file);
566 mc_chmod (file_name_vpath, S_IRWXU);
568 /* Execute the command indirectly to allow execution even on no-exec filesystems. */
569 cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (file_name_vpath), (char *) NULL);
571 if (run_view)
573 mcview_viewer (cmd, NULL, 0, 0, 0);
574 dialog_switch_process_pending ();
576 else if (show_prompt)
577 shell_execute (cmd, EXECUTE_HIDE);
578 else
580 gboolean ok;
582 /* Prepare the terminal by setting its flag to the initial ones. This will cause \r
583 * to work as expected, instead of being ignored. */
584 tty_reset_shell_mode ();
586 ok = (system (cmd) != -1);
588 /* Restore terminal configuration. */
589 tty_raw_mode ();
591 /* Redraw the original screen's contents. */
592 tty_clear_screen ();
593 repaint_screen ();
595 if (!ok)
596 message (D_ERROR, MSG_ERROR, "%s", _("Error calling program"));
599 g_free (cmd);
601 mc_unlink (file_name_vpath);
602 vfs_path_free (file_name_vpath, TRUE);
605 /* --------------------------------------------------------------------------------------------- */
607 ** Check owner of the menu file. Using menu file is allowed, if
608 ** owner of the menu is root or the actual user. In either case
609 ** file should not be group and word-writable.
611 ** Q. Should we apply this routine to system and home menu (and .ext files)?
614 static gboolean
615 menu_file_own (char *path)
617 struct stat st;
619 if (stat (path, &st) == 0 && (st.st_uid == 0 || (st.st_uid == geteuid ()) != 0)
620 && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0))
621 return TRUE;
623 if (verbose)
624 message (D_NORMAL, _("Warning -- ignoring file"),
625 _("File %s is not owned by root or you or is world writable.\n"
626 "Using it may compromise your security"), path);
628 return FALSE;
631 /* --------------------------------------------------------------------------------------------- */
632 /*** public functions ****************************************************************************/
633 /* --------------------------------------------------------------------------------------------- */
635 /* Formats defined:
636 %% The % character
637 %f The current file in the active panel (if non-local vfs, file will be copied locally
638 and %f will be full path to it) or the opened file in the internal editor.
639 %p Likewise.
640 %d The current working directory
641 %s "Selected files"; the tagged files if any, otherwise the current file
642 %t Tagged files
643 %u Tagged files (and they are untagged on return from expand_format)
644 %view Runs the commands and pipes standard output to the view command.
645 If %view is immediately followed by '{', recognize keywords
646 ascii, hex, nroff and unform
648 If the format letter is in uppercase, it refers to the other panel.
650 With a number followed the % character you can turn quoting on (default)
651 and off. For example:
652 %f quote expanded macro
653 %1f ditto
654 %0f don't quote expanded macro
656 expand_format returns a memory block that must be free()d.
659 /* Returns how many characters we should advance if %view was found */
661 check_format_view (const char *p)
663 const char *q = p;
665 if (strncmp (p, "view", 4) == 0)
667 q += 4;
668 if (*q == '{')
670 for (q++; *q != '\0' && *q != '}'; q++)
672 if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
674 mcview_global_flags.hex = FALSE;
675 q += 4;
677 else if (strncmp (q, "hex", 3) == 0)
679 mcview_global_flags.hex = TRUE;
680 q += 2;
682 else if (strncmp (q, "nroff", 5) == 0)
684 mcview_global_flags.nroff = TRUE;
685 q += 4;
687 else if (strncmp (q, "unform", 6) == 0)
689 mcview_global_flags.nroff = FALSE;
690 q += 5;
693 if (*q == '}')
694 q++;
696 return q - p;
698 return 0;
701 /* --------------------------------------------------------------------------------------------- */
704 check_format_cd (const char *p)
706 return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
709 /* --------------------------------------------------------------------------------------------- */
710 /* Check if p has a "^var\{var-name\}" */
711 /* Returns the number of skipped characters (zero on not found) */
712 /* V will be set to the expanded variable name */
715 check_format_var (const char *p, char **v)
717 *v = NULL;
719 if (strncmp (p, "var{", 4) == 0)
721 const char *q = p;
722 const char *dots = NULL;
723 const char *value;
724 char *var_name;
726 for (q += 4; *q != '\0' && *q != '}'; q++)
728 if (*q == ':')
729 dots = q + 1;
731 if (*q == '\0')
732 return 0;
734 if (dots == NULL || dots == q + 5)
736 message (D_ERROR,
737 _("Format error on file Extensions File"),
738 !dots ? _("The %%var macro has no default")
739 : _("The %%var macro has no variable"));
740 return 0;
743 /* Copy the variable name */
744 var_name = g_strndup (p + 4, dots - 2 - (p + 3));
745 value = getenv (var_name);
746 g_free (var_name);
748 if (value != NULL)
749 *v = g_strdup (value);
750 else
751 *v = g_strndup (dots, q - dots);
753 return q - p;
755 return 0;
758 /* --------------------------------------------------------------------------------------------- */
760 char *
761 expand_format (const Widget * edit_widget, char c, gboolean do_quote)
763 WPanel *panel = NULL;
764 char *(*quote_func) (const char *, gboolean);
765 const char *fname = NULL;
766 char *result;
767 char c_lc;
769 #ifdef USE_INTERNAL_EDIT
770 const WEdit *e = CONST_EDIT (edit_widget);
771 #else
772 (void) edit_widget;
773 #endif
775 if (c == '%')
776 return g_strdup ("%");
778 switch (mc_global.mc_run_mode)
780 case MC_RUN_FULL:
781 #ifdef USE_INTERNAL_EDIT
782 if (e != NULL)
783 fname = edit_get_file_name (e);
784 else
785 #endif
787 if (g_ascii_islower ((gchar) c))
788 panel = current_panel;
789 else
791 if (get_other_type () != view_listing)
792 return NULL;
793 panel = other_panel;
796 fname = panel_current_entry (panel)->fname->str;
798 break;
800 #ifdef USE_INTERNAL_EDIT
801 case MC_RUN_EDITOR:
802 fname = edit_get_file_name (e);
803 break;
804 #endif
806 case MC_RUN_VIEWER:
807 /* mc_run_param0 is not NULL here because mcviewer isn't run without input file */
808 fname = (const char *) mc_run_param0;
809 break;
811 default:
812 /* other modes don't use formats */
813 return NULL;
816 if (do_quote)
817 quote_func = name_quote;
818 else
819 quote_func = fake_name_quote;
821 c_lc = g_ascii_tolower ((gchar) c);
823 switch (c_lc)
825 case 'f':
826 case 'p':
827 result = quote_func (fname, FALSE);
828 goto ret;
829 case 'x':
830 result = quote_func (extension (fname), FALSE);
831 goto ret;
832 case 'd':
834 const char *cwd;
836 if (panel != NULL)
837 cwd = vfs_path_as_str (panel->cwd_vpath);
838 else
839 cwd = vfs_get_current_dir ();
841 result = quote_func (cwd, FALSE);
842 goto ret;
844 case 'c':
845 #ifdef USE_INTERNAL_EDIT
846 if (e != NULL)
848 result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (e));
849 goto ret;
851 #endif
852 break;
853 case 'i': /* indent equal number cursor position in line */
854 #ifdef USE_INTERNAL_EDIT
855 if (e != NULL)
857 result = g_strnfill (edit_get_curs_col (e), ' ');
858 goto ret;
860 #endif
861 break;
862 case 'y': /* syntax type */
863 #ifdef USE_INTERNAL_EDIT
864 if (e != NULL)
866 const char *syntax_type;
868 syntax_type = edit_get_syntax_type (e);
869 if (syntax_type != NULL)
871 result = g_strdup (syntax_type);
872 goto ret;
875 #endif
876 break;
877 case 'k': /* block file name */
878 case 'b': /* block file name / strip extension */
879 #ifdef USE_INTERNAL_EDIT
880 if (e != NULL)
882 char *file;
884 file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
885 result = quote_func (file, FALSE);
886 g_free (file);
887 goto ret;
889 #endif
890 if (c_lc == 'b')
892 result = strip_ext (quote_func (fname, FALSE));
893 goto ret;
895 break;
896 case 'n': /* strip extension in editor */
897 #ifdef USE_INTERNAL_EDIT
898 if (e != NULL)
900 result = strip_ext (quote_func (fname, FALSE));
901 goto ret;
903 #endif
904 break;
905 case 'm': /* menu file name */
906 if (menu != NULL)
908 result = quote_func (menu, FALSE);
909 goto ret;
911 break;
912 case 's':
913 if (panel == NULL || panel->marked == 0)
915 result = quote_func (fname, FALSE);
916 goto ret;
919 MC_FALLTHROUGH;
921 case 't':
922 case 'u':
924 GString *block = NULL;
925 int i;
927 if (panel == NULL)
929 result = NULL;
930 goto ret;
933 for (i = 0; i < panel->dir.len; i++)
934 if (panel->dir.list[i].f.marked != 0)
936 char *tmp;
938 tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
939 if (tmp != NULL)
941 if (block == NULL)
942 block = g_string_new_take (tmp);
943 else
945 g_string_append (block, tmp);
946 g_free (tmp);
948 g_string_append_c (block, ' ');
951 if (c_lc == 'u')
952 do_file_mark (panel, i, 0);
954 result = block == NULL ? NULL : g_string_free (block, block->len == 0);
955 goto ret;
956 } /* sub case block */
957 default:
958 break;
959 } /* switch */
961 result = g_strdup ("% ");
962 result[1] = c;
963 ret:
964 return result;
967 /* --------------------------------------------------------------------------------------------- */
969 * If edit_widget is NULL then we are called from the mc menu,
970 * otherwise we are called from the mcedit menu.
973 gboolean
974 user_menu_cmd (const Widget * edit_widget, const char *menu_file, int selected_entry)
976 char *p;
977 char *data, **entries;
978 int max_cols, menu_lines, menu_limit;
979 int col, i;
980 gboolean accept_entry = TRUE;
981 int selected;
982 gboolean old_patterns;
983 gboolean res = FALSE;
984 gboolean interactive = TRUE;
986 if (!vfs_current_is_local ())
988 message (D_ERROR, MSG_ERROR, "%s", _("Cannot execute commands on non-local filesystems"));
989 return FALSE;
991 if (menu_file != NULL)
992 menu = g_strdup (menu_file);
993 else
994 menu = g_strdup (edit_widget != NULL ? EDIT_LOCAL_MENU : MC_LOCAL_MENU);
995 if (!exist_file (menu) || !menu_file_own (menu))
997 if (menu_file != NULL)
999 message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu,
1000 unix_error_string (errno));
1001 MC_PTR_FREE (menu);
1002 return FALSE;
1005 g_free (menu);
1006 if (edit_widget != NULL)
1007 menu = mc_config_get_full_path (EDIT_HOME_MENU);
1008 else
1009 menu = mc_config_get_full_path (MC_USERMENU_FILE);
1011 if (!exist_file (menu))
1013 g_free (menu);
1014 menu =
1015 mc_build_filename (mc_config_get_home_dir (),
1016 edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
1017 (char *) NULL);
1018 if (!exist_file (menu))
1020 g_free (menu);
1021 menu =
1022 mc_build_filename (mc_global.sysconfig_dir,
1023 edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
1024 (char *) NULL);
1025 if (!exist_file (menu))
1027 g_free (menu);
1028 menu =
1029 mc_build_filename (mc_global.share_data_dir,
1030 edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
1031 (char *) NULL);
1037 if (!g_file_get_contents (menu, &data, NULL, NULL))
1039 message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu, unix_error_string (errno));
1040 MC_PTR_FREE (menu);
1041 return FALSE;
1044 max_cols = 0;
1045 selected = 0;
1046 menu_limit = 0;
1047 entries = NULL;
1049 /* Parse the menu file */
1050 old_patterns = easy_patterns;
1051 p = check_patterns (data);
1052 for (menu_lines = col = 0; *p != '\0'; str_next_char (&p))
1054 if (menu_lines >= menu_limit)
1056 char **new_entries;
1058 menu_limit += MAX_ENTRIES;
1059 new_entries = g_try_realloc (entries, sizeof (new_entries[0]) * menu_limit);
1060 if (new_entries == NULL)
1061 break;
1063 entries = new_entries;
1064 new_entries += menu_limit;
1065 while (--new_entries >= &entries[menu_lines])
1066 *new_entries = NULL;
1069 if (col == 0 && entries[menu_lines] == NULL)
1070 switch (*p)
1072 case '#':
1073 /* do not show prompt if first line of external script is #silent */
1074 if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
1075 interactive = FALSE;
1076 /* A commented menu entry */
1077 accept_entry = TRUE;
1078 break;
1080 case '+':
1081 if (*(p + 1) == '=')
1083 /* Combined adding and default */
1084 p = test_line (edit_widget, p + 1, &accept_entry);
1085 if (selected == 0 && accept_entry)
1086 selected = menu_lines;
1088 else
1090 /* A condition for adding the entry */
1091 p = test_line (edit_widget, p, &accept_entry);
1093 break;
1095 case '=':
1096 if (*(p + 1) == '+')
1098 /* Combined adding and default */
1099 p = test_line (edit_widget, p + 1, &accept_entry);
1100 if (selected == 0 && accept_entry)
1101 selected = menu_lines;
1103 else
1105 /* A condition for making the entry default */
1106 i = 1;
1107 p = test_line (edit_widget, p, &i);
1108 if (selected == 0 && i != 0)
1109 selected = menu_lines;
1111 break;
1113 default:
1114 if (!whitespace (*p) && str_isprint (p))
1116 /* A menu entry title line */
1117 if (accept_entry)
1118 entries[menu_lines] = p;
1119 else
1120 accept_entry = TRUE;
1122 break;
1125 if (*p == '\n')
1127 if (entries[menu_lines] != NULL)
1129 menu_lines++;
1130 accept_entry = TRUE;
1132 max_cols = MAX (max_cols, col);
1133 col = 0;
1135 else
1137 if (*p == '\t')
1138 *p = ' ';
1139 col++;
1143 if (menu_lines == 0)
1145 message (D_ERROR, MSG_ERROR, _("No suitable entries found in %s"), menu);
1146 res = FALSE;
1148 else
1150 if (selected_entry >= 0)
1151 selected = selected_entry;
1152 else
1154 Listbox *listbox;
1156 max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1158 /* Create listbox */
1159 listbox = listbox_window_new (menu_lines, max_cols + 2, _("User menu"),
1160 "[Edit Menu File]");
1161 /* insert all the items found */
1162 for (i = 0; i < menu_lines; i++)
1164 p = entries[i];
1165 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1166 extract_line (p, p + MAX_ENTRY_LEN), p, FALSE);
1168 /* Select the default entry */
1169 listbox_set_current (listbox->list, selected);
1171 selected = listbox_run (listbox);
1173 if (selected >= 0)
1175 execute_menu_command (edit_widget, entries[selected], interactive);
1176 res = TRUE;
1179 do_refresh ();
1182 easy_patterns = old_patterns;
1183 MC_PTR_FREE (menu);
1184 g_free (entries);
1185 g_free (data);
1186 return res;
1189 /* --------------------------------------------------------------------------------------------- */