/*  zvtterm.c - Zed's Virtual Terminal
 *  Copyright (C) 1998  Michael Zucchi
 *
 *  The zvtterm widget.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <config.h>

/* please use K&R style indenting if you make changes.
   2, 4, or 8 character tabs are fine - MPZ */

/* need for 'gcc -ansi -pedantic' under GNU/Linux */
#ifndef _POSIX_SOURCE
#  define _POSIX_SOURCE 1
#endif
#include <sys/types.h>

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>

#include "zvtterm.h"

#include <gdk/gdkx.h>
#include <gdk_imlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/Xos.h>


/* define to 'x' to enable copious debug output */
#define d(x)

/* default font */
#define DEFAULT_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso8859-1"

#define PADDING 2

/* forward declararations */
static void zvt_term_init (ZvtTerm *term);
static void zvt_term_class_init (ZvtTermClass *class);
static void zvt_term_init (ZvtTerm *term);
static void zvt_term_destroy (GtkObject *object);
static void zvt_term_realize (GtkWidget *widget);
static void zvt_term_unrealize (GtkWidget *widget);
static void zvt_term_map (GtkWidget *widget);
static void zvt_term_unmap (GtkWidget *widget);
static void zvt_term_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void zvt_term_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
static gint zvt_term_expose (GtkWidget *widget, GdkEventExpose *event);
static void zvt_term_draw (GtkWidget *widget, GdkRectangle *area);
static gint zvt_term_button_press (GtkWidget *widget, GdkEventButton *event);
static gint zvt_term_button_release (GtkWidget *widget, GdkEventButton *event);
static gint zvt_term_motion_notify (GtkWidget *widget, GdkEventMotion *event);
static gint zvt_term_key_press (GtkWidget *widget, GdkEventKey *event);
static gint zvt_term_focus_in (GtkWidget *widget, GdkEventFocus *event);
static gint zvt_term_focus_out (GtkWidget *widget, GdkEventFocus *event);
static void zvt_term_selection_received (GtkWidget *widget, 
					 GtkSelectionData *selection_data, 
					 guint time);
static gint zvt_term_selection_clear (GtkWidget *widget, GdkEventSelection *event);
static void zvt_term_selection_get (GtkWidget *widget,
				    GtkSelectionData *selection_data_ptr, 
				    guint info, 
				    guint time);
static void zvt_term_child_died (ZvtTerm *term);
static void zvt_term_title_changed (ZvtTerm *term, VTTITLE_TYPE type, char *str);
static void zvt_term_title_changed_raise (void *user_data, VTTITLE_TYPE type, char *str);
static gint zvt_term_cursor_blink (gpointer data);
static void zvt_term_scrollbar_moved (GtkAdjustment *adj, GtkWidget *widget);
static void zvt_term_readdata (gpointer data, gint fd, GdkInputCondition condition);
static void zvt_term_readmsg (gpointer data, gint fd, GdkInputCondition condition);
static void zvt_term_fix_scrollbar (ZvtTerm *term);
static void vtx_unrender_selection (struct _vtx *vx);
static void zvt_term_scroll (ZvtTerm *term, int n);
static void zvt_term_scroll_by_lines (ZvtTerm *term, int n);
static int vt_cursor_state(void *user_data, int state);

/* callbacks from update layer */
void vt_draw_text(void *user_data, struct vt_line *line, int row, int col, int len, int attr);
void vt_scroll_area(void *user_data, int firstrow, int count, int offset, int fill);

/* transparent terminal prototypes */
static Window     get_desktop_window (Window the_window);
static Pixmap     get_pixmap_prop (Window the_window, char *prop_id);
static GdkPixmap *load_pixmap_back (char *file, int shaded);
static GdkPixmap *create_shaded_pixmap (Pixmap p, int x, int y, int w, int h);
static gint       safe_strcmp(gchar *a, gchar *b);

/* load the "current" background, from file or from system */
static void       load_background (ZvtTerm *term);


/* static data */

/* The first 16 values are the ansi colors, the last
 * two are the default foreground and default background
 */
static gushort default_red[] = 
{0x0000,0xaaaa,0x0000,0xaaaa,0x0000,0xaaaa,0x0000,0xaaaa,
 0x5555,0xffff,0x5555,0xffff,0x5555,0xffff,0x5555,0xffff,
 0x0000, 0xffff};

static gushort default_grn[] = 
{0x0000,0x0000,0xaaaa,0x5555,0x0000,0x0000,0xaaaa,0xaaaa,
 0x5555,0x5555,0xffff,0xffff,0x5555,0x5555,0xffff,0xffff,
 0x0000, 0xffff};

static gushort default_blu[] = 
{0x0000,0x0000,0x0000,0x0000,0xaaaa,0xaaaa,0xaaaa,0xaaaa,
 0x5555,0x5555,0x5555,0x5555,0xffff,0xffff,0xffff,0xffff,
 0x0000, 0xffff};


/* GTK signals */
enum 
{
  CHILD_DIED,
  TITLE_CHANGED,
  LAST_SIGNAL
};
static guint term_signals[LAST_SIGNAL] = { 0 };


/* GTK parent class */
static GtkWidgetClass *parent_class = NULL;


guint
zvt_term_get_type (void)
{
  static guint term_type = 0;
  
  if (!term_type) {
      GtkTypeInfo term_info = {
	"ZvtTerm",
	sizeof (ZvtTerm),
	sizeof (ZvtTermClass),
	(GtkClassInitFunc) zvt_term_class_init,
	(GtkObjectInitFunc) zvt_term_init,
	(GtkArgSetFunc) NULL,
	(GtkArgGetFunc) NULL,
      };
      
      term_type = gtk_type_unique (gtk_widget_get_type (), &term_info);
    }

  return term_type;
}


static void
zvt_term_class_init (ZvtTermClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  ZvtTermClass *term_class;

  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;
  term_class = (ZvtTermClass*) class;

  parent_class = gtk_type_class (gtk_widget_get_type ());

  term_signals[CHILD_DIED] =
    gtk_signal_new ("child_died",
                    GTK_RUN_FIRST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (ZvtTermClass, child_died),
                    gtk_signal_default_marshaller,
                    GTK_TYPE_NONE, 0);

  term_signals[TITLE_CHANGED] =
    gtk_signal_new ("title_changed",
                    GTK_RUN_FIRST,
                    object_class->type,
                    GTK_SIGNAL_OFFSET (ZvtTermClass, child_died),
                    gtk_marshal_NONE__INT_POINTER,
                    GTK_TYPE_NONE, 2,
		    GTK_TYPE_INT,
		    GTK_TYPE_STRING);

  gtk_object_class_add_signals (object_class, term_signals, LAST_SIGNAL);

  object_class->destroy = zvt_term_destroy;

  widget_class->realize = zvt_term_realize;
  widget_class->unrealize = zvt_term_unrealize;
  widget_class->map = zvt_term_map;
  widget_class->unmap = zvt_term_unmap;
  widget_class->draw = zvt_term_draw;
  widget_class->expose_event = zvt_term_expose;
  widget_class->focus_in_event = zvt_term_focus_in;
  widget_class->focus_out_event = zvt_term_focus_out;
  widget_class->size_request = zvt_term_size_request;
  widget_class->size_allocate = zvt_term_size_allocate;
  widget_class->key_press_event = zvt_term_key_press;
  widget_class->button_press_event = zvt_term_button_press;
  widget_class->button_release_event = zvt_term_button_release;
  widget_class->motion_notify_event = zvt_term_motion_notify;

  widget_class->selection_clear_event = zvt_term_selection_clear;
  widget_class->selection_received = zvt_term_selection_received;
  widget_class->selection_get = zvt_term_selection_get;

  term_class->child_died = zvt_term_child_died;
  term_class->title_changed = zvt_term_title_changed;
}


static void
zvt_term_init (ZvtTerm *term)
{
  struct _zvtprivate *zp;

  GTK_WIDGET_SET_FLAGS (term, GTK_CAN_FOCUS);

  /* create and configure callbacks for the teminal back-end */
  term->vx = vtx_new (80, 24, term);
  term->vx->vt.ring_my_bell = zvt_term_bell;
  term->vx->vt.change_my_name = zvt_term_title_changed_raise;

  /* set rendering callbacks */
  term->vx->draw_text = vt_draw_text;
  term->vx->scroll_area = vt_scroll_area;
  term->vx->cursor_state = vt_cursor_state;

  term->shadow_type = GTK_SHADOW_NONE;
  term->term_window = NULL;
  term->cursor_bar = NULL;
  term->cursor_dot = NULL;
  term->cursor_current = NULL;
  term->font = NULL;
  term->font_bold = NULL;
  term->scroll_gc = NULL;
  term->fore_gc = NULL;
  term->back_gc = NULL;
  term->fore_last = 0;
  term->back_last = 0;
  term->color_ctx = 0;
  term->ic = NULL;

  /* background pixmap */
  term->pixmap_filename = NULL;
  term->background.pix = NULL;
  term->background.x = 0;
  term->background.y = 0;
  term->background.w = 0;
  term->background.h = 0;

  /* grid sizing */
  term->set_grid_size_pending = TRUE;
  term->grid_width = term->vx->vt.width;
  term->grid_height = term->vx->vt.height;

  /* input handlers */
  term->input_id = -1;
  term->msg_id = -1;
  term->timeout_id = -1;

  /* bitfield flags */
  term->cursor_on = 0;
  term->cursor_filled = 1;
  term->cursor_blink_state = 0;
  term->scroll_on_keystroke = 0;
  term->scroll_on_output = 0;
  term->blink_enabled = 1;
  term->ic = NULL;
  term->in_expose = 0;
  term->transparent = 0;
  term->shaded = 0;

  /* private data - set before calling functions */
  zp = g_malloc(sizeof(*zp));
  if (zp) {
    zp->scrollselect_id = -1;
    zp->text_expand = 0;
    zp->text_expandlen = 0;
    zp->scroll_position = 0;
    zp->fonttype=0;
    zp->default_char=0;
    zp->bold_save = 0;
    zp->transpix = 0;
  }
  gtk_object_set_data(GTK_OBJECT (term), "_zvtprivate", zp);

  /* charwidth, charheight set here */
  zvt_term_set_font_name (term, DEFAULT_FONT);

  /* scrollback position adjustment */
  term->adjustment =
    GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
  gtk_object_ref (GTK_OBJECT (term->adjustment));
  gtk_object_sink (GTK_OBJECT (term->adjustment));

  gtk_signal_connect (
      GTK_OBJECT (term->adjustment),
      "value_changed",
      GTK_SIGNAL_FUNC (zvt_term_scrollbar_moved),
      term);

  /* selection received */
  gtk_selection_add_target (
      GTK_WIDGET (term),
      GDK_SELECTION_PRIMARY,
      GDK_SELECTION_TYPE_STRING, 
      0);
#ifdef ZVT_UTF
  gtk_selection_add_target (
      GTK_WIDGET (term),
      GDK_SELECTION_PRIMARY,
      gdk_atom_intern ("UTF-8", FALSE),
      1);
#endif

}

/**
 * zvt_term_set_blink:
 * @term: A &ZvtTerm widget.
 * @state: The blinking state.  If %TRUE, the cursor will blink.
 *
 * Use this to control the way the cursor is displayed (blinking/solid)
 */
void
zvt_term_set_blink (ZvtTerm *term, int state)
{
  g_return_if_fail (term != NULL);                     
  g_return_if_fail (ZVT_IS_TERM (term));

  if (!(term->blink_enabled ^ (state?1:0)))
    return;

  if (term->blink_enabled) {
    if (term->timeout_id != -1){
      gtk_timeout_remove (term->timeout_id);
      term->timeout_id = -1;
    }
    
    if (GTK_WIDGET_REALIZED (term))
      vt_cursor_state (GTK_WIDGET (term), 1);
    
    term->blink_enabled = 0;
  } else {
    term->timeout_id = gtk_timeout_add (500, zvt_term_cursor_blink, term);
    term->blink_enabled = 1;
  }
}

/**
 * zvt_term_set_scroll_on_keystroke:
 * @term: A &ZvtTerm widget.
 * @state: Desired state.
 * 
 * If @state is %TRUE, forces the terminal to jump out of the
 * scrollback buffer whenever a keypress is received.
 **/
void 
zvt_term_set_scroll_on_keystroke(ZvtTerm *term, int state)
{
  term->scroll_on_keystroke = (state != 0);
}

/**
 * zvt_term_set_scroll_on_output:
 * @term: A &ZvtTerm widget.
 * @state: Desired state.
 *
 * If @state is %TRUE, forces the terminal to scroll on output
 * being generated by a child process or by zvt_term_feed().
 */
void 
zvt_term_set_scroll_on_output   (ZvtTerm *term, int state)
{
  g_return_if_fail (term != NULL);                     
  g_return_if_fail (ZVT_IS_TERM (term));

  term->scroll_on_output = (state != 0);
}

