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

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