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 <stdlib.h>
  35 #include <stdio.h>
  36 #include <string.h>
  37 
  38 #include "lib/global.h"
  39 #include "lib/fileloc.h"
  40 #include "lib/tty/tty.h"
  41 #include "lib/skin.h"
  42 #include "lib/search.h"
  43 #include "lib/vfs/vfs.h"
  44 #include "lib/strutil.h"
  45 #include "lib/util.h"
  46 
  47 #ifdef USE_INTERNAL_EDIT
  48 #include "src/editor/edit.h"  // WEdit
  49 #endif
  50 #include "src/viewer/mcviewer.h"  // for default_* externs
  51 
  52 #include "src/args.h"  // mc_run_param0
  53 #include "src/execute.h"
  54 #include "src/setup.h"
  55 #include "src/history.h"
  56 #include "src/util.h"  // file_error_message()
  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         file_error_message (_ ("Cannot create temporary command file"), NULL);
 469         return;
 470     }
 471 
 472     cmd_file = fdopen (cmd_file_fd, "w");
 473     fputs ("#! /bin/sh\n", cmd_file);
 474     commands++;
 475 
 476     for (col = 0; *commands != '\0'; commands++)
 477     {
 478         if (col == 0)
 479         {
 480             if (!whitespace (*commands))
 481                 break;
 482             while (whitespace (*commands))
 483                 commands++;
 484             if (*commands == '\0')
 485                 break;
 486         }
 487         col++;
 488         if (*commands == '\n')
 489             col = 0;
 490         if (parameter != NULL)
 491         {
 492             if (*commands == '}')
 493             {
 494                 *parameter = '\0';
 495                 parameter = input_dialog (
 496                     _ ("Parameter"), lc_prompt, MC_HISTORY_FM_MENU_EXEC_PARAM, "",
 497                     INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_HOSTNAMES
 498                         | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES);
 499                 if (parameter == NULL || *parameter == '\0')
 500                 {
 501                     // User canceled
 502                     g_free (parameter);
 503                     fclose (cmd_file);
 504                     mc_unlink (file_name_vpath);
 505                     vfs_path_free (file_name_vpath, TRUE);
 506                     return;
 507                 }
 508                 if (do_quote)
 509                 {
 510                     char *tmp;
 511 
 512                     tmp = name_quote (parameter, FALSE);
 513                     if (tmp != NULL)
 514                     {
 515                         fputs (tmp, cmd_file);
 516                         g_free (tmp);
 517                     }
 518                 }
 519                 else
 520                     fputs (parameter, cmd_file);
 521 
 522                 MC_PTR_FREE (parameter);
 523             }
 524             else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
 525                 *parameter++ = *commands;
 526         }
 527         else if (expand_prefix_found)
 528         {
 529             expand_prefix_found = FALSE;
 530             if (g_ascii_isdigit ((gchar) *commands))
 531             {
 532                 do_quote = (atoi (commands) != 0);
 533                 while (g_ascii_isdigit ((gchar) *commands))
 534                     commands++;
 535             }
 536             if (*commands == '{')
 537                 parameter = lc_prompt;
 538             else
 539             {
 540                 char *text;
 541 
 542                 text = expand_format (edit_widget, *commands, do_quote);
 543                 if (text != NULL)
 544                 {
 545                     fputs (text, cmd_file);
 546                     g_free (text);
 547                 }
 548             }
 549         }
 550         else if (*commands == '%')
 551         {
 552             int i;
 553 
 554             i = check_format_view (commands + 1);
 555             if (i != 0)
 556             {
 557                 commands += i;
 558                 run_view = TRUE;
 559             }
 560             else
 561             {
 562                 do_quote = TRUE;  // Default: Quote expanded macro
 563                 expand_prefix_found = TRUE;
 564             }
 565         }
 566         else
 567             fputc (*commands, cmd_file);
 568     }
 569 
 570     fclose (cmd_file);
 571     mc_chmod (file_name_vpath, S_IRWXU);
 572 
 573     // Execute the command indirectly to allow execution even on no-exec filesystems.
 574     cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (file_name_vpath), (char *) NULL);
 575 
 576     if (run_view)
 577     {
 578         mcview_viewer (cmd, NULL, 0, 0, 0);
 579         dialog_switch_process_pending ();
 580     }
 581     else if (show_prompt)
 582         shell_execute (cmd, EXECUTE_HIDE);
 583     else
 584     {
 585         gboolean ok;
 586 
 587         /* Prepare the terminal by setting its flag to the initial ones. This will cause \r
 588          * to work as expected, instead of being ignored. */
 589         tty_reset_shell_mode ();
 590 
 591         ok = (system (cmd) != -1);
 592 
 593         // Restore terminal configuration.
 594         tty_raw_mode ();
 595 
 596         // Redraw the original screen's contents.
 597         tty_clear_screen ();
 598         repaint_screen ();
 599 
 600         if (!ok)
 601             message (D_ERROR, MSG_ERROR, "%s", _ ("Error calling program"));
 602     }
 603 
 604     g_free (cmd);
 605 
 606     mc_unlink (file_name_vpath);
 607     vfs_path_free (file_name_vpath, TRUE);
 608 }
 609 
 610 /* --------------------------------------------------------------------------------------------- */
 611 /**
 612  **     Check owner of the menu file. Using menu file is allowed, if
 613  **     owner of the menu is root or the actual user. In either case
 614  **     file should not be group and word-writable.
 615  **
 616  **     Q. Should we apply this routine to system and home menu (and .ext files)?
 617  */
 618 
 619 static gboolean
 620 menu_file_own (char *path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 621 {
 622     struct stat st;
 623 
 624     if (stat (path, &st) == 0 && (st.st_uid == 0 || (st.st_uid == geteuid ()) != 0)
 625         && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0))
 626         return TRUE;
 627 
 628     if (verbose)
 629         message (D_NORMAL, _ ("Warning -- ignoring file"),
 630                  _ ("File %s is not owned by root or you or is world writable.\n"
 631                     "Using it may compromise your security"),
 632                  path);
 633 
 634     return FALSE;
 635 }
 636 
 637 /* --------------------------------------------------------------------------------------------- */
 638 /*** public functions ****************************************************************************/
 639 /* --------------------------------------------------------------------------------------------- */
 640 
 641 /*  Formats defined:
 642 
 643     mc.menu formats:
 644 
 645         %f The name of the current file without the path on the active panel.
 646         %p Same as %f.
 647         %n The current file name without extension.
 648         %x The extension of the current file name.
 649         %d The directory of the active panel.
 650         %t The tagged files.
 651         %s The selected files: the tagged files if any, otherwise the current file.
 652         %u Similar to the %t macros, but in addition the files are untagged.
 653             You can use this macro only once per menu file entry or extension file entry,
 654             because next time there will be no tagged files.
 655 
 656         If the format letter is in uppercase, it refers to the other panel.
 657 
 658 
 659     mcedit.menu formats:
 660 
 661         %f The current file name without the path.
 662         %p Same as %f.
 663         %n The current file name without extension.
 664         %x The extension of the current file name.
 665         %d The current working directory.
 666 
 667         Unlike mc.menu file, file and directory macros are not referenced file on the panels.
 668         Therefore uppercase and lowercase macros above are same.
 669 
 670         %c The cursor column position number.
 671         %i The indent of blank space, equal the cursor column position.
 672         %y The syntax type of current file.
 673         %b The block file name.
 674 
 675 
 676     Common (for mc.menu and mcedit.menu):
 677         %% The % character
 678 
 679         %view Runs the commands and pipes standard output to the view command.
 680             If %view is immediately followed by '{', keywords 'ascii', 'hex', 'nroff'
 681             and 'unform' are recognized.
 682 
 683         %{some text} Prompt for the substitution. An input box is shown and the text inside
 684             the braces is used as a prompt. The macro is substituted by the text typed
 685             by the user. The user can press ESC or F10 to cancel. This macro doesn't work
 686             on the command line yet.
 687 
 688         With a number followed the % character you can turn quoting on (default)
 689         and off. For example:
 690             %f  quote expanded macro
 691             %1f ditto
 692             %0f don't quote expanded macro.
 693 
 694     Expand_format returns a memory block that must be free()d.
 695  */
 696 
 697 /* Returns how many characters we should advance if %view was found */
 698 int
 699 check_format_view (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 700 {
 701     const char *q = p;
 702 
 703     if (strncmp (p, "view", 4) == 0)
 704     {
 705         q += 4;
 706         if (*q == '{')
 707         {
 708             for (q++; *q != '\0' && *q != '}'; q++)
 709             {
 710                 if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
 711                 {
 712                     mcview_global_flags.hex = FALSE;
 713                     q += 4;
 714                 }
 715                 else if (strncmp (q, "hex", 3) == 0)
 716                 {
 717                     mcview_global_flags.hex = TRUE;
 718                     q += 2;
 719                 }
 720                 else if (strncmp (q, "nroff", 5) == 0)
 721                 {
 722                     mcview_global_flags.nroff = TRUE;
 723                     q += 4;
 724                 }
 725                 else if (strncmp (q, "unform", 6) == 0)
 726                 {
 727                     mcview_global_flags.nroff = FALSE;
 728                     q += 5;
 729                 }
 730             }
 731             if (*q == '}')
 732                 q++;
 733         }
 734         return q - p;
 735     }
 736     return 0;
 737 }
 738 
 739 /* --------------------------------------------------------------------------------------------- */
 740 
 741 int
 742 check_format_cd (const char *p)
     /* [previous][next][first][last][top][bottom][index][help]  */
 743 {
 744     return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
 745 }
 746 
 747 /* --------------------------------------------------------------------------------------------- */
 748 /* Check if p has a "^var\{var-name\}" */
 749 /* Returns the number of skipped characters (zero on not found) */
 750 /* V will be set to the expanded variable name */
 751 
 752 int
 753 check_format_var (const char *p, char **v)
     /* [previous][next][first][last][top][bottom][index][help]  */
 754 {
 755     *v = NULL;
 756 
 757     if (strncmp (p, "var{", 4) == 0)
 758     {
 759         const char *q = p;
 760         const char *dots = NULL;
 761         const char *value;
 762         char *var_name;
 763 
 764         for (q += 4; *q != '\0' && *q != '}'; q++)
 765         {
 766             if (*q == ':')
 767                 dots = q + 1;
 768         }
 769         if (*q == '\0')
 770             return 0;
 771 
 772         if (dots == NULL || dots == q + 5)
 773         {
 774             message (D_ERROR, _ ("Format error on file Extensions File"),
 775                      dots == NULL ? _ ("The %%var macro has no default")
 776                                   : _ ("The %%var macro has no variable"));
 777             return 0;
 778         }
 779 
 780         // Copy the variable name
 781         var_name = g_strndup (p + 4, dots - 2 - (p + 3));
 782         value = getenv (var_name);
 783         g_free (var_name);
 784 
 785         if (value != NULL)
 786             *v = g_strdup (value);
 787         else
 788             *v = g_strndup (dots, q - dots);
 789 
 790         return q - p;
 791     }
 792     return 0;
 793 }
 794 
 795 /* --------------------------------------------------------------------------------------------- */
 796 
 797 char *
 798 expand_format (const Widget *edit_widget, char c, gboolean do_quote)
     /* [previous][next][first][last][top][bottom][index][help]  */
 799 {
 800     WPanel *panel = NULL;
 801     char *(*quote_func) (const char *, gboolean);
 802     const char *fname = NULL;
 803     char *result;
 804     char c_lc;
 805 
 806 #ifdef USE_INTERNAL_EDIT
 807     const WEdit *e = CONST_EDIT (edit_widget);
 808 #else
 809     (void) edit_widget;
 810 #endif
 811 
 812     if (c == '%')
 813         return g_strdup ("%");
 814 
 815     switch (mc_global.mc_run_mode)
 816     {
 817     case MC_RUN_FULL:
 818 #ifdef USE_INTERNAL_EDIT
 819         if (e != NULL)
 820             fname = edit_get_file_name (e);
 821         else
 822 #endif
 823         {
 824             const file_entry_t *fe;
 825 
 826             if (g_ascii_islower ((gchar) c))
 827                 panel = current_panel;
 828             else
 829             {
 830                 if (get_other_type () != view_listing)
 831                     return NULL;
 832                 panel = other_panel;
 833             }
 834 
 835             fe = panel_current_entry (panel);
 836             fname = fe == NULL ? NULL : fe->fname->str;
 837         }
 838         break;
 839 
 840 #ifdef USE_INTERNAL_EDIT
 841     case MC_RUN_EDITOR:
 842         fname = edit_get_file_name (e);
 843         break;
 844 #endif
 845 
 846     case MC_RUN_VIEWER:
 847         // mc_run_param0 is not NULL here because mcviewer isn't run without input file
 848         fname = (const char *) mc_run_param0;
 849         break;
 850 
 851     default:
 852         // other modes don't use formats
 853         return NULL;
 854     }
 855 
 856     if (do_quote)
 857         quote_func = name_quote;
 858     else
 859         quote_func = fake_name_quote;
 860 
 861     c_lc = g_ascii_tolower ((gchar) c);
 862 
 863     switch (c_lc)
 864     {
 865     case 'f':
 866     case 'p':
 867         result = quote_func (fname, FALSE);
 868         goto ret;
 869     case 'x':
 870         result = quote_func (extension (fname), FALSE);
 871         goto ret;
 872     case 'n':  // strip extension
 873         result = strip_ext (quote_func (fname, FALSE));
 874         goto ret;
 875     case 'd':
 876     {
 877         const char *cwd;
 878 
 879         if (panel != NULL)
 880             cwd = vfs_path_as_str (panel->cwd_vpath);
 881         else
 882             cwd = vfs_get_current_dir ();
 883 
 884         result = quote_func (cwd, FALSE);
 885         goto ret;
 886     }
 887     case 'c':
 888 #ifdef USE_INTERNAL_EDIT
 889         if (e != NULL)
 890         {
 891             result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (e));
 892             goto ret;
 893         }
 894 #endif
 895         break;
 896     case 'i':  // indent equal number cursor position in line
 897 #ifdef USE_INTERNAL_EDIT
 898         if (e != NULL)
 899         {
 900             result = g_strnfill (edit_get_curs_col (e), ' ');
 901             goto ret;
 902         }
 903 #endif
 904         break;
 905     case 'y':  // syntax type
 906 #ifdef USE_INTERNAL_EDIT
 907         if (e != NULL)
 908         {
 909             const char *syntax_type;
 910 
 911             syntax_type = edit_get_syntax_type (e);
 912             if (syntax_type != NULL)
 913             {
 914                 result = g_strdup (syntax_type);
 915                 goto ret;
 916             }
 917         }
 918 #endif
 919         break;
 920     case 'b':  // block file name
 921 #ifdef USE_INTERNAL_EDIT
 922         if (e != NULL)
 923         {
 924             char *file;
 925 
 926             file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
 927             result = quote_func (file, FALSE);
 928             g_free (file);
 929             goto ret;
 930         }
 931 #endif
 932         break;
 933     case 'm':  // menu file name
 934         if (menu != NULL)
 935         {
 936             result = quote_func (menu, FALSE);
 937             goto ret;
 938         }
 939         break;
 940     case 's':
 941         if (panel == NULL || panel->marked == 0)
 942         {
 943             result = quote_func (fname, FALSE);
 944             goto ret;
 945         }
 946 
 947         MC_FALLTHROUGH;
 948 
 949     case 't':
 950     case 'u':
 951     case 'v':
 952     {
 953         GString *block = NULL;
 954         int i;
 955         char *qcwd = NULL;
 956 
 957         if (panel == NULL)
 958         {
 959             result = NULL;
 960             goto ret;
 961         }
 962 
 963         if (c_lc == 'v')
 964         {
 965             const char *cwd = vfs_path_as_str (panel->cwd_vpath);
 966 
 967             qcwd = quote_func (cwd, FALSE);
 968         }
 969 
 970         block = g_string_sized_new (64);
 971 
 972         for (i = 0; i < panel->dir.len; i++)
 973             if (panel->dir.list[i].f.marked != 0)
 974             {
 975                 char *tmp;
 976 
 977                 if (qcwd != NULL)
 978                 {
 979                     g_string_append (block, qcwd);
 980                     g_string_append (block, PATH_SEP_STR);
 981                 }
 982 
 983                 tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
 984 
 985                 if (tmp != NULL)
 986                 {
 987                     g_string_append (block, tmp);
 988                     g_free (tmp);
 989                     g_string_append_c (block, ' ');
 990                 }
 991 
 992                 if (c_lc == 'u')
 993                     do_file_mark (panel, i, 0);
 994             }
 995         g_free (qcwd);
 996         result = g_string_free (block, block->len == 0);
 997         goto ret;
 998     }  // sub case block
 999     default:
1000         break;
1001     }  // switch
1002 
1003     result = g_strdup ("% ");
1004     result[1] = c;
1005 ret:
1006     return result;
1007 }
1008 
1009 /* --------------------------------------------------------------------------------------------- */
1010 /**
1011  * If edit_widget is NULL then we are called from the mc menu,
1012  * otherwise we are called from the mcedit menu.
1013  */
1014 
1015 gboolean
1016 user_menu_cmd (const Widget *edit_widget, const char *menu_file, int selected_entry)
     /* [previous][next][first][last][top][bottom][index][help]  */
1017 {
1018     char *data, *p;
1019     GPtrArray *entries = NULL;
1020     int max_cols = 0;
1021     int col = 0;
1022     gboolean accept_entry = TRUE;
1023     int selected = -1;
1024     gboolean old_patterns;
1025     gboolean res = FALSE;
1026     gboolean interactive = TRUE;
1027 
1028     if (!vfs_current_is_local ())
1029     {
1030         message (D_ERROR, MSG_ERROR, "%s", _ ("Cannot execute commands on non-local filesystems"));
1031         return FALSE;
1032     }
1033 
1034     menu = g_strdup (menu_file != NULL         ? menu_file
1035                          : edit_widget != NULL ? EDIT_LOCAL_MENU
1036                                                : MC_LOCAL_MENU);
1037 
1038     if (!exist_file (menu) || !menu_file_own (menu))
1039     {
1040         if (menu_file != NULL)
1041         {
1042             file_error_message (_ ("Cannot open file\n%s"), menu);
1043             MC_PTR_FREE (menu);
1044             return FALSE;
1045         }
1046 
1047         g_free (menu);
1048         menu = mc_config_get_full_path (edit_widget != NULL ? EDIT_HOME_MENU : MC_USERMENU_FILE);
1049         if (!exist_file (menu))
1050         {
1051             const char *global_menu;
1052 
1053             global_menu = edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU;
1054 
1055             g_free (menu);
1056             menu = mc_build_filename (mc_config_get_home_dir (), global_menu, (char *) NULL);
1057             if (!exist_file (menu))
1058             {
1059                 g_free (menu);
1060                 menu = mc_build_filename (mc_global.sysconfig_dir, global_menu, (char *) NULL);
1061                 if (!exist_file (menu))
1062                 {
1063                     g_free (menu);
1064                     menu = mc_build_filename (mc_global.share_data_dir, global_menu, (char *) NULL);
1065                 }
1066             }
1067         }
1068     }
1069 
1070     if (!g_file_get_contents (menu, &data, NULL, NULL))
1071     {
1072         file_error_message (_ ("Cannot open file\n%s"), menu);
1073         MC_PTR_FREE (menu);
1074         return FALSE;
1075     }
1076 
1077     old_patterns = easy_patterns;
1078 
1079     // Parse the menu file
1080     for (p = check_patterns (data); *p != '\0'; str_next_char (&p))
1081     {
1082         unsigned int menu_lines = entries == NULL ? 0 : entries->len;
1083 
1084         if (col == 0 && (entries == NULL || menu_lines == entries->len))
1085             switch (*p)
1086             {
1087             case '#':
1088                 // do not show prompt if first line of external script is #silent
1089                 if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
1090                     interactive = FALSE;
1091                 // A commented menu entry
1092                 accept_entry = TRUE;
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 adding the entry
1106                     p = test_line (edit_widget, p, &accept_entry);
1107                 }
1108                 break;
1109 
1110             case '=':
1111                 if (*(p + 1) == '+')
1112                 {
1113                     // Combined adding and default
1114                     p = test_line (edit_widget, p + 1, &accept_entry);
1115                     if (selected < 0 && accept_entry)
1116                         selected = menu_lines;
1117                 }
1118                 else
1119                 {
1120                     // A condition for making the entry default
1121                     gboolean ok = TRUE;
1122 
1123                     p = test_line (edit_widget, p, &ok);
1124                     if (selected < 0 && ok)
1125                         selected = menu_lines;
1126                 }
1127                 break;
1128 
1129             default:
1130                 if (!whitespace (*p) && str_isprint (p))
1131                 {
1132                     // A menu entry title line
1133                     if (accept_entry)
1134                     {
1135                         if (entries == NULL)
1136                             entries = g_ptr_array_new ();
1137                         g_ptr_array_add (entries, p);
1138                     }
1139                     else
1140                         accept_entry = TRUE;
1141                 }
1142                 break;
1143             }
1144 
1145         if (*p == '\n')
1146         {
1147             if (entries != NULL && entries->len > menu_lines)
1148                 accept_entry = TRUE;
1149             max_cols = MAX (max_cols, col);
1150             col = 0;
1151         }
1152         else
1153         {
1154             if (*p == '\t')
1155                 *p = ' ';
1156             col++;
1157         }
1158     }
1159 
1160     if (entries == NULL)
1161         message (D_ERROR, MSG_ERROR, _ ("No suitable entries found in %s"), menu);
1162     else
1163     {
1164         if (selected_entry >= 0)
1165             selected = selected_entry;
1166         else
1167         {
1168             Listbox *listbox;
1169             unsigned int i;
1170 
1171             max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1172 
1173             // Create listbox
1174             listbox = listbox_window_new (entries->len, max_cols + 2, _ ("User menu"),
1175                                           "[Edit Menu File]");
1176             // insert all the items found
1177             for (i = 0; i < entries->len; i++)
1178             {
1179                 p = g_ptr_array_index (entries, i);
1180                 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1181                                      extract_line (p, p + MAX_ENTRY_LEN, NULL), p, FALSE);
1182             }
1183             // Select the default entry
1184             listbox_set_current (listbox->list, selected);
1185 
1186             selected = listbox_run (listbox);
1187         }
1188 
1189         if (selected >= 0)
1190         {
1191             execute_menu_command (edit_widget, g_ptr_array_index (entries, selected), interactive);
1192             res = TRUE;
1193         }
1194 
1195         g_ptr_array_free (entries, TRUE);
1196 
1197         do_refresh ();
1198     }
1199 
1200     easy_patterns = old_patterns;
1201     MC_PTR_FREE (menu);
1202     g_free (data);
1203     return res;
1204 }
1205 
1206 /* --------------------------------------------------------------------------------------------- */

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