/* shell.c - user defined shell commands Copyright (C) 1996-2000 Paul Sheer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include #include #if HAVE_SYS_WAIT_H #include #endif #ifndef WEXITSTATUS #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED #define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #include #ifdef HAVE_SYS_ERRNO_H #include #endif #include "editoptions.h" #include "shell.h" #include "pool.h" #include "mad.h" #define shell_error_dialog(h,t) CErrorDialog(e->widget->winid,20,20,h,"%s",t) #undef gettext_noop #define gettext_noop(x) x extern struct look *look; int save_options_section (const char *file, const char *section, const char *text); extern char *editor_options_file; extern char *init_font; extern Window main_window; /* Here are the default example shells */ struct shell_cmd default_scripts[] = { { " Sed ", "Sed...\tC-M-d", '~', XK_d, Mod1Mask | ControlMask, gettext_noop (" Enter sed arguments (see sed manpage) : "), SHELL_OPTION_SAVE_BLOCK | SHELL_OPTION_REQUEST_ARGUMENTS | SHELL_OPTION_DELETE_BLOCK | SHELL_OPTION_INSERT_STDOUT | SHELL_OPTION_DISPLAY_ERROR_FILE | SHELL_OPTION_CHECK_ERROR_FILE, 0, "#!/bin/sh\n" \ "cat %b | sed %a 2>%e\n" }, { gettext_noop (" Indent "), gettext_noop ("'indent' C Formatter\tS-F9"), '~', XK_F9, ShiftMask, "", SHELL_OPTION_SAVE_BLOCK | SHELL_OPTION_DELETE_BLOCK | SHELL_OPTION_DISPLAY_ERROR_FILE | SHELL_OPTION_CHECK_ERROR_FILE | SHELL_OPTION_INSERT_BLOCK_FILE, 0, "#!/bin/sh\n" \ "indent -kr -pcs %b 2>%e\n" }, { gettext_noop (" Sort "), gettext_noop ("Sort...\tM-t"), '~', XK_t, Mod1Mask, gettext_noop (" Enter sort options (see sort manpage) : "), SHELL_OPTION_SAVE_BLOCK | SHELL_OPTION_REQUEST_ARGUMENTS | SHELL_OPTION_DELETE_BLOCK | SHELL_OPTION_INSERT_STDOUT | SHELL_OPTION_DISPLAY_ERROR_FILE | SHELL_OPTION_CHECK_ERROR_FILE, 0, "#!/bin/sh\n" \ "sort %a %b 2>%e\n" }, { " Ispell ", gettext_noop ("'ispell' Spell Check\tC-p"), '~', XK_p, ControlMask, "", SHELL_OPTION_SAVE_BLOCK | SHELL_OPTION_DELETE_BLOCK | SHELL_OPTION_INSERT_BLOCK_FILE, 0, "#!/bin/sh\n" \ XTERM_CMD " -e ispell %b\n" }, { " Make ", gettext_noop ("Run make in curr. dir\tM-F7"), '~', XK_F7, Mod1Mask, "", SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS | SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS, 0, "#!/bin/sh\n" \ "cd %d\n" \ "make\n" \ "echo Done\n" }, { " Latex ", "Latex\tC-M-l", '~', XK_l, Mod1Mask | ControlMask, "", SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS | SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS, 0, "#!/bin/sh\n" \ "cd %p\n" \ "latex %f\n" \ "echo Done\n" }, { " Latex to PS ", "Latex to PS\tC-M-p", '~', XK_p, Mod1Mask | ControlMask, "", SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS | SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS, 0, "#!/bin/sh\n" \ "cd %p\n" \ "latex %f\n" \ "dvips %n.dvi\n" \ "echo Done\n" }, { " Xdvi ", "Xdvi\tC-M-d", '~', XK_d, Mod1Mask | ControlMask, "", SHELL_OPTION_RUN_IN_BACKGROUND, 0, "#!/bin/sh\n" \ "cd %p\n" \ "xdvi %n.dvi 2>%e\n" }, { " GhostView ", "GhostView\tC-M-g", '~', XK_g, Mod1Mask | ControlMask, "", SHELL_OPTION_RUN_IN_BACKGROUND, 0, "#!/bin/sh\n" \ "cd %p\n" \ "ghostview %n.ps 2>%e\n" }, { gettext_noop (" Run Application "), gettext_noop ("Run Application...\tC-M-a"), '~', XK_a, Mod1Mask | ControlMask, gettext_noop (" Enter command : "), SHELL_OPTION_REQUEST_ARGUMENTS | SHELL_OPTION_RUN_IN_BACKGROUND, 0, "#!/bin/sh\n" \ "%a 2>%e\n" }, { gettext_noop (" Terminal Application "), gettext_noop ("Run Terminal App...\tC-M-e"), '~', XK_e, Mod1Mask | ControlMask, gettext_noop (" Enter command : "), SHELL_OPTION_REQUEST_ARGUMENTS | SHELL_OPTION_RUN_IN_BACKGROUND, 0, "#!/bin/sh\n" \ XTERM_CMD " -e %a\n" }, { " Translate ", "Translate...\tC-M-t", '~', XK_t, Mod1Mask | ControlMask, gettext_noop (" Enter args for tr (See `man tr' for help): "), SHELL_OPTION_SAVE_BLOCK | SHELL_OPTION_REQUEST_ARGUMENTS | SHELL_OPTION_DELETE_BLOCK | SHELL_OPTION_INSERT_STDOUT | SHELL_OPTION_DISPLAY_ERROR_FILE | SHELL_OPTION_CHECK_ERROR_FILE, 0, "#!/bin/sh\n" \ "cat %b | tr %a 2>%e\n" }, { gettext_noop (" C Run "), gettext_noop ("Run this file\tC-M-r"), '~', XK_r, Mod1Mask | ControlMask, "", SHELL_OPTION_RUN_IN_BACKGROUND, 0, "#!/bin/sh\n" \ XTERM_CMD " -e %p/%n\n" }, { gettext_noop (" C Compile "), "Cc\tC-M-c", '~', XK_c, Mod1Mask | ControlMask, "", SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS | SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS, 0, "#!/bin/sh\n" \ "cd %p\n" \ "cc -g -Wall -o %n %f\n" \ "echo Done\n" } }; char *hme (char *text); char *substitute_strings (char *text, char *cmdline_options, char *editor_file, char *dnd_major_type, char *dnd_minor_type, char *dnd_data_file); /* {{{ dynamic display of shell output in a dialog box */ #define MAX_RUNNING_SHELLS 32 static struct running_shell { pid_t shell_pid; int shell_pipe; POOL *shell_pool; const char *shell_name; CWidget *w; int killme; } running_shell[MAX_RUNNING_SHELLS]; static void kill_process (pid_t p) { if (p) kill (p, SIGTERM); } /* one of these must be non-zero */ static int find_shell (pid_t p, const char *name, CWidget * w) { int i; for (i = 0; i < MAX_RUNNING_SHELLS; i++) { if (p) if (running_shell[i].shell_pid == p) return i; if (name && running_shell[i].shell_name) if (*name) if (!strcmp (name, running_shell[i].shell_name)) return i; if (w) if ((unsigned long) w == (unsigned long) running_shell[i].w) return i; } return -1; } static int new_shell (const char *name) { int i; i = find_shell (0, name, 0); if (i < 0) { for (i = 0; i < MAX_RUNNING_SHELLS; i++) if (!running_shell[i].shell_name) { memset (&running_shell[i], 0, sizeof (struct running_shell)); running_shell[i].shell_pipe = -1; running_shell[i].shell_name = name; /* FIXME: strdup() then free() later - static is ok for now */ return i; } } return -1; } void text_free (void *x) { if (x) free (x); } static void shell_pool_update (int fd, fd_set * reading, fd_set * writing, fd_set * error, void *data); char *shell_free_pool (int i) { char *s = 0; if (i < 0) return 0; if (running_shell[i].shell_pool) s = (char *) pool_break (running_shell[i].shell_pool); if (running_shell[i].shell_pipe >= 0) { CRemoveWatch (running_shell[i].shell_pipe , shell_pool_update, WATCH_READING); close (running_shell[i].shell_pipe); } memset (&running_shell[i], 0, sizeof (struct running_shell)); running_shell[i].shell_pipe = -1; return s; } /* kills an executing shell whose output is being dynamically displayed */ void set_to_kill (pid_t p) { int i; i = find_shell (p, 0, 0); if (i >= 0) running_shell[i].killme = 1; } /* kills an executing shell whose output is being dynamically displayed */ static int kill_shell (pid_t p, char *name, CWidget * w) { int i; i = find_shell (p, name, w); if (i < 0) return -1; kill_process (running_shell[i].shell_pid); set_to_kill (running_shell[i].shell_pid); return i; } static int restart_shell (pid_t p, const char *name, CWidget * w) { int i; i = find_shell (p, name, w); if (i < 0) return new_shell (name); if (running_shell[i].shell_pipe >= 0) { CRemoveWatch (running_shell[i].shell_pipe, shell_pool_update, WATCH_READING); close (running_shell[i].shell_pipe); } kill_process (running_shell[i].shell_pid); running_shell[i].shell_pid = 0; running_shell[i].shell_pipe = -1; running_shell[i].killme = 0; if (running_shell[i].shell_pool) { pool_break (running_shell[i].shell_pool); running_shell[i].shell_pool = 0; } return i; } void goto_error (char *message, int raise_wm_window); /* if you double click on a line of gcc output, this will take you to the file */ static int goto_file_callback (CWidget * w, XEvent * x, CEvent * c) { if (c->double_click || (c->command == CK_Enter && !c->handled)) { int width; char *q; CPushFont ("editor", 0); width = w->options & TEXTBOX_WRAP ? (w->width - TEXTBOX_BDR) / FONT_MEAN_WIDTH : 32000; CPopFont (); q = strline (w->text, strmovelines (w->text, w->current, w->cursor - w->firstline, width)); goto_error (q, 1); } return 0; } static char *nm (int i, char *a, char *b, char *c) { static char id[36]; sprintf (id, "%.3d%s", i, a); if (b) { strcat (id, "."); strcat (id, b); if (c) { strcat (id, "."); strcat (id, c); } } return id; } /* if the dynamic output dialog's tick button is click, then terminate: */ static int display_file_callback (CWidget * w, XEvent * x, CEvent * c) { shell_free_pool (kill_shell (0, 0, CIdent (nm (atoi (w->ident), "shelldisplaytext", "text", 0)))); CDestroyWidget (nm (atoi (w->ident), "shelldisplaytext", 0, 0)); return 1; } #if 0 fd_set reading; struct timeval tv; FD_ZERO (&reading); for (i = 0; i < MAX_RUNNING_SHELLS; i++) { if (running_shell[i].shell_pipe >= 0 && running_shell[i].shell_name) { FD_SET (running_shell[i].shell_pipe, &reading); if (n < (int) running_shell[i].shell_pipe) n = running_shell[i].shell_pipe; } } if (n < 0) { CAddCallback ("AlarmCallback", 0); return 0; } tv.tv_sec = 0; tv.tv_usec = 0; #endif #define CHUNK 8192 static void shell_pool_update (int fd, fd_set * reading, fd_set * writing, fd_set * error, void *data) { CWidget *w; struct running_shell *r; int i, count = 0, do_redraw = 0; r = &running_shell[i = (int) data]; if (r->shell_pipe != fd) { printf ("huh??\n"); } /* first check if we are still on the screen */ w = CIdent (nm (i, "shelldisplaytext", "text", 0)); if (!w) { shell_free_pool (kill_shell (0, 0, w)); return; } /* is the buffer initialised? : */ if (!r->shell_pool) r->shell_pool = pool_init (); /* read a little */ for (;;) { int c, j; unsigned char *p; if (pool_freespace (r->shell_pool) < CHUNK + 1) { pool_advance (r->shell_pool, CHUNK + 1); do_redraw = 1; /* must redraw cause pool has changed */ } while ((c = read (fd, pool_current (r->shell_pool), CHUNK)) == -1 && errno == EINTR); /* translate unreadables */ for (j = 0, p = (unsigned char *) pool_current (r->shell_pool); j < c; j++, p++) { if (*p == '\t') *p = ' '; else if (*p == '\n') *p = '\n'; else if (!FONT_PER_CHAR(*p)) *p = '?'; } if (c <= 0) break; count += c; pool_current (r->shell_pool) += c; break; } pool_null (r->shell_pool); /* adds a zero to the end */ /* do we need to refresh? : */ if (count || do_redraw) { /* we do a manual appending of the text for optimization purposes, instead of CRedrawTextbox */ w->text = (char *) pool_start (r->shell_pool); CPushFont ("editor", 0); w->numlines += strcountlines (w->text + w->textlength, 0, 2000000000, w->options & TEXTBOX_WRAP ? (w->width - TEXTBOX_BDR) / FONT_MEAN_WIDTH : 32000); w->textlength = pool_length (r->shell_pool); /* only redraw if we crossed a newline */ if (strchr (w->text + w->textlength - count, '\n')) { CExpose (w->ident); if (w->winid != CGetFocus ()) if (w->numlines > w->firstline + (w->height / FONT_PIX_PER_LINE - 1)) CSetTextboxPos (w, TEXT_SET_LINE, w->numlines - (w->height / FONT_PIX_PER_LINE - 1)); } CPopFont (); } r->killme |= CChildExitted (r->shell_pid, 0); if (!count && r->killme) { pool_break (r->shell_pool); r->shell_pool = 0; CRemoveWatch (fd, shell_pool_update, WATCH_READING); close (fd); r->shell_pipe = -1; } } /* draws a textbox dialog for showing the shells output */ static void shell_display_output (char *text, const char *heading, int (*select_line_callback) (CWidget *, XEvent *, CEvent *), const char *name) { int i; if (!text) text = ""; i = find_shell (0, name, 0); if (i >= 0 && CIdent (nm (i, "shelldisplaytext", 0, 0))) { /* exists ? */ CWidget *w; w = CIdent (nm (i, "shelldisplaytext", "text", 0)); free (w->text); w->text = 0; CRedrawTextbox (nm (i, "shelldisplaytext", "text", 0), text, 0); CRedrawText (nm (i, "shelldisplaytext", "header", 0), heading); CTryFocus (CIdent (nm (i, "shelldisplaytext", "text", 0)), 1); } else { int x, y; Window win; CWidget *w; win = CDrawMainWindow (nm (i, "shelldisplaytext", 0, 0), heading); CGetHintPos (&x, &y); CPushFont ("editor", 0); running_shell[i].w = w = CDrawTextbox (nm (i, "shelldisplaytext", "text", 0), win, x, y, 80 * FONT_MEAN_WIDTH + 7, 25 * FONT_PIX_PER_LINE + 6, 0, 0, text, TEXTBOX_WRAP | TEXTBOX_NO_STRDUP); /* Toolhint */ CSetToolHint (nm (i, "shelldisplaytext", "text", 0), _ ("Double click on file:line type messages to goto the\nfile and line number. Note that the file will not\nauto-load unless there is a full path in the message.")); w->position |= POSITION_HEIGHT | POSITION_WIDTH; (CIdent (nm (i, "shelldisplaytext", "text", "vsc")))->position |= POSITION_HEIGHT | POSITION_RIGHT; CGetHintPos (0, &y); (CDrawPixmapButton (nm (i, "shelldisplaytext", "done", 0), win, 0, y, PIXMAP_BUTTON_TICK))->position = POSITION_BOTTOM | POSITION_CENTRE; /* Toolhint */ CSetToolHint (nm (i, "shelldisplaytext", "done", 0), _ ("Kill the running script")); CCentre (nm (i, "shelldisplaytext", "done", 0)); CSetSizeHintPos (nm (i, "shelldisplaytext", 0, 0)); CSetWindowResizable (nm (i, "shelldisplaytext", 0, 0), FONT_MEAN_WIDTH * 15, FONT_PIX_PER_LINE * 15, 1600, 1200); /* minimum and maximum sizes */ CPopFont (); CMapDialog (nm (i, "shelldisplaytext", 0, 0)); CAddCallback (nm (i, "shelldisplaytext", "text", 0), select_line_callback); CAddCallback (nm (i, "shelldisplaytext", "done", 0), display_file_callback); CFocus (CIdent (nm (i, "shelldisplaytext", "done", 0))); } } /* }}} dynamic display of shell output in a dialog box */ static char *hme_i (char *h, int i) { static char s[MAX_PATH_LEN]; sprintf (s, "%s-%d", hme (h), i); return s; } /* returns non-zero on error */ int execute_background_display_output (const char *title, const char *s, const char *name) { char *argv[] = {0, 0}; pid_t p; int i; i = restart_shell (0, name, 0); argv[0] = hme_i (SCRIPT_FILE, i); savefile (argv[0], s, strlen (s), 0700); if ((p = triple_pipe_open (0, &running_shell[i].shell_pipe, 0, 1, argv[0], argv)) < 0) return 1; running_shell[i].shell_pid = p; if (!running_shell[i].shell_pool) { running_shell[i].shell_pool = pool_init (); pool_null (running_shell[i].shell_pool); } shell_display_output ((char *) pool_start (running_shell[i].shell_pool), title, goto_file_callback, name); CAddWatch (running_shell[i].shell_pipe, shell_pool_update, WATCH_READING, (void *) i); return 0; } /* Executes the shell in the background. The "Insert File" options will be ignored if this is called. */ static int execute_background_shell (struct shell_cmd *s, char *script, char *name) { char *argv[] = {0, 0}; pid_t p = 0; int i; i = restart_shell (0, name, 0); argv[0] = hme_i (SCRIPT_FILE, i); savefile (argv[0], script, strlen (script), 0700); if (s->options & SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS) { if ((p = triple_pipe_open (0, &running_shell[i].shell_pipe, 0, (s->options & SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS) ? 1 : 0, hme_i (SCRIPT_FILE, i), argv)) < 0) return 0; } else { if (!(s->options & SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS)) { /* no output desired, just run in background */ switch (fork ()) { case 0:{ int nulldevice_wr, nulldevice_rd; nulldevice_wr = open ("/dev/null", O_WRONLY); nulldevice_rd = open ("/dev/null", O_RDONLY); close (0); dup (nulldevice_rd); close (1); dup (nulldevice_wr); close (2); dup (nulldevice_wr); set_signal_handlers_to_default (); execlp (argv[0], argv[0], 0); exit (0); /* should never reach */ } case -1: return 0; default: return 1; } } if ((p = triple_pipe_open (0, 0, &running_shell[i].shell_pipe, 0, hme_i (SCRIPT_FILE, i), argv)) < 0) return 0; } running_shell[i].shell_pid = p; if (!running_shell[i].shell_pool) { running_shell[i].shell_pool = pool_init (); pool_null (running_shell[i].shell_pool); } shell_display_output ((char *) pool_start (running_shell[i].shell_pool), catstrs (s->name, _ (" Output "), NULL), goto_file_callback, name); CAddWatch (running_shell[i].shell_pipe, shell_pool_update, WATCH_READING, (void *) i); return 1; } char *read_pipe (int fd, int *len); /* Returns shell_output on success, 0 on error. Result must be free'd. Unlike the above routine, this blocks waiting for the shell to exit. */ static char *execute_foreground_shell (struct shell_cmd *s, char *script) { pid_t p = 0; char *argv[] = {0, 0}; int shell_foreground_pipe = -1; char *t; argv[0] = hme (SCRIPT_FILE); savefile (argv[0], script, strlen (script), 0700); if (s->options & SHELL_OPTION_INSERT_STDOUT) { if ((p = triple_pipe_open (0, &shell_foreground_pipe, 0, (s->options & SHELL_OPTION_INSERT_STDERR) ? 1 : 0, hme (SCRIPT_FILE), argv)) < 0) return 0; t = read_pipe (shell_foreground_pipe, 0); } else { if ((p = triple_pipe_open (0, 0, &shell_foreground_pipe, 0, hme (SCRIPT_FILE), argv)) < 0) return 0; t = read_pipe (shell_foreground_pipe, 0); if (!(s->options & SHELL_OPTION_INSERT_STDERR)) { if (t) free (t); t = (char *) strdup (""); } } if (shell_foreground_pipe >= 0) close (shell_foreground_pipe); kill_process (p); return t; } int edit_save_block (WEdit * edit, const char *filename, long start, long finish); /* This is called from the envokation dialog below */ static int run_shell (WEdit * e, struct shell_cmd *s, char *cmdline_options, char *name) { struct stat st; long start_mark, end_mark; char *script; char *output = 0; int i; i = find_shell (0, name, 0); if (i >= 0) if (!(s->options & (SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS | SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS))) /* --> these kill any running script automatically */ if (running_shell[i].shell_pid) { shell_error_dialog (_ (" Shell script "), _ (" A script is already running ")); return -1; } if (s->options & SHELL_OPTION_SAVE_BLOCK) { eval_marks (e, &start_mark, &end_mark); edit_save_block (e, hme (BLOCK_FILE), start_mark, end_mark); } if (s->options & SHELL_OPTION_SAVE_EDITOR_FILE) if (!edit_save_file (e, catstrs (e->dir, e->filename, NULL))) if (!edit_save_as_cmd (e)) return -1; script = substitute_strings (s->script, cmdline_options, catstrs (e->dir, e->filename, NULL), "", "", ""); if ((s->options & (SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS | SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS | SHELL_OPTION_RUN_IN_BACKGROUND))) { if (!execute_background_shell (s, script, name)) { shell_error_dialog (_ (" Shell script "), get_sys_error (_ (" Error trying to pipe script. "))); return -1; } if (script) free (script); return 0; } else { CHourGlass (main_window); output = execute_foreground_shell (s, script); CUnHourGlass (main_window); if (!output) { shell_error_dialog (_ (" Shell script "), get_sys_error (_ (" Error trying to pipe script. "))); if (script) free (script); return -1; } } if (script) free (script); script = 0; if (s->options & SHELL_OPTION_CHECK_ERROR_FILE) { if (stat (hme (ERROR_FILE), &st) == 0) { if (st.st_size) { char *error; if (s->options & SHELL_OPTION_DISPLAY_ERROR_FILE) { error = loadfile (hme (ERROR_FILE), 0); if (error) { CTextboxMessageDialog (main_window, 20, 20, 80, 20, catstrs (s->name, _ (" Error "), NULL), error, 0); free (error); } } return -1; } } } if (s->options & SHELL_OPTION_DELETE_BLOCK) if (edit_block_delete_cmd (e)) return 1; if (output) if (*output) for (i = strlen (output) - 1; i >= 0; i--) edit_insert_ahead (e, output[i]); if (output) free (output); if (s->options & SHELL_OPTION_INSERT_TEMP_FILE) if (!edit_insert_file (e, hme (TEMP_FILE))) shell_error_dialog (_ (" Shell script "), get_sys_error (_ (" Error trying to insert temp file. "))); if (s->options & SHELL_OPTION_INSERT_BLOCK_FILE) if (!edit_insert_file (e, hme (BLOCK_FILE))) shell_error_dialog (_ (" Shell script "), get_sys_error (_ (" Error trying to insert block file. "))); if (s->options & SHELL_OPTION_INSERT_CLIP_FILE) if (!edit_insert_file (e, hme (CLIP_FILE))) shell_error_dialog (_ (" Shell script "), get_sys_error (_ (" Error trying to insert clip file. "))); if (s->options & SHELL_OPTION_DISPLAY_ERROR_FILE) if (stat (hme (ERROR_FILE), &st) == 0) if (st.st_size) { /* no error messages */ char *error; error = loadfile (hme (ERROR_FILE), 0); if (error) { CTextboxMessageDialog (main_window, 20, 20, 80, 25, s->name, error, 0); free (error); } } return 0; } int edit_execute_cmd (WEdit * edit, int command, int char_for_insertion); /* Main entry point. Request args if option is set and calls run_shell. Returns 0 on success, -1 on error and 1 on cancel. */ static int run_shell_dialog (WEdit * e, struct shell_cmd *s) { char *cmdline_options; int r; long start_mark, end_mark; if (!s) return -1; if (s->options & SHELL_OPTION_SAVE_BLOCK) if (eval_marks (e, &start_mark, &end_mark)) { shell_error_dialog (_ (" Shell Script "), _ (" Script requires some text to be highlighted. ")); return 1; } if (s->options & SHELL_OPTION_REQUEST_ARGUMENTS) { char *p, *q; p = (char *) strdup (s->name); /* create a name for the input dialog by stripping spaces */ for (q = p; *q; q++) if (*q == ' ') memmove (q, q + 1, strlen (q)); if (strlen (p) > 20) p[20] = 0; cmdline_options = CInputDialog (p, 0, 0, 0, 40 * FONT_MEAN_WIDTH, s->last_options ? s->last_options : "", s->name, s->prompt); free (p); if (!cmdline_options) return 1; if (s->last_options) free (s->last_options); s->last_options = cmdline_options; } else { cmdline_options = ""; } r = run_shell (e, s, cmdline_options, s->menu); CRefreshEditor (e); return r; } static struct shell_cmd *scripts[MAX_NUM_SCRIPTS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* loads from options file */ void load_scripts () { char *s = 0, *p, *q; unsigned char *r; int i = 0, n; memset (&running_shell, 0, sizeof (running_shell)); s = get_options_section (editor_options_file, "[Shell Scripts]"); if (!s) { goto load_default_scripts; } else { if (!*s) goto load_default_scripts; } p = q = s; for (i = 0; i < MAX_NUM_SCRIPTS; i++) { if (!*q || *q == '\n') break; q = strchr (p, '\n'); if (!q) break; *q++ = 0; scripts[i] = CMalloc (sizeof (struct shell_cmd)); memset (scripts[i], 0, sizeof (struct shell_cmd)); strncpy (scripts[i]->name, p, 39); p = q; q = strchr (p, '\n'); *q++ = 0; strncpy (scripts[i]->menu, p, 39); p = q; q = strchr (p, '\n'); *q++ = 0; scripts[i]->menu_hot_key = *p; p = q; q = strchr (p, '\n'); *q++ = 0; scripts[i]->key = (KeySym) atoi (p); p = q; q = strchr (p, '\n'); *q++ = 0; scripts[i]->keyboard_state = atoi (p); p = q; q = strchr (p, '\n'); *q++ = 0; strncpy (scripts[i]->prompt, p, 159); p = q; q = strchr (p, '\n'); *q++ = 0; scripts[i]->options = atoi (p); p = q; q = strchr (p, '\n'); *q++ = 0; scripts[i]->last_options = (char *) strdup (p); p = q; q = strchr (p, '\n'); *q++ = 0; scripts[i]->script = (char *) strdup (p); for (r = (unsigned char *) scripts[i]->script; *r; r++) if (*r == 129) *r = '\n'; p = q; } load_default_scripts: n = sizeof (default_scripts) / sizeof (struct shell_cmd); if (i < n) { for (; i < n; i++) { scripts[i] = CMalloc (sizeof (struct shell_cmd)); memset (scripts[i], 0, sizeof (struct shell_cmd)); memcpy (scripts[i], &default_scripts[i], sizeof (struct shell_cmd)); scripts[i]->script = (char *) strdup (default_scripts[i].script); } } if (s) free (s); } /* saves to options file */ static void save_scripts (void) { char *s, *p; int i = 0, n; p = s = CMalloc (65536); /* make longer if overwrites */ while (scripts[i]) { unsigned char *t, *r; t = (unsigned char *) strdup (scripts[i]->script); for (r = t; *r; r++) if (*r == '\n') *r = 129; /* replace newlines with 129 */ sprintf (p, "%s\n%s\n%c\n%d\n%d\n%s\n%d\n%s\n%s\n%n", scripts[i]->name ? scripts[i]->name : 0, scripts[i]->menu ? scripts[i]->menu : 0, scripts[i]->menu_hot_key ? scripts[i]->menu_hot_key : ' ', (int) scripts[i]->key, (int) scripts[i]->keyboard_state, scripts[i]->prompt ? scripts[i]->prompt : "", (int) scripts[i]->options, scripts[i]->last_options ? scripts[i]->last_options : "", t, &n); free (t); p += n; i++; } *p++ = '\n'; *p = 0; save_options_section (editor_options_file, "[Shell Scripts]", s); free (s); } #define N_ITEMS 4 extern CWidget *edit[]; extern int current_edit; /* straight from the menu */ static void script_menu_callback (unsigned long ignored) { int i; i = (CIdent ("menu.scripts"))->current - N_ITEMS; run_shell_dialog (edit[current_edit]->editor, scripts[i]); } /* this is called from edit_translate_key.c */ int get_script_number_from_key (unsigned int state, KeySym keysym) { int i; for (i = 0; i < MAX_NUM_SCRIPTS; i++) { if (!scripts[i]) break; if (keysym < 256) keysym = my_lower_case (keysym); if (scripts[i]->keyboard_state == state && scripts[i]->key == keysym) return i; } return -1; } /* This is called from the editor: see main.c: edit_set_user_command (execute_script); */ void execute_script (WEdit * e, int i) { run_shell_dialog (e, scripts[i]); } /* Updates updates the menu when a new shell has been added or removed */ void update_script_menu_items () { int i, n; n = (CIdent ("menu.scripts"))->numlines + 2; for (i = N_ITEMS; i < n; i++) CRemoveMenuItemNumber ("menu.scripts", N_ITEMS); for (n = 0; n < MAX_NUM_SCRIPTS; n++) { if (!scripts[n]) break; CAddMenuItem ("menu.scripts", scripts[n]->menu, scripts[n]->menu_hot_key, script_menu_callback, 0); } } #define NUM_OPTS 14 /* Edits a scripts: returns 1 on cancel, 0 on success, -1 on error */ int edit_scripts_dialog (Window parent, int x, int y, struct shell_cmd *s) { int i, r = 0; CState state; struct { char *name; char *toolhint; int flag; CWidget *w; } options[] = { { gettext_noop ("Save block on commence"), gettext_noop ("Save the current highlighted text to %b before executing"), SHELL_OPTION_SAVE_BLOCK, 0 }, { gettext_noop ("Save editor file on commence"), gettext_noop ("Save the entire edit buffer to %f before executing"), SHELL_OPTION_SAVE_EDITOR_FILE, 0 }, { gettext_noop ("Prompt for arguments on commence"), gettext_noop ("Prompt the user for a string to be replaced with %a"), SHELL_OPTION_REQUEST_ARGUMENTS, 0 }, { gettext_noop ("Display script's stdout continuously"), gettext_noop ("Dynamically view the scripts output"), SHELL_OPTION_DISPLAY_STDOUT_CONTINUOUS, 0 }, { gettext_noop ("Display script's stderr continuously"), gettext_noop ("Dynamically view the scripts output"), SHELL_OPTION_DISPLAY_STDERR_CONTINUOUS, 0 }, { gettext_noop ("Run in background"), "", SHELL_OPTION_RUN_IN_BACKGROUND, 0 }, { gettext_noop ("Delete block on commence"), gettext_noop ("Use when you want to replace highlighted text"), SHELL_OPTION_DELETE_BLOCK, 0 }, { gettext_noop ("Insert temp file on completion"), gettext_noop ("Types out file %t's contents on completion of the script"), SHELL_OPTION_INSERT_TEMP_FILE, 0 }, { gettext_noop ("Insert block file on completion"), gettext_noop ("Types out file %b's contents on completion of the script"), SHELL_OPTION_INSERT_BLOCK_FILE, 0 }, { gettext_noop ("Insert clip file on completion"), gettext_noop ("Types out file %c's contents on completion of the script"), SHELL_OPTION_INSERT_CLIP_FILE, 0 }, { gettext_noop ("Insert stdout on completion"), gettext_noop ("Types out the script's output"), SHELL_OPTION_INSERT_STDOUT, 0 }, { gettext_noop ("Insert stderr on completion"), gettext_noop ("Types out the script's output"), SHELL_OPTION_INSERT_STDERR, 0 }, { gettext_noop ("Display error file"), gettext_noop ("Displays %e on completion of the script"), SHELL_OPTION_DISPLAY_ERROR_FILE, 0 }, { gettext_noop ("Subs only if error file is empty"), gettext_noop ("Only replace the highlighted text if %e is empty"), SHELL_OPTION_CHECK_ERROR_FILE, 0 } }; int xs, ys, x2, o, yu; char hot[2] = "\0\0"; Window win; CWidget *w; CEvent cw; hot[0] = s->menu_hot_key; CBackupState (&state); CDisable ("*"); win = CDrawHeadedDialog ("shellopt", parent, x, y, " Script Control "); CGetHintPos (&x, &y); yu = y; CDrawText ("shellopt.namet", win, x, y, " Name : "); CGetHintPos (&x2, 0); CDrawTextInput ("shellopt.name", win, x2, y, FONT_MEAN_WIDTH * 27, AUTO_HEIGHT, 39, s->name); /* Toolhint */ CSetToolHint ("shellopt.namet", _ ("Name to appear in the listbox")); CSetToolHint ("shellopt.name", _ ("Name to appear in the listbox")); CGetHintPos (0, &y); CDrawText ("shellopt.menut", win, x, y, " Menu item : "); CGetHintPos (&x2, 0); CDrawTextInput ("shellopt.menu", win, x2, y, FONT_MEAN_WIDTH * 27, AUTO_HEIGHT, 39, s->menu); /* Toolhint */ CSetToolHint ("shellopt.menut", _ ("Menu item entry with hot-key")); CSetToolHint ("shellopt.menu", _ ("Menu item entry with hot-key")); CGetHintPos (0, &y); CDrawText ("shellopt.hott", win, x, y, " Menu hotkey : "); CGetHintPos (&x2, 0); CDrawTextInput ("shellopt.hot", win, x2, y, FONT_MEAN_WIDTH * 10, AUTO_HEIGHT, 3, hot); /* Toolhint */ CSetToolHint ("shellopt.hott", _ ("Letter to underline")); CSetToolHint ("shellopt.hot", _ ("Letter to underline")); CGetHintPos (0, &y); CDrawText ("shellopt.promptt", win, x, y, " Argument prompt : "); CGetHintPos (&x2, 0); CDrawTextInput ("shellopt.prompt", win, x2, y, FONT_MEAN_WIDTH * 24, AUTO_HEIGHT, 159, s->prompt); /* Toolhint */ CSetToolHint ("shellopt.promptt", _ ("Message for %a prompting")); CSetToolHint ("shellopt.prompt", _ ("Message for %a prompting")); CGetHintPos (0, &y); xs = x; ys = y; o = (32 - (FONT_PIX_PER_LINE + 2 + TEXT_RELIEF * 2)) / 2; for (i = 0; i < NUM_OPTS / 2; i++) { options[i].w = CDrawSwitch (catstrs ("shellopt.", itoa (i), NULL), win, x, y, (s->options & options[i].flag) ? 1 : 0, _ (options[i].name), 0); CSetToolHint (options[i].w->ident, _ (options[i].toolhint)); CSetToolHint (catstrs (options[i].w->ident, ".label", NULL), _ (options[i].toolhint)); CGetHintPos (0, &y); } x = xs + CImageStringWidth (_ (options[3].name)) + 32 + WIDGET_SPACING * 2 + 10; y = ys; for (i = NUM_OPTS / 2; i < NUM_OPTS; i++) { options[i].w = CDrawSwitch (catstrs ("shellopt.", itoa (i), NULL), win, x, y, (s->options & options[i].flag) ? 1 : 0, _ (options[i].name), 0); CSetToolHint (options[i].w->ident, _ (options[i].toolhint)); CSetToolHint (catstrs (options[i].w->ident, ".label", NULL), _ (options[i].toolhint)); CGetHintPos (0, &y); } CPushFont ("editor", 0); w = CDrawEditor ("shellopt.edit", win, xs, y, 77 * FONT_MEAN_WIDTH, 10 * FONT_PIX_PER_LINE, s->script, 0, 0, EDITOR_NO_TEXT | EDITOR_NO_FILE, strlen (s->script)); CPopFont (); /* Toolhint */ CSetToolHint ("shellopt.edit", _ ("Enter your shell script here")); get_hint_limits (&x, &y); x -= WIDGET_SPACING * 2 + TICK_BUTTON_WIDTH * 2; CDrawPixmapButton ("shellopt.ok", win, x, yu, PIXMAP_BUTTON_TICK); /* Toolhint */ CSetToolHint ("shellopt.ok", _ ("Press to accept. You will then be\nprompted for the hot-key combination")); CGetHintPos (&x, 0); CDrawPixmapButton ("shellopt.cancel", win, x, yu, PIXMAP_BUTTON_CROSS); /* Toolhint */ CSetToolHint ("shellopt.cancel", _ ("Abort operation")); CSetSizeHintPos ("shellopt"); CFocus (CIdent ("shellopt.name")); CMapDialog ("shellopt"); while (1) { CNextEvent (0, &cw); if (!CIdent ("shellopt")) /* destroyed by WM close */ break; if (!strcmp (cw.ident, "shellopt.cancel")) { r = 1; break; } if (!strcmp (cw.ident, "shellopt.ok")) { XEvent *p; #ifdef USE_XIM XIC ic = 0; #endif p = CRawkeyQuery ((CIdent ("shellopt.ok"))->mainid, 20, 20, _ (" Script Edit "), _ (" Press the key combination to envoke the script : ")); if (!p) continue; #ifdef USE_XIM ic = CIC; CIC = 0; s->key = CKeySym (p); CIC = ic; #else s->key = CKeySym (p); #endif s->keyboard_state = p->xkey.state; s->options = 0; for (i = 0; i < NUM_OPTS; i++) if (options[i].w->keypressed) s->options |= options[i].flag; strncpy (s->name, (CIdent ("shellopt.name"))->text, 39); strncpy (s->menu, (CIdent ("shellopt.menu"))->text, 39); strncpy (s->prompt, (CIdent ("shellopt.prompt"))->text, 159); s->menu_hot_key = (CIdent ("shellopt.hot"))->text[0]; if (s->script) free (s->script); s->script = edit_get_buffer_as_text (w->editor); break; } } CDestroyWidget ("shellopt"); CRestoreState (&state); return r; } int edit_script (Window parent, int x, int y, int which_script) { return edit_scripts_dialog (parent, x, y, scripts[which_script]); } static char *get_a_line (void *data, int line) { static char t[128]; struct shell_cmd **s; s = data; strcpy (t, s[line]->name); return t; } int script_list_box_dialog (Window parent, int x, int y, const char *heading) { int n; for (n = 0; n < MAX_NUM_SCRIPTS; n++) if (!scripts[n]) break; return CListboxDialog (parent, x, y, 30, 10, heading, 0, 0, n, get_a_line, scripts); } /* straight from menu */ void edit_a_script_cmd (unsigned long ignored) { int i; i = script_list_box_dialog (main_window, 40, 40, _ (" Pick a Script to Edit ")); if (i >= 0) { if (!edit_script (CRoot, 20, 20, i)) save_scripts (); update_script_menu_items (); } } void delete_script (int i) { if (scripts[i]) { if (scripts[i]->script) free (scripts[i]->script); if (scripts[i]->last_options) free (scripts[i]->last_options); free (scripts[i]); scripts[i] = 0; memmove (scripts + i, scripts + i + 1, (MAX_NUM_SCRIPTS - i - 1) * sizeof (struct shell_cmd *)); } } /* called on application shutdown */ void free_all_scripts (void) { while (scripts[0]) delete_script (0); } /* straight from menu */ void delete_a_script_cmd (unsigned long ignored) { int i; i = script_list_box_dialog (main_window, 20, 20, _ (" Pick a Script to Delete ")); if (i >= 0) { delete_script (i); save_scripts (); update_script_menu_items (); } } /* straight from menu */ void new_script_cmd (unsigned long ignored) { int n; for (n = 0; n < MAX_NUM_SCRIPTS; n++) if (!scripts[n]) break; if (n > MAX_NUM_SCRIPTS - 2) { CErrorDialog (0, 0, 0, _ (" New Script "), \ _ (" Max number of scripts has been reached, \n" \ " increase MAX_NUM_SCRIPTS in the file shell.h ")); return; } scripts[n] = CMalloc (sizeof (struct shell_cmd)); memset (scripts[n], 0, sizeof (struct shell_cmd)); scripts[n]->script = (char *) strdup ("#!/bin/sh\n"); if (edit_script (main_window, 20, 20, n)) { free (scripts[n]); scripts[n] = 0; } else save_scripts (); update_script_menu_items (); } static CWidget *CDrawMiniSwitch (const char *identifier, Window parent, int x, int y, int d, int on) { CWidget *w; w = CSetupWidget (identifier, parent, x, y, d, d, C_SWITCH_WIDGET, INPUT_BUTTON, COLOR_FLAT, 1); w->fg = COLOR_BLACK; w->bg = COLOR_FLAT; w->keypressed = on; w->render = render_switch; w->options |= WIDGET_TAKES_FOCUS_RING; return w; } pid_t open_under_pty (int *in, int *out, char *line, const char *file, char *const argv[]); int option_shell_command_line_sticky = 0; int option_shell_command_line_pty = 0; void shell_output_add_job (WEdit * edit, int in, int out, pid_t pid, char *name, int close_on_error); static struct shell_job *get_job (WEdit * edit, int n) { struct shell_job *j; int i; for (i = 0, j = edit->jobs; j && i < n; j = j->next, i++); return j; } static char *list_jobs_get_line (void *data, int line) { struct shell_job *j; j = get_job ((WEdit *) data, line); if (j) return j->name; return ""; } static int list_jobs (void) { struct shell_job *j; int i, c, n; WEdit *e; e = edit[current_edit]->editor; c = max (20, e->num_widget_columns - 5); for (n = 0, j = e->jobs; j; j = j->next, n++); i = CListboxDialog (edit[current_edit]->mainid, 20, 20, c, 10, 0, 0, 0, n, list_jobs_get_line, (void *) e); if (i >= 0) { j = get_job (e, i); if (j) shell_output_kill_job (e, j->pid, 1); } return 0; } void edit_insert_shell_output (WEdit * edit) { char id[33], q[1024], *p; CWidget *w, *v, *i, *b, *h, *c; int tolong = 0, done = 0; CState s; while (!done) { strcpy (id, CIdentOf (edit->widget)); strcat (id, ".text"); w = CIdent (id); if (!w) return; CBackupState (&s); CDisable ("*"); p = getenv ("PWD"); get_current_wd (q, 1023); q[1023] = '\0'; p = q; CPushFont ("widget", 0); while (*p && CImageStringWidth (p) > CWidthOf (w) * 2 / 3 - 20) { tolong = 1; p++; } CPopFont (); h = CDrawButton ("status_button", edit->widget->parentid, CXof (w), CYof (w), CHeightOf (w), CHeightOf (w), 0); CSetToolHint (h->ident, _("Click for list of running jobs.")); b = CDrawMiniSwitch ("status_switch", edit->widget->parentid, CXof (w) + CWidthOf (h), CYof (w), CHeightOf (w), option_shell_command_line_sticky); CSetToolHint (b->ident, _("If not depressed then input line will close on Enter.")); c = CDrawMiniSwitch ("status_switch2", edit->widget->parentid, CXof (w) + CWidthOf (h) + CWidthOf (b), CYof (w), CHeightOf (w), option_shell_command_line_pty); CSetToolHint (c->ident, _("If depressed then opens under a tty.")); v = CDrawText ("status_prompt", edit->widget->parentid, CXof (w) + CWidthOf (b) + CWidthOf (c) + CWidthOf (h), CYof (w), "[%s%s]#", tolong ? "..." : "", p); CSetToolHint (v->ident, _("Current directory. This is the global current directory as set from the Command menu.")); i = CDrawTextInput ("status_input", edit->widget->parentid, CXof (w) + CWidthOf (b) + CWidthOf (c) + CWidthOf (v) + CWidthOf (h), CYof (w), CWidthOf (edit->widget) - CWidthOf (v) - CWidthOf (h) - CWidthOf (b) - CWidthOf (c), AUTO_HEIGHT, 32768, TEXTINPUT_LAST_INPUT); CSetToolHint (i->ident, _ ("Enter shell command. Input will be piped to this command from\nselected text. Output will be inserted at editor cursor.")); CFocus (i); edit->force |= REDRAW_PAGE; edit_render_keypress (edit); edit_push_action (edit, KEY_PRESS + edit->start_display); for (;;) { XEvent xev; CEvent cev; CNextEvent (&xev, &cev); option_shell_command_line_pty = c->keypressed; if (xev.type == KeyPress && cev.command == CK_Enter && cev.kind == C_TEXTINPUT_WIDGET) { char *t; for (t = i->text; *t && isspace (*t); t++); if (!strncmp (t, "cd ", 3)) { for (t = t + 3; *t && isspace (*t); t++); if (change_directory (t) < 0) CErrorDialog (main_window, 20, 20, _(" Change directory "), get_sys_error (_(" Error return from chdir. "))); done = !b->keypressed; } else { int in, out; pid_t pid; char *arg[5]; char line[80]; arg[0] = "/bin/sh"; arg[1] = "-c"; arg[2] = i->text; arg[3] = 0; if (option_shell_command_line_pty) { pid = open_under_pty (&in, &out, line, "/bin/sh", arg); } else { pid = triple_pipe_open (&in, &out, 0, 1, "/bin/sh", arg); } shell_output_add_job (edit, in, out, pid, i->text, !option_shell_command_line_pty); done = !b->keypressed; } break; } if (xev.type == KeyPress && cev.command == CK_Cancel) { done = 1; break; } if (xev.type == ButtonPress && cev.window != v->winid && cev.window != i->winid && cev.window != b->winid && cev.window != c->winid && cev.window != h->winid) { done = 1; break; } if (!strcmp (cev.ident, "status_button")) list_jobs (); } option_shell_command_line_sticky = b->keypressed; CDestroyWidget ("status_prompt"); CDestroyWidget ("status_input"); CDestroyWidget ("status_button"); CDestroyWidget ("status_switch"); CDestroyWidget ("status_switch2"); CRestoreState (&s); } }