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

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