/* zterm.c - Zed's Virtual Terminal * Copyright (C) 1998 Michael Zucchi * * A simple terminal program, based on ZTerm. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. Adapted for use in sg by C. Steenberg 23/6/2000 */ /* needed for getopt under 'gcc -ansi -pedantic' on Linux */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_GNOME #include #include #else #include "zvtterm.h" #endif #include "../sg.h" #include "../../config.h" #include "python_command.h" #include "../pixmaps/python_small.xpm" static void sg_term_readdata (gpointer data, gint fd, GdkInputCondition condition); static pid_t child_pid=-1, term_pid=-1; static gboolean child_died_locked = FALSE; static struct sigaction *p_saved_sigchld_action = NULL; gint stdout_pipe[2],stderr_pipe[2],line_pipe[2],com_pipe[2], life_pipe[2], stdout_tag, stderr_tag,line_tag, com_tag, life_tag, term_x,term_y,term_xmin=40,term_ymin=15, sizey_busy=0,restarted=0,term_open=0; PyObject *fo_stdout=NULL,*fo_stderr=NULL; GtkWidget *layout, *hscrollbar, *vscrollbar, *container,*table; GtkWidget *term, *hbox, *scrollbar; gushort *red, *green, *blue; gushort linux_red[] = { 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff, 0x0, 0x0 }; gushort linux_grn[] = { 0x0000, 0x0000, 0xaaaa, 0x5555, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x5555, 0x5555, 0xffff, 0xffff, 0x5555, 0x5555, 0xffff, 0xffff, 0x0, 0x0 }; gushort linux_blu[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, 0x5555, 0x5555, 0x5555, 0x5555, 0xffff, 0xffff, 0xffff, 0xffff, 0x0, 0x0 }; FILE *stdout_fp,*stderr_fp; GArray *fd_array; extern gint py_status; /* 0=normal, 1=old line waiting */ extern PyObject *sys_dict; extern gboolean sg_exiting; #define FONT "-misc-fixed-medium-r-normal--12-200-75-75-c-100-iso8859-1" extern char **environ; static char **env; static char **env_copy; static int winid_pos; static GtkWidget *window = NULL; void child_died(int sig); void hide_window_event (ZvtTerm *term) { gtk_widget_hide(window); } void child_died_event_process (GtkWidget *term,gboolean stop_emit) { gchar buff[10],count=0; struct sigaction sigpipe_action, sigchld_action, tmp_action; if (child_died_locked) return; child_died_locked = TRUE; tmp_action.sa_handler = SIG_IGN; tmp_action.sa_flags = 0; sigemptyset(&tmp_action.sa_mask); sigaction(SIGPIPE, &tmp_action, &sigpipe_action); sigaction(SIGCHLD, &tmp_action, &sigchld_action); term_open=0; py_status=0; if (line_tag) { gdk_input_remove(line_tag); line_tag=0; } if (life_tag) { gdk_input_remove(life_tag); life_tag=0; } if (child_pid>0) { pid_t pid_status; kill(child_pid,SIGKILL); do{ pid_status=waitpid(child_pid,NULL,WNOHANG); } while (pid_status==0); child_pid=-1; } if (term_pid>0) { pid_t pid_status; kill(term_pid,SIGKILL); do{ pid_status=waitpid(term_pid,NULL,WNOHANG); } while (pid_status==0); term_pid=-1; close(stdout_pipe[1]); close(stderr_pipe[1]); } if (fo_stdout) { Py_XDECREF(fo_stdout); fo_stdout=NULL; } if (fo_stderr) { Py_XDECREF(fo_stderr); fo_stderr=NULL; } if (fd_array) g_array_free(fd_array,FALSE); gtk_main_iteration_do(FALSE); fd_array=NULL; if (term && !stop_emit) gtk_signal_emit_stop_by_name (GTK_OBJECT (term), "destroy"); if (window && !stop_emit) gtk_signal_emit_stop_by_name (GTK_OBJECT (window), "destroy"); term_open=FALSE; /* restore signal actions */ sigaction(SIGPIPE, &sigpipe_action, NULL); sigaction(SIGCHLD, &sigchld_action, NULL); } /* Closes the terminal, to be called by outside code */ void term_kill_child (void) { child_died_event_process(term,TRUE); } void child_died_event (GtkWidget *term) { child_died_event_process(term,FALSE); } static void title_changed_event (ZvtTerm *term, VTTITLE_TYPE type, char *newtitle) { switch(type) { case VTTITLE_WINDOW: case VTTITLE_WINDOWICON: gtk_window_set_title (GTK_WINDOW (window), newtitle); break; default: break; } } static int button_press_event (ZvtTerm *term, GdkEventButton *e) { int x,y; GdkModifierType mask; char *data, *str; gdk_window_get_pointer(GTK_WIDGET(term)->window, &x, &y, &mask); str = zvt_term_match_check(term, x/term->charwidth, y/term->charheight,(void *)&data); if (str) { gtk_signal_emit_stop_by_name (GTK_OBJECT (term), "button_press_event"); return TRUE; } return FALSE; } static void set_hints (GtkWidget *widget) { ZvtTerm *term; GdkGeometry hints; GtkWidget *app; g_assert (widget != NULL); term = ZVT_TERM (widget); app = gtk_widget_get_toplevel(widget); g_assert (app != NULL); #define PADDING 2 hints.base_width = (GTK_WIDGET (term)->style->klass->xthickness * 2) + PADDING; hints.base_height = (GTK_WIDGET (term)->style->klass->ythickness * 2); hints.width_inc = term->charwidth; hints.height_inc = term->charheight; hints.min_width = hints.base_width + hints.width_inc; hints.min_height = hints.base_height + hints.height_inc; gtk_window_set_geometry_hints(GTK_WINDOW(app), GTK_WIDGET(term), &hints, GDK_HINT_RESIZE_INC|GDK_HINT_MIN_SIZE|GDK_HINT_BASE_SIZE); } static void sg_term_line_typed (gpointer data, gint fd, GdkInputCondition condition) { gboolean update; gchar buffer[2048],norm[]="R>>> ",cont[]="R... "; gint count, saveerrno,res,wres; ZvtTerm *term; term = (ZvtTerm *) data; saveerrno = 0; while ( (count = read (fd, buffer, 2048)) > 0) { saveerrno = errno; if (count>0 && count<2048) break; } res=python_simple(buffer,count); if (res==2) wres=write(com_pipe[1],cont,strlen(cont)); else wres=write(com_pipe[1],norm,strlen(norm)); if (wres<0){ child_died_locked = FALSE; fprintf(stderr,"Error writing prompt, terminal died.\n"); child_died(0); } return; } void print_raw (GtkWidget * textwidget, unsigned char *text, gint len) { int dotime = FALSE; char num[8]; int reverse = 0, under = 0, bold = 0, comma, k, i = 0, j = 0, maxj = len+1024; unsigned char *newtext = g_new (unsigned char, len+1024); while (i < len) { if (maxj - j < 100) { maxj += 1024; newtext = g_renew (unsigned char, newtext, maxj); } switch (text[i]) { case '\026': if (reverse) { reverse = FALSE; strcpy (&newtext[j], "\033[27m"); } else { reverse = TRUE; strcpy (&newtext[j], "\033[7m"); } j = strlen (newtext); break; case '\037': if (under) { under = FALSE; strcpy (&newtext[j], "\033[24m"); } else { under = TRUE; strcpy (&newtext[j], "\033[4m"); } j = strlen (newtext); break; case '\002': if (bold) { bold = FALSE; strcpy (&newtext[j], "\033[22m"); } else { bold = TRUE; strcpy (&newtext[j], "\033[1m"); } j = strlen (newtext); break; case '\017': strcpy (&newtext[j], "\033[0m"); j = strlen (newtext); reverse = FALSE; bold = FALSE; under = FALSE; break; case '\n': newtext[j++] = '\r'; default: newtext[j] = text[i]; j++; } i++; } newtext[j] = 0; zvt_term_feed ((ZvtTerm *) textwidget, newtext, j); g_free (newtext); } static void sg_term_write (gpointer data, gint fd, GdkInputCondition condition) { gchar buffer[2048]; gint count, saveerrno,*fd2; fd2=(gint *)data; saveerrno = EAGAIN; while (1) { count = read (fd, buffer, 2048); if (count<=0) break; saveerrno = errno; write (fd2[1], buffer, count); } } static void sg_term_readdata (gpointer data, gint fd, GdkInputCondition condition) { fd_set read_set; gchar buffer[4096]; gint count, saveerrno; ZvtTerm *term; struct timeval tv; if (fd!=life_pipe[0]) return; term = (ZvtTerm *) data; saveerrno = EAGAIN; while (1){ tv.tv_sec=0; tv.tv_usec=100000; FD_SET(fd,&read_set); select(fd+1,&read_set,NULL,NULL,&tv); if (FD_ISSET(fd,&read_set)) { count = read (fd, buffer, 4096); saveerrno = errno; if (count>0) print_raw (GTK_WIDGET(term), buffer, count); if (count>0 && count<4096) break; } else break; } } static void sg_term_life (gpointer data, gint fd, GdkInputCondition condition) { gchar buffer[10]; gint count, saveerrno; ZvtTerm *term; saveerrno = EAGAIN; while ( (count = read (fd, buffer, 10)) > 0) { saveerrno = errno; switch (buffer[0]) { case 'T': /* Terminal died */ waitpid(term_pid,NULL,0); break; default: break; } if (count<10) break; } } static void parent_died(int sig) { close(stdout_pipe[0]); close(stderr_pipe[0]); close(life_pipe[1]); _exit(0); } static void buffer_process(void) { fd_set read_set,write_set; gint fd_max=0,i,fd,num,tnum; gchar buffer[2048],*temp; pipe(life_pipe); fcntl(life_pipe[0],F_SETFL,O_NONBLOCK); fcntl(life_pipe[1],F_SETFL,O_NONBLOCK); term_pid=fork(); switch (term_pid) { case -1: perror("Can't fork the buffer process"); return; case 0: /* the child */ { GPtrArray *buffer_array; GArray *len,*pos; gint sel=0; struct timeval tv; struct sigaction action; action.sa_flags = SA_RESTART; sigemptyset(&action.sa_mask); action.sa_handler = parent_died; sigaction(SIGPIPE,&action,NULL); sigaction(SIGHUP,&action,NULL); action.sa_handler = SIG_IGN; sigaction(SIGINT,&action,NULL); close(stdout_pipe[1]); close(stderr_pipe[1]); close(life_pipe[0]); buffer_array=g_ptr_array_new(); len=g_array_new(FALSE,FALSE,sizeof(gint)); pos=g_array_new(FALSE,FALSE,sizeof(gint)); while (1) { fd_max=-1; FD_ZERO(&read_set); for (i=0;ilen;i++) { fd=g_array_index(fd_array,gint,i); FD_SET(fd,&read_set); if (fd>fd_max) fd_max=fd; } if (buffer_array->len) { tv.tv_sec=0; tv.tv_usec=0; sel=select(fd_max+1,&read_set,NULL,NULL,&tv); } else { tv.tv_sec=2; tv.tv_usec=0; sel=select(fd_max+1,&read_set,NULL,NULL,&tv); } if (sel>=0) for (i=0;ilen;i++) { fd=g_array_index(fd_array,gint,i); if (FD_ISSET(fd,&read_set)) while (1) { num=read(fd,buffer,2048); if (num>0) { buffer[num]='\0'; g_ptr_array_add(buffer_array,strdup(buffer)); g_array_append_val(len,num); tnum=0; g_array_append_val(pos,tnum); } if (num<2048) break; } } else { for (i=0;ilen;i++) { fd=g_array_index(fd_array,gint,i); close(fd); } _exit(0); } FD_SET(life_pipe[1],&write_set); tv.tv_sec=0; tv.tv_usec=0; select(life_pipe[1]+1,NULL,&write_set,NULL,&tv); if (FD_ISSET(life_pipe[1],&write_set)) while (buffer_array->len) { temp=(gchar *)g_ptr_array_index(buffer_array,0); temp+=g_array_index(pos,gint,0); num=write(life_pipe[1],temp,g_array_index(len,gint,0)- g_array_index(pos,gint,0)); if (num>0) { gint ti; ti=g_array_index(pos,gint,0)+num; g_array_remove_index(pos,0); g_array_prepend_val(pos,ti); } if (g_array_index(pos,gint,0)>=g_array_index(len,gint,0)) { g_array_remove_index(pos,0); g_array_remove_index(len,0); g_free(g_ptr_array_index(buffer_array,0)); g_ptr_array_remove_index(buffer_array,0); } else break; } } _exit(0); } default: /* the parent */ /* wait for child process to be forked */ while (kill(term_pid,0)<0) usleep(1000); life_tag=gdk_input_add(life_pipe[0], GDK_INPUT_READ, sg_term_readdata, term); break; } } /* create_child: forks off a new process and handles input via readline on * the newly created terminal. Todo: create python command completion code. */ int create_child(void) { gchar buffer [256],buf2[2048],*line; gint len,i,mychar; ZvtTerm *g_term; PyObject *fo; g_term=ZVT_TERM(term); /* Open some pipes */ pipe(line_pipe); /* For the child to tell the parent what line was typed */ fcntl(line_pipe[0],F_SETFL,O_NONBLOCK); fcntl(line_pipe[1],F_SETFL,O_NONBLOCK); pipe(com_pipe); /* For the parent to tell child what is what */ fcntl(com_pipe[1],F_SETFL,O_NONBLOCK); pipe(stdout_pipe); pipe(stderr_pipe); child_pid=vt_forkpty(&g_term->vx->vt,FALSE); switch (child_pid) { case -1: perror ("Error: unable to fork"); return 0; case 0: /* the child */ {struct sigaction action; action.sa_flags = SA_RESTART; sigemptyset(&action.sa_mask); action.sa_handler = parent_died; sigaction(SIGPIPE,&action,NULL); sigaction(SIGHUP,&action,NULL); action.sa_handler = SIG_IGN; sigaction(SIGINT,&action,NULL); close(line_pipe[0]); /* Close read end */ close(com_pipe[1]); /* Close write end */ rl_reset_terminal("vt100"); rl_initialize(); rl_filename_completion_desired=0; rl_bind_key('\t',rl_insert); #ifdef READLINE_4 rl_catch_signals=0; rl_free_line_state(); #endif close(stdout_pipe[0]); close(stderr_pipe[0]); close(1); dup2(stdout_pipe[1],1); close(2); dup2(stderr_pipe[1],2); for (;;) { len=0; i=0; do { len=read(com_pipe[0],buffer,256); if (i>10) { write(line_pipe[1], "\n", 1); usleep(100000); i=0; } } while (len<=0); if (buffer[0]=='R') /* Only action defined so far */ { buffer[len]= '\0'; line=0; line=readline(buffer+1); if (line) { if (*line) add_history(line); len= strlen(line); if (len) write(line_pipe[1], line, len); else write(line_pipe[1], "\n", 1); free(line); } else write(line_pipe[1], "\n", 1); } if (buffer[0]=='K') break; } _exit(0); } default: /* the parent */ {struct sigaction action; if (!p_saved_sigchld_action) p_saved_sigchld_action = g_new(struct sigaction, 1); action.sa_handler = child_died; action.sa_flags = SA_RESTART; sigemptyset(&action.sa_mask); sigaction(SIGCHLD,&action,p_saved_sigchld_action); /* wait for child process to be forked */ while (kill(child_pid,0)<0) usleep(1000); fd_array= g_array_new (FALSE, FALSE, sizeof (gint)); stdout_fp=fdopen(stdout_pipe[1],"w"); setvbuf(stdout_fp,NULL,_IONBF,0); fo_stdout=PyFile_FromFile (stdout_fp, "", "w", NULL); PyDict_SetItemString (sys_dict, "stdout", fo_stdout); stdout_fp=fdopen(stdout_pipe[1],"w"); setvbuf(stdout_fp,NULL,_IONBF,0); fo_stderr=PyFile_FromFile (stdout_fp, "", "w", NULL); PyDict_SetItemString (sys_dict, "stderr", fo_stderr); g_array_append_val(fd_array,stdout_pipe[0]); line_tag=gdk_input_add(line_pipe[0], GDK_INPUT_READ, sg_term_line_typed, term); close(line_pipe[1]); /* Close write end */ close(com_pipe[0]); /* Close read end */ buffer_process(); write(com_pipe[1],"R>>> ",5); close(stdout_pipe[0]); close(stderr_pipe[0]); break; } } } void child_died(int sig) { if (child_died_locked) return; child_died_locked = TRUE; if (life_tag) { gdk_input_remove(life_tag); life_tag=0; } if (line_tag) { gdk_input_remove(line_tag); line_tag=0; } close(com_pipe[1]); close(stdout_pipe[0]); close(stderr_pipe[0]); close(line_pipe[0]); if (child_pid>0) { pid_t pid_status; kill(child_pid,SIGKILL); do{ pid_status=waitpid(child_pid,NULL,WNOHANG); } while (pid_status==0); child_pid=-1; } if (term_pid>0) { pid_t pid_status; kill(term_pid,SIGKILL); do{ pid_status=waitpid(term_pid,NULL,WNOHANG); } while (pid_status==0); term_pid=-1; } if (fo_stdout) { Py_XDECREF(fo_stdout); fo_stdout=NULL; } if (fo_stderr) { Py_XDECREF(fo_stderr); fo_stderr=NULL; } if (fd_array) g_array_free(fd_array,FALSE); fd_array=NULL; if (GTK_IS_WIDGET(window)) { gtk_widget_destroy(GTK_WIDGET(window)); window=NULL; } term_open=FALSE; /* restore the signal handlers in case the parent has called * then call the restored signal handler * hope it works this way - Rob. */ if (sig == SIGCHLD && p_saved_sigchld_action != NULL) {sigaction(SIGCHLD,p_saved_sigchld_action,NULL); raise(SIGCHLD); } /* unlock */ child_died_locked = FALSE; } /* Do setup, initialises windows, forks child. */ gint create_python_term(GtkWidget *widget, gpointer data) { int i, c, cmdindex, scrollbacklines, login_shell; char **p, *fontname=FONT; GdkPixmap *pixmap; GdkBitmap *mask; GdkGC *fg_gc,*bg_gc,*scroll_gc; GdkColor *fg_color,*bg_color; PyObject *fo; int argc=1; char *argv[]={"sga"},buf2[4096]; enum { RIGHT, LEFT } scrollpos = RIGHT; if (term_open) { gtk_widget_show(window); return TRUE; } else term_open=1; login_shell = 0; cmdindex = 0; scrollbacklines = 2000; /* Create widgets and set options */ #ifdef WITH_GNOME window=gnome_app_new (PACKAGE,"Python terminal (SciGraphica)"); #else window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Python terminal (SciGraphica)"); gtk_window_set_wmclass (GTK_WINDOW (window), "zterm", "zterm"); #endif /* create hbox */ hbox = gtk_hbox_new (FALSE, 0); gtk_box_set_spacing (GTK_BOX (hbox), 2); gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); #ifdef WITH_GNOME gnome_app_set_contents(GNOME_APP(window),hbox); #else gtk_container_add (GTK_CONTAINER (window), hbox); #endif gtk_widget_show (hbox); /* create terminal */ term = zvt_term_new_with_size(80,24); zvt_term_set_font_name(ZVT_TERM (term), fontname); zvt_term_set_blink (ZVT_TERM (term), TRUE); zvt_term_set_bell (ZVT_TERM (term), TRUE); zvt_term_set_scrollback(ZVT_TERM (term), scrollbacklines); zvt_term_set_scroll_on_keystroke (ZVT_TERM (term), TRUE); zvt_term_set_scroll_on_output (ZVT_TERM (term), FALSE); zvt_term_set_background (ZVT_TERM (term), NULL, 0, 0); zvt_term_set_wordclass (ZVT_TERM (term), "-A-Za-z0-9/_:.,?+%="); /* ZVT_TERM(term)->fore_gc=fg_gc; ZVT_TERM(term)->back_gc=bg_gc; ZVT_TERM(term)->scroll_gc=bg_gc;*/ gtk_signal_connect ( GTK_OBJECT (term), "button_press_event", GTK_SIGNAL_FUNC (button_press_event), NULL); gtk_signal_connect ( GTK_OBJECT (term), "child_died", GTK_SIGNAL_FUNC (child_died_event), NULL); gtk_signal_connect ( GTK_OBJECT (term), "delete_event", GTK_SIGNAL_FUNC (child_died_event), NULL); gtk_signal_connect ( GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (child_died_event), NULL); gtk_signal_connect ( GTK_OBJECT (term), "title_changed", GTK_SIGNAL_FUNC (title_changed_event), NULL); gtk_signal_connect_after ( GTK_OBJECT (term), "realize", GTK_SIGNAL_FUNC (set_hints), term); /* scrollbar */ scrollbar = gtk_vscrollbar_new (GTK_ADJUSTMENT (ZVT_TERM (term)->adjustment)); GTK_WIDGET_UNSET_FLAGS (scrollbar, GTK_CAN_FOCUS); if (scrollpos == LEFT) { gtk_box_pack_start (GTK_BOX (hbox), scrollbar, FALSE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), term, 1, 1, 0); } else { gtk_box_pack_start (GTK_BOX (hbox), term, 1, 1, 0); gtk_box_pack_start (GTK_BOX (hbox), scrollbar, FALSE, TRUE, 0); } gtk_widget_show (scrollbar); linux_red [16] = linux_red [9]; linux_blu [16] = linux_blu [12]; linux_grn [16] = linux_grn [10]; linux_red [17] = linux_red [0]; linux_blu [17] = linux_blu [0]; linux_grn [17] = linux_grn [0]; gtk_widget_realize (window); /* This causes segfaults on 256 bit displays */ /* zvt_term_set_color_scheme (ZVT_TERM(term), linux_red, linux_grn, linux_blu);*/ zvt_term_set_default_color_scheme(ZVT_TERM(term)); gtk_widget_show (term); /* show them all! */ pixmap = gdk_pixmap_colormap_create_from_xpm_d(NULL, gdk_colormap_get_system(), &mask, NULL, python_small_xpm); gdk_window_set_icon(window->window, NULL, pixmap, mask); gtk_widget_show_all (window); create_child(); /* unlock */ child_died_locked = FALSE; return TRUE; }