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

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