The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
#!/usr/bin/perl -w
# $Source: /home/keck/gen/RCS/xterms,v $
# $Revision: 8.11 $$Date: 2007/07/05 17:00:30 $
# Contents
#   1 standard   4 args/1      7 config    10 display       13 notes
#   2 constants  5 screenname  8 args/2    11 xenvironemnt  14 perldoc
#   3 cmd        6 font        9 geometry  12 main          15 pod

# ----------------------------------------------------------------------

#1# standard

use strict;
use warnings;

(my $cmd = $0) =~ s%.*/%%;

sub usage { print "Usage: $cmd -help\n"; }

sub quit { (@_) ? print STDERR "$cmd quitting: @_\n" : &usage; exit 1 }

my $HOME = $ENV{HOME};

(my $user = `id`) =~ s/^[^(]*\(([^)]+)\).*\n/$1/;

chdir $HOME;

use X11::XTerms;
my $localhost = X11::XTerms->localhost;

use Data::Dumper;

# ----------------------------------------------------------------------

#2# constants

# see font/1, xenvironment

my $xterms = 'xterms';

# ----------------------------------------------------------------------

#3# cmd

(my $remotehost = $cmd) =~ tr/A-Z/a-z/ unless $cmd =~ /^$xterms$/i;
my $uppercase = $cmd =~ /[A-Z]/;

# ----------------------------------------------------------------------

#4# args/1

my %prefs;

my $BIG;
my $C;
my $big;
my $dotfile;
my $display = 1;
my $list;
my $name;
my $scrollbar;
my $show;
my $sleep;
my $u;
my $U;
my $xcmd = 'xterm';

while (@ARGV) {
  $_ = shift;
  &perldoc() if /^-+(man|help)/;
  $sleep = $1, next if /^-(\d+)/;
  $big = 1, next if /^-b/;
  $BIG = 1, next if /^-B/;
  $dotfile = shift, next if /^-c/;
  $C = '-C', next if /^-C/;
  $ENV{DISPLAY} = shift, next if /^-d/;
  $display = 0, next if /^-D/;
  $prefs{bold} = shift, next if /^-fb/;
  $prefs{normal} = shift, next if /^-f/;
  $prefs{height} = shift, next if /^-he/;
  $remotehost = shift, next if /^-h/;
  $list = 1, next if /^-l$/;
  $prefs{name} = shift, next if /^-n/;
  $prefs{rcmd} = 'rlogin', next if /^-rl/;
  $prefs{rcmd} = 'rsh', next if /^-rs/;
  $scrollbar = 1, next if /^-sc/;
  $show = 1, next if /^-sh/;
  $prefs{rcmd} = 'ssh', next if /^-ss/;
  $prefs{rcmd} = 'telnet', next if /^-t/;
  $prefs{rcmd} = 'TELNET', next if /^-T/;
  $u = 1, next if /^-u/;
  $U = 1, next if /^-U/;
  quit("illegal option: $_") if /^-/ && !/^-+$/;
  unshift @ARGV, $_;
  last;
}

# ----------------------------------------------------------------------

#5# screenname

$ENV{DISPLAY} = ':0' unless defined $ENV{DISPLAY};

use X11::Screens;

my $screen = X11::Screens->current; # croaks on failure

my $screenname = $screen->name;

quit("\$screen->name failed") unless $screenname;

# ----------------------------------------------------------------------

#6# font

# lots of notes in 7.3
# +vim +etc2

my $unicode = '-*-*-medium-r-*--8-*-*-*-c-50-iso10646-*';

my $Unicode = '-*-*-medium-r-*--13-*-*-*-c-60-iso10646-*';

my $bigunicode =
  '-misc-fixed-medium-r-normal--15-140-75-75-c-90-iso10646-1';

my $BIGunicode =
  '-sony-fixed-medium-r-normal--24-170-100-100-c-120-iso8859-1';

# bold not handled yet ...
unless (defined $prefs{normal}) {
  if ($u || $U || $screen->unicode) {
    $prefs{normal} =
      $BIG ? "$BIGunicode -u8" :
      $big ? "$bigunicode -u8" :
      $U ? "$Unicode -u8" :
      $u ? "$unicode -u8" :
      undef;
  } else {
    $prefs{normal} =
      $BIG ? '12x24' :
      $big ? '9x15' :
      undef;
  }
}

