>The zterm application

The zterm application

The complete listing for a simple, but fully functional terminal program is explained in this section.


The annotated application

First, the pre-amble and headers

/*  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.
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkprivate.h>
#include <gdk/gdkkeysyms.h>

#include <zvt/zvtterm.h>
	

Yes, you should always remember the license! Of course, i'm rather fond of GNU's version ...

#define FONT "-misc-fixed-medium-r-normal--12-200-75-75-c-100-iso8859-1"
	

Here we have the font we're using. This is the full name of "fixed". A nice readable fixed-width, and small font.

extern char      **environ;		
static char      **env;
static char      **env_copy;
static int         winid_pos;
static GtkWidget  *window = NULL;
	

A few globals needed elsewhere. Including the environment setup.

static void
child_died_event (ZvtTerm *term)
{
	gtk_exit(0);
}
	

This is later setup as the child_died signal handler. In the case of a terminal we just terminate the whole application when this happens.

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;
    }
}
	

Another signal that may be generated by the widget is the title_changed signal. This signal is generated when the xterm escape sequence to set a title is processed. Here we setup a simple handler that will change the window title when this sequence is caught. By default no action is taken.

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);
}
	

Here we have the same code as explained in the section called Window Hints, for setting appropriate window-resize hints.

The main program follows:

gint 
main (gint argc, gchar *argv[])
{
  int i, c, cmdindex, scrollbacklines, login_shell;
  char buffer[60], **p;
  struct passwd *pw;
  GtkWidget *term, *hbox, *scrollbar;
  enum { RIGHT, LEFT } scrollpos = LEFT;
  
  login_shell = 0;
  cmdindex = 0;
  scrollbacklines = 50;
	

The next section sets up the environment as explained elsewhere. We setup the TERM environment and drop the terminal size environment, and add the COLORTERM value, to enable colour output for specific applications.

We also setup a placeholder for the WINDOWID environment when it is known later on. This is used by some X-aware terminal applications to manipulate the terminal window.

  /* set up terminal environment */
  env = environ;

  for (p = env; *p; p++);
    i = p - env;
  env_copy = (char **) g_malloc (sizeof (char *) * (i + 3));
  for (i = 0, p = env; *p; p++) {
    if (strncmp (*p, "TERM=", 5) == 0) {
      env_copy [i++] = "TERM=xterm";
    } else if ((strncmp (*p, "COLUMNS=", 8) == 0) ||
	       (strncmp (*p, "LINES=", 6) == 0)) {
      continue;
    } else {
      env_copy [i++] = *p;
    }
  }

  env_copy [i++] = "COLORTERM=zterm";
  winid_pos = i++;
  env_copy [winid_pos] = "TEST";
  env_copy [i] = NULL;
	  
  gtk_init(&argc, &argv);
	

Process the command line options. Note the saving of the position of the '-e' option and the termination of processing once it has been found. This is a compatability with the 'xterm' '-e' command line option.

  /* process arguments */
  while ( (cmdindex==0) && (c=getopt(argc, argv, "le:s:rh")) != EOF ) {
    switch(c)  {
      case 'e':
      cmdindex = optind-1;	/* index of argv array to pass to exec */
      break;
      
      case 's':
      scrollbacklines = atoi(optarg);
      break;
      
      case 'l':
      login_shell = 1;
      break;
      
      case 'r':
      scrollpos = RIGHT;
      break;
      
      case '?':
      case 'h':
      default:
      fprintf(stderr, "Usage: zterm [-sNN] [-l] [-r] [-e command args]\n");
      exit(1);
      break;
    }
  }
	

Now we are ready to create our application widgets ...

  /* Create widgets and set options */
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "zterm");
  gtk_window_set_wmclass (GTK_WINDOW (window), "zterm", "zterm");
  gtk_widget_realize (window);
  
  /* 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);
  gtk_container_add (GTK_CONTAINER (window), hbox);
  gtk_widget_show (hbox);

	

Here we create the terminal widget, and setup a number of options, as covered in the section called Terminal properties in the chapter called ZVT Terminal Widget. We setup a basic set of options which are much the same as the defaults (but i also like a blinking cursor!).

  /* create terminal */
  term = zvt_term_new_with_size(80,24);
  zvt_term_set_font_name(ZVT_TERM (term), FONT);
  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/_:.,?+%=");

	

Attatch the signal handlers we defined above. We also attach the destroy signal to the exit handling event - if the window is closed by the window manager then this catches that event. Otherwise the close button doesn't work!

  gtk_signal_connect (GTK_OBJECT (term),
		      "child_died",
		      GTK_SIGNAL_FUNC (child_died_event),
		      NULL);
  
  gtk_signal_connect (GTK_OBJECT (term),
		      "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);

  gtk_widget_show (term);

	

And here we show how the scrollbar is attached to the term adjustment designed for this purpose, and how the scrollbar has its focus disabled. We also give the user the option for a left or right scrollbar.

Although many perfer the windows/motif convention of a right-mounted scrollbar, but a left-mounted scrollbar should be an option if possible, as it is often a much more practical position to have it in. Particularly if there are overlapping windows, or windows partly off-screen.

  /* 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);
  
  /* show them all! */
  gtk_widget_show (window);

	

Here we show how the sub-shell is created using zvt_term_forkpty(). We also give the user the option of logging the session in utmp/wtmp. This probably isn't that necessary for a non-shell application, but might be useful.

It also shows how either a shell is executed, or the command given on the command line is executed. And how the login shell arguments are processed. Again this is not normally required for a non-shell application.

  /* fork the shell/program */
  switch (zvt_term_forkpty(ZVT_TERM (term), ZVT_TERM_DO_UTMP_LOG |  ZVT_TERM_DO_WTMP_LOG)) {
    case -1:
    perror("ERROR: unable to fork:");
    exit(1);
    break;
    
    case 0:
    if (cmdindex) {
      environ = env_copy;
      execvp(argv[cmdindex], &argv[cmdindex]);
    } else {
      GString *shell, *name;
      
      /* get shell from passwd */
      pw = getpwuid(getuid());
      if (pw) {
	shell = g_string_new(pw->pw_shell);
	if (login_shell)
	  name = g_string_new("-");
	else
	  name = g_string_new("");
	
	g_string_append(name, strrchr(pw->pw_shell, '/'));
      } else {
	shell = g_string_new("/bin/sh");
	if (login_shell)
	  name = g_string_new("-sh");
	else
	  name = g_string_new("sh");
      }

      execle (shell->str, name->str, NULL, env_copy);
      perror ("Could not exec\n");
      _exit (127);
    }
    perror("ERROR: Cannot exec command:");
    exit(1);

    default:
    break;
  }
	

And thats it! We're ready to go into the main processing loop!

  /* main loop */
  gtk_main ();
  gtk_exit(0);
  return 0;
}
	

Further examples

This example is basically the 'zterm' test-application which is in the source code tree under gnome-libs/zvt/zterm.c. The gnome-terminal application (in gnome-core/gnome-terminal) provides a much richer set of features. It exercises all configurable options of the terminal emulator as well.

Crescendo and xchat are two other examples that use the widget in different ways. Crescendo runs a subordinate TinyFuge mud client, and xchat uses it as a color output device.