/**
 * zvt_term_set_wordclass:
 * @term: A &ZvtTerm widget.
 * @class: A string of characters to consider a "word" character.
 *
 * Sets the list of characters (character class) that are considered
 * part of a word, when selecting by word.  The @class is defined
 * the same was as a regular expression character class (as normally
 * defined using []'s, but without those included).  A leading or trailing
 * hypen (-) is used to include a hyphen in the character class.
 *
 * Passing a %NULL @class restores the default behaviour of alphanumerics
 * plus "_"  (i.e. "A-Za-z0-9_").
 */
void zvt_term_set_wordclass(ZvtTerm *term, unsigned char *class)
{
  g_return_if_fail (term != NULL);                     
  g_return_if_fail (ZVT_IS_TERM (term));

  vt_set_wordclass(term->vx, class);
}

/**
 * zvt_term_new_with_size:
 * @cols: Number of columns required.
 * @rows: Number of rows required.
 *
 * Creates a new ZVT Terminal widget of the given character dimentions.
 * If the encompassing widget is resizable, then this size may change
 * afterwards, but should be correct at realisation time.
 *
 * Return Value: A pointer to a &ZvtTerm widget is returned, or %NULL
 * on error.
 */
GtkWidget*
zvt_term_new_with_size (int cols, int rows)
{
  ZvtTerm *term;
  term = gtk_type_new (zvt_term_get_type ());

  /* fudge the pixel size, not (really) used anyway */
  vt_resize (&term->vx->vt, cols, rows, cols*8, rows*8);
  term->grid_width = cols;
  term->grid_height = rows;
  term->set_grid_size_pending = TRUE;

  return GTK_WIDGET (term);
}


/**
 * zvt_term_new:
 *
 * Creates a new ZVT Terminal widget.  By default the terminal will be
 * setup as 80 colmns x 24 rows, but it will size automatically to its
 * encompassing widget, and may be smaller or larger upon realisation.
 *
 * Return Value: A pointer to a &ZvtTerm widget is returned, or %NULL
 * on error.
 */
GtkWidget*
zvt_term_new (void)
{
  ZvtTerm *term;
  term = gtk_type_new (zvt_term_get_type ());
  return GTK_WIDGET (term);
}

/* free's the tranparenxy pixmap */
/* make special care any call to this also overrides or
   clears the background gc to which the transparency pixmap
   has been set as the tile */
static void
free_transpix(ZvtTerm *term)
{
  struct _zvtprivate *zp;
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");
  if (zp && zp->transpix) {
    gdk_xid_table_remove (GDK_WINDOW_XWINDOW(zp->transpix));
    g_dataset_destroy (zp->transpix);
    g_free (zp->transpix);
    zp->transpix = 0;
  }
}


static void
zvt_term_destroy (GtkObject *object)
{
  ZvtTerm *term;
  struct _zvtprivate *zp;

  g_return_if_fail (object != NULL);
  g_return_if_fail (ZVT_IS_TERM (object));

  term = ZVT_TERM (object);
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  if (term->timeout_id != -1)
    gtk_timeout_remove(term->timeout_id);

  zvt_term_closepty (term);
  vtx_destroy (term->vx);

  if (term->font) {
    gdk_font_unref (term->font);
    term->font = NULL;
  }

  if (term->font_bold) {
    gdk_font_unref (term->font_bold);
    term->font_bold = NULL;
  }

  /* release the adjustment */
  if (term->adjustment) {
    gtk_signal_disconnect_by_data (GTK_OBJECT (term->adjustment), term);
    gtk_object_unref (GTK_OBJECT (term->adjustment));
    term->adjustment = NULL;
  }

  if (term->ic) {
    gdk_ic_destroy(term->ic);
    term->ic = NULL;
  }

  /* release private data */
  if (zp) {
    if (zp->text_expand)
      g_free(zp->text_expand);
    if (zp->bold_save)
      gdk_pixmap_unref(zp->bold_save);
    free_transpix(term);
    g_free(zp);
    gtk_object_set_data (GTK_OBJECT (term), "_zvtprivate", 0);
  }

  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

/**
 * zvt_term_reset:
 * @term: A &ZvtTerm widget.
 * @hard: If %TRUE, then perform a HARD reset.
 * 
 * Performs a complete reset on the terminal.  Resets all
 * attributes, and if @hard is %TRUE, also clears the screen.
 **/
void
zvt_term_reset (ZvtTerm *term, int hard)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  vt_cursor_state (term, 0);
  vt_reset_terminal(&term->vx->vt, hard);
  vt_update (term->vx, UPDATE_CHANGES);
  vt_cursor_state (term, 1);
}


/**
 * zvt_term_set_color_scheme:
 * @term: A &ZvtTerm widget.
 * @red:  pointer to a gushort array of 18 elements with red values.
 * @grn:  pointer to a gushort array of 18 elements with green values.
 * @blu:  pointer to a gushort array of 18 elements with blue values.
 *
 * This function sets the colour palette for the terminal @term.  Each
 * pointer points to a gushort array of 18 elements.  White is 0xffff
 * in all elements.
 *
 * The elements 0 trough 15 are the first 16 colours for the terminal,
 * with element 16 and 17 the default foreground and background colour
 * respectively.
 */
void
zvt_term_set_color_scheme (ZvtTerm *term, gushort *red, gushort *grn, gushort *blu)
{
  int  nallocated;
  GdkColor c;
  
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));
  g_return_if_fail (red != NULL);
  g_return_if_fail (grn != NULL);
  g_return_if_fail (blu != NULL);
  
  memset (term->colors, 0, sizeof (term->colors));
  nallocated = 0;
  gdk_color_context_get_pixels (term->color_ctx, red, grn, blu, 18, 
				term->colors, &nallocated);
  c.pixel = term->colors [17];
#if 1
  gdk_window_set_background (GTK_WIDGET (term)->window, &c);
  gdk_window_clear (GTK_WIDGET (term)->window);
#endif
}

/**
 * zvt_term_set_default_color_scheme:
 * @term: A &ZvtTerm widget.
 *
 * Resets the color values to the default color scheme.
 */
void
zvt_term_set_default_color_scheme (ZvtTerm *term)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));
  
  zvt_term_set_color_scheme (term, default_red, default_grn, default_blu);
}

/**
 * zvt_term_set_size:
 * @term: A &ZvtTerm widget.
 * @width: Width of terminal, in columns.
 * @height: Height of terminal, in rows.
 * 
 * Causes the terminal to attempt to resize to the absolute character
 * size of @width rows by @height columns.
 **/
void
zvt_term_set_size (ZvtTerm *term, guint width, guint height)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  term->set_grid_size_pending = TRUE;
  term->grid_width = width;
  term->grid_height = height;

  gtk_widget_queue_resize (GTK_WIDGET (term));
}

static void
zvt_term_realize (GtkWidget *widget)
{
  ZvtTerm *term;
  GdkWindowAttr attributes;
  GdkPixmap *cursor_dot_pm;
  gint attributes_mask;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
  term = ZVT_TERM (widget);

  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width - (2 * widget->style->klass->xthickness) - PADDING;
  attributes.height = widget->allocation.height - (2 * widget->style->klass->ythickness);
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask = gtk_widget_get_events (widget) |
    GDK_EXPOSURE_MASK | 
    GDK_BUTTON_PRESS_MASK | 
    GDK_BUTTON_MOTION_MASK |
    GDK_POINTER_MOTION_MASK | 
    GDK_BUTTON_RELEASE_MASK | 
    GDK_KEY_PRESS_MASK;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);

  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

  /* main window */
  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
				   &attributes, attributes_mask);
  widget->style = gtk_style_attach (widget->style, widget->window);
  gdk_window_set_user_data (widget->window, widget);
#if 1
  gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
#else
  gdk_window_set_back_pixmap(widget->window, 0, 1);
#endif

  /* this should never have been created *sigh* */
  /* keep for compatability */
  term->term_window = widget->window;

  /* create pixmaps for this window */
  cursor_dot_pm = 
    gdk_pixmap_create_from_data(widget->window,
				"\0", 1, 1, 1,
				&widget->style->fg[GTK_STATE_ACTIVE],
				&widget->style->bg[GTK_STATE_ACTIVE]);

  /* Get I beam cursor, and also create a blank one based on the blank image */
  term->cursor_bar = gdk_cursor_new(GDK_XTERM);
  term->cursor_dot = 
    gdk_cursor_new_from_pixmap(cursor_dot_pm, cursor_dot_pm,
			       &widget->style->fg[GTK_STATE_ACTIVE],
			       &widget->style->bg[GTK_STATE_ACTIVE],
			       0, 0);
  gdk_window_set_cursor(widget->window, term->cursor_bar);
  gdk_pixmap_unref (cursor_dot_pm);
  term->cursor_current = term->cursor_bar;

  /* setup blinking cursor */
  if (term->blink_enabled)
    term->timeout_id = gtk_timeout_add(500, zvt_term_cursor_blink, term);

  /* setup scrolling gc */
  term->scroll_gc = gdk_gc_new (widget->window);
  gdk_gc_set_exposures (term->scroll_gc, TRUE);

  /* Colors */
  term->fore_gc = gdk_gc_new (widget->window);
  term->back_gc = gdk_gc_new (widget->window);
  term->color_ctx = 
      gdk_color_context_new (gtk_widget_get_visual (GTK_WIDGET (term)),
			     gtk_widget_get_colormap (GTK_WIDGET (term)));

  /* Allocate default color set */
  zvt_term_set_default_color_scheme (term);
  
  /* set the initial colours */
  term->back_last = -1;
  term->fore_last = -1;

  /* input context */
  if (gdk_im_ready () && !term->ic) {
    GdkICAttr attr;
    
    /* FIXME: do we have any window yet? */
    attr.style = GDK_IM_PREEDIT_NOTHING | GDK_IM_STATUS_NOTHING;
    attr.client_window = attr.focus_window = widget->window;
    term->ic = gdk_ic_new(&attr, GDK_IC_ALL_REQ);
    
    if (!term->ic) {
      g_warning("Can't create input context.");
    }
  }
}

static void
zvt_term_unrealize (GtkWidget *widget)
{
  ZvtTerm *term;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  term = ZVT_TERM (widget);

  /* free resources */
  gdk_cursor_destroy (term->cursor_bar);
  term->cursor_bar = NULL;

  gdk_cursor_destroy (term->cursor_dot);
  term->cursor_dot = NULL;

  term->cursor_current = NULL;

  gdk_color_context_free (term->color_ctx);
  term->color_ctx = NULL;

  gdk_gc_destroy (term->scroll_gc);
  term->scroll_gc = NULL;

  gdk_gc_destroy (term->back_gc);
  term->back_gc = NULL;

  gdk_gc_destroy (term->fore_gc);
  term->fore_gc = NULL;

  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (*GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}


static void
zvt_term_map (GtkWidget *widget)
{
  ZvtTerm *term;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  term = ZVT_TERM (widget);

  if (!GTK_WIDGET_MAPPED (widget)) {
    GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
    
    gdk_window_show (widget->window);
    
    /* XXX: is this right? --JMP */
    if (!GTK_WIDGET_HAS_FOCUS (widget))
      gtk_widget_grab_focus (widget);
  }
}


static void
zvt_term_unmap (GtkWidget *widget)
{
  ZvtTerm *term;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  term = ZVT_TERM (widget);

  if (GTK_WIDGET_MAPPED (widget)) {
    GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
    
    gdk_window_hide (widget->window);
  }
}


static gint
zvt_term_focus_in(GtkWidget *widget, GdkEventFocus *event)
{
  ZvtTerm *term;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  term = ZVT_TERM (widget);
  GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);

  vt_cursor_state(term, 0);
  term->cursor_filled = 1;
  vt_cursor_state(term, 1);

  /* setup blinking cursor */
  if (term->blink_enabled && term->timeout_id == -1)
    term->timeout_id = gtk_timeout_add (500, zvt_term_cursor_blink, term);

  if (term->ic)
    gdk_im_begin (term->ic, widget->window);

  return FALSE;
}


static gint
zvt_term_focus_out(GtkWidget *widget, GdkEventFocus *event)
{
  ZvtTerm *term;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  term = ZVT_TERM (widget);
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);

  vt_cursor_state(term, 0);
  term->cursor_filled = 0;
  vt_cursor_state(term, 1);

  /* setup blinking cursor */
  if (term->blink_enabled && term->timeout_id != -1) {
    gtk_timeout_remove(term->timeout_id);
    term->timeout_id = -1;
  }
  
  if (term->ic)
    gdk_im_end ();

  return FALSE;
}


static void 
zvt_term_size_request (GtkWidget      *widget,
		       GtkRequisition *requisition)
{
  ZvtTerm *term;
  guint grid_width, grid_height;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));
  g_return_if_fail (requisition != NULL);

  term = ZVT_TERM (widget);

  if (term->set_grid_size_pending) {
    grid_width = term->grid_width;
    grid_height = term->grid_height;
    
    if (grid_width <= 0)
      grid_width = 8;
    
    if (grid_height <= 0)
      grid_height = 2;
    term->set_grid_size_pending = FALSE;
  } else {
    grid_width = term->grid_width;
    grid_height = term->grid_height;

    grid_width = term->vx->vt.width;
    grid_height = term->vx->vt.height;
  }

  requisition->width = (grid_width * term->charwidth) + 
    (widget->style->klass->xthickness * 2) + PADDING;
  requisition->height = (grid_height * term->charheight) + 
    (widget->style->klass->ythickness * 2);

  /* debug ouput */
  d( printf("zvt_term_size_request x=%d y=%d\n", grid_width, grid_height) );
  d( printf("   requestion size w=%d, h=%d\n", requisition->width, requisition->height) );
}

static void
zvt_term_size_allocate (GtkWidget     *widget,
			GtkAllocation *allocation)
{
  gint term_width, term_height;
  guint grid_width, grid_height;
  ZvtTerm *term;
  
  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));
  g_return_if_fail (allocation != NULL);

  if (GTK_WIDGET_REALIZED (widget)) {
      term = ZVT_TERM (widget);

      d( printf("zvt_term_size_allocate old grid x=%d y=%d\n", 
		term->vx->vt.width,
		term->vx->vt.height) );
      
      d(printf("old size w=%d h=%d\n", widget->allocation.width, widget->allocation.height));
      d(printf("new allocation w=%d h=%d\n", allocation->width, allocation->height));

      gdk_window_move_resize (widget->window,
			      allocation->x,
			      allocation->y,
			      allocation->width,
			      allocation->height);

      term_width = allocation->width - (2 * widget->style->klass->xthickness) - PADDING;
      term_height = allocation->height - (2 * widget->style->klass->ythickness);

      /* resize the virtual terminal buffer, if its size has changed, minimal size is 1x1 */
      grid_width = MAX(term_width / term->charwidth, 1);
      grid_height = MAX(term_height / term->charheight, 1);
      if (grid_width != term->charwidth
	  || grid_height != term->charheight) {

	/* turn off the selection */
	term->vx->selstartx = term->vx->selendx;
	term->vx->selstarty = term->vx->selendy;
	term->vx->selected = 0;

	vt_resize (&term->vx->vt, grid_width, grid_height, term_width, term_height);
	vt_update (term->vx, UPDATE_REFRESH|UPDATE_SCROLLBACK);
	
	d(printf("zvt_term_size_allocate grid calc x=%d y=%d\n", 
		 grid_width,
		 grid_height) );

	/* is this right? Seems we have to update this... */
	/* term->set_grid_size_pending = TRUE; */
	term->grid_height = grid_height;
	term->grid_width = grid_width;
      }

      /* resize the scrollbar */
      zvt_term_fix_scrollbar (term);

      d( printf("zvt_term_size_allocate new grid x=%d y=%d\n", 
		term->vx->vt.width,
		term->vx->vt.height) );
  }
  widget->allocation = *allocation;
}


static void 
zvt_term_draw (GtkWidget *widget, GdkRectangle *area)
{
  gint width, height;
  ZvtTerm *term;
  int fill;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));
  g_return_if_fail (area != NULL);

  term = ZVT_TERM (widget);

  d( printf("zvt_term_draw xx=%d y=%d, w=%d, h=%d\n", 
	   area->x, area->y, area->width, area->height));

  if (GTK_WIDGET_DRAWABLE (widget)) {
    d( printf("zvt_term_draw is drawable\n") );

    /* make sure we've got the right background, incase some stupid style changed it */
    if (term->transparent==0 && term->pixmap_filename==0) {
      GdkColor c;
      c.pixel = term->colors [17];
      gdk_window_set_background (GTK_WIDGET (term)->window, &c);
    }
    
    term->in_expose = 1;
    
    gdk_window_get_size (widget->window, &width, &height);

    /* always load the background */
    load_background(term);

    if(term->transparent || term->pixmap_filename) {
      gdk_draw_rectangle (widget->window,
			  term->back_gc, 1,
			  0, 0,
			  widget->allocation.width,
			  widget->allocation.height);
    }
    fill=17;

    /* assume the screen is filled with background? */
    vt_update_rect (term->vx, fill, 0, 0,
		    width / term->charwidth,
		    height / term->charheight);
    
    term->in_expose = 0;

    /* draw shadow/border */
    if (term->shadow_type != GTK_SHADOW_NONE)
      gtk_draw_shadow (widget->style, widget->window,
		       GTK_STATE_NORMAL, term->shadow_type, 0, 0, 
		       widget->allocation.width,
		       widget->allocation.height);
  }	  
}


static gint
zvt_term_expose (GtkWidget      *widget,
		 GdkEventExpose *event)
{
  ZvtTerm *term;
  int offx, offy;
  int fill;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  offx = widget->style->klass->xthickness + PADDING;
  offy = widget->style->klass->ythickness;

  d(printf("exposed!  (%d,%d) %dx%d\n",
	   event->area.x, 
	   event->area.y,
	   event->area.width, 
	   event->area.height));

  if (GTK_WIDGET_DRAWABLE (widget)) {
    term = ZVT_TERM (widget);
    
    /* FIXME: may update 1 more line/char than needed */
    term->in_expose = 1;

    /*
      this could probably do what draw does, but it looks a bit slower if you do.
     */
    if(term->transparent || term->pixmap_filename) {
      gdk_draw_rectangle (widget->window,
			  term->back_gc, 1,
			  event->area.x, 
			  event->area.y,
			  event->area.width, 
			  event->area.height);
    }
    fill = 17;

    vt_update_rect (term->vx, fill,
		    (event->area.x-offx) / term->charwidth,
		    (event->area.y-offy) / term->charheight,
		    (event->area.x + event->area.width) / term->charwidth+1,
		    (event->area.y + event->area.height) / term->charheight+1);

    term->in_expose = 0;

    if (term->shadow_type != GTK_SHADOW_NONE)
      gtk_draw_shadow (widget->style, widget->window,
		       GTK_STATE_NORMAL, term->shadow_type, 0, 0, 
		       widget->allocation.width,
		       widget->allocation.height); 
  }

  return FALSE;
}


/**
 * zvt_term_show_pointer:
 * @term: A &ZvtTerm widget.
 *
 * Show the default I beam pointer.
 */
void
zvt_term_show_pointer (ZvtTerm *term)
{
  g_return_if_fail (term != NULL);

  if (term->cursor_current != term->cursor_bar) {
    gdk_window_set_cursor(GTK_WIDGET(term)->window, term->cursor_bar);
    term->cursor_current = term->cursor_bar;
  }
}

/**
 * zvt_term_show_pointer:
 * @term: A &ZvtTerm widget.
 *
 * Hide the pointer.  In reality the pointer is changed to a
 * single-pixel black dot.
 */
void
zvt_term_hide_pointer (ZvtTerm *term)
{
  g_return_if_fail (term != NULL);

  if (term->cursor_current != term->cursor_dot) {
    gdk_window_set_cursor(GTK_WIDGET(term)->window, term->cursor_dot);
    term->cursor_current = term->cursor_dot;
  }
}

/**
 * zvt_term_set_scrollback:
 * @term: A &ZvtTerm widget.
 * @lines: Number of lines desired for the scrollback buffer.
 *
 * Set the maximum number of scrollback lines for the widget @term to
 * @lines lines.
 */
void
zvt_term_set_scrollback (ZvtTerm *term, int lines)
{
  g_return_if_fail (term != NULL);

  vt_scrollback_set (&term->vx->vt, lines);
  zvt_term_fix_scrollbar (term);
}


/* Load a set of fonts into the terminal.
 * These fonts should be the same size, otherwise it could get messy ...
i to * if font_bold is NULL, then the font is emboldened manually (overstrike)
 */
static void
zvt_term_set_fonts_internal(ZvtTerm *term, GdkFont *font, GdkFont *font_bold)
{
  struct _zvtprivate *zp;

  /* ignore no-font setting */
  if (font==NULL)
    return;

  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  /* get the font/fontset size, and set the font type */
  switch (font->type) {
  case GDK_FONT_FONT: {
    XFontStruct *xfont;
    xfont = (XFontStruct *)GDK_FONT_XFONT(font);
    term->charwidth = xfont->max_bounds.width;
    term->charheight = font->ascent + font->descent;
    if ((xfont->min_byte1 == 0) && (xfont->max_byte1 == 0))
      zp->fonttype = ZVT_FONT_1BYTE;
    else
      zp->fonttype = ZVT_FONT_2BYTE;
    break;
  }
  case GDK_FONT_FONTSET: {
    XFontSet fontset = (XFontSet) ((GdkFontPrivate *)font)->xfont;
    XFontSetExtents *extents = XExtentsOfFontSet(fontset);
    term->charwidth = extents->max_logical_extent.width;
    term->charheight = extents->max_logical_extent.height;
    zp->fonttype = ZVT_FONT_FONTSET;
  }
  }
  d(printf("fonttype = %d\n", zp->fonttype));

  /* set up a size pending request so that no matter
   * what the queue resize will resize the widget to
   * have the same grid size
   */
  if (!term->set_grid_size_pending) {
    term->set_grid_size_pending = TRUE;
    term->grid_width = term->vx->vt.width;
    term->grid_height = term->vx->vt.height;
  }

  if (term->font)
    gdk_font_unref (term->font);
  term->font = font;

  if (term->font_bold)
    gdk_font_unref (term->font_bold);
  term->font_bold = font_bold;

  /* setup bold_save pixmap, for when drawing bold, we need to save
     what was at the end of the text, so we can restore it.  Affects
     tiny fonts only - what a mess */
  if (zp && term->font_bold==0) {
    int depth;

    if (zp->bold_save)
      gdk_pixmap_unref(zp->bold_save);
    gdk_window_get_geometry(GTK_WIDGET(term)->window,NULL,NULL,NULL,NULL,&depth);

    zp->bold_save = gdk_pixmap_new(GTK_WIDGET(term)->window,
				   1, term->charheight, depth);
  }

  gtk_widget_queue_resize (GTK_WIDGET (term));
}

/**
 * zvt_term_set_fonts:
 * @term: A &ZvtTerm widget.
 * @font: Font used for regular text.
 * @font_bold: Font used for bold text.  May be null, in which case the bold
 * font is rendered by over-striking.
 *
 * Load a set of fonts into the terminal.
 * 
 * These fonts should be the same size, otherwise it could get messy ...
 */
void
zvt_term_set_fonts (ZvtTerm *term, GdkFont *font, GdkFont *font_bold)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));
  g_return_if_fail (font != NULL);

  zvt_term_set_fonts_internal (term, font, font_bold);

  gdk_font_ref (font);
  if (font_bold)
      gdk_font_ref (font_bold);
}

/**
 * zvt_term_set_font_name:
 * @term: A &ZvtTerm widget.
 * @name: A full X11 font name string.
 *
 * Set a font by name only.  If font aliases such as 'fixed' or
 * '10x20' are passed to this function, then both the bold and
 * non-bold font will be identical.  In colour mode bold fonts are
 * always the top 8 colour scheme entries, and so bold is still
 * rendered.
 *
 * Tries to calculate bold font name from the base name.  This only
 * works with fonts where the names are alike.
 */
void
zvt_term_set_font_name (ZvtTerm *term, char *name)
{
  int count;
  char c, *ptr, *rest;
  GString *newname, *outname;
  GdkFont *font, *font_bold;

  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));
  g_return_if_fail (name != NULL);

  newname = g_string_new (name);
  outname = g_string_new ("");

  rest = 0;
  ptr = newname->str;

  for (count = 0; (c = *ptr++);) {
    if (c == '-') {
      count++;
      
      d(printf("scanning (%c) (%d)\n", c, count));
      d(printf("newname = %s ptr = %s\n", newname->str, ptr));
      
      switch (count) {
      case 3:
	ptr[-1] = 0;
	break;
	
      case 5:
	rest = ptr - 1;
	break;
      }
    }
  }

  if (rest) {
    g_string_sprintf (outname, "%s-medium-r%s", newname->str, rest);
    font = gdk_font_load (outname->str);
    d( printf("loading normal font %s\n", outname->str) );
    
    g_string_sprintf (outname, "%s-bold-r%s", newname->str, rest); 
    font_bold = gdk_font_load (outname->str);
    d( printf("loading bold font %s\n", outname->str) );
    
    zvt_term_set_fonts_internal (term, font, font_bold);
  } else {
    font = gdk_font_load (name);
    zvt_term_set_fonts_internal (term, font, 0);
  }

  g_string_free (newname, TRUE);
  g_string_free (outname, TRUE);
}


