root/src/usermenu.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. strip_ext
  2. check_patterns
  3. extract_arg
  4. test_type
  5. test_condition
  6. debug_out
  7. test_line
  8. execute_menu_command
  9. menu_file_own
  10. check_format_view
  11. check_format_cd
  12. check_format_var
  13. expand_format
  14. user_menu_cmd

   1 /*
   2    User Menu implementation
   3 
   4    Copyright (C) 1994-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Slava Zanko <slavazanko@gmail.com>, 2013
   9    Andrew Borodin <aborodin@vmail.ru>, 2013
  10 
  11    This file is part of the Midnight Commander.
  12 
  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.
  17 
  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.
  22 
  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/>.
  25  */
  26 
  27 /** \file usermenu.c
  28  *  \brief Source: user menu implementation
  29  */
  30 
  31 #include <config.h>
  32 
  33 #include <ctype.h>
  34 #include <errno.h>
  35 #include <stdlib.h>
  36 #include <stdio.h>
  37 #include <string.h>
  38 
  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"
  47 
  48 #ifdef USE_INTERNAL_EDIT
  49 #include "src/editor/edit.h"    /* WEdit */
  50 #endif
  51 #include "src/viewer/mcviewer.h"        /* for default_* externs */
  52 
  53 #include "src/args.h"           /* mc_run_param0 */
  54 #include "src/execute.h"
  55 #include "src/setup.h"
  56 #include "src/history.h"
  57 
  58 #include "src/filemanager/dir.h"
  59 #include "src/filemanager/filemanager.h"
  60 #include "src/filemanager/layout.h"
  61 
  62 #include "usermenu.h"
  63 
  64 /*** global variables ****************************************************************************/
  65 
  66 /*** file scope macro definitions ****************************************************************/
  67 
  68 #define MAX_ENTRIES 16
  69 #define MAX_ENTRY_LEN 60
  70 
  71 /*** file scope type declarations ****************************************************************/
  72 
  73 /*** forward declarations (file scope functions) *************************************************/
  74 
  75 /*** file scope variables ************************************************************************/
  76 
  77 static gboolean debug_flag = FALSE;
  78 static gboolean debug_error = FALSE;
  79 static char *menu = NULL;
  80 
  81 /* --------------------------------------------------------------------------------------------- */
  82 /*** file scope functions ************************************************************************/
  83 /* --------------------------------------------------------------------------------------------- */
  84 
  85 /** strip file's extension */
  86 static char *
  87 strip_ext (char *ss)
     /* [previous][next][first][last][top][bottom][index][help]  */
  88 {
  89     char *s;
  90     char *e = NULL;
  91 
  92     if (ss == NULL)
  93         return NULL;
  94 
  95     for (s = ss; *s != '\0'; s++)
  96     {
  97         if (*s == '.')
  98             e = s;
  99         if (IS_PATH_SEP (*s) && e != NULL)
 100             e = NULL;           /* '.' in *directory* name */
 101     }
 102 
 103     if (e != NULL)
 104         *e = '\0';
 105 
 106     return (*ss == '\0' ? NULL : ss);
 107 }
 108 
 109 /* --------------------------------------------------------------------------------------------- */
 110 /**
 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.
 114  */
 115 
 116 static char *
 117 check_patterns (char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 118 {
 119     static const char def_name[] = "shell_patterns=";
 120     char *p0 = p;
 121 
 122     if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
 123         return p0;
 124 
 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;
 132 
 133     /* Skip spaces */
 134     p++;
 135     while (whiteness (*p))
 136         p++;
 137     return p;
 138 }
 139 
 140 /* --------------------------------------------------------------------------------------------- */
 141 /** Copies a whitespace separated argument from p to arg. Returns the
 142    point after argument. */
 143 
 144 static char *
 145 extract_arg (char *p, char *arg, int size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 146 {
 147     while (*p != '\0' && whiteness (*p))
 148         p++;
 149 
 150     /* support quote space .mnu */
 151     while (*p != '\0' && (*p != ' ' || *(p - 1) == '\\') && *p != '\t' && *p != '\n')
 152     {
 153         char *np;
 154 
 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;
 162     }
 163     *arg = '\0';
 164     if (*p == '\0' || *p == '\n')
 165         str_prev_char (&p);
 166     return p;
 167 }
 168 
 169 /* --------------------------------------------------------------------------------------------- */
 170 /* Tests whether the selected file in the panel is of any of the types
 171    specified in argument. */
 172 
 173 static gboolean
 174 test_type (WPanel * panel, char *arg)
     /* [previous][next][first][last][top][bottom][index][help]  */
 175 {
 176     int result = 0;             /* False by default */
 177     mode_t st_mode;
 178 
 179     st_mode = panel_current_entry (panel)->st.st_mode;
 180 
 181     for (; *arg != '\0'; arg++)
 182     {
 183         switch (*arg)
 184         {
 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;
 218         }
 219     }
 220 
 221     return (result != 0);
 222 }
 223 
 224 /* --------------------------------------------------------------------------------------------- */
 225 /** Calculates the truth value of the next condition starting from
 226    p. Returns the point after condition. */
 227 
 228 static char *
 229 test_condition (const Widget * edit_widget, char *p, gboolean * condition)
     /* [previous][next][first][last][top][bottom][index][help]  */
 230 {
 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
 236 
 237     /* Handle one condition */
 238     for (; *p != '\n' && *p != '&' && *p != '|'; p++)
 239     {
 240         WPanel *panel = NULL;
 241 
 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;
 249 
 250         *p |= 0x20;
 251 
 252         switch (*p++)
 253         {
 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)
 263             {
 264                 const char *edit_filename;
 265 
 266                 edit_filename = edit_get_file_name (e);
 267                 *condition = mc_search (arg, DEFAULT_CHARSET, edit_filename, search_type);
 268             }
 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)
 278             {
 279                 const char *syntax_type;
 280 
 281                 syntax_type = edit_get_syntax_type (e);
 282                 if (syntax_type != NULL)
 283                 {
 284                     p = extract_arg (p, arg, sizeof (arg));
 285                     *condition = mc_search (arg, DEFAULT_CHARSET, syntax_type, MC_SEARCH_T_NORMAL);
 286                 }
 287             }
 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 */
 301             {
 302                 struct stat status;
 303 
 304                 p = extract_arg (p, arg, sizeof (arg));
 305                 *condition = stat (arg, &status) == 0 && is_exe (status.st_mode);
 306                 break;
 307             }
 308         default:
 309             debug_error = TRUE;
 310             break;
 311         }                       /* switch */
 312     }                           /* while */
 313     return p;
 314 }
 315 
 316 /* --------------------------------------------------------------------------------------------- */
 317 /** General purpose condition debug output handler */
 318 
 319 static void
 320 debug_out (char *start, char *end, gboolean condition)
     /* [previous][next][first][last][top][bottom][index][help]  */
 321 {
 322     static char *msg = NULL;
 323 
 324     if (start == NULL && end == NULL)
 325     {
 326         /* Show output */
 327         if (debug_flag && msg != NULL)
 328         {
 329             size_t len;
 330 
 331             len = strlen (msg);
 332             if (len != 0)
 333                 msg[len - 1] = '\0';
 334             message (D_NORMAL, _("Debug"), "%s", msg);
 335 
 336         }
 337         debug_flag = FALSE;
 338         MC_PTR_FREE (msg);
 339     }
 340     else
 341     {
 342         const char *type;
 343         char *p;
 344 
 345         /* Save debug info for later output */
 346         if (!debug_flag)
 347             return;
 348         /* Save the result of the condition */
 349         if (debug_error)
 350         {
 351             type = _("ERROR:");
 352             debug_error = FALSE;
 353         }
 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;
 365     }
 366 }
 367 
 368 /* --------------------------------------------------------------------------------------------- */
 369 /** Calculates the truth value of one lineful of conditions. Returns
 370    the point just before the end of line. */
 371 
 372 static char *
 373 test_line (const Widget * edit_widget, char *p, gboolean * result)
     /* [previous][next][first][last][top][bottom][index][help]  */
 374 {
 375     char operator;
 376 
 377     /* Repeat till end of line */
 378     while (*p != '\0' && *p != '\n')
 379     {
 380         char *debug_start, *debug_end;
 381         gboolean condition = TRUE;
 382 
 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 == '?')
 390         {
 391             debug_flag = TRUE;
 392             p++;
 393         }
 394         /* support quote space .mnu */
 395         while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
 396             p++;
 397         if (*p == '\0' || *p == '\n')
 398             break;
 399 
 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);
 405 
 406         switch (operator)
 407         {
 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);
 425 
 426     }                           /* while (*p != '\n') */
 427     /* Report debug message */
 428     debug_out (NULL, NULL, TRUE);
 429 
 430     if (*p == '\0' || *p == '\n')
 431         str_prev_char (&p);
 432     return p;
 433 }
 434 
 435 /* --------------------------------------------------------------------------------------------- */
 436 /** FIXME: recode this routine on version 3.0, it could be cleaner */
 437 
 438 static void
 439 execute_menu_command (const Widget * edit_widget, const char *commands, gboolean show_prompt)
     /* [previous][next][first][last][top][bottom][index][help]  */
 440 {
 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;
 451 
 452     /* Skip menu entry title line */
 453     commands = strchr (commands, '\n');
 454     if (commands == NULL)
 455         return;
 456 
 457     cmd_file_fd = mc_mkstemps (&file_name_vpath, "mcusr", SCRIPT_SUFFIX);
 458 
 459     if (cmd_file_fd == -1)
 460     {
 461         message (D_ERROR, MSG_ERROR, _("Cannot create temporary command file\n%s"),
 462                  unix_error_string (errno));
 463         return;
 464     }
 465 
 466     cmd_file = fdopen (cmd_file_fd, "w");
 467     fputs ("#! /bin/sh\n", cmd_file);
 468     commands++;
 469 
 470     for (col = 0; *commands != '\0'; commands++)
 471     {
 472         if (col == 0)
 473         {
 474             if (!whitespace (*commands))
 475                 break;
 476             while (whitespace (*commands))
 477                 commands++;
 478             if (*commands == '\0')
 479                 break;
 480         }
 481         col++;
 482         if (*commands == '\n')
 483             col = 0;
 484         if (parameter != NULL)
 485         {
 486             if (*commands == '}')
 487             {
 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')
 495                 {
 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;
 502                 }
 503                 if (do_quote)
 504                 {
 505                     char *tmp;
 506 
 507                     tmp = name_quote (parameter, FALSE);
 508                     if (tmp != NULL)
 509                     {
 510                         fputs (tmp, cmd_file);
 511                         g_free (tmp);
 512                     }
 513                 }
 514                 else
 515                     fputs (parameter, cmd_file);
 516 
 517                 MC_PTR_FREE (parameter);
 518             }
 519             else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
 520                 *parameter++ = *commands;
 521         }
 522         else if (expand_prefix_found)
 523         {
 524             expand_prefix_found = FALSE;
 525             if (g_ascii_isdigit ((gchar) * commands))
 526             {
 527                 do_quote = (atoi (commands) != 0);
 528                 while (g_ascii_isdigit ((gchar) * commands))
 529                     commands++;
 530             }
 531             if (*commands == '{')
 532                 parameter = lc_prompt;
 533             else
 534             {
 535                 char *text;
 536 
 537                 text = expand_format (edit_widget, *commands, do_quote);
 538                 if (text != NULL)
 539                 {
 540                     fputs (text, cmd_file);
 541                     g_free (text);
 542                 }
 543             }
 544         }
 545         else if (*commands == '%')
 546         {
 547             int i;
 548 
 549             i = check_format_view (commands + 1);
 550             if (i != 0)
 551             {
 552                 commands += i;
 553                 run_view = TRUE;
 554             }
 555             else
 556             {
 557                 do_quote = TRUE;        /* Default: Quote expanded macro */
 558                 expand_prefix_found = TRUE;
 559             }
 560         }
 561         else
 562             fputc (*commands, cmd_file);
 563     }
 564 
 565     fclose (cmd_file);
 566     mc_chmod (file_name_vpath, S_IRWXU);
 567 
 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);
 570 
 571     if (run_view)
 572     {
 573         mcview_viewer (cmd, NULL, 0, 0, 0);
 574         dialog_switch_process_pending ();
 575     }
 576     else if (show_prompt)
 577         shell_execute (cmd, EXECUTE_HIDE);
 578     else
 579     {
 580         gboolean ok;
 581 
 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 ();
 585 
 586         ok = (system (cmd) != -1);
 587 
 588         /* Restore terminal configuration. */
 589         tty_raw_mode ();
 590 
 591         /* Redraw the original screen's contents. */
 592         tty_clear_screen ();
 593         repaint_screen ();
 594 
 595         if (!ok)
 596             message (D_ERROR, MSG_ERROR, "%s", _("Error calling program"));
 597     }
 598 
 599     g_free (cmd);
 600 
 601     mc_unlink (file_name_vpath);
 602     vfs_path_free (file_name_vpath, TRUE);
 603 }
 604 
 605 /* --------------------------------------------------------------------------------------------- */
 606 /**
 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.
 610  **
 611  **     Q. Should we apply this routine to system and home menu (and .ext files)?
 612  */
 613 
 614 static gboolean
 615 menu_file_own (char *path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 616 {
 617     struct stat st;
 618 
 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;
 622 
 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);
 627 
 628     return FALSE;
 629 }
 630 
 631 /* --------------------------------------------------------------------------------------------- */
 632 /*** public functions ****************************************************************************/
 633 /* --------------------------------------------------------------------------------------------- */
 634 
 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
 647 
 648    If the format letter is in uppercase, it refers to the other panel.
 649 
 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
 655 
 656    expand_format returns a memory block that must be free()d.
 657  */
 658 
 659 /* Returns how many characters we should advance if %view was found */
 660 int
 661 check_format_view (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 662 {
 663     const char *q = p;
 664 
 665     if (strncmp (p, "view", 4) == 0)
 666     {
 667         q += 4;
 668         if (*q == '{')
 669         {
 670             for (q++; *q != '\0' && *q != '}'; q++)
 671             {
 672                 if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
 673                 {
 674                     mcview_global_flags.hex = FALSE;
 675                     q += 4;
 676                 }
 677                 else if (strncmp (q, "hex", 3) == 0)
 678                 {
 679                     mcview_global_flags.hex = TRUE;
 680                     q += 2;
 681                 }
 682                 else if (strncmp (q, "nroff", 5) == 0)
 683                 {
 684                     mcview_global_flags.nroff = TRUE;
 685                     q += 4;
 686                 }
 687                 else if (strncmp (q, "unform", 6) == 0)
 688                 {
 689                     mcview_global_flags.nroff = FALSE;
 690                     q += 5;
 691                 }
 692             }
 693             if (*q == '}')
 694                 q++;
 695         }
 696         return q - p;
 697     }
 698     return 0;
 699 }
 700 
 701 /* --------------------------------------------------------------------------------------------- */
 702 
 703 int
 704 check_format_cd (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 705 {
 706     return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
 707 }
 708 
 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 */
 713 
 714 int
 715 check_format_var (const char *p, char **v)
     /* [previous][next][first][last][top][bottom][index][help]  */
 716 {
 717     *v = NULL;
 718 
 719     if (strncmp (p, "var{", 4) == 0)
 720     {
 721         const char *q = p;
 722         const char *dots = NULL;
 723         const char *value;
 724         char *var_name;
 725 
 726         for (q += 4; *q != '\0' && *q != '}'; q++)
 727         {
 728             if (*q == ':')
 729                 dots = q + 1;
 730         }
 731         if (*q == '\0')
 732             return 0;
 733 
 734         if (dots == NULL || dots == q + 5)
 735         {
 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;
 741         }
 742 
 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);
 747 
 748         if (value != NULL)
 749             *v = g_strdup (value);
 750         else
 751             *v = g_strndup (dots, q - dots);
 752 
 753         return q - p;
 754     }
 755     return 0;
 756 }
 757 
 758 /* --------------------------------------------------------------------------------------------- */
 759 
 760 char *
 761 expand_format (const Widget * edit_widget, char c, gboolean do_quote)
     /* [previous][next][first][last][top][bottom][index][help]  */
 762 {
 763     WPanel *panel = NULL;
 764     char *(*quote_func) (const char *, gboolean);
 765     const char *fname = NULL;
 766     char *result;
 767     char c_lc;
 768 
 769 #ifdef USE_INTERNAL_EDIT
 770     const WEdit *e = CONST_EDIT (edit_widget);
 771 #else
 772     (void) edit_widget;
 773 #endif
 774 
 775     if (c == '%')
 776         return g_strdup ("%");
 777 
 778     switch (mc_global.mc_run_mode)
 779     {
 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
 786         {
 787             if (g_ascii_islower ((gchar) c))
 788                 panel = current_panel;
 789             else
 790             {
 791                 if (get_other_type () != view_listing)
 792                     return NULL;
 793                 panel = other_panel;
 794             }
 795 
 796             fname = panel_current_entry (panel)->fname->str;
 797         }
 798         break;
 799 
 800 #ifdef USE_INTERNAL_EDIT
 801     case MC_RUN_EDITOR:
 802         fname = edit_get_file_name (e);
 803         break;
 804 #endif
 805 
 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;
 810 
 811     default:
 812         /* other modes don't use formats */
 813         return NULL;
 814     }
 815 
 816     if (do_quote)
 817         quote_func = name_quote;
 818     else
 819         quote_func = fake_name_quote;
 820 
 821     c_lc = g_ascii_tolower ((gchar) c);
 822 
 823     switch (c_lc)
 824     {
 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':
 833         {
 834             const char *cwd;
 835 
 836             if (panel != NULL)
 837                 cwd = vfs_path_as_str (panel->cwd_vpath);
 838             else
 839                 cwd = vfs_get_current_dir ();
 840 
 841             result = quote_func (cwd, FALSE);
 842             goto ret;
 843         }
 844     case 'c':
 845 #ifdef USE_INTERNAL_EDIT
 846         if (e != NULL)
 847         {
 848             result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (e));
 849             goto ret;
 850         }
 851 #endif
 852         break;
 853     case 'i':                  /* indent equal number cursor position in line */
 854 #ifdef USE_INTERNAL_EDIT
 855         if (e != NULL)
 856         {
 857             result = g_strnfill (edit_get_curs_col (e), ' ');
 858             goto ret;
 859         }
 860 #endif
 861         break;
 862     case 'y':                  /* syntax type */
 863 #ifdef USE_INTERNAL_EDIT
 864         if (e != NULL)
 865         {
 866             const char *syntax_type;
 867 
 868             syntax_type = edit_get_syntax_type (e);
 869             if (syntax_type != NULL)
 870             {
 871                 result = g_strdup (syntax_type);
 872                 goto ret;
 873             }
 874         }
 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)
 881         {
 882             char *file;
 883 
 884             file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
 885             result = quote_func (file, FALSE);
 886             g_free (file);
 887             goto ret;
 888         }
 889 #endif
 890         if (c_lc == 'b')
 891         {
 892             result = strip_ext (quote_func (fname, FALSE));
 893             goto ret;
 894         }
 895         break;
 896     case 'n':                  /* strip extension in editor */
 897 #ifdef USE_INTERNAL_EDIT
 898         if (e != NULL)
 899         {
 900             result = strip_ext (quote_func (fname, FALSE));
 901             goto ret;
 902         }
 903 #endif
 904         break;
 905     case 'm':                  /* menu file name */
 906         if (menu != NULL)
 907         {
 908             result = quote_func (menu, FALSE);
 909             goto ret;
 910         }
 911         break;
 912     case 's':
 913         if (panel == NULL || panel->marked == 0)
 914         {
 915             result = quote_func (fname, FALSE);
 916             goto ret;
 917         }
 918 
 919         MC_FALLTHROUGH;
 920 
 921     case 't':
 922     case 'u':
 923         {
 924             GString *block = NULL;
 925             int i;
 926 
 927             if (panel == NULL)
 928             {
 929                 result = NULL;
 930                 goto ret;
 931             }
 932 
 933             for (i = 0; i < panel->dir.len; i++)
 934                 if (panel->dir.list[i].f.marked != 0)
 935                 {
 936                     char *tmp;
 937 
 938                     tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
 939                     if (tmp != NULL)
 940                     {
 941                         if (block == NULL)
 942                             block = g_string_new_take (tmp);
 943                         else
 944                         {
 945                             g_string_append (block, tmp);
 946                             g_free (tmp);
 947                         }
 948                         g_string_append_c (block, ' ');
 949                     }
 950 
 951                     if (c_lc == 'u')
 952                         do_file_mark (panel, i, 0);
 953                 }
 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 */
 960 
 961     result = g_strdup ("% ");
 962     result[1] = c;
 963   ret:
 964     return result;
 965 }
 966 
 967 /* --------------------------------------------------------------------------------------------- */
 968 /**
 969  * If edit_widget is NULL then we are called from the mc menu,
 970  * otherwise we are called from the mcedit menu.
 971  */
 972 
 973 gboolean
 974 user_menu_cmd (const Widget * edit_widget, const char *menu_file, int selected_entry)
     /* [previous][next][first][last][top][bottom][index][help]  */
 975 {
 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;
 985 
 986     if (!vfs_current_is_local ())
 987     {
 988         message (D_ERROR, MSG_ERROR, "%s", _("Cannot execute commands on non-local filesystems"));
 989         return FALSE;
 990     }
 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))
 996     {
 997         if (menu_file != NULL)
 998         {
 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;
1003         }
1004 
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);
1010 
1011         if (!exist_file (menu))
1012         {
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))
1019             {
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))
1026                 {
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);
1032                 }
1033             }
1034         }
1035     }
1036 
1037     if (!g_file_get_contents (menu, &data, NULL, NULL))
1038     {
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;
1042     }
1043 
1044     max_cols = 0;
1045     selected = 0;
1046     menu_limit = 0;
1047     entries = NULL;
1048 
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))
1053     {
1054         if (menu_lines >= menu_limit)
1055         {
1056             char **new_entries;
1057 
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;
1062 
1063             entries = new_entries;
1064             new_entries += menu_limit;
1065             while (--new_entries >= &entries[menu_lines])
1066                 *new_entries = NULL;
1067         }
1068 
1069         if (col == 0 && entries[menu_lines] == NULL)
1070             switch (*p)
1071             {
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;
1079 
1080             case '+':
1081                 if (*(p + 1) == '=')
1082                 {
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;
1087                 }
1088                 else
1089                 {
1090                     /* A condition for adding the entry */
1091                     p = test_line (edit_widget, p, &accept_entry);
1092                 }
1093                 break;
1094 
1095             case '=':
1096                 if (*(p + 1) == '+')
1097                 {
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;
1102                 }
1103                 else
1104                 {
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;
1110                 }
1111                 break;
1112 
1113             default:
1114                 if (!whitespace (*p) && str_isprint (p))
1115                 {
1116                     /* A menu entry title line */
1117                     if (accept_entry)
1118                         entries[menu_lines] = p;
1119                     else
1120                         accept_entry = TRUE;
1121                 }
1122                 break;
1123             }
1124 
1125         if (*p == '\n')
1126         {
1127             if (entries[menu_lines] != NULL)
1128             {
1129                 menu_lines++;
1130                 accept_entry = TRUE;
1131             }
1132             max_cols = MAX (max_cols, col);
1133             col = 0;
1134         }
1135         else
1136         {
1137             if (*p == '\t')
1138                 *p = ' ';
1139             col++;
1140         }
1141     }
1142 
1143     if (menu_lines == 0)
1144     {
1145         message (D_ERROR, MSG_ERROR, _("No suitable entries found in %s"), menu);
1146         res = FALSE;
1147     }
1148     else
1149     {
1150         if (selected_entry >= 0)
1151             selected = selected_entry;
1152         else
1153         {
1154             Listbox *listbox;
1155 
1156             max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1157 
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++)
1163             {
1164                 p = entries[i];
1165                 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1166                                      extract_line (p, p + MAX_ENTRY_LEN), p, FALSE);
1167             }
1168             /* Select the default entry */
1169             listbox_set_current (listbox->list, selected);
1170 
1171             selected = listbox_run (listbox);
1172         }
1173         if (selected >= 0)
1174         {
1175             execute_menu_command (edit_widget, entries[selected], interactive);
1176             res = TRUE;
1177         }
1178 
1179         do_refresh ();
1180     }
1181 
1182     easy_patterns = old_patterns;
1183     MC_PTR_FREE (menu);
1184     g_free (entries);
1185     g_free (data);
1186     return res;
1187 }
1188 
1189 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */