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     {
 952         GString *block = NULL;
 953         int i;
 954 
 955         if (panel == NULL)
 956         {
 957             result = NULL;
 958             goto ret;
 959         }
 960 
 961         for (i = 0; i < panel->dir.len; i++)
 962             if (panel->dir.list[i].f.marked != 0)
 963             {
 964                 char *tmp;
 965 
 966                 tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
 967                 if (tmp != NULL)
 968                 {
 969                     if (block == NULL)
 970                         block = g_string_new_take (tmp);
 971                     else
 972                     {
 973                         g_string_append (block, tmp);
 974                         g_free (tmp);
 975                     }
 976                     g_string_append_c (block, ' ');
 977                 }
 978 
 979                 if (c_lc == 'u')
 980                     do_file_mark (panel, i, 0);
 981             }
 982         result = block == NULL ? NULL : g_string_free (block, block->len == 0);
 983         goto ret;
 984     }  // sub case block
 985     default:
 986         break;
 987     }  // switch
 988 
 989     result = g_strdup ("% ");
 990     result[1] = c;
 991 ret:
 992     return result;
 993 }
 994 
 995 /* --------------------------------------------------------------------------------------------- */
 996 /**
 997  * If edit_widget is NULL then we are called from the mc menu,
 998  * otherwise we are called from the mcedit menu.
 999  */
1000 
1001 gboolean
1002 user_menu_cmd (const Widget *edit_widget, const char *menu_file, int selected_entry)
     /* [previous][next][first][last][top][bottom][index][help]  */
1003 {
1004     char *data, *p;
1005     GPtrArray *entries = NULL;
1006     int max_cols = 0;
1007     int col = 0;
1008     gboolean accept_entry = TRUE;
1009     int selected = -1;
1010     gboolean old_patterns;
1011     gboolean res = FALSE;
1012     gboolean interactive = TRUE;
1013 
1014     if (!vfs_current_is_local ())
1015     {
1016         message (D_ERROR, MSG_ERROR, "%s", _ ("Cannot execute commands on non-local filesystems"));
1017         return FALSE;
1018     }
1019 
1020     menu = g_strdup (menu_file != NULL         ? menu_file
1021                          : edit_widget != NULL ? EDIT_LOCAL_MENU
1022                                                : MC_LOCAL_MENU);
1023 
1024     if (!exist_file (menu) || !menu_file_own (menu))
1025     {
1026         if (menu_file != NULL)
1027         {
1028             file_error_message (_ ("Cannot open file\n%s"), menu);
1029             MC_PTR_FREE (menu);
1030             return FALSE;
1031         }
1032 
1033         g_free (menu);
1034         menu = mc_config_get_full_path (edit_widget != NULL ? EDIT_HOME_MENU : MC_USERMENU_FILE);
1035         if (!exist_file (menu))
1036         {
1037             const char *global_menu;
1038 
1039             global_menu = edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU;
1040 
1041             g_free (menu);
1042             menu = mc_build_filename (mc_config_get_home_dir (), global_menu, (char *) NULL);
1043             if (!exist_file (menu))
1044             {
1045                 g_free (menu);
1046                 menu = mc_build_filename (mc_global.sysconfig_dir, global_menu, (char *) NULL);
1047                 if (!exist_file (menu))
1048                 {
1049                     g_free (menu);
1050                     menu = mc_build_filename (mc_global.share_data_dir, global_menu, (char *) NULL);
1051                 }
1052             }
1053         }
1054     }
1055 
1056     if (!g_file_get_contents (menu, &data, NULL, NULL))
1057     {
1058         file_error_message (_ ("Cannot open file\n%s"), menu);
1059         MC_PTR_FREE (menu);
1060         return FALSE;
1061     }
1062 
1063     old_patterns = easy_patterns;
1064 
1065     // Parse the menu file
1066     for (p = check_patterns (data); *p != '\0'; str_next_char (&p))
1067     {
1068         unsigned int menu_lines = entries == NULL ? 0 : entries->len;
1069 
1070         if (col == 0 && (entries == NULL || menu_lines == entries->len))
1071             switch (*p)
1072             {
1073             case '#':
1074                 // do not show prompt if first line of external script is #silent
1075                 if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
1076                     interactive = FALSE;
1077                 // A commented menu entry
1078                 accept_entry = TRUE;
1079                 break;
1080 
1081             case '+':
1082                 if (*(p + 1) == '=')
1083                 {
1084                     // Combined adding and default
1085                     p = test_line (edit_widget, p + 1, &accept_entry);
1086                     if (selected < 0 && accept_entry)
1087                         selected = menu_lines;
1088                 }
1089                 else
1090                 {
1091                     // A condition for adding the entry
1092                     p = test_line (edit_widget, p, &accept_entry);
1093                 }
1094                 break;
1095 
1096             case '=':
1097                 if (*(p + 1) == '+')
1098                 {
1099                     // Combined adding and default
1100                     p = test_line (edit_widget, p + 1, &accept_entry);
1101                     if (selected < 0 && accept_entry)
1102                         selected = menu_lines;
1103                 }
1104                 else
1105                 {
1106                     // A condition for making the entry default
1107                     gboolean ok = TRUE;
1108 
1109                     p = test_line (edit_widget, p, &ok);
1110                     if (selected < 0 && ok)
1111                         selected = menu_lines;
1112                 }
1113                 break;
1114 
1115             default:
1116                 if (!whitespace (*p) && str_isprint (p))
1117                 {
1118                     // A menu entry title line
1119                     if (accept_entry)
1120                     {
1121                         if (entries == NULL)
1122                             entries = g_ptr_array_new ();
1123                         g_ptr_array_add (entries, p);
1124                     }
1125                     else
1126                         accept_entry = TRUE;
1127                 }
1128                 break;
1129             }
1130 
1131         if (*p == '\n')
1132         {
1133             if (entries != NULL && entries->len > menu_lines)
1134                 accept_entry = TRUE;
1135             max_cols = MAX (max_cols, col);
1136             col = 0;
1137         }
1138         else
1139         {
1140             if (*p == '\t')
1141                 *p = ' ';
1142             col++;
1143         }
1144     }
1145 
1146     if (entries == NULL)
1147         message (D_ERROR, MSG_ERROR, _ ("No suitable entries found in %s"), menu);
1148     else
1149     {
1150         if (selected_entry >= 0)
1151             selected = selected_entry;
1152         else
1153         {
1154             Listbox *listbox;
1155             unsigned int i;
1156 
1157             max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
1158 
1159             // Create listbox
1160             listbox = listbox_window_new (entries->len, max_cols + 2, _ ("User menu"),
1161                                           "[Edit Menu File]");
1162             // insert all the items found
1163             for (i = 0; i < entries->len; i++)
1164             {
1165                 p = g_ptr_array_index (entries, i);
1166                 LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
1167                                      extract_line (p, p + MAX_ENTRY_LEN, NULL), p, FALSE);
1168             }
1169             // Select the default entry
1170             listbox_set_current (listbox->list, selected);
1171 
1172             selected = listbox_run (listbox);
1173         }
1174 
1175         if (selected >= 0)
1176         {
1177             execute_menu_command (edit_widget, g_ptr_array_index (entries, selected), interactive);
1178             res = TRUE;
1179         }
1180 
1181         g_ptr_array_free (entries, TRUE);
1182 
1183         do_refresh ();
1184     }
1185 
1186     easy_patterns = old_patterns;
1187     MC_PTR_FREE (menu);
1188     g_free (data);
1189     return res;
1190 }
1191 
1192 /* --------------------------------------------------------------------------------------------- */

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