/*
  Called when something has changed, size of window or scrollback.

  Fixes the adjustment and notifies the system.
*/
static void 
zvt_term_fix_scrollbar (ZvtTerm *term)
{
  GTK_ADJUSTMENT(term->adjustment)->upper = 
    term->vx->vt.scrollbacklines + term->vx->vt.height - 1;

  GTK_ADJUSTMENT(term->adjustment)->value = 
    term->vx->vt.scrollbackoffset + term->vx->vt.scrollbacklines;

  GTK_ADJUSTMENT(term->adjustment)->page_increment = 
    term->vx->vt.height - 1;

  GTK_ADJUSTMENT(term->adjustment)->page_size =
    term->vx->vt.height - 1;

  gtk_signal_emit_by_name (GTK_OBJECT (term->adjustment), "changed");
}

/* keep calling with incrementing 'type' until you get a valid return,
   or nothing
   returns true if a paste was actually requested. */
static int
request_paste (GtkWidget *widget, int type)
{
  GdkAtom string_atom;
#ifdef ZVT_UTF
  char *types[] = {"UTF-8", "STRING"};
  int index;
  struct _zvtprivate *zp = _ZVT_PRIVATE(widget);

  if ( ((ZvtTerm *)widget)->vx->vt.coding != ZVT_CODE_UTF8)
    index = type+1;
  else
    index = type;

  if (index>=sizeof(types)/sizeof(types[0])) {
    zp->lastselectiontype = -1;
    return 0;
  }

  string_atom = gdk_atom_intern (types[index], FALSE);
  zp->lastselectiontype = type;

  d(printf(" %s atom = %d\n", types[index], (int)string_atom));
#else
  /* Get the atom corresonding to the target "STRING" */
  string_atom = gdk_atom_intern ("STRING", FALSE);
#endif

  if (string_atom == GDK_NONE) {
    g_warning("WARNING: Could not get string atom\n");
  }

  /* And request the "STRING" target for the primary selection */
  gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, string_atom,
			 GDK_CURRENT_TIME);
  return 1;
}

/*
  perhaps most of the button press stuff could be shifted
  to the update file.  as for the report_button function
  shifted to the vt file ?

*/

static gint
zvt_term_button_press (GtkWidget      *widget,
		       GdkEventButton *event)
{
  gint x,y;
  GdkModifierType mask;
  struct _vtx *vx;
  ZvtTerm *term;
  struct _zvtprivate *zp;

  d(printf("button pressed\n"));

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  term = ZVT_TERM (widget);
  vx = term->vx;
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  zvt_term_show_pointer (term);


  gdk_window_get_pointer(widget->window, &x, &y, &mask);
  x /= term->charwidth;
  y = y / term->charheight + vx->vt.scrollbackoffset;

  if (zp && zp->scrollselect_id!=-1) {
    gtk_timeout_remove(zp->scrollselect_id);
    zp->scrollselect_id=-1;
  }

  /* Shift is an overwrite key for the reporting of the buttons */
  if (!(event->state & GDK_SHIFT_MASK))
    if (vt_report_button(&vx->vt, event->button, event->state, x, y)) 
      return FALSE;

  /* ignore all control-clicks' at this level */
  if (event->state & GDK_CONTROL_MASK) 
    {
      return FALSE;
    }
    
  switch(event->button) {
  case 1:			/* left button */

    /* set selection type, and from which end we are selecting */
    switch(event->type) {
    case GDK_BUTTON_PRESS:
      vx->selectiontype = VT_SELTYPE_CHAR|VT_SELTYPE_BYSTART;
      break;
    case GDK_2BUTTON_PRESS:
      vx->selectiontype = VT_SELTYPE_WORD|VT_SELTYPE_BYSTART|VT_SELTYPE_MOVED;
      break;
    case GDK_3BUTTON_PRESS:
      vx->selectiontype = VT_SELTYPE_LINE|VT_SELTYPE_BYSTART|VT_SELTYPE_MOVED;
      break;
    default:
      break;
    }
    
    /* reset selection */
    vx->selstartx = x;
    vx->selstarty = y;
    vx->selendx = x;
    vx->selendy = y;
    
    /* reset 'drawn' screen (to avoid mis-refreshes) */
    if (!vx->selected) {
      vx->selstartxold = x;
      vx->selstartyold = y;
      vx->selendxold = x;
      vx->selendyold = y;
      vx->selected =1;
    }

    if (event->type != GDK_BUTTON_PRESS) {
      vt_fix_selection(vx);	/* handles by line/by word select update */
    }

    /* either draw (double/triple click) or undraw (single click) selection */
    vt_draw_selection(vx);

    d( printf("selection starting %d %d\n", x, y) );

    gtk_grab_add (widget);
    gdk_pointer_grab (widget->window, TRUE,
		      GDK_BUTTON_RELEASE_MASK |
		      GDK_BUTTON_MOTION_MASK |
		      GDK_POINTER_MOTION_HINT_MASK,
		      NULL, NULL, 0);

    /* 'block' input while we're selecting text */
    if (term->input_id!=-1) {
      gdk_input_remove(term->input_id);
      term->input_id=-1;
    }
    break;

  case 2:			/* middle button - paste */
    request_paste (widget, 0);
    break;

  case 3:			/* right button - select extend? */
    if (vx->selected) {
      int midpos;
      int mypos;

      switch(event->type) {
      case GDK_BUTTON_PRESS:
	vx->selectiontype = VT_SELTYPE_CHAR;
	break;
      case GDK_2BUTTON_PRESS:
	vx->selectiontype = VT_SELTYPE_WORD;
	break;
      case GDK_3BUTTON_PRESS:
	vx->selectiontype = VT_SELTYPE_LINE;
	break;
      default:
	break;
      }

      midpos = ((vx->selstarty+vx->selendy)/2)*vx->vt.width + (vx->selendx+vx->selstartx)/2;
      mypos = y*vx->vt.width + x;
      if (mypos < midpos) {
	vx->selstarty=y;
	vx->selstartx=x;
	vx->selectiontype |= VT_SELTYPE_BYEND;
      } else {
	vx->selendy=y;
	vx->selendx=x;
	vx->selectiontype |= VT_SELTYPE_BYSTART;
      }

      vt_fix_selection(vx);
      vt_draw_selection(vx);
      
      gtk_grab_add (widget);
      gdk_pointer_grab (widget->window, TRUE,
			GDK_BUTTON_RELEASE_MASK |
			GDK_BUTTON_MOTION_MASK |
			GDK_POINTER_MOTION_HINT_MASK,
			NULL, NULL, 0);
      /* 'block' input while we're selecting text */
      if (term->input_id!=-1) {
	gdk_input_remove(term->input_id);
	term->input_id=-1;
      }
    }
    break;

  case 4:
    zvt_term_scroll_by_lines (term, -12);
    break;

  case 5:
    zvt_term_scroll_by_lines (term, 12);
    break;
  }
  return FALSE;
}

static gint
zvt_term_button_release (GtkWidget      *widget,
			 GdkEventButton *event)
{
  ZvtTerm *term;
  gint x, y;
  GdkModifierType mask;
  struct _vtx *vx;
  struct _zvtprivate *zp;

  d(printf("button released\n"));

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  term = ZVT_TERM (widget);
  vx = term->vx;
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  gdk_window_get_pointer (widget->window, &x, &y, &mask);

  x = x/term->charwidth;
  y = y/term->charheight + vx->vt.scrollbackoffset;

  /* reset scrolling selection timer if it is enabled */
  if (zp && zp->scrollselect_id!=-1) {
    gtk_timeout_remove(zp->scrollselect_id);
    zp->scrollselect_id=-1;
  }

  /* ignore wheel mice buttons (4 and 5) */
  /* otherwise they affect the selection */
  if (event->button == 4 || event->button == 5)
    return FALSE;
  
  if (vx->selectiontype == VT_SELTYPE_NONE) {
    /* report mouse to terminal */
    if (!(event->state & GDK_SHIFT_MASK))
      if (vt_report_button(&vx->vt, 0, event->state, x, y))
	return FALSE;
    
    /* ignore all control-clicks' at this level */
    if (event->state & GDK_CONTROL_MASK) {
      return FALSE;
    }
  }

  if (vx->selectiontype & VT_SELTYPE_BYSTART) {
    vx->selendx = x;
    vx->selendy = y;
  } else {
    vx->selstartx = x;
    vx->selstarty = y;
  }

  switch(event->button) {
  case 1:
  case 3:
    d(printf("select from (%d,%d) to (%d,%d)\n", vx->selstartx, vx->selstarty,
	     vx->selendx, vx->selendy));

    gtk_grab_remove (widget);
    gdk_pointer_ungrab (0);

    /* re-enable input */
    if (term->input_id==-1 && term->vx->vt.childfd !=-1) {
      term->input_id =
	gdk_input_add(term->vx->vt.childfd, GDK_INPUT_READ, zvt_term_readdata, term);
    }

    if (vx->selectiontype & VT_SELTYPE_MOVED) {
      vt_fix_selection(vx);
      vt_draw_selection(vx);
          
      /* get the selection as 32 bit utf */
      vt_get_selection(vx, 4, 0);
      
      gtk_selection_owner_set (widget,
			       GDK_SELECTION_PRIMARY,
			       event->time);
    }

    vx->selectiontype = VT_SELTYPE_NONE; /* 'turn off' selecting */

  }

  return FALSE;
}

static void
zvt_term_scroll_by_lines (ZvtTerm *term, int n)
{
  gfloat new_value = 0;

  if (n > 0)
    new_value = MIN (term->adjustment->value + n, term->adjustment->upper - n);
  else if (n < 0)
    new_value = MAX (term->adjustment->value + n, term->adjustment->lower);
  else
    return;

  gtk_adjustment_set_value (term->adjustment, new_value);
}

static gint zvt_selectscroll(gpointer data)
{
  ZvtTerm *term;
  GtkWidget *widget;
  struct _zvtprivate *zp;

  widget = data;
  term = ZVT_TERM (widget);
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  if (zp) {
    zvt_term_scroll_by_lines(term, zp->scrollselect_dir);
  }
  return TRUE;
}

/*
  mouse motion notify.
  only gets called for the first motion?  why?
*/

static gint
zvt_term_motion_notify (GtkWidget      *widget,
			GdkEventMotion *event)
{
  struct _vtx *vx;
  gint x, y;
  ZvtTerm *term;
  struct _zvtprivate *zp;
  
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  term = ZVT_TERM (widget);
  vx = term->vx;
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  if (vx->selectiontype != VT_SELTYPE_NONE){
    x=(((int)event->x))/term->charwidth;
    y=(((int)event->y))/term->charheight;
    
    /* move end of selection, and draw it ... */
    if (vx->selectiontype & VT_SELTYPE_BYSTART) {
      vx->selendx = x;
      vx->selendy = y + vx->vt.scrollbackoffset;
    } else {
      vx->selstartx = x;
      vx->selstarty = y + vx->vt.scrollbackoffset;
    }

    vx->selectiontype |= VT_SELTYPE_MOVED;
    
    vt_fix_selection(vx);
    vt_draw_selection(vx);

    if (zp) {
      if (zp->scrollselect_id!=-1) {
	gtk_timeout_remove(zp->scrollselect_id);
	zp->scrollselect_id = -1;
      }
      
      if (y<0 || y>vx->vt.height) {
	if (y<0)
	  zp->scrollselect_dir = -1;
	else
	  zp->scrollselect_dir = 1;
	zp->scrollselect_id = gtk_timeout_add(100, zvt_selectscroll, term);
      }
    }
  }
  /* otherwise, just a mouse event */
  /* make sure the pointer is visible! */
  zvt_term_show_pointer(term);

  return FALSE;
}

/*
 * zvt_term_selection_clear: [internal]
 * @term: A &ZvtTerm widget.
 * @event: Event that triggered the selection claim.
 *
 * Called when another application claims the selection.
 */
gint
zvt_term_selection_clear (GtkWidget *widget, GdkEventSelection *event)
{
  struct _vtx *vx;
  ZvtTerm *term;
  
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  /* Let the selection handling code know that the selection
   * has been changed, since we've overriden the default handler */
  if (!gtk_selection_clear (widget, event))
    return FALSE;

  term = ZVT_TERM (widget);
  vx = term->vx;

  vtx_unrender_selection(vx);
  vt_clear_selection(vx);
  return TRUE;
}

#ifdef ZVT_UTF

