Manual pages: mcmcdiffmceditmcview

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-2025
   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 <https://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_ENTRY_LEN 60
  69 
  70 /*** file scope type declarations ****************************************************************/
  71 
  72 /*** forward declarations (file scope functions) *************************************************/
  73 
  74 /*** file scope variables ************************************************************************/
  75 
  76 static gboolean debug_flag = FALSE;
  77 static gboolean debug_error = FALSE;
  78 static char *menu = NULL;
  79 
  80 /* --------------------------------------------------------------------------------------------- */
  81 /*** file scope functions ************************************************************************/
  82 /* --------------------------------------------------------------------------------------------- */
  83 
  84 /** strip file's extension */
  85 static char *
  86 strip_ext (char *ss)
     /* [previous][next][first][last][top][bottom][index][help]  */
  87 {
  88     char *s;
  89     char *e = NULL;
  90 
  91     if (ss == NULL)
  92         return NULL;
  93 
  94     for (s = ss; *s != '\0'; s++)
  95     {
  96         if (*s == '.')
  97             e = s;
  98         if (IS_PATH_SEP (*s) && e != NULL)
  99             e = NULL;  // '.' in *directory* name
 100     }
 101 
 102     if (e != NULL)
 103         *e = '\0';
 104 
 105     return (*ss == '\0' ? NULL : ss);
 106 }
 107 
 108 /* --------------------------------------------------------------------------------------------- */
 109 /**
 110  * Check for the "shell_patterns" directive.  If it's found and valid,
 111  * interpret it and move the pointer past the directive.  Return the
 112  * current pointer.
 113  */
 114 
 115 static char *
 116 check_patterns (char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 117 {
 118     static const char def_name[] = "shell_patterns=";
 119     char *p0 = p;
 120 
 121     if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
 122         return p0;
 123 
 124     p += sizeof (def_name) - 1;
 125     if (*p == '1')
 126         easy_patterns = TRUE;
 127     else if (*p == '0')
 128         easy_patterns = FALSE;
 129     else
 130         return p0;
 131 
 132     // Skip spaces
 133     p++;
 134     while (whiteness (*p))
 135         p++;
 136     return p;
 137 }
 138 
 139 /* --------------------------------------------------------------------------------------------- */
 140 /** Copies a whitespace separated argument from p to arg. Returns the
 141    point after argument. */
 142 
 143 static char *
 144 extract_arg (char *p, char *arg, int size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 145 {
 146     while (*p != '\0' && whiteness (*p))
 147         p++;
 148 
 149     // support quote space .mnu
 150     while (*p != '\0' && (*p != ' ' || *(p - 1) == '\\') && *p != '\t' && *p != '\n')
 151     {
 152         char *np;
 153 
 154         np = str_get_next_char (p);
 155         if (np - p >= size)
 156             break;
 157         memcpy (arg, p, np - p);
 158         arg += np - p;
 159         size -= np - p;
 160         p = np;
 161     }
 162     *arg = '\0';
 163     if (*p == '\0' || *p == '\n')
 164         str_prev_char (&p);
 165     return p;
 166 }
 167 
 168 /* --------------------------------------------------------------------------------------------- */
 169 /* Tests whether the selected file in the panel is of any of the types
 170    specified in argument. */
 171 
 172 static gboolean
 173 test_type (WPanel *panel, char *arg)
     /* [previous][next][first][last][top][bottom][index][help]  */
 174 {
 175     const file_entry_t *fe;
 176     int result = 0;  // False by default
 177     mode_t st_mode;
 178 
 179     fe = panel_current_entry (panel);
 180     if (fe == NULL)
 181         return FALSE;
 182 
 183     st_mode = fe->st.st_mode;
 184 
 185     for (; *arg != '\0'; arg++)
 186     {
 187         switch (*arg)
 188         {
 189         case 'n':  // Not a directory
 190             result |= !S_ISDIR (st_mode);
 191             break;
 192         case 'r':  // Regular file
 193             result |= S_ISREG (st_mode);
 194             break;
 195         case 'd':  // Directory
 196             result |= S_ISDIR (st_mode);
 197             break;
 198         case 'l':  // Link
 199             result |= S_ISLNK (st_mode);
 200             break;
 201         case 'c':  // Character special
 202             result |= S_ISCHR (st_mode);
 203             break;
 204         case 'b':  // Block special
 205             result |= S_ISBLK (st_mode);
 206             break;
 207         case 'f':  // Fifo (named pipe)
 208             result |= S_ISFIFO (st_mode);
 209             break;
 210         case 's':  // Socket
 211             result |= S_ISSOCK (st_mode);
 212             break;
 213         case 'x':  // Executable
 214             result |= (st_mode & 0111) != 0 ? 1 : 0;
 215             break;
 216         case 't':
 217             result |= panel->marked != 0 ? 1 : 0;
 218             break;
 219         default:
 220             debug_error = TRUE;
 221             break;
 222         }
 223     }
 224 
 225     return (result != 0);
 226 }
 227 
 228 /* --------------------------------------------------------------------------------------------- */
 229 /** Calculates the truth value of the next condition starting from
 230    p. Returns the point after condition. */
 231 
 232 static char *
 233 test_condition (const Widget *edit_widget, char *p, gboolean *condition)
     /* [previous][next][first][last][top][bottom][index][help]  */
 234 {
 235     char arg[256];
 236     const mc_search_type_t search_type = easy_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
 237 #ifdef USE_INTERNAL_EDIT
 238     const WEdit *e = CONST_EDIT (edit_widget);
 239 #endif
 240 
 241     // Handle one condition
 242     for (; *p != '\n' && *p != '&' && *p != '|'; p++)
 243     {
 244         WPanel *panel = NULL;
 245 
 246         // support quote space .mnu
 247         if ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
 248             continue;
 249         if (*p >= 'a')
 250             panel = current_panel;
 251         else if (get_other_type () == view_listing)
 252             panel = other_panel;
 253 
 254         *p |= 0x20;
 255 
 256         switch (*p++)
 257         {
 258         case '!':
 259             p = test_condition (edit_widget, p, condition);
 260             *condition = !*condition;
 261             str_prev_char (&p);
 262             break;
 263         case 'f':  // file name pattern
 264             p = extract_arg (p, arg, sizeof (arg));
 265 #ifdef USE_INTERNAL_EDIT
 266             if (e != NULL)
 267             {
 268                 const char *edit_filename = edit_get_file_name (e);
 269 
 270                 *condition = mc_search (arg, NULL, edit_filename, search_type);
 271             }
 272             else
 273 #endif
 274             {
 275                 if (panel == NULL)
 276                     *condition = FALSE;
 277                 else
 278                 {
 279                     const file_entry_t *fe = panel_current_entry (panel);
 280 
 281                     *condition = fe != NULL && mc_search (arg, NULL, fe->fname->str, search_type);
 282                 }
 283             }
 284             break;
 285         case 'y':  // syntax pattern
 286 #ifdef USE_INTERNAL_EDIT
 287             if (e != NULL)
 288             {
 289                 const char *syntax_type = edit_get_syntax_type (e);
 290 
 291                 if (syntax_type != NULL)
 292                 {
 293                     p = extract_arg (p, arg, sizeof (arg));
 294                     *condition = mc_search (arg, NULL, syntax_type, MC_SEARCH_T_NORMAL);
 295                 }
 296             }
 297 #endif
 298             break;
 299         case 'd':
 300             p = extract_arg (p, arg, sizeof (arg));
 301             *condition = panel != NULL
 302                 && mc_search (arg, NULL, vfs_path_as_str (panel->cwd_vpath), search_type);
 303             break;
 304         case 't':
 305             p = extract_arg (p, arg, sizeof (arg));
 306             *condition = panel != NULL && test_type (panel, arg);
 307             break;
 308         case 'x':  // executable
 309         {
 310             struct stat status;
 311 
 312             p = extract_arg (p, arg, sizeof (arg));
 313             *condition = stat (arg, &status) == 0 && is_exe (status.st_mode);
 314             break;
 315         }
 316         default:
 317             debug_error = TRUE;
 318             break;
 319         }  // switch
 320     }  // while
 321     return p;
 322 }
 323 
 324 /* --------------------------------------------------------------------------------------------- */
 325 /** General purpose condition debug output handler */
 326 
 327 static void
 328 debug_out (char *start, char *end, gboolean condition)
     /* [previous][next][first][last][top][bottom][index][help]  */
 329 {
 330     static char *msg = NULL;
 331 
 332     if (start == NULL && end == NULL)
 333     {
 334         // Show output
 335         if (debug_flag && msg != NULL)
 336         {
 337             size_t len;
 338 
 339             len = strlen (msg);
 340             if (len != 0)
 341                 msg[len - 1] = '\0';
 342             message (D_NORMAL, _ ("Debug"), "%s", msg);
 343         }
 344         debug_flag = FALSE;
 345         MC_PTR_FREE (msg);
 346     }
 347     else
 348     {
 349         const char *type;
 350         char *p;
 351 
 352         // Save debug info for later output
 353         if (!debug_flag)
 354             return;
 355         // Save the result of the condition
 356         if (debug_error)
 357         {
 358             type = _ ("ERROR:");
 359             debug_error = FALSE;
 360         }
 361         else if (condition)
 362             type = _ ("True:");
 363         else
 364             type = _ ("False:");
 365         // This is for debugging, don't need to be super efficient.
 366         if (end == NULL)
 367             p = g_strdup_printf ("%s %s %c \n", msg ? msg : "", type, *start);
 368         else
 369             p = g_strdup_printf ("%s %s %.*s \n", msg ? msg : "", type, (int) (end - start), start);
 370         g_free (msg);
 371         msg = p;
 372     }
 373 }
 374 
 375 /* --------------------------------------------------------------------------------------------- */
 376 /** Calculates the truth value of one lineful of conditions. Returns
 377    the point just before the end of line. */
 378 
 379 static char *
 380 test_line (const Widget *edit_widget, char *p, gboolean *result)
     /* [previous][next][first][last][top][bottom][index][help]  */
 381 {
 382     char operator;
 383 
 384     // Repeat till end of line
 385     while (*p != '\0' && *p != '\n')
 386     {
 387         char *debug_start, *debug_end;
 388         gboolean condition = TRUE;
 389 
 390         // support quote space .mnu
 391         while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
 392             p++;
 393         if (*p == '\0' || *p == '\n')
 394             break;
 395         operator= * p++;
 396         if (*p == '?')
 397         {
 398             debug_flag = TRUE;
 399             p++;
 400         }
 401         // support quote space .mnu
 402         while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
 403             p++;
 404         if (*p == '\0' || *p == '\n')
 405             break;
 406 
 407         debug_start = p;
 408         p = test_condition (edit_widget, p, &condition);
 409         debug_end = p;
 410         // Add one debug statement
 411         debug_out (debug_start, debug_end, condition);
 412 
 413         switch (operator)
 414         {
 415         case '+':
 416         case '=':
 417             // Assignment
 418             *result = condition;
 419             break;
 420         case '&':  // Logical and
 421             *result = *result && condition;
 422             break;
 423         case '|':  // Logical or
 424             *result = *result || condition;
 425             break;
 426         default:
 427             debug_error = TRUE;
 428             break;
 429         }  // switch
 430         // Add one debug statement
 431         debug_out (&operator, NULL, *result);
 432 
 433     }  // while (*p != '\n')
 434     // Report debug message
 435     debug_out (NULL, NULL, TRUE);
 436 
 437     if (*p == '\0' || *p == '\n')
 438         str_prev_char (&p);
 439     return p;
 440 }
 441 
 442 /* --------------------------------------------------------------------------------------------- */
 443 /** FIXME: recode this routine on version 3.0, it could be cleaner */
 444 
 445 static void
 446 execute_menu_command (const Widget *edit_widget, const char *commands, gboolean show_prompt)
     /* [previous][next][first][last][top][bottom][index][help]  */
 447 {
 448     FILE *cmd_file;
 449     int cmd_file_fd;
 450     gboolean expand_prefix_found = FALSE;
 451     char *parameter = NULL;
 452     gboolean do_quote = FALSE;
 453     char lc_prompt[80];
 454     int col;
 455     vfs_path_t *file_name_vpath;
 456     gboolean run_view = FALSE;
 457     char *cmd;
 458 
 459     // Skip menu entry title line
 460     commands = strchr (commands, '\n');
 461     if (commands == NULL)
 462         return;
 463 
 464     cmd_file_fd = mc_mkstemps (&file_name_vpath, "mcusr", SCRIPT_SUFFIX);
 465 
 466     if (cmd_file_fd == -1)
 467     {
 468         message (D_ERROR, MSG_ERROR, _ ("Cannot create temporary command file\n%s"),
 469                  unix_error_string (errno));
 470         return;
 471     }
 472 
 473     cmd_file = fdopen (cmd_file_fd, "w");
 474     fputs ("#! /bin/sh\n", cmd_file);
 475     commands++;
 476 
 477     for (col = 0; *commands != '\0'; commands++)
 478     {
 479         if (col == 0)
 480         {
 481             if (!whitespace (*commands))
 482                 break;
 483             while (whitespace (*commands))
 484                 commands++;
 485             if (*commands == '\0')
 486                 break;
 487         }
 488         col++;
 489         if (*commands == '\n')
 490             col = 0;
 491         if (parameter != NULL)
 492         {
 493             if (*commands == '}')
 494             {
 495                 *parameter = '\0';
 496                 parameter = input_dialog (
 497                     _ ("Parameter"), lc_prompt, MC_HISTORY_FM_MENU_EXEC_PARAM, "",
 498                     INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_HOSTNAMES
 499                         | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES);
 500                 if (parameter == NULL || *parameter == '\0')
 501                 {
 502                     // User canceled
 503                     g_free (parameter);
 504                     fclose (cmd_file);
 505                     mc_unlink (file_name_vpath);
 506                     vfs_path_free (file_name_vpath, TRUE);
 507                     return;
 508                 }
 509                 if (do_quote)
 510                 {
 511                     char *tmp;
 512 
 513                     tmp = name_quote (parameter, FALSE);
 514                     if (tmp != NULL)
 515                     {
 516                         fputs (tmp, cmd_file);
 517                         g_free (tmp);
 518                     }
 519                 }
 520                 else
 521                     fputs (parameter, cmd_file);
 522 
 523                 MC_PTR_FREE (parameter);
 524             }
 525             else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
 526                 *parameter++ = *commands;
 527         }
 528         else if (expand_prefix_found)
 529         {
 530             expand_prefix_found = FALSE;
 531             if (g_ascii_isdigit ((gchar) *commands))
 532             {
 533                 do_quote = (atoi (commands) != 0);
 534                 while (g_ascii_isdigit ((gchar) *commands))
 535                     commands++;
 536             }
 537             if (*commands == '{')
 538                 parameter = lc_prompt;
 539             else
 540             {
 541                 char *text;
 542 
 543                 text = expand_format (edit_widget, *commands, do_quote);
 544                 if (text != NULL)
 545                 {
 546                     fputs (text, cmd_file);
 547                     g_free (text);
 548                 }
 549             }
 550         }
 551         else if (*commands == '%')
 552         {
 553             int i;
 554 
 555             i = check_format_view (commands + 1);
 556             if (i != 0)
 557             {
 558                 commands += i;
 559                 run_view = TRUE;
 560             }
 561             else
 562             {
 563                 do_quote = TRUE;  // Default: Quote expanded macro
 564                 expand_prefix_found = TRUE;
 565             }
 566         }
 567         else
 568             fputc (*commands, cmd_file);
 569     }
 570 
 571     fclose (cmd_file);
 572     mc_chmod (file_name_vpath, S_IRWXU);
 573 
 574     // Execute the command indirectly to allow execution even on no-exec filesystems.
 575     cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (file_name_vpath), (char *) NULL);
 576 
 577     if (run_view)
 578     {
 579         mcview_viewer (cmd, NULL, 0, 0, 0);
 580         dialog_switch_process_pending ();
 581     }
 582     else if (show_prompt)
 583         shell_execute (cmd, EXECUTE_HIDE);
 584     else
 585     {
 586         gboolean ok;
 587 
 588         /* Prepare the terminal by setting its flag to the initial ones. This will cause \r
 589          * to work as expected, instead of being ignored. */
 590         tty_reset_shell_mode ();
 591 
 592         ok = (system (cmd) != -1);
 593 
 594         // Restore terminal configuration.
 595         tty_raw_mode ();
 596 
 597         // Redraw the original screen's contents.
 598         tty_clear_screen ();
 599         repaint_screen ();
 600 
 601         if (!ok)
 602             message (D_ERROR, MSG_ERROR, "%s", _ ("Error calling program"));
 603     }
 604 
 605     g_free (cmd);
 606 
 607     mc_unlink (file_name_vpath);
 608     vfs_path_free (file_name_vpath, TRUE);
 609 }
 610 
 611 /* --------------------------------------------------------------------------------------------- */
 612 /**
 613  **     Check owner of the menu file. Using menu file is allowed, if
 614  **     owner of the menu is root or the actual user. In either case
 615  **     file should not be group and word-writable.
 616  **
 617  **     Q. Should we apply this routine to system and home menu (and .ext files)?
 618  */
 619 
 620 static gboolean
 621 menu_file_own (char *path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 622 {
 623     struct stat st;
 624 
 625     if (stat (path, &st) == 0 && (st.st_uid == 0 || (st.st_uid == geteuid ()) != 0)
 626         && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0))
 627         return TRUE;
 628 
 629     if (verbose)
 630         message (D_NORMAL, _ ("Warning -- ignoring file"),
 631                  _ ("File %s is not owned by root or you or is world writable.\n"
 632                     "Using it may compromise your security"),
 633                  path);
 634 
 635     return FALSE;
 636 }
 637 
 638 /* --------------------------------------------------------------------------------------------- */
 639 /*** public functions ****************************************************************************/
 640 /* --------------------------------------------------------------------------------------------- */
 641 
 642 /* Formats defined:
 643    %%  The % character
 644    %f  The current file in the active panel (if non-local vfs, file will be copied locally
 645    and %f will be full path to it) or the opened file in the internal editor.
 646    %p  Likewise.
 647    %d  The current working directory
 648    %s  "Selected files"; the tagged files if any, otherwise the current file
 649    %t  Tagged files
 650    %u  Tagged files (and they are untagged on return from expand_format)
 651    %view Runs the commands and pipes standard output to the view command.
 652    If %view is immediately followed by '{', recognize keywords
 653    ascii, hex, nroff and unform
 654 
 655    If the format letter is in uppercase, it refers to the other panel.
 656 
 657    With a number followed the % character you can turn quoting on (default)
 658    and off. For example:
 659    %f    quote expanded macro
 660    %1f   ditto
 661    %0f   don't quote expanded macro
 662 
 663    expand_format returns a memory block that must be free()d.
 664  */
 665 
 666 /* Returns how many characters we should advance if %view was found */
 667 int
 668 check_format_view (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 669 {
 670     const char *q = p;
 671 
 672     if (strncmp (p, "view", 4) == 0)
 673     {
 674         q += 4;
 675         if (*q == '{')
 676         {
 677             for (q++; *q != '\0' && *q != '}'; q++)
 678             {
 679                 if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
 680                 {
 681                     mcview_global_flags.hex = FALSE;
 682                     q += 4;
 683                 }
 684                 else if (strncmp (q, "hex", 3) == 0)
 685                 {
 686                     mcview_global_flags.hex = TRUE;
 687                     q += 2;
 688                 }
 689                 else if (strncmp (q, "nroff", 5) == 0)
 690                 {
 691                     mcview_global_flags.nroff = TRUE;
 692                     q += 4;
 693                 }
 694                 else if (strncmp (q, "unform", 6) == 0)
 695                 {
 696                     mcview_global_flags.nroff = FALSE;
 697                     q += 5;
 698                 }
 699             }
 700             if (*q == '}')
 701                 q++;
 702         }
 703         return q - p;
 704     }
 705     return 0;
 706 }
 707 
 708 /* --------------------------------------------------------------------------------------------- */
 709 
 710 int
 711 check_format_cd (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 712 {
 713     return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
 714 }
 715 
 716 /* --------------------------------------------------------------------------------------------- */
 717 /* Check if p has a "^var\{var-name\}" */
 718 /* Returns the number of skipped characters (zero on not found) */
 719 /* V will be set to the expanded variable name */
 720 
 721 int
 722 check_format_var (const char *p, char **v)
     /* [previous][next][first][last][top][bottom][index][help]  */
 723 {
 724     *v = NULL;
 725 
 726     if (strncmp (p, "var{", 4) == 0)
 727     {
 728         const char *q = p;
 729         const char *dots = NULL;
 730         const char *value;
 731         char *var_name;
 732 
 733         for (q += 4; *q != '\0' && *q != '}'; q++)
 734         {
 735             if (*q == ':')
 736                 dots = q + 1;
 737         }
 738         if (*q == '\0')
 739             return 0;
 740 
 741         if (dots == NULL || dots == q + 5)
 742         {
 743             message (D_ERROR, _ ("Format error on file Extensions File"),
 744                      dots == NULL ? _ ("The %%var macro has no default")
 745                                   : _ ("The %%var macro has no variable"));
 746             return 0;
 747         }
 748 
 749         // Copy the variable name
 750         var_name = g_strndup (p + 4, dots - 2 - (p + 3));
 751         value = getenv (var_name);
 752         g_free (var_name);
 753 
 754         if (value != NULL)
 755             *v = g_strdup (value);
 756         else
 757             *v = g_strndup (dots, q - dots);
 758 
 759         return q - p;
 760     }
 761     return 0;
 762 }
 763 
 764 /* --------------------------------------------------------------------------------------------- */
 765 
 766 char *
 767 expand_format (const Widget *edit_widget, char c, gboolean do_quote)
     /* [previous][next][first][last][top][bottom][index][help]  */
 768 {
 769     WPanel *panel = NULL;
 770     char *(*quote_func) (const char *, gboolean);
 771     const char *fname = NULL;
 772     char *result;
 773     char c_lc;
 774 
 775 #ifdef USE_INTERNAL_EDIT
 776     const WEdit *e = CONST_EDIT (edit_widget);
 777 #else
 778     (void) edit_widget;
 779 #endif
 780 
 781     if (c == '%')
 782         return g_strdup ("%");
 783 
 784     switch (mc_global.mc_run_mode)
 785     {
 786     case MC_RUN_FULL:
 787 #ifdef USE_INTERNAL_EDIT
 788         if (e != NULL)
 789             fname = edit_get_file_name (e);
 790         else
 791 #endif
 792         {
 793             const file_entry_t *fe;
 794 
 795             if (g_ascii_islower ((gchar) c))
 796                 panel = current_panel;
 797             else
 798             {
 799                 if (get_other_type () != view_listing)
 800                     return NULL;
 801                 panel = other_panel;
 802             }
 803 
 804             fe = panel_current_entry (panel);
 805             fname = fe == NULL ? NULL : fe->fname->str;
 806         }
 807         break;
 808 
 809 #ifdef USE_INTERNAL_EDIT
 810     case MC_RUN_EDITOR:
 811         fname = edit_get_file_name (e);
 812         break;
 813 #endif
 814 
 815     case MC_RUN_VIEWER:
 816         // mc_run_param0 is not NULL here because mcviewer isn't run without input file
 817         fname = (const char *) mc_run_param0;
 818         break;
 819 
 820     default:
 821         // other modes don't use formats
 822         return NULL;
 823     }
 824 
 825     if (do_quote)
 826         quote_func = name_quote;
 827     else
 828         quote_func = fake_name_quote;
 829 
 830     c_lc = g_ascii_tolower ((gchar) c);
 831 
 832     switch (c_lc)
 833     {
 834     case 'f':
 835     case 'p':
 836         result = quote_func (fname, FALSE);
 837         goto ret;
 838     case 'x':
 839         result = quote_func (extension (fname), FALSE);
 840         goto ret;
 841     case 'd':
 842     {
 843         const char *cwd;
 844 
 845         if (panel != NULL)
 846             cwd = vfs_path_as_str (panel->cwd_vpath);
 847         else
 848             cwd = vfs_get_current_dir ();
 849 
 850         result = quote_func (cwd, FALSE);
 851         goto ret;
 852     }
 853     case 'c':
 854 #ifdef USE_INTERNAL_EDIT
 855         if (e != NULL)
 856         {
 857             result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (e));
 858             goto ret;
 859         }
 860 #endif
 861         break;
 862     case 'i':  // indent equal number cursor position in line
 863 #ifdef USE_INTERNAL_EDIT
 864         if (e != NULL)
 865         {
 866             result = g_strnfill (edit_get_curs_col (e), ' ');
 867             goto ret;
 868         }
 869 #endif
 870         break;
 871     case 'y':  // syntax type
 872 #ifdef USE_INTERNAL_EDIT
 873         if (e != NULL)
 874         {
 875             const char *syntax_type;
 876 
 877             syntax_type = edit_get_syntax_type (e);
 878             if (syntax_type != NULL)
 879             {
 880                 result = g_strdup (syntax_type);
 881                 goto ret;
 882             }
 883         }
 884 #endif
 885         break;
 886     case 'k':  // block file name
 887     case 'b':  // block file name / strip extension
 888 #ifdef USE_INTERNAL_EDIT
 889         if (e != NULL)
 890         {
 891             char *file;
 892 
 893             file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
 894             result = quote_func (file, FALSE);
 895             g_free (file);
 896             goto ret;
 897         }
 898 #endif
 899         if (c_lc == 'b')
 900         {
 901             result = strip_ext (quote_func (fname, FALSE));
 902             goto ret;
 903         }
 904         break;
 905     case 'n':  // strip extension in editor
 906 #ifdef USE_INTERNAL_EDIT
 907         if (e != NULL)
 908         {
 909             result = strip_ext (quote_func (fname, FALSE));
 910             goto ret;
 911         }
 912 #endif
 913         break;
 914     case 'm':  // menu file name
 915         if (menu != NULL)
 916         {
 917             result = quote_func (menu, FALSE);
 918             goto ret;
 919         }
 920         break;
 921     case 's':
 922         if (panel == NULL || panel->marked == 0)
 923         {
 924             result = quote_func (fname, FALSE);
 925             goto ret;
 926         }
 927 
 928         MC_FALLTHROUGH;
 929 
 930     case 't':
 931     case 'u':
 932     {
 933         GString *block = NULL;
 934         int i;
 935 
 936         if (panel == NULL)
 937         {
 938             result = NULL;
 939             goto ret;
 940         }
 941 
 942         for (i = 0; i < panel->dir.len; i++)
 943             if (panel->dir.list[i].f.marked != 0)
 944             {
 945                 char *tmp;
 946 
 947                 tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
 948                 if (tmp != NULL)
 949                 {
 950                     if (block == NULL)
 951                         block = g_string_new_take (tmp);
 952                     else
 953                     {
 954                         g_string_append (block, tmp);
 955                         g_free (tmp);
 956                     }
 957                     g_string_append_c (block, ' ');
 958                 }
 959 
 960                 if (c_lc == 'u')
 961                     do_file_mark (panel, i, 0);
 962             }
 963         result = block == NULL ? NULL : g_string_free (block, block->len == 0);
 964         goto ret;
 965     }  // sub case block
 966     default:
 967         break;
 968     }  // switch
 969 
 970     result = g_strdup ("% ");
 971     result[1] = c;
 972 ret:
 973     return result;
 974 }
 975 
 976 /* --------------------------------------------------------------------------------------------- */
 977 /**
 978  * If edit_widget is NULL then we are called from the mc menu,
 979  * otherwise we are called from the mcedit menu.
 980  */
 981 
 982 gboolean
 983 user_menu_cmd (const Widget *edit_widget, const char *menu_file, int selected_entry)
     /* [previous][next][first][last][top][bottom][index][help]  */
 984 {
 985     char *data, *p;
 986     GPtrArray *entries = NULL;
 987     int max_cols = 0;
 988     int col = 0;
 989     gboolean accept_entry = TRUE;
 990     int selected = 0;
 991     gboolean old_patterns;
 992     gboolean res = FALSE;
 993     gboolean interactive = TRUE;
 994 
 995     if (!vfs_current_is_local ())
 996     {
 997         message (D_ERROR, MSG_ERROR, "%s", _ ("Cannot execute commands on non-local filesystems"));
 998         return FALSE;
 999     }
1000 
1001     menu = g_strdup (menu_file != NULL         ? menu_file
1002                          : edit_widget != NULL ? EDIT_LOCAL_MENU
1003                                                : MC_LOCAL_MENU);
1004 
1005     if (!exist_file (menu) || !menu_file_own (menu))
1006     {
1007         if (menu_file != NULL)
1008         {
1009             message (D_ERROR, MSG_ERROR, _ ("Cannot open file %s\n%s"), menu,
1010                      unix_error_string (errno));
1011             MC_PTR_FREE (menu);
1012             return FALSE;
1013         }
1014 
1015         g_free (menu);
1016         menu = mc_config_get_full_path (edit_widget != NULL ? EDIT_HOME_MENU : MC_USERMENU_FILE);
1017         if (!exist_file (menu))
1018         {
1019             const char *global_menu;
1020 
1021             global_menu = edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU;
1022 
1023             g_free (menu);
1024             menu = mc_build_filename (mc_config_get_home_dir (), global_menu, (char *) NULL);
1025             if (!exist_file (menu))
1026             {
1027                 g_free (menu);
1028                 menu = mc_build_filename (mc_global.sysconfig_dir, global_menu, (char *) NULL);
1029                 if (!exist_file (menu))
1030                 {
1031                     g_free (menu);
1032                     menu = mc_build_filename (mc_global.share_data_dir, global_menu, (char *) NULL);
1033                 }
1034             }
1035         }
1036     }
1037 
1038     if (!g_file_get_contents (menu, &data, NULL, NULL))
1039     {
1040         message (D_ERROR, MSG_ERROR, _ ("Cannot open file %s\n%s"), menu,
1041                  unix_error_string (errno));
1042         MC_PTR_FREE (menu);
1043         return FALSE;
1044     }
1045 
1046     old_patterns = easy_patterns;
1047 
1048     // Parse the menu file
1049     for (p = check_patterns (data); *p != '\0'; str_next_char (&p))
1050     {
1051         unsigned int menu_lines = entries == NULL ? 0 : entries->len;
1052 
1053         if (col == 0 && (entries == NULL || menu_lines == entries->len))
1054             switch (*p)
1055             {
1056             case '#':
1057                 // do not show prompt if first line of external script is #silent
1058                 if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
1059                     interactive = FALSE;
1060                 // A commented menu entry
1061                 accept_entry = TRUE;
1062                 break;
1063 
1064             case '+':
1065                 if (*(p + 1) == '=')
1066                 {
1067                     // Combined adding and default
1068                     p = test_line (edit_widget, p + 1, &accept_entry);
1069                     if (selected == 0 && accept_entry)
1070                         selected = menu_lines;
1071                 }
1072                 else
1073                 {
1074                     // A condition for adding the entry
1075                     p = test_line (edit_widget, p, &accept_entry);
1076                 }
1077                 break;
1078 
1079             case '=':
1080                 if (*(p + 1) == '+')
1081                 {
1082                     // Combined adding and default
1083                     p = test_line (edit_widget, p + 1, &accept_entry);
1084                     if (selected == 0 && accept_entry)
1085                         selected = menu_lines;
1086                 }
1087                 else
1088                 {
1089                     // A condition for making the entry default
1090                     gboolean ok = TRUE;
1091 
1092                     p = test_line (edit_widget, p, &ok);
1093                     if (selected == 0 && ok)
1094                         selected = menu_lines;
1095                 }
1096                 break;
1097 
1098             default:
1099                 if (!whitespace (*p) && str_isprint (p))
1100                 {
1101                     // A menu entry title line
1102                     if (accept_entry)
1103                     {
1104                         if (entries == NULL)
1105                             entries = g_ptr_array_new ();
1106                         g_ptr_array_add (entries, p);
1107                     }
1108                     else
1109                         accept_entry = TRUE;
1110                 }
1111                 break;
1112             }
1113 
1114         if (*p == '\n')
1115         {
1116             if (entries != NULL && entries->len > menu_lines)
1117                 accept_entry = TRUE;
1118             max_cols = MAX (max_cols, col);
1119             col = 0;
1120         }
1121         else
1122         {
1123             if (*p == '\t')
1124                 *p = ' ';
1125             col++;
1126         }
1127     }
1128 
1129     if (entries == NULL)
1130         message (D_ERROR, MSG_ERROR, _ ("No suitable entries found in %s"), menu);
1131     else
1132     {
1133         if (selected_entry >= 0)
1134             selected = selected_entry;
1135         else
1136         {
1137             Listbox *listbox;
1138             unsigned int i;
1139 
1140             max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1141 
1142             // Create listbox
1143             listbox = listbox_window_new (entries->len, max_cols + 2, _ ("User menu"),
1144                                           "[Edit Menu File]");
1145             // insert all the items found
1146             for (i = 0; i < entries->len; i++)
1147             {
1148                 p = g_ptr_array_index (entries, i);
1149                 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1150                                      extract_line (p, p + MAX_ENTRY_LEN, NULL), p, FALSE);
1151             }
1152             // Select the default entry
1153             listbox_set_current (listbox->list, selected);
1154 
1155             selected = listbox_run (listbox);
1156         }
1157 
1158         if (selected >= 0)
1159         {
1160             execute_menu_command (edit_widget, g_ptr_array_index (entries, selected), interactive);
1161             res = TRUE;
1162         }
1163 
1164         g_ptr_array_free (entries, TRUE);
1165 
1166         do_refresh ();
1167     }
1168 
1169     easy_patterns = old_patterns;
1170     MC_PTR_FREE (menu);
1171     g_free (data);
1172     return res;
1173 }
1174 
1175 /* --------------------------------------------------------------------------------------------- */

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