# ----------------------------------------------------------------------

#7# config

if (defined $dotfile) {
  quit("no such file: $dotfile") unless -f $dotfile;
} else {
  $dotfile = "$HOME/.$xterms";
}

if (-f $dotfile) {
  if ($list) {
    my $pager = $ENV{PAGER} ? $ENV{PAGER} : 'more';
    exec "$pager $dotfile";
  }
  my $config = X11::XTerms->new(file => $dotfile);
  %prefs = %{$config->prefs(\%prefs, $remotehost)};
}

# needs to stay '' if defined to be '' in .xterms (cu for ipaq) ...
my $loginhost =
  defined $prefs{address} ? $prefs{address} :
  defined $prefs{between} ? $prefs{between} :
  $remotehost;

if ($loginhost) {
  $loginhost = "$loginhost $prefs{port}" if $prefs{port};
  unless ($prefs{user}) {
    if ($loginhost =~ /(.*)@(.*)/) {
      $prefs{user} = $1;
      $loginhost = $2;
    } else {
      $prefs{user} = $user;
    }
  }
  $prefs{rcmd} = $uppercase ? 'SSH' : 'ssh' if !$prefs{rcmd};
  $prefs{rcmd} .= " -l $prefs{user}"
    if $prefs{rcmd} =~ /rsh|rlogin|ssh/;
}

# ----------------------------------------------------------------------

#8# args/2

quit("no places specified") unless @ARGV;
# for ion, where -geometry ignored, allow '-', '--', etc

my $i = 0;
my @xterms;

for (@ARGV) {
  my @places = ();
  my @ichars = ();
  my $last;
  if (/^-+$/) {
    @places = split '';
  } elsif (/(.+)\.(.+)/) {
    my @ranges = split '', $1; # position chars
    my @iranges = split '', $2; # instance chars
    while (@ranges)  {
      local $_ = shift @ranges;
      push(@places, $_), $last = $_, next unless /-/;
      $_ = shift @ranges;
      my @extra = $last .. $_;
      shift @extra;
      push @places, @extra;
    }
    while (@iranges)  {
      local $_ = shift @iranges;
      push(@ichars, $_), $last = $_, next unless /-/;
      $_ = shift @iranges;
      my @extra = $last .. $_;
      shift @extra;
      push @ichars, @extra;
    }
    quit("more places than names in '$_'") if @places > @ichars;
    push(@places, $places[-1]) while @places < @ichars;
  } else {
    my @ranges = split('', $_);
    while (@ranges)  {
      $_ = shift @ranges;
      push(@places, $_), $last = $_, next unless /-/;
      $_ = shift @ranges;
      my @extra = $last .. $_;
      shift @extra;
      push @places, @extra;
    }
  }
  for (@places) {
    my $ichar = shift @ichars;
    my $id = sprintf("%05d", $$) . sprintf("%02d", $i++);
    my $xterm = {
      place => $_,
      id => $id,
      instance => (
        defined $name ? $name :
        defined $remotehost ? $remotehost :
	'xterm'
      ) . "/$ichar",
      ichar => $ichar,
    };
    push @xterms, $xterm;
  }
}

# print Dumper(\@xterms); exit;

# ----------------------------------------------------------------------

#9# geometry

my %clients = %{$screen->clients};

# ----------------------------------------------------------------------

#10# display

# notes in 7.3

$ENV{DISPLAY} = "$localhost:$1" if
  defined $remotehost &&
  $ENV{DISPLAY} =~ /^:(.*)/ &&
  $prefs{rcmd} =~ /^rsh/;

# ----------------------------------------------------------------------

#11# xenvironemnt

my $xenvironment =
  $prefs{noxenv} ? undef :
  $ENV{XENVIRONMENT} ? $ENV{XENVIRONMENT} :
  -f "$HOME/.Xdefaults.d/$screenname" ? "$HOME/.Xdefaults.d/$screenname" :
  undef;

# ----------------------------------------------------------------------

#12# main

my $remote =
  defined $prefs{rcmd} && $prefs{rcmd} =~ /^rsh/; # xterm remote

for (@xterms) {
  my $place = $_->{place};
  my $client = $clients{$place}; # empty for $place eq '-'
  my %screen;
  $screen{font} = $screen->font;
  my %char;
  $char{geometry} = $client->geometry('chars');
  $char{normal} = $client->font('normal');
  $char{bold} = $client->font('bold');
  my $geometry = $char{geometry};
  $geometry =~ s/x\d+/x$prefs{height}/ if $prefs{height};
  my $normal =
    $prefs{normal} ? $prefs{normal} :
    $char{normal} ? $char{normal} :
    $screen{font} ? $screen{font} :
    undef;
  my $bold =
    $prefs{bold} ? $prefs{bold} :
    $char{bold} ? $char{bold} :
    $normal ? $normal :
    undef;
  my $id = $_->{id},
  my $instance = $_->{instance},
  my $ichar = $_->{ichar},
  my @command;
  push @command, "DISPLAY=$ENV{DISPLAY}" if $display && $remote;
  push @command, "XENVIRONMENT=$xenvironment" if defined $xenvironment;
  push @command,
    'PATH=$PATH:/usr/local/bin:/usr/openwin/bin:/usr/X11R6/bin'
    if $remote;
  push @command, "XTERMID=$id"; # for scrollbuffer [+xterm]
  push @command, $xcmd, qw(-ut -sl 8192);
  push @command, $C if $C;
  push @command, '-fn', $normal if $normal;
  push @command, '-sb', if $scrollbar;
  push @command, '-fb', $bold if $bold;
  push @command, '-name', $instance;
  push @command, '-geometry', $geometry if $geometry;
  my $command;
  if ($remote) {
    $command = join(' ', @command);
    quit "\$loginhost undefined" unless $loginhost;
    $command = qq{$prefs{rcmd} $loginhost '$command >/dev/null 2>&1 &'};
  } else {
    push @command, '-n', $remotehost if $remotehost;
    push @command, '-e', $prefs{rcmd} if defined $loginhost;
    push @command, $loginhost if $loginhost;
    $command = join(' ', @command, '&');
  }
  if ($show) { print $command, "\n" } else { system $command }
  sleep $sleep if $sleep;
}

# ----------------------------------------------------------------------

#13# notes

# XX84-7
# +xdm +xwin/config +mandrake +erx +iplab/configs
# +etc2 +xterm2

# 1.3
#   reduced '-title $remotehost -n $remotehost -name $iman' to '-name $iman '
# 1.4
#   -k
#   a-d
# 1.5
#   -telnet, -TELNET, -font
#   classes
# 1.12
#   +abbreviation
# 1.17
#   .bash/c works with telnet
# 2.1
#   having got masquerading working
#   dropped gen/readmes & gen/classes
#   dropped -name
#   can use rxosh with places
#   +abb moved into place arguments
#   works from ramidus to chook, not to laptop
# 2.3
#   cleaned up
#   laptop OK
# 2.5
#   restored -name because 139.130.239.254 too long
#   added '' keys to .xterms for defaults
#   improved .xterms format
#   improved .xterms processing
#   iman internal rather than gen/iman (easier error handling)
# 2.6
#   logic simplified
#   usage much revised
# 2.8
#   moved hardwired r => readme etc out into .xterms
#   usage much changed
# 2.10
#   redesigned .xterms
#   iman explicit in .xterms rather than derived from variable name
#   allowed for inboxes
# 2.12
#   added 'between' for isp1
# 2.14
#   added keck@ermintrude & 'user' for ermintrude
# 2.15
#   passed XENVIRONMENT to remote host, adding /nfs/host if missing
#   already done by gen/det, forgotten here
# 2.16
#   drastic changes
#   logic maybe not recovered
#   at same time removed nearly all logic from gen/menu login proc
#   matches gen/menu 5.41
#   all done in .xterms now
#   here ...
#     dropped -local & -remote
#     added -rsh
#   .xterms ...
#     added address
#     dropped boolean values keys rlogin, TELNET, etc
#     added string valued key rcmd
#   remote iff -rsh or rcmd eq rsh
#   so now script arguments not one-to-one with dotfile keys
# 2.23
#   -C
# 2.28
#   -sl 1024 -> 8192
# 2.34
#   castor -k
# 2.35
#   -height & height attribute (for ced)
# 2.38
#   -u & $xwin->{unicode} [+vim]
# 2.40
#   $wm from .wmpid (.xinitrc, gen/wm)
#   doesn't work
# 2.43
#   pass XENVIRONMENT=$HOME rather than /nfs/...
#   +xterm
# 2.44
#   $wm works here (maybe not in .xinitrc & gen/wm)
#   $maxname changed from 13 to 20 (using twm ... timbaz broken)
#     ditto in gen/twmrc & gen/Xdefaults
#     should be in .xwin
# 2.45
#   $maxname from .xwin & renamed to $chars
# 2.46
#   $chars overused, so above renamed to $twmchars
# 3.1
#   same as 2.46
#   +xwin/config
# 3.2
#   works with new .xwin
# 3.4
#   fixed $wmpid to work with .xinitrc
# 4.1
#   Xwin.pm
# 5.1
#   replaced Xwin by Screen
# 5.3
#   noxenvironment [+mandrake]
# 6.1
#   got rid of detach (not in trl0.tgz)
#   worked out how to stop rsh's waiting
#   got rid of -k ... guess in detach case rsh's were waiting ... had
#     thought not, but can't see how -k worked otherwise
#   check that $loginhost defined for remote case ... not sure of old
#     logic
#   here() & there() simpler
#   dunno that ancient twisting & turning all necessary or adequate
# 6.3
#   iPAQ serial restored ... realize depended on address => '' causing
#     $loginhost to be defined but ''
# 6.5
#   added -n $remotehost to there() to set iconname for routers (only
#     transient for hosts that use xdeco)
# 6.6
#   -show
#   deleted old -e usage entry ... relic of detach
#   deleted gen/classes usage entry ... seems to have been retired to
#     RCS years ago
# 6.7
#   deleted LC_CTYPE=$lang because of font errors [+dia]
#   originally for unicode: +vim
#   $lang still set to fa_IR above & with -u, no reason, but not used
# 6.10
#   set ICONWIDTH for xdeco
#   renamed $twmchars to $iconwidth
# 6.12
#   dropped farsi
# 6.13
#   -sleep
# 6.15
#   '-' for unspecified geometry
# 6.17
#   pod
#   adjusted order of getting preferences, so with
#       xxx => { '' => ...
#       '' => { yyy => ... }
#     first overrides second when connecting from xxx to yyy
#   makemaker ... make test fails ...
#     OK if delete enough of blib/script/xterms
# 6.27
#   changed default from rsh to ssh
# 6.33
#   -b (see fonts)
# 6.45
#   location.instance begun [+taskbar1]
# 6.46
#   ~/.tmgr
# 6.47
#   WM_INSTANCE
# 6.50
#   per-xterm fonts
#   merged &here and &there into main
# 6.54
#   renamed .tmgr to .xup
# 6.55
#   having trouble with xchar<->instance [+taskbar1]
#   simpler for mo to have some redundancy
#   have been thinking of splitting config again anyway
#   moved .xterms to .xterms/hosts
#   made .xterms/chars ... currently also have .xchars
# 6.57
#   $xterm->geometry & $xterm->font
# 7.x
#   +taskbar7
# 8.x
#   XTerms.pm
#   +taskbar8
# 8.6
#   dropped WM_INSTANCE & WM_ICHAR [+taskbar9]
# 8.8
#   moved XTerms to X11::Xterms

# $Revision: 8.11 $

# ----------------------------------------------------------------------

#14# perldoc

# gen/makemaker 'make test' fails: +makemaker

sub perldoc {
  my ($perldoc, $less);
  for (split /:/, $ENV{PATH}) {
    $perldoc = "$_/perldoc" if -x "$_/perldoc";
    last if $perldoc; next if $less;
    $less = "$_/less" if -x "$_/less";
  }
  if ($perldoc) {
    $ENV{LESSCHARSET} = 'latin1';
    exec $perldoc, $0
  } elsif ($less) {
    exec $less, '+/^# Sorry.*', $0
  } else {
    print
      "Sorry, there's no perldoc(1) or even less(1) in your \$PATH\n" .
      "The documentation can be found at the end of $0\n";
    exit 1
  }
}

__END__

# ----------------------------------------------------------------------

#15# pod

# sub usage in standard

# Sorry, there's no perldoc in your $PATH, so here's the raw pod

=head1 NAME

xterms - run multiple xterms and remote logins

=head1 SYNOPSIS

 xterms [-host host] [-name name] \
      [-rsh] [-rlogin] [-telnet] [-TELNET] [-ssh] [-height height] \
      [-display display] [-fn font] [-u|-U] [-C] [-sleep] \
      [-c config] \
      where_and_what where_and_what ...

=head1 DESCRIPTION

Does one of (first is default) ...

 * runs local xterms at specified places on the display,
   & logs into the specified host (usually) in all of
   them via ssh (default) or telnet or TELNET or rsh

 * runs remote xterms via ssh to the specified host (usually)
   at specified places on the display (remember .rhosts &
   .Xauthority)

It can also be used simply to start some xterms.

All the arguments in the 3rd row above are passed to the xterms.

A where_and_what argument specifies (indirectly) the geometry of the
xterms and (optionally) some environment for the xterms.

Much of the behaviour of xterms is controlled by the config file
$HOME/.xterms, or as specified with -c

Finally $HOME/.screens is used, via the Perl module X11::Screen.pm, which has
its own documentation.

The (xterms) config file is a perl fragment returning a reference to a
hash (associative array).  Most keys are hostnames, but the empty key
('') and the key containing just an underscore have special meanings.
The empty key means the default host.

=head1 HOSTNAME CONFIG FILE ENTRIES

For hostname & empty keys, the value is again a reference to a hash with
either hostname or empty keys.  The outer hostname is the name of the
local host (where xterms is run), and the inner hostname the name of the
remote host (as specified in the -host parameter or the name used to run
$cmd).  The inner values are hashrefs specifying properties of the login
specific to the particular local & remotes hosts.  The keys recognized
are:

 address  IP address, in case the remote host has no name
 between  log into this host instead
 font     used for xterm argument
 height   xterm height (see below)
 name     xterm instance name (see below)
 noxenv   XENVIRONMENT not set for xterm
 port     instead of normal telnet port
 rcmd     telnet or TELNET or rlogin or rsh or ssh (default)
 user     remote user name, if different to local

The point of 'between' is that you may only be able to reach
the desired host from an intermediate host.

Several of these correspond to command line arguments.

See below for how empty hostnames are handled.

=head1 WHERE

In the simplest cases the where_and_what argument only specifies
where.  It is, or expands to, a string of characters each of which
specifies an xterm geometry via $HOME/.screens (above).

For example, the command

	xterms a-d

starts 4 xterms, with the geometry specified for the keys a, b, c, d in
$HOME/.screens.

The window height can be overridden by -height or a height attribute in
the config file.

If where_and_what consists solely of dashes (-) then one xterm is
created for each dash, with unspecified geometry.  For no good reason,
this is hardwired into xterms (maybe could have been left to
$HOME/.screens).

=head1 WHAT

The underscore key in the config file allows for more information
being provided by where_and_what arguments.

Here is an example:

  xxx
        xterms aar bbx cci

will start 6 xterms, 2 with geometry a, 2 with geometry b, & 2 with
geometry c.  The first 2 will have the environment variable CLASS set to
readme, and the arguments '-name --classes--'.  The second 2 are
similar.  The 3rd 2 will have the environment variable INBOXES set to c
(from %p, p for place), & the arguments '-name ---inboxes---'.

The '+' in the example is treated as a separator, the where_and_what
argument being split into 2 words.  The 1st word is handled as above,
and the second is set as the value of the environment variable ABB.

The command

        xterms ar+www

will start an xterm with geometry a, with CLASS set to readme
and the environment variable ABB set to www (from %w, w for word).

=head1 XTERM INSTANCE NAME

The application name (xterm -name) (which is used for example by twm for
icon manager names) is generated by padding out a base name to $maxname
characters with dashes before & after.  The base name is decided rather
historically as follows.  For xterms associated with an environment
setting character, the base name is the name (not value) of the
environment variable.  If -name is used, then all other base names have
the name thus specified.  If not, then the remote host name is used,
failing which 'xterm'.

The iconname is explicitly set to the -name value, lacking which to the
remote host name, if any.  The shell can override this by sending xterm
control sequences, so this is most useful when the remote host is a
router.  If there is no -name or remote host, then nothing is done,
leaving it to xterm, which sets it to 'telnet' or such.