/* perform data-conversion on the selection */
static char *
zvt_term_convert_selection(ZvtTerm *term, int type, int *outlen)
{
  char *out;
  int i;
  uint32 c;

  switch (type) {
  default:
  case 0: {			/* this is ascii/isolatin1 */
    unsigned char *o;
    d(printf("converting selection to ISOLATIN1\n"));
    out = g_malloc(term->vx->selection_size);
    o = out;
    for(i=0;i<term->vx->selection_size;i++) {
      c = term->vx->selection_data[i];
      o[i] = c<0x100?c:'?';
    }
    *outlen = term->vx->selection_size;
    break;
  }
  case 1: {			/* this is utf-8, basically a local implementation of wcstombs() */
    unsigned char *o;
    unsigned int len=0;
    d(printf("converting selection to UTF-8\n"));
    for(i=0;i<term->vx->selection_size;i++) {
      c = term->vx->selection_data[i];
      if (c<0x80)
	len+=1;
      else if (c<0x800)
	len+=2;
      else if (c<0x10000)
	len+=3;
      else if (c<0x200000)
	len+=4;
      else if (c<0x4000000)
	len+=5;
      else
	len+=6;
    }

    out = g_malloc(len);
    o = out;
    *outlen = len;

    for(i=0;i<term->vx->selection_size;i++) {
      c = term->vx->selection_data[i];
      if (c<0x80)
	*o++=c;
      else if (c<0x800) {
	*o++=(c>>6)|0xc0;
	*o++=(c&0x3f)|0x80;
      } else if (c<0x10000) {
	*o++=(c>>12)|0xe0;
	*o++=((c>>6)&0x3f)|0x80;
	*o++=(c&0x3f)|0x80;
      } else if (c<0x200000) {
	*o++=(c>>18)|0xf0;
	*o++=((c>>12)&0x3f)|0x80;
	*o++=((c>>6)&0x3f)|0x80;
	*o++=(c&0x3f)|0x80;
      } else if (c<0x4000000) {
	*o++=(c>>24)|0xf8;
	*o++=((c>>18)&0x3f)|0x80;
	*o++=((c>>12)&0x3f)|0x80;
	*o++=((c>>6)&0x3f)|0x80;
	*o++=(c&0x3f)|0x80;
      } else
	/* these wont happen */
	;
    }
    d(printf("len = %d, but length\n", len);
    {
      unsigned char *p = out;
      printf("in utf: ");
      while(p<o) {
	printf("%02x", *p++);
      }
      printf("\n");
    });
    break;
  }
  }

  return out;
}
#endif

/* supply the current selection to the caller */
static void
zvt_term_selection_get (GtkWidget        *widget, 
			GtkSelectionData *selection_data_ptr,
			guint             info,
			guint             time)
{
  struct _vtx *vx;
  ZvtTerm *term;
#ifdef ZVT_UTF
  char *converted;
  int len;
  GdkAtom atom;
#endif

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));
  g_return_if_fail (selection_data_ptr != NULL);

  term = ZVT_TERM (widget);
  vx = term->vx;

#ifdef ZVT_UTF
  /* convert selection based on info */
  /* the selection is actually stored in 32 bit chars */
  if (info==1)
    atom = gdk_atom_intern ("UTF-8", FALSE);
  else
    atom = GDK_SELECTION_TYPE_STRING;

  converted = zvt_term_convert_selection(term, info, &len);
  gtk_selection_data_set (selection_data_ptr, atom,
			  8, converted, len);
  g_free(converted);
#else
  gtk_selection_data_set (selection_data_ptr, GDK_SELECTION_TYPE_STRING,
			  8, vx->selection_data, vx->selection_size);
#endif
}

/* receive a selection */
/* Signal handler called when the selections owner returns the data */
static void
zvt_term_selection_received (GtkWidget *widget, GtkSelectionData *selection_data, 
			     guint time)
{
  struct _vtx *vx;
  ZvtTerm *term;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));
  g_return_if_fail (selection_data != NULL);

  term = ZVT_TERM (widget);
  vx = term->vx;

  d(printf("got selection from system\n"));

  /* **** IMPORTANT **** Check to see if retrieval succeeded  */
  /* If we have more selection types we can ask for, try the next one,
     until there are none left */
  if (selection_data->length < 0) {
    struct _zvtprivate *zp = _ZVT_PRIVATE(widget);

    /* now, try again with next selection type */
    if (request_paste(widget, zp->lastselectiontype+1)==0)
      g_print ("Selection retrieval failed\n");
    return;
  }

  /* we will get a selection type of atom(UTF-8) for utf text,
     perhaps that needs to do something different if the terminal
     isn't actually in utf8 mode? */

  /* Make sure we got the data in the expected form */
  if (selection_data->type != GDK_SELECTION_TYPE_STRING
      && selection_data->type != gdk_atom_intern("UTF-8", FALSE)) {
    g_print ("Selection \"STRING\" was not returned as strings!\n");
    return;
  }

  /* paste selection into window! */
  if (selection_data->length)
    {
      int i;
      char *ctmp = selection_data->data;

      for(i = 0; i < selection_data->length; i++)
	if(ctmp[i] == '\n') ctmp[i] = '\r';

      if (term->scroll_on_keystroke)
	zvt_term_scroll (term, 0);
      vt_writechild(&vx->vt, selection_data->data, selection_data->length);
    }
}  


static gint
zvt_term_cursor_blink(gpointer data)
{
  ZvtTerm *term;
  GtkWidget *widget;

  widget = data;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);

  term = ZVT_TERM (widget);

  term->cursor_blink_state ^= 1;
  vt_cursor_state(data, term->cursor_blink_state);

  return TRUE;
}

/*
 * Callback for when the adjustment changes - i.e., the scrollbar
 * moves.
 */
static void 
zvt_term_scrollbar_moved (GtkAdjustment *adj, GtkWidget *widget)
{
  int line;
  ZvtTerm *term;
  struct _vtx *vx;
  struct _zvtprivate *zp;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  term = ZVT_TERM (widget);
  vx = term->vx;
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  line = term->vx->vt.scrollbacklines - (int)adj->value;

  /* needed for floating point errors in slider code */
  if (line < 0)
    line = 0;

  term->vx->vt.scrollbackoffset = -line;

  d(printf("zvt_term_scrollbar_moved adj->value=%f\n", adj->value));

  /* will redraw if scrollbar moved */
  vt_update (term->vx, UPDATE_SCROLLBACK);

  /* for scrolling selection */
  if (zp && zp->scrollselect_id != -1) {
    int x,y;

    if (zp->scrollselect_dir>0) {
      x = vx->vt.width-1;
      y = vx->vt.height-1;
    } else {
      x = 0;
      y = 0;
    }

    if (vx->selectiontype & VT_SELTYPE_BYSTART) {
      vx->selendx = x;
      vx->selendy = y + vx->vt.scrollbackoffset;
    } else {
      vx->selstartx = x;
      vx->selstarty = y + vx->vt.scrollbackoffset;
    }

    vt_fix_selection(vx);
    vt_draw_selection(vx);
  }
}


/**
 * zvt_term_forkpty:
 * @term: A &ZvtTerm widget.
 * @do_uwtmp_log: If %TRUE, then log the session in wtmp(4) and utmp(4).
 *
 * Fork a child process, with a master controlling terminal.
 */
int
zvt_term_forkpty (ZvtTerm *term, int do_uwtmp_log)
{
  int pid;

  g_return_val_if_fail (term != NULL, -1);
  g_return_val_if_fail (ZVT_IS_TERM (term), -1);

  /* cannot fork twice! */
  if (term->input_id != -1)
    return -1;

  pid = vt_forkpty (&term->vx->vt, do_uwtmp_log);
  if (pid > 0) {
    term->input_id = 
      gdk_input_add(term->vx->vt.childfd, GDK_INPUT_READ, zvt_term_readdata, term);
    term->msg_id =
      gdk_input_add(term->vx->vt.msgfd, GDK_INPUT_READ, zvt_term_readmsg, term);
  }

  return pid;
}

/**
 * zvt_term_killchild:
 * @term: A &ZvtTerm widget.
 * @signal: A signal number.
 *
 * Send the signal @signal to the child process.  Note that a child
 * process must have first been started using zvt_term_forkpty().
 *
 * Return Value: See kill(2).
 * See Also: signal(5).
 */
int
zvt_term_killchild (ZvtTerm *term, int signal)
{
  g_return_val_if_fail (term != NULL, -1);
  g_return_val_if_fail (ZVT_IS_TERM (term), -1);

  return vt_killchild (&term->vx->vt, signal);
}

/**
 * zvt_term_closepty:
 * @term: A &ZvtTerm widget.
 *
 * Close master pty to the child process.  It is upto the child to
 * recognise its pty has been closed, and to exit appropriately.
 *
 * Note that a child process must have first been started using
 * zvt_term_forkpty().
 *
 * Return Value: See close(2).
 */
int
zvt_term_closepty (ZvtTerm *term)
{
  g_return_val_if_fail (term != NULL, -1);
  g_return_val_if_fail (ZVT_IS_TERM (term), -1);

  if (term->input_id != -1) {
    gdk_input_remove (term->input_id);
    term->input_id = -1;
  }

  if (term->msg_id != -1) {
    gdk_input_remove (term->msg_id);
    term->msg_id = -1;
  }

  return vt_closepty (&term->vx->vt);
}

static void
zvt_term_scroll (ZvtTerm *term, int n)
{
  gfloat new_value = 0;
  
  if (n) {
    new_value = term->adjustment->value + (n * term->adjustment->page_size);
  } else if (new_value == (term->adjustment->upper - term->adjustment->page_size)) {
    return;
  } else {
    new_value = term->adjustment->upper - term->adjustment->page_size;
  }

  gtk_adjustment_set_value (
      term->adjustment,
      n > 0 ? MIN(new_value, term->adjustment->upper- term->adjustment->page_size) :
      MAX(new_value, term->adjustment->lower));
}

/*
 * Keyboard input callback
 */

/* remapping table for function keys 5-20 */
static unsigned char f5_f20_remap[] =
   {15,17,18,19,20,21,23,24,25,26,28,29,31,32,33,34};

