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

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