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

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