static gint
zvt_term_key_press (GtkWidget *widget, GdkEventKey *event)
{
  char buffer[64];
  char *p=buffer;
  struct _vtx *vx;
  ZvtTerm *term;
  int handled;
  char *cursor;
  int appl_keypad;
  
  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (ZVT_IS_TERM (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  term = ZVT_TERM (widget);
  vx = term->vx;

  appl_keypad = (vx->vt.mode & VTMODE_APP_KEYPAD) != 0;

  zvt_term_hide_pointer(term);
  
  d(printf("keyval = %04x state = %x\n", event->keyval, event->state));
  handled = TRUE;
  switch (event->keyval) {
  case GDK_BackSpace:
    if (event->state & GDK_MOD1_MASK)
      *p++ = '\033';

    if (term->swap_del_key)
      *p++ = '\177';
    else
      *p++ = 8;
    break;
  case GDK_KP_Right:
  case GDK_Right:
    cursor ="C";
    goto do_cursor;
  case GDK_KP_Left:
  case GDK_Left:
    cursor = "D";
    goto do_cursor;
  case GDK_KP_Up:
  case GDK_Up:
    cursor = "A";
    goto do_cursor;
  case GDK_KP_Down:
  case GDK_Down:
    cursor = "B";
    do_cursor:
    if (vx->vt.mode & VTMODE_APP_CURSOR)
      p+=sprintf (p, "\033O%s", cursor);
    else
      p+=sprintf (p, "\033[%s", cursor);
    break;
  case GDK_KP_Insert:
  case GDK_Insert:
    if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK){
        request_paste (widget, 0);
    } else {
      p+=sprintf (p, "\033[2~");
    }
    break;
  case GDK_Delete:
    if (event->state & GDK_MOD1_MASK)
      *p++ = '\033';
    if (term->swap_del_key)
      *p++ = 8;
    else
      *p++ = '\177';
    break;
  case GDK_KP_Delete:
    p+=sprintf (p, "\033[3~");
    break;
  case GDK_KP_Home:
  case GDK_Home:
    p+=sprintf (p, "\033[1~");
    break;
  case GDK_KP_End:
  case GDK_End:
    p+=sprintf (p, "\033[4~");
    break;
  case GDK_KP_Page_Up:
  case GDK_Page_Up:
    if (event->state & GDK_SHIFT_MASK){
      zvt_term_scroll (term, -1);
    } else
      p+=sprintf (p, "\033[5~");
    break;
  case GDK_KP_Page_Down:
  case GDK_Page_Down:
    if (event->state & GDK_SHIFT_MASK){
      zvt_term_scroll (term, 1);
    } else
      p+=sprintf (p, "\033[6~");
    break;

  case GDK_KP_F1:
  case GDK_F1:
    p+=sprintf (p, "\033OP");
    break;
  case GDK_KP_F2:
  case GDK_F2:
    p+=sprintf (p, "\033OQ");
    break;
  case GDK_KP_F3:
  case GDK_F3:
    p+=sprintf (p, "\033OR");
    break;
  case GDK_KP_F4:
  case GDK_F4:
    p+=sprintf (p, "\033OS");
    break;
  case GDK_F5:  case GDK_F6:  case GDK_F7:  case GDK_F8:
  case GDK_F9:  case GDK_F10:  case GDK_F11:  case GDK_F12:
  case GDK_F13:  case GDK_F14:  case GDK_F15:  case GDK_F16:
  case GDK_F17:  case GDK_F18:  case GDK_F19:  case GDK_F20:
    p+=sprintf (p, "\033[%d~", f5_f20_remap[event->keyval-GDK_F5]);
    break;

  case GDK_KP_0:  case GDK_KP_1:  case GDK_KP_2:  case GDK_KP_3:
  case GDK_KP_4:  case GDK_KP_5:  case GDK_KP_6:  case GDK_KP_7:
  case GDK_KP_8:  case GDK_KP_9:
    if (appl_keypad) {
      p+=sprintf (p, "\033O%c", 'p' + (event->keyval - GDK_KP_0));
    } else {
      *p++ = '0' + (event->keyval - GDK_KP_0);
    }
    break;
  case GDK_KP_Enter:
    if (appl_keypad) {
      p+=sprintf (p, "\033OM");
    } else {
      *p++ = '\r';
    }
    break;
  case GDK_KP_Add:
    if (appl_keypad) {
      p+=sprintf (p, "\033Ok");
    } else {
      *p++ = '+';
    }
    break;
  case GDK_KP_Subtract:
    if (appl_keypad) {
      p+=sprintf (p, "\033Om");
    } else {
      *p++ = '-';
    }
    break;
  case GDK_KP_Separator:  /* aka KP Comma */
    if (appl_keypad) {
      p+=sprintf (p, "\033Ol");
    } else {
      *p++ = '-';
    }
    break;
  case GDK_KP_Decimal:
    if (appl_keypad) {
      p+=sprintf (p, "\033On");
    } else {
      *p++ = '.';
    }
    break;

  case GDK_KP_Begin:
    /* ? middle key of keypad */
  case GDK_Print:
  case GDK_Scroll_Lock:
  case GDK_Pause:
    /* control keys */
  case GDK_Shift_Lock:
  case GDK_Num_Lock:
  case GDK_Caps_Lock:
    /* ignore - for now FIXME: do something here*/
    break;
  case GDK_Control_L:
  case GDK_Control_R:
    break;
  case GDK_Shift_L:
  case GDK_Shift_R:
    break;
  case GDK_Alt_L:
  case GDK_Alt_R:
  case GDK_Meta_L:
  case GDK_Meta_R:
    break;
  case GDK_Mode_switch:
  case GDK_Multi_key:
    break;
  case ' ':
    /* maps single characters to correct control and alt versions */
    if (event->state & GDK_CONTROL_MASK)
      *p++=event->keyval & 0x1f;
    else if (event->state & GDK_MOD1_MASK)
      *p++=event->keyval + 0x80; /* this works for space at least */
    else
      *p++=event->keyval;
    break;
  default:
      if (event->length > 0){
	if (event->state & GDK_MOD1_MASK){
	   *p++ = '\033';
        }
	memcpy(p, event->string, event->length*sizeof(char));
	p += event->length;
      } else {
	handled = FALSE;
      }
      d(printf ("[%s,%d,%d]\n", event->string, event->length, handled));
  }
  if (handled && p>buffer) {
    vt_writechild(&vx->vt, buffer, (p-buffer));
    if (term->scroll_on_keystroke) zvt_term_scroll (term, 0);
  }

  return handled;
}

/* dummy default signal handler */
static void
zvt_term_child_died (ZvtTerm *term)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  /* perhaps we should do something here? */
}

/* dummy default signal handler for title_changed */
static void
zvt_term_title_changed (ZvtTerm *term, VTTITLE_TYPE type, char *str)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  /* perhaps we should do something here? */
}

/* raise the title_changed signal */
static void
zvt_term_title_changed_raise (void *user_data, VTTITLE_TYPE type, char *str)
{
  ZvtTerm *term = user_data;
  
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  gtk_signal_emit(GTK_OBJECT(term), term_signals[TITLE_CHANGED], type, str);
}

static void
vtx_unrender_selection (struct _vtx *vx)
{
  /* need to 'un-render' selected area */
  if (vx->selected)
    {
      vx->selstartx = vx->selendx;
      vx->selstarty = vx->selendy;
      vt_draw_selection(vx);	/* un-render selection */
      vx->selected = 0;
    }
}

/*
 * this callback is called when data is ready on the child's file descriptor
 *
 * Read all data waiting on the file descriptor, updating the virtual
 * terminal buffer, until there is no more data to read, and then render it.
 *
 * NOTE: this may impact on slow machines, but it may not also ...!
 */
static void
zvt_term_readdata (gpointer data, gint fd, GdkInputCondition condition)
{
  gboolean update;
  gchar buffer[4096];
  gint count, saveerrno;
  struct _vtx *vx;
  ZvtTerm *term;

  d( printf("zvt_term_readdata\n") );

  term = (ZvtTerm *) data;

  if (term->input_id == -1)
    return;

  update = FALSE;
  vx = term->vx;
  vtx_unrender_selection (vx);
  saveerrno = EAGAIN;

  vt_cursor_state (term, 0);
  while ( (saveerrno == EAGAIN) && (count = read (fd, buffer, 4096)) > 0)  {
    update = TRUE;
    saveerrno = errno;
    vt_parse_vt (&vx->vt, buffer, count);
  }

  if (update) {
    if (GTK_WIDGET_DRAWABLE (term)) {
      d( printf("zvt_term_readdata: update from read\n") );
      vt_update (vx, UPDATE_CHANGES);
    } else {
      d( printf("zvt_term_readdata: suspending update -- not drawable\n") );
    }
  } else {
    saveerrno = errno;
  }

  /* *always* turn the cursor back on */
  vt_cursor_state (term, 1);
  
  /* fix scroll bar */
  if (term->scroll_on_output) {
      zvt_term_scroll (term, 0);
  }

  /* flush all X events - this is really necessary to stop X queuing up
   * lots of screen updates and reducing interactivity on a busy terminal
   */
  gdk_flush ();

  /* read failed? oh well, that's life -- we handle dead children via
   * SIGCHLD
   */
  zvt_term_fix_scrollbar (term);
}

/**
 * zvt_term_feed:
 * @term: A &ZvtTerm widget.
 * @text: The text to feed.
 * @len:  The text length.
 *
 * This makes the terminal emulator process the stream of
 * characters in @text for @len bytes.  The text is interpreted
 * by the terminal emulator as if it were generated by a child
 * process.
 *
 * This is used by code that needs a terminal emulator, but
 * does not use a child process.
 */
void
zvt_term_feed (ZvtTerm *term, char *text, int len)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));
  g_return_if_fail (text != NULL);
  
  vt_cursor_state (term, 0);
  vtx_unrender_selection (term->vx);
  vt_parse_vt (&term->vx->vt, text, len);
  vt_update (term->vx, UPDATE_CHANGES);
  
  /* *always* turn the cursor back on */
  vt_cursor_state (term, 1);
  
  /* fix scroll bar */
  if (term->scroll_on_output)
    zvt_term_scroll (term, 0);
  
  /* flush all X events - this is really necessary to stop X queuing up
   * lots of screen updates and reducing interactivity on a busy terminal
   */
  gdk_flush ();
  
  /* read failed? oh well, that's life -- we handle dead children via
   *  SIGCHLD
   */
  zvt_term_fix_scrollbar (term);
}

static void
zvt_term_readmsg (gpointer data, gint fd, GdkInputCondition condition)
{
  ZvtTerm *term = (ZvtTerm *)data;

  /* I suppose I should bother reading the message from the fd, but
   * it doesn't seem worth the trouble <shrug>
   */
  if (term->input_id != -1) {
    gdk_input_remove (term->input_id);
    term->input_id=-1;
  }

  zvt_term_closepty (term);

  /* signal application FIXME: include error/non error code */
  gtk_signal_emit (GTK_OBJECT(term), term_signals[CHILD_DIED]);
}

/**
 * zvt_term_set_background:
 * @terminal: A &ZvtTerm widget.
 * @pixmap_file: file containing the pixmap image
 * @transparent: true if we want to run in transparent mode
 * @flags: A bitmask of background options:
 *   ZVT_BACKGROUND_SHADED, shade the transparency pixmap.
 *   ZVT_BACKGROUND_SCROLL, allow smart scrolling of the pixmap,
 *   ignored if transparency is requested.
 *
 * Sets the background of the @terminal.  If @pixmap_file and
 * @transparent are %NULL and %FALSE, then a standard filled background
 * is set.
 */
void
zvt_term_set_background (ZvtTerm *terminal, char *pixmap_file, 
			 int transparent, int flags)
{
  if (!(zvt_term_get_capabilities (terminal) & ZVT_TERM_PIXMAP_SUPPORT))  
    return; 

  /* if there no changes return */
  if(!((flags & ZVT_BACKGROUND_SHADED) == terminal->shaded &&
       safe_strcmp(pixmap_file,terminal->pixmap_filename)==0 &&
       transparent == terminal->transparent)) {
  
    if (terminal->background.pix) {
      gdk_pixmap_unref(terminal->background.pix);
      terminal->background.pix = NULL;
    }

    terminal->transparent = transparent;
    terminal->shaded = (flags & ZVT_BACKGROUND_SHADED) != 0;
    g_free (terminal->pixmap_filename);
    
    if (pixmap_file)
      terminal->pixmap_filename = g_strdup (pixmap_file);
    else
      terminal->pixmap_filename = NULL;

    /* load it, set it in the gc's */
    load_background (terminal);
  }

  /* determine the scroll-mode to use */
  if (transparent)
    terminal->vx->scroll_type=VT_SCROLL_NEVER;
  else if (terminal->pixmap_filename) {
    if (flags & ZVT_BACKGROUND_SCROLL)
      terminal->vx->scroll_type=VT_SCROLL_SOMETIMES;
    else
      terminal->vx->scroll_type=VT_SCROLL_NEVER;
  } else
    terminal->vx->scroll_type=VT_SCROLL_ALWAYS;
}

/*
 * callback rendering functions called by vt_update, etc
 */
static int
vt_cursor_state(void *user_data, int state)
{
  ZvtTerm *term;
  GtkWidget *widget;
  int old_state;

  widget = user_data;

  g_return_val_if_fail (widget != NULL, 0);
  g_return_val_if_fail (ZVT_IS_TERM (widget), 0);

  term = ZVT_TERM (widget);
  old_state = term->cursor_on;

  /* only call vt_draw_cursor if the state has changed */
  if (old_state ^ state) {
    if (GTK_WIDGET_DRAWABLE (widget))	{
      vt_draw_cursor(term->vx, state);
      term->cursor_on = state;
    }
  }
  return old_state;
}