=head1 DISPLAY

The default DISPLAY is taken from the environment, or :0.0, or xxx:0.0
where xxx is the local host name, etc.  Normally passed to remote host,
but not with -D.

=head1 REMOTE HOST

If no host is specified then no rlogin or such is done.  The current
directory is changed to $HOME before the xterms are started.

If the command is called by a name different to xterms then it's taken
to be the host name (cf Note below).

An exception to the above is that if the entry in the config file has a
'between' key, whose argument is a host name, then the rlogin etc is
done to that host instead.  This is normally used when the final target
host is only reachable from an intermediate (the 'between' value).  The
rlogin or such from the intermediate host to the final host is not done
by xterms.

=head1 USER

The host argument can have the form user@host.
The remote user name can also be set in the config file.

=head1 FONT

The font is passed as the parameter of xterm's -fn flag.
It can be set in the config file (see above) or on the command line.

The defaults are:

 6x13
 -misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1

The latter (unicode) is used with -U or if specified in $HOME/.xscreens
(via X11::Screen).  In this case, -u8 is passed to xterm.  With -u a smaller
unicode font is used, but it's hard to read.

With -b (big font), defaults are:

 9x15
 -misc-fixed-medium-r-normal--15-140-75-75-c-90-iso10646-1

=head1 SLEEP

With for example -2 there's a pause of 2 seconds between starting each
xterm.  The reason for this is that they are started in background, & on
a slow machine the window manager can occasionally learn of them out of
order, which may be undesirable.

=head1 OPTION ABBREVIATION

All the options can be abbreviated to one letter, or 2 for -rlogin &
-remote.

=head1 DEFAULT ATTRIBUTES

The config file can specify some defaults (rlogin/telnet/TELNET/ssh,
name, font) through empty keys ('') in place of hostnames, as for
example in ...

      { ...
	local1 => {
	  remote1 => { ... },
          remote2 => { ... },
          '' => { ... }
	},
        local2 => {
	  remote1 => { ... },
	  remote3 => { ... },
	  '' => { ... }
	},
        '' => {
	  remote2 => { ... },
	  remote3 => { ... },
	  '' => { ... }
	},
        ...
      }

Here local1, remote1, etc are hostnames, & the { ... } contain attribute
definitions.

The order of preference for taking attribute values from the config file
is:

  (1) both local & remote hostnames nonempty
  (2) remote hostname nonempty, local hostname empty
  (3) local hostname nonempty, remote hostname empty
  (4) both local & remote hostname empty

That is, for a given local & remote host & given attribute, the value
used will be from an entry of type (1) if given there, otherwise from an
entry of type (2), etc.

=head1 CONFIG FILE EXAMPLE

 { '_' => {
     chars => {
       r => { var => 'CLASS', val => 'readme', man => 'classes', },
       x => { var => 'CLASS', val => 'x', man => 'classes', },
       s => { var => 'CLASS', val => 'script', man => 'classes', },
       h => { var => 'CLASS', val => 'html', man => 'classes', },
       o => { var => 'CLASS', val => 'odd', man => 'classes', },
       i => { var => 'INBOXES', val => '%p', man => 'inboxes', },
     },
     words => {
       '+' => { var => 'ABB', val => '%w', },
     },
   },
   ram => {
     pollux => { rcmd => 'rsh', },
     '' => { rcmd => 'telnet', },
   },
   '' => {
     lonsdale => { address => '139.130.239.254', rcmd => 'ssh', },
     isp1 => { between => 'evunka', rcmd = 'rlogin', },
     ermintrude => { user => 'keck', },
   },
 }

=head1 OTHER NOTES

The xterms are put in background so xterms & any ssh's don't wait.

The config file name is always the same, regardless of renaming or
symlinking.

=head1 OTHER OPTIONS

 xterms -l

Outputs (lists) the config file via $PAGER or more.

 xterms -show ...

Just outputs the commands that would be run.
Still uses X11::Screen, so DISPLAY or -display needs to be correct.

=head1 AUTHOR

Brian Keck E<lt>bwkeck@gmail.comE<gt>

=head1 VERSION

 $Source: /home/keck/gen/RCS/xterms,v $
 $Revision: 8.11 $
 $Date: 2007/07/05 17:00:30 $
 xchar 0.2

=cut