void
vt_draw_text(void *user_data, struct vt_line *line, int row, int col, int len, int attr)
{
  GdkFont *f;
  struct _vtx *vx;
  ZvtTerm *term;
  GtkWidget *widget;
  int fore, back, or;
  GdkColor pen;
  GdkGC *fgc, *bgc;
  int overstrike=0;
  int dofill=0;
  int offx, offy, x, y;
  int i;
  uint32 c;
  struct _zvtprivate *zp=0;

  GdkFontPrivate *font_private;
  GdkWindowPrivate *drawable_private;
  GdkGCPrivate *gc_private;
  
  widget = user_data;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  if (!GTK_WIDGET_DRAWABLE (widget))
    return;

  term = ZVT_TERM (widget);

  vx = term->vx;

  if (len+col>vx->vt.width)
    len = vx->vt.width-col;

  /* rendering offsetx */
  x = col * term->charwidth;
  y = row * term->charheight + term->font->ascent;
  offx = widget->style->klass->xthickness + PADDING;
  offy = widget->style->klass->ythickness;
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  if (attr & VTATTR_BOLD)  {
    or = 8;
    f = term->font_bold;
    if (f==NULL) {
      f = term->font;
      overstrike = 1;
      if (zp && zp->bold_save) {
	gdk_draw_pixmap(zp->bold_save, term->fore_gc, GTK_WIDGET(term)->window,
			x + offx + len*term->charwidth, offy + row*term->charheight,
			0, 0, 1, term->charheight);
      }
    }
  } else  {
    or = 0;
    f = term->font;
  }

  fore = (attr & VTATTR_FORECOLOURM) >> VTATTR_FORECOLOURB;
  back = (attr & VTATTR_BACKCOLOURM) >> VTATTR_BACKCOLOURB;

  if (fore < 8)
    fore |= or;

  /* set the right colour in the appropriate gc */
  fgc = term->fore_gc;
  bgc = term->back_gc;

  /* for reverse, swap colours's */
  if (attr & VTATTR_REVERSE) {
    int tmp;
    tmp = fore;fore=back;back=tmp;
  }

  if (term->back_last != back) {
    pen.pixel = term->colors [back];
    gdk_gc_set_background (fgc, &pen);
    term->back_last = back;
  }
  
  if (term->fore_last != fore) {
    pen.pixel = term->colors [fore];
    gdk_gc_set_foreground (fgc, &pen);
    term->fore_last = fore;
  }

  /* optimise: dont 'clear' background if not in 
   * expose, and background colour == window colour
   * this may look a bit weird, it really does need to 
   * be this way - so dont touch!
   *
   * if the terminal is transparent we must redraw 
   * all the time as we can't optimize in that case
   *
   * This needs a re-visit due to recent changes for pixmap
   * support.
   */
#if 0
  printf("f=%d:%d ", fore,back);
  printf("e= %d ", term->in_expose);
  printf("b= %d ", vx->back_match);
  printf("t= %d ", term->transparent);
#endif

#if 1

  if (term->in_expose || vx->back_match == 0) {
    if ((term->transparent || term->pixmap_filename)
	&& back==17) {
      gdk_draw_rectangle (widget->window,
			  bgc, 1,
			  offx + col * term->charwidth,
			  offy + row * term->charheight,
			  len * term->charwidth,
			  term->charheight);
      d(printf("done fill "));
    } else {
      dofill=1;
      d(printf("do fill "));
    }
  }

#else

  if (term->in_expose || vx->back_match == 0) {
    if ((term->transparent || term->pixmap_filename)
	&& ( term->in_expose==0 && !vx->back_match )) {
      /*	&& !term->in_expose && back >= 17) {*/
      gdk_draw_rectangle (widget->window,
			  bgc, 1,
			  offx + col * term->charwidth,
			  offy + row * term->charheight,
			  len * term->charwidth,
			  term->charheight);
      d(printf("draw pixmap\n"));
    } else if ( (term->in_expose && back < 17)
		|| ( term->in_expose==0 && !vx->back_match )) {
      dofill=1;
      /* if we can get away with it, use XDrawImageString() to do
	 the background fill! */
      d(printf("fill via image\n"));
    } else {
      d(printf("do nothing\n"));
    }
  } else {
    d(printf("not clearing background in_expose = %d, back_match=%d\n", 
	     term->in_expose, vx->back_match));
    d(printf("txt = '%.*s'\n", len, text));
  }
#endif
  d(printf("txt = '%.*s'\n", len, text));

  font_private = (GdkFontPrivate*) f;
  drawable_private = (GdkWindowPrivate *)widget->window;
  gc_private = (GdkGCPrivate *)fgc;

  /* make sure we have the space to expand the text */
  if (zp->text_expand==0 || zp->text_expandlen<len) {
    zp->text_expand = g_realloc(zp->text_expand, len*sizeof(uint32));
    zp->text_expandlen = len;
  }

  /* convert text in input into the format suitable for output ... */
  switch (zp->fonttype) {
  case ZVT_FONT_1BYTE: {		/* simple single-byte font */
    char *expand = zp->text_expand;
    XFontStruct *xfont;

    for(i=0;i<len;i++) {
      c=VT_ASCII(line->data[i+col]);
      if (c>=256)
	c='?';
      expand[i]=c&0xff;
    }

    /* render characters, with fill if we can */
    xfont = (XFontStruct *) font_private->xfont;
    XSetFont(drawable_private->xdisplay, gc_private->xgc, xfont->fid);
    if (dofill) {
      XDrawImageString(drawable_private->xdisplay, drawable_private->xwindow,
		       gc_private->xgc, offx + x, offy + y, expand, len);
    } else {
      XDrawString(drawable_private->xdisplay, drawable_private->xwindow,
		  gc_private->xgc, offx + x, offy + y, expand, len);
    }
    if (overstrike)
      XDrawString(drawable_private->xdisplay, drawable_private->xwindow,
		  gc_private->xgc, offx + x + 1, offy + y, expand, len);
  }
  break;
  case ZVT_FONT_2BYTE: {
    XChar2b *expand16 = zp->text_expand;
    XFontStruct *xfont;

    /* this needs to check for valid chars? */
    for (i=0;i<len;i++) {
      c=VT_ASCII(line->data[i+col]);
      expand16[i].byte2=c&0xff;
      expand16[i].byte1=(c>>8)&0xff;
      /*printf("(%04x)", c);*/
    }
    /*    printf("\n");*/

    /* render 2-byte characters, with fill if we can */
    xfont = (XFontStruct *) font_private->xfont;
    XSetFont(drawable_private->xdisplay, gc_private->xgc, xfont->fid);
    if (dofill) {
      XDrawImageString16(drawable_private->xdisplay, drawable_private->xwindow,
			 gc_private->xgc, offx + x, offy + y, expand16, len);
    } else {
      XDrawString16(drawable_private->xdisplay, drawable_private->xwindow,
		    gc_private->xgc, offx + x, offy + y, expand16, len);
    }
    if (overstrike)
      XDrawString16(drawable_private->xdisplay, drawable_private->xwindow,
		    gc_private->xgc, offx + x + 1, offy + y, expand16, len);
  }
  break;
  /* this is limited to 65535 characters! */
  case ZVT_FONT_FONTSET: {
    wchar_t *expandwc = zp->text_expand;
    XFontSet fontset = (XFontSet) font_private->xfont;

    for (i=0;i<len;i++) {
      expandwc[i] = VT_ASCII(line->data[i+col]);
    }

    /* render wide characters, with fill if we can */
    if (dofill) {
      XwcDrawImageString(drawable_private->xdisplay, drawable_private->xwindow,
			 fontset, gc_private->xgc, offx + x, offy + y, expandwc, len);
    } else {
      XwcDrawString(drawable_private->xdisplay, drawable_private->xwindow,
		    fontset, gc_private->xgc, offx + x, offy + y, expandwc, len);
    }
    if (overstrike)
      XwcDrawString(drawable_private->xdisplay, drawable_private->xwindow,
		    fontset, gc_private->xgc, offx + x + 1, offy + y, expandwc, len);
  }
  }

  /* check for underline */
  if (attr&VTATTR_UNDERLINE) {
    gdk_draw_line(widget->window, fgc,
		  offx + x,
		  offy + y + 1,
		  offx + (col + len) * term->charwidth - 1,
		  offy + y + 1);
  }
  
  if (overstrike && zp && zp->bold_save) {
    gdk_draw_pixmap(GTK_WIDGET(term)->window,
		    term->fore_gc,
		    zp->bold_save,
		    0, 0,
		    x + offx + len*term->charwidth, offy + row*term->charheight,
		    1, term->charheight);
  }
}





void
vt_scroll_area(void *user_data, int firstrow, int count, int offset, int fill)
{
  int width, offx, offy;
  ZvtTerm *term;
  GtkWidget *widget;
  GdkEvent *event;
  GdkGC *gc;
  struct _zvtprivate *zp;
  
  widget = user_data;
  
  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  if (!GTK_WIDGET_DRAWABLE (widget)) {
    return;
  }

  term = ZVT_TERM (widget);
  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  d(printf("scrolling %d rows from %d, by %d lines, fill=%d\n",
	  count,firstrow,offset,fill));

  width = term->charwidth * term->vx->vt.width;
  offx = widget->style->klass->xthickness + PADDING;
  offy = widget->style->klass->ythickness;

  /* "scroll" area */
  gdk_draw_pixmap(widget->window,
		  term->scroll_gc, /* must use this to generate expose events */
		  widget->window,
		  offx, offy+(firstrow+offset)*term->charheight,
		  offx, offy+firstrow*term->charheight,
		  width, count*term->charheight);

  /* clear the other part of the screen */
  /* check fill colour is right */
  if (fill >= 17) {
    gc=term->back_gc;
  } else {
    gc=term->fore_gc;
    if (term->fore_last != fill) {
      GdkColor pen;
      
      pen.pixel = term->colors[fill];
      gdk_gc_set_foreground (term->fore_gc, &pen);
      term->fore_last = fill;
    }
  }

  /* this does a 'scrolling' pixmap */
  if (term->transparent || term->pixmap_filename) {
    zp->scroll_position = (zp->scroll_position + offset*term->charheight) % term->background.h;
    gdk_gc_set_ts_origin (term->back_gc, 0, -zp->scroll_position);
  }

  /* fill the exposed area with blank */
  if (offset > 0) {
    gdk_draw_rectangle(widget->window,
		       gc,
		       1,
		       offx, offy+(firstrow+count)*term->charheight,
		       term->vx->vt.width*term->charwidth, offset*term->charheight);
  } else {
    gdk_draw_rectangle(widget->window,
		       gc,
		       1,
		       offx, offy+(firstrow+offset)*term->charheight,
		       term->vx->vt.width*term->charwidth, (-offset)*term->charheight);
  }

  /* fix up the screen display after a scroll - ensures all expose events handled
     before continuing.
     This only seems to be needed if we have a pixmap scrolled */
  if (term->transparent || term->pixmap_filename) {
    while ((event = gdk_event_get_graphics_expose (widget->window)) != NULL) {
      gtk_widget_event (widget, event);
      if (event->expose.count == 0) {
	gdk_event_free (event);
	break;
      }
      gdk_event_free (event);
    }
  }
}

/**
 * zvt_term_set_del_key_swap:
 * @term:   A &ZvtTerm widget.
 * @state:  If true it swaps the del/backspace definitions
 * 
 * Sets the mode for interpreting the DEL and Backspace keys.
 **/
void
zvt_term_set_del_key_swap (ZvtTerm *term, int state)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));
  
  term->swap_del_key = state != 0;
}

/*
 * zvt_term_bell:
 * @term: Terminal.
 *
 * Generate a terminal bell.  Currently this is just a beep.
 **/
void
zvt_term_bell(void *user_data)
{
  gdk_beep();
}


/**
 * zvt_term_set_bell:
 * @term: A &ZvtTerm widget.
 * @state: New bell state.
 * 
 * Enable or disable the terminal bell.  If @state is %TRUE, then the
 * bell is enabled.
 **/
void
zvt_term_set_bell(ZvtTerm *term, int state)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  if (state)
    term->vx->vt.ring_my_bell = zvt_term_bell;
  else
    term->vx->vt.ring_my_bell = 0;
}

/**
 * zvt_term_get_bell:
 * @term: A &ZvtTerm widget.
 * 
 * get the terminal bell state.  If the bell on then %TRUE is
 * returned, otherwise %FALSE.
 **/
gboolean
zvt_term_get_bell(ZvtTerm *term)
{
  g_return_val_if_fail (term != NULL, 0);
  g_return_val_if_fail (ZVT_IS_TERM (term), 0);

  return (term->vx->vt.ring_my_bell)?TRUE:FALSE;
}

void
zvt_term_set_shadow_type(ZvtTerm  *term, GtkShadowType type)
{
  g_return_if_fail (term != NULL);
  g_return_if_fail (ZVT_IS_TERM (term));

  term->shadow_type = type;

  if (GTK_WIDGET_VISIBLE (term))
    gtk_widget_queue_resize (GTK_WIDGET (term));
}

/**
 * zvt_term_get_capabilities:
 * @term: A &ZvtTerm widget.
 * 
 * Description: Gets the compiled in capabilities of the terminal widget, for
 * now this is only pixmap support which %ZVT_TERM_PIXMAP_SUPPORT
 *
 * Returns: a bitmask of the capabilities
 **/
guint32
zvt_term_get_capabilities (ZvtTerm *term)
{
  Atom prop, prop2;
  guint32 out = ZVT_TERM_EMBOLDEN_SUPPORT|ZVT_TERM_PIXMAPSCROLL_SUPPORT;

  /* pixmap and transparency support */
  if (gdk_imlib_get_visual () == gtk_widget_get_default_visual ())
    out |= ZVT_TERM_PIXMAP_SUPPORT;

  /* check if we really have transparency support - i think this works MPZ */
  prop = XInternAtom(GDK_DISPLAY(), "_XROOTPMAP_ID", True);
  prop2 = XInternAtom(GDK_DISPLAY(), "_XROOTCOLOR_PIXEL", True);  
  if (prop != None || prop2 != None)
    out |= ZVT_TERM_TRANSPARENCY_SUPPORT;

  return out;
}

/**
 * zvt_term_get_buffer:
 * @term: Valid &ZvtTerm widget.
 * @len: Placeholder to store the length of text selected.  May be
 *       %NULL in which case the value is not returned.
 * @type: Type of selection.  %VT_SELTYPE_LINE, select by line,
 *       %VT_SELTYPE_WORD, select by word, or %VT_SELTYPE_CHAR, select
 *       by character.
 * @sx: Start of selection, horizontal.
 * @sy: Start of selection, vertical.  0 is the top of the visible
 *      screen, <0 is scrollback lines, >0 is visible lines (upto the
 *      height of the window).
 * @ex: End of selection, horizontal.
 * @ey: End of selection, vertical, as above.
 * 
 * Convert the buffer memory into a contiguous array which may be
 * saved or processed.  Note that this is not gauranteed to match the
 * order of characters processed by the terminal, only the order in
 * which they were displayed.  Tabs will normally be preserved in
 * the output.
 *
 * All inputs are range-checked first, so it is possible to fudge
 * a full buffer grab.
 *
 * Examples:
 *  data = zvt_term_get_buffer(term, NULL, VT_SELTYPE_LINE,
 *       -term->vx->vt.scrollbackmax, 0,
 *        term->vx->vt.height, 0);
 *  or, as a rule -
 *  data = zvt_term_get_buffer(term, NULL, VT_SELTYPE_LINE,
 *       -10000, 0, 10000, 0);
 *
 * Will return the contents of the entire scrollback and on-screen
 * buffers, remembering that all inputs are range-checked first.
 *
 *  data = zvt_term_get_buffer(term, NULL, VT_SELTYPE_CHAR,
 *       0, 0, 5, 10);
 *
 * Will return the first 5 lines of the visible screen, and the 6th
 * line upto column 10.
 * 
 * Return value: A pointer to a %NUL terminated buffer containing the
 * raw text from the buffer.  If memory could not be allocated, then
 * returns %NULL.  Note that it is upto the caller to free the memory,
 * using g_free(3c).  If @len was supplied, then the length of data is
 * stored there.
 **/
char *
zvt_term_get_buffer(ZvtTerm *term, int *len, int type, int sx, int sy, int ex, int ey)
{
  struct _vtx *vx;
  int ssx, ssy, sex, sey, stype, slen;
  uint32 *sdata;
  char *data;

  g_return_val_if_fail (term != NULL, 0);
  g_return_val_if_fail (ZVT_IS_TERM (term), 0);

  vx = term->vx;

  /* this is a bit messy - we save the current selection state,
   * override it, 'select' the new text, then restore the old
   *  selection state but return the new ...
   *  api should be more separated.  vt_get_block() is a start on this.
   */

  ssx = vx->selstartx;
  ssy = vx->selstarty;
  sex = vx->selendx;
  sey = vx->selendy;
  sdata = vx->selection_data;
  slen = vx->selection_size;
  stype = vx->selectiontype;

  vx->selstartx = sx;
  vx->selstarty = sy;
  vx->selendx = ex;
  vx->selendy = ey;
  vx->selection_data = 0;
  vx->selectiontype = type & VT_SELTYPE_MASK;

  vt_fix_selection(vx);

  /* this always currently gets the data as bytes */
  data = vt_get_selection(vx, 1, len);
  
  vx->selstartx = ssx;
  vx->selstarty = ssy;
  vx->selendx = sex;
  vx->selendy = sey;
  vx->selection_data = sdata;
  vx->selection_size = slen;
  vx->selectiontype = stype;

  return data;
}

/******************************/
/**** TRANSPARENT TERMINAL ****/


/* kind of stolen from Eterm and needs heavy cleanup */
static Window desktop_window = None;

static Window
get_desktop_window (Window the_window)
{
  Atom prop, type, prop2;
  int format;
  unsigned long length, after;
  unsigned char *data;
  unsigned int nchildren;
  Window w, root, *children, parent;
  
  prop = XInternAtom(GDK_DISPLAY(), "_XROOTPMAP_ID", True);
  prop2 = XInternAtom(GDK_DISPLAY(), "_XROOTCOLOR_PIXEL", True);
  
  if (prop == None && prop2 == None)
    return None;
  
#ifdef WATCH_DESKTOP_OPTION
  if (Options & Opt_watchDesktop)  {
    if (TermWin.wm_parent != None)
      XSelectInput(GDK_DISPLAY(), TermWin.wm_parent, None);
    
    if (TermWin.wm_grandparent != None)
      XSelectInput(GDK_DISPLAY(), TermWin.wm_grandparent, None);
  }
#endif
  
  for (w = the_window; w; w = parent) {
    if ((XQueryTree(GDK_DISPLAY(), w, &root, &parent, &children, &nchildren)) == False) 
      return None;
      
    if (nchildren) 
      XFree(children);

#ifdef WATCH_DESKTOP_OPTION
    if (Options & Opt_watchDesktop) {
      if (w == TermWin.parent) {
	TermWin.wm_parent = parent;
	XSelectInput(GDK_DISPLAY(), TermWin.wm_parent, StructureNotifyMask);
      } else if (w == TermWin.wm_parent) {
	TermWin.wm_grandparent = parent;
	XSelectInput(GDK_DISPLAY(), TermWin.wm_grandparent, StructureNotifyMask);
      }
    }
#endif
    
    if (prop != None) {
      XGetWindowProperty(GDK_DISPLAY(), w, prop, 0L, 1L, False, AnyPropertyType,
			 &type, &format, &length, &after, &data);
    } else if (prop2 != None) {
      XGetWindowProperty(GDK_DISPLAY(), w, prop2, 0L, 1L, False, AnyPropertyType,
			 &type, &format, &length, &after, &data);
    } else  {
      continue;
    }
    
    if (type != None) {
      return (desktop_window = w);
    }
  }
  
  return (desktop_window = None);
}

static Pixmap
get_pixmap_prop (Window the_window, char *prop_id)
{
  Atom prop, type;
  int format;
  unsigned long length, after;
  unsigned char *data;
  
  /*this should be changed when desktop changes I guess*/
  if(desktop_window == None)
    desktop_window = get_desktop_window(the_window);
  if(desktop_window == None)
    desktop_window = GDK_ROOT_WINDOW();
  
  prop = XInternAtom(GDK_DISPLAY(), prop_id, True);
  
  if (prop == None)
    return None;
  
  XGetWindowProperty(GDK_DISPLAY(), desktop_window, prop, 0L, 1L, False,
		     AnyPropertyType, &type, &format, &length, &after,
		     &data);

  if (type == XA_PIXMAP)
    return *((Pixmap *)data);

  return None;
}

static GdkPixmap *
load_pixmap_back(char *file, int shaded)
{
  GdkPixmap *pix;
  GdkImlibColorModifier mod;
  GdkImlibImage *iim;
  
  if(!file)
    return NULL;
  
  iim = gdk_imlib_load_image(file);
  if(!iim)
    return NULL;
  mod.contrast=256;
  mod.gamma=256;
  if(shaded)
    mod.brightness=190;
  else
    mod.brightness=256;
  
  gdk_imlib_set_image_modifier(iim,&mod);
  gdk_imlib_render(iim, iim->rgb_width,iim->rgb_height);
  pix = gdk_imlib_move_image(iim);
  gdk_imlib_destroy_image(iim);
  
  return pix;
}

static GdkPixmap *
create_shaded_pixmap (Pixmap p, int x, int y, int w, int h)
{
  GdkPixmap *pix,*pp,*tmp;
  GdkImlibColorModifier mod;
  GdkImlibImage *iim;
  GdkGC *tgc;
  int width,height,depth;
  
  if (p == None)
    return NULL;

  pp = gdk_pixmap_foreign_new(p);
  gdk_window_get_geometry(pp,NULL,NULL,&width,&height,&depth);

  if (width<x+w || height<y+h || x<0 || y<0) {
    tgc = gdk_gc_new(pp);
    tmp = gdk_pixmap_new(pp,w,h,depth);
    
    gdk_gc_set_tile (tgc, pp);
    gdk_gc_set_fill (tgc, GDK_TILED);
    gdk_gc_set_ts_origin(tgc, -x, -y);
    gdk_draw_rectangle (tmp, tgc, TRUE, 0, 0, w, h);
    gdk_gc_destroy(tgc);
    
    iim = gdk_imlib_create_image_from_drawable(tmp,
					       NULL, 0, 0, w, h);
    gdk_pixmap_unref(tmp);
  } else {
    iim = gdk_imlib_create_image_from_drawable(pp,
					       NULL, x, y, w, h);
  }
  gdk_xid_table_remove (GDK_WINDOW_XWINDOW(pp));
  g_dataset_destroy (pp);
  g_free (pp);

  if(!iim)
    return NULL;

  mod.contrast = 256;
  mod.brightness = 190;
  mod.gamma = 256;
  gdk_imlib_set_image_modifier (iim, &mod);
  gdk_imlib_render (iim, iim->rgb_width, iim->rgb_height);
  pix = gdk_imlib_move_image (iim);
  gdk_imlib_destroy_image (iim);
  
  return pix;
}

/* sets the selected pixmap as our background fill */
static void
load_background (ZvtTerm *widget)
{
  int x,y;
  Window childret;
  ZvtTerm *term;
  GdkGC *bgc;
  Pixmap p;
  int width,height;
  struct _zvtprivate *zp;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (ZVT_IS_TERM (widget));

  term = ZVT_TERM (widget);
  bgc = term->back_gc;

  if (bgc == 0)
    return;

  if (term->pixmap_filename==0 && !term->transparent) {
    GdkColor pen;
    gdk_gc_set_fill (bgc, GDK_SOLID);
    pen.pixel = term->colors[17];
    gdk_gc_set_foreground (bgc, &pen);
    if (term->background.pix) {
      gdk_pixmap_unref(term->background.pix);
      term->background.pix = 0;
    }
    return;
  }

  zp = gtk_object_get_data (GTK_OBJECT (term), "_zvtprivate");

  /* we will be doing a background pixmap, not transparency */
  if (term->pixmap_filename) {

    if (!term->background.pix) {
      term->background.pix =
	load_pixmap_back (term->pixmap_filename, term->shaded);
      /* must reset origin */
      gdk_gc_set_ts_origin(bgc,0, 0);
    }

    if (!term->background.pix && !term->transparent) {
      GdkColor pen;
      gdk_gc_set_fill (bgc, GDK_SOLID);
      pen.pixel = term->colors[17];
      gdk_gc_set_foreground (bgc, &pen);
      g_free (term->pixmap_filename);
      term->pixmap_filename = NULL;
      gdk_gc_set_fill (bgc, GDK_SOLID);
      return;
    } else if (term->background.pix) {
      GdkWindowPrivate *wp = (GdkWindowPrivate *)term->background.pix;

      /* free the transparency pixmap, if it exists */
      free_transpix(term);

      term->background.w = wp->width;
      term->background.h = wp->height;
      term->background.x = wp->x;
      term->background.y = wp->y;

      d(printf("height = %d\n", wp->height));

      gdk_gc_set_tile (bgc, term->background.pix);
      gdk_gc_set_ts_origin (term->back_gc, 0, -zp->scroll_position);
      gdk_gc_set_fill (bgc, GDK_TILED);
      return;
    }
 
    /* if we can't load a pixmap and transparency is set,
     * default to the transparency setting
     */
  }
  
  p = get_pixmap_prop (GDK_WINDOW_XWINDOW(GTK_WIDGET(widget)->window), "_XROOTPMAP_ID");
  if (p == None) {
    GdkColor pen;
    term->transparent = 0;
    gdk_gc_set_fill (bgc, GDK_SOLID);
    pen.pixel = term->colors[17];
    gdk_gc_set_foreground (term->back_gc, &pen);
    return;
  }
  
  XTranslateCoordinates (
      GDK_WINDOW_XDISPLAY (GTK_WIDGET(widget)->window),
      GDK_WINDOW_XWINDOW (GTK_WIDGET(widget)->window),
      GDK_ROOT_WINDOW (),
      0, 0,
      &x, &y,
      &childret);

  gdk_window_get_size(GTK_WIDGET(widget)->window, &width, &height);
  
  /* dont update if we're actually not visible? */
  if (x<-width || y<-height) {
    d(printf("oi, you can't see me, why ask me to redraw!?\n"));
    return;
  }

  if ((term->background.pix == NULL && term->shaded)
      || (zp->transpix == NULL && !term->shaded)
      || term->background.x != x
      || term->background.y != y
      || term->background.w != width
      || term->background.h != height) {

    term->background.x = x;
    term->background.y = y;
    term->background.w = width;
    term->background.h = height;

    /* free any pixmaps we already have */
    if (term->background.pix) {
      gdk_pixmap_unref(term->background.pix);
      term->background.pix = 0;
    }
    free_transpix(term);

    if (term->shaded) {

      term->background.pix = create_shaded_pixmap (p, x, y, width, height);
      gdk_gc_set_ts_origin (term->back_gc, 0, 0);
      gdk_gc_set_tile (bgc, term->background.pix);

      gdk_gc_set_fill (bgc, GDK_TILED);
    } else {
      GdkPixmap *pp;
      
      d(printf("loading background at %d,%d\n", x, y));

      /*non-shaded is simple?*/
      pp = gdk_pixmap_foreign_new(p);
      
      gdk_gc_set_tile (bgc, pp);
      gdk_gc_set_ts_origin(bgc,-x,-y);
      zp->transpix = pp;
      gdk_gc_set_fill (bgc, GDK_TILED);
    }
  } else {
    d(printf("background hasn't moved, leaving\n"));
  }
}

static gint
safe_strcmp(gchar *a, gchar *b)
{
  if (!a && !b)
    return 0;

  if (a && b)
    return strcmp(a,b);

  return 1;
}
