Cleaned up demos, various build fixes
Steve Scaffidi [Tue, 22 Sep 2009 22:01:14 +0000 (18:01 -0400)]
Build.PL
BuildStem.pm
bin/demo/chat2_demo [moved from demo/chat2_demo with 67% similarity]
bin/demo/chat_demo [new file with mode: 0755]
bin/demo/hello_demo [moved from demo/hello_demo with 100% similarity]
bin/demo/inetd_demo [moved from demo/inetd_demo with 68% similarity]
bin/demo/quote_serve [moved from bin/quote_serve with 100% similarity]
bin/demo/tail_demo [moved from demo/tail_demo with 68% similarity]
demo/chat_demo [deleted file]
extras/ssfe.1 [new file with mode: 0644]
extras/ssfe.c [new file with mode: 0644]

index 3da144c..702ee1a 100644 (file)
--- a/Build.PL
+++ b/Build.PL
@@ -37,33 +37,13 @@ $build->is_unixish() || die "Stem currently only installs properly on *nix-like
 
 print <<'EOT';
 
-Building Stem
-
-This script will ask you various questions in order to properly
-configure, build and install Stem on your system.  Whenever a question
-is asked, the default answer will be shown inside [brackets].
-Pressing enter will accept the default answer. If a choice needs to be
-made from a list of values, that list will be inside (parentheses).
-
-If you have already configured Stem in a previous build, you can put
-use_defaults=1 on the Build command line and you won't be prompted for
-any answers and the previous settings will be used.
-
-If you want to force a new build, run Build clean.
-
-----------------------------------------------------------------------------
-
-EOT
-
-print <<'EOT';
-
 Stem comes with a utility called 'run_stem' which takes care of things
-like initalizing Stem with a configuration file and controlling it's 
-operation via various parameters you can pass in as environment 
-variables or command line arguments. 
+like initalizing Stem with a configuration file and controlling it's
+operation via various parameters you can pass in as environment
+variables or command line arguments.
 
 Stem configuration files are used to create and initialize Stem Cells
-(objects). run_stem can search a path list for config files, so you 
+(objects). run_stem can search a path list for config files, so you
 can set that list of directories here.
 
 Note that you can easily override this path with either a shell environment
@@ -73,96 +53,52 @@ run_stem for how so do this.
 The last directory in the list is where the standard and demo Stem
 configuration files will be installed.
 
-Please enter a list of directory paths separated by ':'.
-
 EOT
 
 my $conf_path = $build->prompt(
-       "What directories do you want Stem to search for configuration files?\n",
+       "Please enter a list of directory paths separated by ':'\n",
        '.:~/.stem/conf:/usr/local/stem/conf'
 );
 $build->config_data(conf_path => $conf_path);
 
 
 
-
-
-
-=begin comment
-
 print "\n\nChecking to see if you have a good C compiler...\n\n" ;
 if ( $build->have_c_compiler() ) {
        print <<'EOT';
-       
-       
+
+
 ssfe (Split Screen Front End) is a compiled program optionally used by
 the Stem demonstration scripts that provides a full screen interface
 with command line editing and history. It is not required to run Stem
 but it makes the demonstrations easier to work with and they look much
 nicer. To use ssfe add the '-s' option when you run any demonstration
-script. You can also use ssfe for your own programs.  Install ssfe in
-some place in your $PATH ($conf->{'bin_path'} is where Stem executables
-are being installed) so it can be used by the demo scripts. The ssfe
-install script will do this for you or you can do it manually after
-building it.
+script. You can also use ssfe for your own programs. If you build ssfe
+it will be installed in the demo/ directory. You may copy it to a place
+in your $PATH is you wish.
 
 EOT
-       my $install_ssfe = $build->y_n("Do you want to install ssfe?\n", 'y');
-       $build->config_data(install_ssfe => $install_ssfe);
-       if ( $install_ssfe ) {
-
-               # Do horrible, nasty things.
-               # This really should be done with a proper makefile.
-
-       }
+       my $build_ssfe = $build->y_n("Do you want to  build ssfe?\n", 'y');
+       $build->config_data(build_ssfe => $build_ssfe);
 }
 
-=cut
-
-
-
-
 
 print <<'EOT';
 
-Stem comes with a variety of demos to show how to get started and do some 
-basic things.
+Stem comes with a variety of demos to show how to get started and do some
+basic things. If you wish, they can be configured to run locally out of the 
+demo/ directory.
 
 EOT
-my $install_demos = $build->y_n("\nDo you want to install the demos?\n",'n');
-$build->config_data( install_demos => $install_demos ) ;
-$build->config_data( build_demos => $install_demos ) ;
-if ( $install_demos ) {
-
-       my $demo_dir = $build->prompt(
-               "\nWhere do you want to install the demo scripts?\n",
-               '/usr/local/stem/demo'
-       );
-       $build->config_data(demo_dir => $demo_dir);
-       $build->install_path()->{demo} ||= $demo_dir;
-
-
-
-       my $demo_conf_dir = $build->prompt(
-               "\nWhere do you want to install the demo config files?\n",
-               '/usr/local/stem/conf'
-       );
-       $build->config_data(demo_conf_dir => $demo_conf_dir);
-       $build->install_path()->{conf} ||= $demo_conf_dir;
-       $build->add_build_element('conf');
-
-       my $cur_conf_path = $build->config_data('conf_path') ;
-       my $new_conf_path = $cur_conf_path =~ /(^|:)$demo_conf_dir(:|$)/ ?
-               $cur_conf_path : "$cur_conf_path:$demo_conf_dir" ;
-       $build->config_data( conf_path => $new_conf_path ) ;
-
+my $build_demos = $build->y_n("\nDo you want to build the demos?\n",'y');
+$build->config_data(build_demos => $build_demos);
+if ( $build_demos ) {
 
        # Check for telnet
        my $telnet_path = $build->find_binary( 'telnet' ) || '' ;
        while ( ! -x $telnet_path && ! $build->_is_unattended() ) {
                print <<'EOT';
 
-
 telnet was not found on this system. you can't run the demo programs
 without telnet.  Make sure you enter a valid path to telnet or some other
 terminal emulator.
@@ -202,7 +138,7 @@ EOT
                        . "(or another compatible terminal emulator)",
             '/usr/bin/xterm'
                ) ;
-       }
+}
        $build->config_data( xterm_path => $xterm_path ) ;
 
 }
index a6aa8cb..53b52dd 100644 (file)
@@ -13,32 +13,53 @@ use Module::Build;
 use vars qw(@ISA);
 @ISA = qw(Module::Build);
 
+sub ACTION_build {
 
-sub process_script_files {
-       my ( $self ) = @_ ;
-       my $files = $self->find_script_files();
-       return unless keys %$files;
+    my ( $self ) = @_;
 
-       my $script_dir = File::Spec->catdir($self->blib, 'script');
-       my $demo_dir   = File::Spec->catdir($self->blib, 'demo');
-       File::Path::mkpath( $script_dir );
-       File::Path::mkpath( $demo_dir );
-    $self->add_to_cleanup($demo_dir);
+    if ( $self->config_data( 'build_demos' ) ) {
+       $self->build_demo_scripts();
+    }
 
-       foreach my $file (keys %$files) {
-               my $dest_dir = $file =~ /_demo$/ ? $demo_dir : $script_dir ;
-               my $result = $self->copy_if_modified($file, $dest_dir, 'flatten') or next;
-               $self->fix_shebang_line($result) if $self->is_unixish();
-               $self->make_executable($result);
-        my $demo_run_dir = File::Spec->catdir($self->base_dir(), 'demo');
-               if ( $result =~ /(?:run_stem$)|(?:_demo$)/ ) {
-                       my $result2 = $self->copy_if_modified($result, $demo_run_dir, 'flatten') or next;
-                       $self->add_to_cleanup($result2);
-               }
-       }
-       return 1;
+    if ( $self->config_data( 'build_ssfe' ) ) {
+       $self->build_ssfe();
+    }
+
+    $self->SUPER::ACTION_build();
+}
+
+
+# yes, hard coded, will fix some other time
+sub build_ssfe {
+    my ( $self ) = @_;
+    print "Compiling ssfe\n";
+    system( "cd extras; tar xzf sirc-2.211.tar.gz; cp sirc-2.211/ssfe.c ../demo" );
+    system( "cc -o demo/ssfe demo/ssfe.c -ltermcap 2>/dev/null" );
+    $self->add_to_cleanup(qw(demo/ssfe demo/ssfe.c ));
 }
 
+
+
+sub build_demo_scripts {
+    my ( $self ) = @_;
+    
+    my $demo_dir = 'demo';
+
+    my @files = <bin/demo/*>;
+
+    for my $file (@files) {
+       my $result = $self->copy_if_modified(
+                   $file, $demo_dir, 'flatten');
+       
+       $self->fix_shebang_line($result) if $self->is_unixish();
+       $self->make_executable($result);
+       $self->add_to_cleanup($result);
+    }
+
+}
+
+###########################################################
+# Find stem config files under the build dir and notify M::B about them.
 sub process_conf_files {
        my ( $self ) = @_ ;
        my $files = $self->_find_file_by_type('stem','conf');
@@ -48,21 +69,31 @@ sub process_conf_files {
        File::Path::mkpath( $conf_dir );
 
        foreach my $file (keys %$files) {
-               my $result = $self->copy_if_modified($file, $conf_dir, 'flatten') or next;
-               $self->fix_shebang_line($result) if $self->is_unixish();
+               my $result = $self->copy_if_modified(
+                   $file, $conf_dir, 'flatten');
+               next unless $result;
        }
        return 1;
 }
 
+
+###########################################################
+# A horrible hack to attempt to find the location of a binary program...
+# It would be nice if this functionality was already part of M::B
+# or there was a CPAN module for it that didn't suck.
 sub find_binary {
        my ( $self, $prog ) = @_ ;
-       if ( $self->do_system( "which $prog >/dev/null" ) ) {
-               return `which $prog` ;
+       # make sure the command will succeed before extracting the path.
+        if ( $self->do_system( "which $prog >/dev/null" ) ) {
+               my $path = `which $prog` ;
+                chomp $path;
+                return $path;
        }
        return;
 }
 
 
+
 ###########################################################
 # Various convenience routines.
 #
similarity index 67%
rename from demo/chat2_demo
rename to bin/demo/chat2_demo
index 1340a79..4fe3f92 100755 (executable)
@@ -1,20 +1,17 @@
 #!/usr/local/bin/perl -s
 
+use lib qw(../blib/lib blib/lib);
+use Stem::ConfigData;
+$ENV{PATH} .= ':blib/script:../blib/script:demo:.';
+my $xterm = Stem::ConfigData->config('xterm_path');
+my $telnet = Stem::ConfigData->config('telnet_path');
+$conf_path='conf:../conf';
+
 $line_cnt = 10 ;
 $offset = 175 ;
 $base_off = 0 ;
 $xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
 
-use File::Basename qw( basename );
-$ENV{PATH} .= ':' . basename( $0 ) . ':.';
-<<<<<<< HEAD:bin/chat2_demo
-=======
-
->>>>>>> origin/master:bin/chat2_demo
-for my $prog qw( run_stem xterm telnet ) {
-    die "Can't find $prog in PATH\n" if
-        system( "which $prog >/dev/null 2>&1" ) != 0;
-}
 
 my @children ;
 
@@ -23,14 +20,14 @@ $SIG{ 'INT' } = \&cleanup ;
 if ( $s ) {
 
        $ssfe = 'ssfe' ;
-       $prompt = '-prompt Stem:' ;
-       $prompt2 = '-prompt Chat:' ;
+       $prompt = '-prompt "Stem: "' ;
+       $prompt2 = '-prompt "Chat: "' ;
        $echo = 'console_echo=1'
 }
 
 foreach $cmd ( split /\n/, <<EOT ) {
-xterm -T Chat1 -n Chat1 -geometry 80x25+XSKIP+0   -e $ssfe $prompt run_stem $echo  chat_server
-xterm -T Chat2 -n Chat2 -geometry 80x25+XSKIP+250 -e $ssfe $prompt run_stem $echo  chat_client
+xterm -T Chat1 -n Chat1 -geometry 80x25+XSKIP+0   -e $ssfe $prompt run_stem conf_path=$conf_path $echo chat_server
+xterm -T Chat2 -n Chat2 -geometry 80x25+XSKIP+250 -e $ssfe $prompt run_stem conf_path=$conf_path $echo chat_client
 xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
 xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
 xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6668
@@ -44,11 +41,7 @@ EOT
 
 print "$cmd\n" ;
 
-       @cmd = split ' ', $cmd ;
-
-       s/:/: / for @cmd ;
-
-       fork_exec( @cmd ) ;
+       fork_exec( $cmd ) ;
        sleep 2 unless $cmd =~ /localhost/ ;
 }
 
@@ -70,7 +63,7 @@ sub cleanup {
 
 sub fork_exec {
 
-       my( @exec ) = @_ ;
+       my( $exec ) = @_ ;
 
        if ( $pid = fork() ) {
 
@@ -78,5 +71,5 @@ sub fork_exec {
                return ;
        }
 
-       exec @exec ;
+       exec $exec ;
 }
diff --git a/bin/demo/chat_demo b/bin/demo/chat_demo
new file mode 100755 (executable)
index 0000000..07e8903
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/local/bin/perl -s
+
+use lib qw(../blib/lib blib/lib);
+use Stem::ConfigData;
+$ENV{PATH} .= ':blib/script:../blib/script:demo:.';
+my $xterm = Stem::ConfigData->config('xterm_path');
+my $telnet = Stem::ConfigData->config('telnet_path');
+$conf_path='conf:../conf';
+
+
+$line_cnt = 10 ;
+$offset = 175 ;
+$base_off = 0 ;
+$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
+
+print "CHAT DEMO\n" ;
+
+
+my @children ;
+
+$SIG{ 'INT' } = \&cleanup ;
+
+if ( $s ) {
+
+       $ssfe = 'ssfe' ;
+       $prompt = '-prompt "Stem: "' ;
+       $prompt2 = '-prompt "Chat: "' ;
+       $echo = 'console_echo=1'
+}
+
+foreach $cmd ( split /\n/, <<EOT ) {
+$xterm -T Chat -n Chat -geometry 80x40+XSKIP+0 -e $ssfe $prompt run_stem conf_path=$conf_path $echo chat
+$xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 $telnet localhost 6666
+$xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 $telnet localhost 6667
+$xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 $telnet localhost 6668
+$xterm -T D -n D -geometry 80xLINE+0+OFF -e $ssfe $prompt2 $telnet localhost 6669
+EOT
+
+       $cmd =~ s/XSKIP/$xskip/ ;
+
+       $cmd =~ s/LINE/$line_cnt/ ;
+       $cmd =~ s/OFF/$base_off/ and $base_off += $offset ;
+
+print "$cmd\n" ;
+print `pwd`;
+
+#      @cmd = split ' ', $cmd ;
+        
+       fork_exec( $cmd ) ;
+       sleep 4 unless $cmd =~ /localhost/ ;
+}
+
+1 while wait() != -1;
+
+exit;
+
+
+
+sub cleanup {
+
+       print "clean up\n" ;
+
+       kill 9, @children ;
+
+       wait ;  
+       exit ;
+
+}
+
+sub fork_exec {
+
+       my( $exec ) = @_ ;
+
+       if ( $pid = fork() ) {
+
+               push @children, $pid ;
+               return ;
+       }
+
+       exec $exec ;
+}
similarity index 100%
rename from demo/hello_demo
rename to bin/demo/hello_demo
similarity index 68%
rename from demo/inetd_demo
rename to bin/demo/inetd_demo
index 0a80d12..3511a70 100755 (executable)
@@ -1,5 +1,12 @@
 #!/usr/local/bin/perl -s
 
+use lib qw(../blib/lib blib/lib);
+use Stem::ConfigData;
+$ENV{PATH} .= ':blib/script:../blib/script:demo:.';
+my $xterm = Stem::ConfigData->config('xterm_path');
+my $telnet = Stem::ConfigData->config('telnet_path');
+my $conf_path='conf:../conf';
+
 $line_cnt = 10 ;
 $offset = 175 ;
 $base_off = 0 ;
@@ -7,27 +14,19 @@ $xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
 
 my @children ;
 
-print "INETD: $ENV{PATH}\n" ;
-
-use File::Basename qw( basename );
-$ENV{PATH} .= ':' . basename( $0 ) . ':.:../bin:./bin';
-for my $prog qw( run_stem xterm telnet ) {
-    die "Can't find $prog in PATH\n" if
-        system( "which $prog >/dev/null 2>&1" ) != 0;
-}
 
 $SIG{ 'INT' } = \&cleanup ;
 
 if ( $s ) {
 
        $ssfe = 'ssfe' ;
-       $prompt = '-prompt Stem:' ;
-       $echo = 'console_echo=1'
-#      $prompt2 = '-prompt Chat:' ;
+       $prompt = '-prompt "Stem: "' ;
+       $echo = 'console_echo=1';
+       $prompt2 = '-prompt "Chat: "' ;
 }
 
 foreach $cmd ( split /\n/, <<EOT ) {
-xterm -T Stem -n Stem -geometry 80x40+XSKIP+0 -e $ssfe $prompt run_stem $echo inetd
+xterm -T Stem -n Stem -geometry 80x40+XSKIP+0 -e $ssfe $prompt run_stem conf_path=$conf_path $echo inetd
 xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
 xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
 xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
@@ -41,11 +40,7 @@ EOT
 
 print "CMD $cmd\n" ;
 
-       @cmd = split ' ', $cmd ;
-
-       s/:/: / for @cmd ;
-
-       fork_exec( @cmd ) ;
+       fork_exec( $cmd ) ;
        sleep 4 unless $cmd =~ /localhost/ ;
 }
 
@@ -68,7 +63,7 @@ sub cleanup {
 
 sub fork_exec {
 
-       my( @exec ) = @_ ;
+       my( $exec ) = @_ ;
 
        if ( $pid = fork() ) {
 
@@ -76,5 +71,5 @@ sub fork_exec {
                return ;
        }
 
-       exec @exec ;
+       exec $exec ;
 }
similarity index 100%
rename from bin/quote_serve
rename to bin/demo/quote_serve
similarity index 68%
rename from demo/tail_demo
rename to bin/demo/tail_demo
index 6011199..e9c995f 100755 (executable)
@@ -1,17 +1,17 @@
 #!/usr/local/bin/perl -s
 
+use lib qw(../blib/lib blib/lib);
+use Stem::ConfigData;
+$ENV{PATH} .= ':blib/script:../blib/script:demo:.';
+my $xterm = Stem::ConfigData->config('xterm_path');
+my $telnet = Stem::ConfigData->config('telnet_path');
+$conf_path='conf:../conf';
+
 $line_cnt = 10 ;
 $offset = 175 ;
 $base_off = 0 ;
 $xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
 
-use File::Basename qw( basename );
-$ENV{PATH} .= ':' . basename( $0 ) . ':.';
-for my $prog qw( run_stem xterm telnet ) {
-    die "Can't find $prog in PATH\n" if
-        system( "which $prog >/dev/null 2>&1" ) != 0;
-}
-
 my @children ;
 
 my $tail_dir = 'tail' ;
@@ -21,7 +21,7 @@ $SIG{ 'INT' } = \&cleanup ;
 if ( $s ) {
 
        $ssfe = 'ssfe' ;
-       $prompt = '-prompt Stem:' ;
+       $prompt = '-prompt "Stem: "' ;
        $echo = 'console_echo=1'
 }
 
@@ -34,20 +34,16 @@ foreach my $log ( qw( foo bar bar_status ) ) {
 
 
 foreach $cmd ( split /\n/, <<EOT ) {
-xterm -T Archive -n Archive -geometry 80x35+0+0 -e $ssfe $prompt run_stem $echo archive
-xterm -T Monitor -n Monitor -geometry 80x35+$xskip+0 -e $ssfe $prompt run_stem $echo monitor
+xterm -T Archive -n Archive -geometry 80x35+0+0 -e $ssfe $prompt run_stem conf_path=$conf_path $echo archive
+xterm -T Monitor -n Monitor -geometry 80x35+$xskip+0 -e $ssfe $prompt run_stem conf_path=$conf_path $echo monitor
 xterm -T Tail -n Tail -geometry 80x20+275+420
 EOT
 
        print "$cmd\n" ;
 
-       @cmd = split ' ', $cmd ;
-
-       s/:/: / for @cmd ;
-
        chdir $tail_dir unless $cmd =~ /run_stem/ ;
 
-       fork_exec( @cmd ) ;
+       fork_exec( $cmd ) ;
 
        sleep 2 ;
 }
@@ -70,7 +66,7 @@ sub cleanup {
 
 sub fork_exec {
 
-       my( @exec ) = @_ ;
+       my( $exec ) = @_ ;
 
        if ( $pid = fork() ) {
 
@@ -78,5 +74,5 @@ sub fork_exec {
                return ;
        }
 
-       exec @exec ;
+       exec $exec ;
 }
diff --git a/demo/chat_demo b/demo/chat_demo
deleted file mode 100755 (executable)
index 435af50..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/local/bin/perl -s
-
-$line_cnt = 10 ;
-$offset = 175 ;
-$base_off = 0 ;
-$xskip = ( $^O eq 'solaris' ) ? 600 : 500 ;
-
-print "CHAT DEMO\n" ;
-
-use File::Basename qw( basename );
-$ENV{PATH} .= ':' . basename( $0 ) . ':.';
-for my $prog qw( run_stem xterm telnet ) {
-    die "Can't find $prog in PATH\n" if
-        system( "which $prog >/dev/null 2>&1" ) != 0;
-}
-
-my @children ;
-
-$SIG{ 'INT' } = \&cleanup ;
-
-if ( $s ) {
-
-       $ssfe = 'ssfe' ;
-       $prompt = '-prompt Stem:' ;
-       $prompt2 = '-prompt Chat:' ;
-       $echo = 'console_echo=1'
-}
-
-foreach $cmd ( split /\n/, <<EOT ) {
-xterm -T Chat -n Chat -geometry 80x40+XSKIP+0 -e $ssfe $prompt run_stem $echo chat
-xterm -T A -n A -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6666
-xterm -T B -n B -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6667
-xterm -T C -n C -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6668
-xterm -T D -n D -geometry 80xLINE+0+OFF -e $ssfe $prompt2 telnet localhost 6669
-EOT
-
-       $cmd =~ s/XSKIP/$xskip/ ;
-
-       $cmd =~ s/LINE/$line_cnt/ ;
-       $cmd =~ s/OFF/$base_off/ and $base_off += $offset ;
-
-print "$cmd\n" ;
-
-       @cmd = split ' ', $cmd ;
-
-       s/:/: / for @cmd ;
-
-       fork_exec( @cmd ) ;
-       sleep 4 unless $cmd =~ /localhost/ ;
-}
-
-1 while wait() != -1;
-
-exit;
-
-
-
-sub cleanup {
-
-       print "clean up\n" ;
-
-       kill 9, @children ;
-
-       wait ;  
-       exit ;
-
-}
-
-sub fork_exec {
-
-       my( @exec ) = @_ ;
-
-       if ( $pid = fork() ) {
-
-               push @children, $pid ;
-               return ;
-       }
-
-       exec @exec ;
-}
diff --git a/extras/ssfe.1 b/extras/ssfe.1
new file mode 100644 (file)
index 0000000..c71ff35
--- /dev/null
@@ -0,0 +1,163 @@
+.TH SSFE 1 "" "Roger Espel Llima"
+.SH NAME
+ssfe \- split-screen front-end
+.SH SYNOPSIS
+.B ssfe
+[options] program-name [program-options]
+.SH DESCRIPTION
+.B ssfe
+runs a line-oriented program in the background, and provides a full
+screen interface for it.  The bottom line of the screen is the input
+line, which you can edit using emacs-like keys; above that is the
+status bar, and the scrolling area with the program's output.
+.PP
+You can configure 
+.B ssfe
+to reprint or not your own input lines, to use prompts, and to do word-wrap
+on the program's output.
+.PP
+.B ssfe
+also understands a little protocol to communicate with the program it is
+running, which can be used to change the contents of the status line,
+to set ssfe's modes and to prompt for input.
+.SH OPTIONS
+.TP
+.BR "-raw"
+Disables word-wrap and handling of control characters.  In this mode, ssfe
+will not attempt keep track of the cursor's position.
+.TP
+.BR "-cooked"
+Enables word-wrap, and prints control characters using inverse-video;  this
+is the default mode.
+.TP
+.BR "-irc"
+Same as -cooked, but also interprets Ctrl-B, Ctrl-V and Ctrl-_ with the
+IRC convention (toggles for bold, inverse, underlined, respectively).
+.TP
+.BR "-hold"
+Sets hold-mode.  In hold mode, 
+.B ssfe
+will stop after each screenful, and wait for the user to hit TAB.
+.TP
+.BR "-beep"
+In cooked or IRC mode, enables beeps.  When beeps are disabled, the
+character Ctrl-G is displayed as an inverse-video G.
+.TP
+.BR "-flow"
+Enables flow-control with ^S and ^Q.  
+.B ssfe
+normally disables those, but some terminals require them to operate properly.
+.TP
+.BR "-print"
+Enables printing of your own commands back in the scrolling area.
+.TP
+.BR "-prompt \fI<prompt>\fR"
+Sets a prompt for the user input line.  The default prompt is none, or
+``> '' if -print is specified.
+
+.SH ARGUMENTS
+.TP
+.BR "program [options]"
+Names the program that 
+.B ssfe
+should run.
+
+.SH KEYS
+.B ssfe 
+understands these keys (^ means Control):
+.TP
+.BR "^\e"
+Interrupt ssfe and whatever program it's running, and exit back to the unix
+prompt.
+.TP
+.BR "^a"
+Go to the beginning of the line.
+.TP
+.BR "^b, left arrow"
+Move left a letter.
+.TP
+.BR "^c"
+Interrupt: ignored by the front-end, can be used to interrupt connecting to a
+server, with sirc.
+.TP
+.BR "^d"
+Delete the character under the cursor.
+.TP
+.BR "^e"
+Go to the end of the line.
+.TP
+.BR "^f, right arrow"
+Move right a letter.
+.TP
+.BR "^h, DEL"
+Erase the previous character.
+.TP
+.BR "^i, TAB"
+Go to next /msg in msg history.
+.TP
+.BR "^j, ^m, Enter"
+.TP
+.BR "^k"
+Erase from the cursor to the end of the line.
+.TP
+.BR "^l"
+Redisplay the status bar and the command line.
+.TP
+.BR "^n, down arrow"
+Go to the next line in command-line history.
+.TP
+.BR "^o"
+With sirc, type the last msg you got on the command line
+.TP
+.BR "^p, up arrow"
+Go to the previous line in command-line history.
+.TP
+.BR "^t"
+With sirc, switch to the next channel you're on.
+.TP
+.BR "^u"
+Erase command-line.
+.TP 
+.BR "^v"
+Insert the next character literally, even if it's a ^something.
+.TP
+.BR "^x b"
+Toggle beep on or off (off by default).
+.TP
+.BR "^x c"
+Exit the front end, back to the unix prompt.
+.TP
+.BR "^x h"
+Toggle hold mode.
+.TP
+.BR "^x i"
+Toggle irc-mode (^b^v^_ handling) on and off.
+.TP
+.BR "^y"
+Yank the current line in the history without sending it.
+.TP
+.BR "^z"
+Suspend ssfe and sirc and go back to the unix prompt - you come back with 'fg'.
+
+.SH COPYING
+.B ssfe
+is free software. You can redistribute it and/or modify it under the GNU
+General Public License as published by the Free Software Foundation.  See
+the file LICENSE for details.
+
+.SH SEE ALSO
+.BR sirc (1)
+
+.SH ENVIRONMENT VARIABLES
+.TP
+.BR TERM 
+sets the terminal type.  The terminal needs to be able to set a scrolling
+zone for ssfe to work.
+
+.SH BUGS
+None known, please report to the author.
+
+.SH AUTHOR
+.B sirc
+was written by Roger Espel Llima <roger.espel.llima@pobox.com>. 
+
diff --git a/extras/ssfe.c b/extras/ssfe.c
new file mode 100644 (file)
index 0000000..b37f1dc
--- /dev/null
@@ -0,0 +1,1314 @@
+/* An ircII-like split-screen front end
+   Copyright (C) 1995 Roger Espel Llima
+
+   Started: 17 Feb 95 by orabidoo <roger.espel.llima@ens.fr>
+   Latest modification: 7 June 97
+
+   To compile: gcc ssfe.c -o ssfe -ltermcap
+
+   If it doesn't work, try gcc ssfe.c -o ssfe -lcurses
+   or try cc, acc or c89 instead of gcc, or -lncurses.
+
+   Use: ssfe [options] program arguments
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation. See the file LICENSE for details.
+*/
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#ifdef USE_SGTTY
+#include <sgtty.h>
+#else
+#include <termios.h>
+#endif
+
+#include <sys/ioctl.h>
+
+#ifdef _AIX
+#include <sys/select.h>
+#endif
+
+#define BUF_SIZE 512
+#define MAX_COLS 512
+
+unsigned char *statusline;
+int ystatus, yinput;     /* line number of the status line, input line */
+
+int ttyfd;
+#ifdef TIOCGWINSZ
+struct winsize wsz;
+#endif
+
+#ifdef USE_SGTTY
+struct sgttyb term, term0;
+struct tchars tch, tch0;
+struct ltchars lch, lch0;
+#else
+struct termios term, term0;
+#endif
+
+int pid, mypid;
+int i;
+int cols, lines;
+int readfd, writefd, errfd;
+
+unsigned char *t, *w;
+unsigned char tmpstr[BUF_SIZE], extrainput[BUF_SIZE+20], readbuf[2*BUF_SIZE],
+             *input, *writebuf, o_buffer[BUF_SIZE];
+int bold=0, inv=0, under=0, wherex=0, wherey=0, donl=0;
+int hold_mode=0, hold_lines=0, ctrlx=0, beep=0, flow=0;
+
+unsigned char defprompt[]="> ",
+        nullstring[]="",
+        *prompt;
+int plen=0, specialprompt=0, modified=1, no_echo=0;
+
+#define MAX_TAB_LINES 20
+struct tabinfo {
+  unsigned char string[BUF_SIZE];
+  struct tabinfo *prev, *next;
+};
+int tablines=0;
+struct tabinfo *curtabt=NULL, *curtabr=NULL, *oldest=NULL;
+
+#define MAX_HIST_LINES 50
+struct histinfo {
+  unsigned char string[BUF_SIZE+20];
+  int len, plen;
+  struct histinfo *prev, *next;
+};
+int histlines=0;
+struct histinfo *histcurrent=NULL, *histoldest=NULL;
+
+char ctrl_t[128] = "/next\n";
+
+unsigned char id[]="`#ssfe#", *inid=id, protcmd[BUF_SIZE], *wpc=protcmd;
+int idstatus=0;  /* 0 looking for/in the word, 1 in the arguments */
+#define ID_BACK "@ssfe@"
+
+int rc, rrc, inputcursor, inputlast, inputofs, inarrow=0, quote=0;
+int cursorwhere;     /* 0 = up, 1 = down, 2 = undef */
+int dispmode=1; /* 0=raw, 1=wordwrap, 2=process ^b^v^_ */
+int printmode=0;
+int cutline=0;
+
+char *termtype, termcap[1024], *tc, capabilities[2048];
+char *t_cm, *t_cl, *t_mr, *t_md, *t_me, *t_cs, *t_ce, *t_us;
+int ansi_cs = 0;
+
+fd_set ready, result;
+extern int errno;
+
+#ifdef __GNUC__
+extern unsigned char *tgoto(unsigned char *cm, int col, int line);
+#else
+extern unsigned char *tgoto();
+#endif
+
+#ifdef __GNUC__
+int myputchar(int c) {
+#else
+int myputchar(c) {
+#endif
+  unsigned char cc=(unsigned char)c;
+  return(write(1, &cc, 1));
+}
+
+#ifdef __GNUC__
+int addchar(int c) {
+#else
+int addchar(c) {
+#endif
+  (*w++)=(unsigned char)c;
+}
+
+#ifdef __GNUC__
+void putcap(unsigned char *s) {
+#else
+void putcap(s)
+unsigned char *s; {
+#endif
+  tputs(s, 0, myputchar);
+}
+
+#ifdef __GNUC__
+int do_cs(int y1, int y2) {
+#else
+int do_cs(y1, y2) {
+#endif
+  static char temp[16];
+  if (ansi_cs) {
+    sprintf(temp, "\e[%d;%dr", y1, y2);
+    write(1, temp, strlen(temp));
+  } else putcap((char *)tgoto(t_cs, y2-1, y1-1));
+}
+
+#ifdef __GNUC__
+void writecap(unsigned char *s) {
+#else
+void writecap(s)
+unsigned char *s; {
+#endif
+  tputs(s, 0, addchar);
+}
+
+#ifdef __GNUC__
+void gotoxy(int x, int y) {
+#else
+void gotoxy(x, y) {
+#endif
+/* left upper = 0, 0 */
+  putcap(tgoto(t_cm, x, y));
+}
+
+#define clearscreen() (putcap(t_cl))
+#define cleareol() (putcap(t_ce))
+#define fullscroll() (do_cs(0, 0))
+#define winscroll() (do_cs(1, lines-2))
+#define setbold() (putcap(t_md))
+#define setunder() (putcap(t_us))
+#define setinv() (putcap(t_mr))
+#define normal() (putcap(t_me))
+
+#ifdef __GNUC__
+void ofsredisplay(int x);
+void inschar(unsigned char t);
+void dokbdchar(unsigned char t);
+#else
+void ofsredisplay();
+void inschar();
+void dokbdchar();
+#endif
+void displaystatus();
+
+#ifdef __GNUC__
+void cleanupexit(int n, unsigned char *error) {
+#else
+void cleanupexit(n, error)
+int n;
+unsigned char *error; {
+#endif
+  normal();
+  fullscroll();
+  gotoxy(0, lines-1);
+  cleareol();
+#ifdef USE_SGTTY
+  ioctl(ttyfd, TIOCSETP, &term0);
+  ioctl(ttyfd, TIOCSETC, &tch0);
+  ioctl(ttyfd, TIOCSLTC, &lch0);
+#else
+  tcsetattr(ttyfd, TCSADRAIN, &term0);
+#endif
+  close(ttyfd);
+  if (error!=NULL)
+    fprintf(stderr, "%s\n", error);
+  exit(n);
+}
+
+void allsigs();
+
+void interrupted() {
+  cleanupexit(1, "interrupted");
+}
+
+void sigpipe() {
+  cleanupexit(1, "program died");
+}
+
+void sigcont() {
+  allsigs();
+#ifdef USE_SGTTY
+  ioctl(ttyfd, TIOCSETP, &term);
+  ioctl(ttyfd, TIOCSETC, &tch);
+  ioctl(ttyfd, TIOCSLTC, &lch);
+#else
+  tcsetattr(ttyfd, TCSANOW, &term);
+#endif
+  wherex=0;
+  wherey=ystatus-1;
+  displaystatus();
+  ofsredisplay(0);
+}
+
+void suspend() {
+  normal();
+  fullscroll();
+  gotoxy(0, ystatus);
+  cleareol();
+#ifdef USE_SGTTY
+  ioctl(ttyfd, TIOCSETP, &term0);
+  ioctl(ttyfd, TIOCSETC, &tch0);
+  ioctl(ttyfd, TIOCSLTC, &lch0);
+#else
+  tcsetattr(ttyfd, TCSANOW, &term0);
+#endif
+  kill(pid, SIGCONT);
+  signal(SIGTSTP, SIG_DFL);
+  signal(SIGCONT, sigcont);
+  kill(mypid, SIGTSTP);
+}
+
+void sigwinch() {
+#ifdef TIOCGWINSZ
+  signal(SIGWINCH, sigwinch);
+  if (ioctl(ttyfd, TIOCGWINSZ, &wsz)>=0 && wsz.ws_row>0 && wsz.ws_col>0) {
+    lines=wsz.ws_row;
+    cols=wsz.ws_col;
+    cursorwhere=2;
+    ystatus=lines-2;
+    yinput=lines-1;
+    wherex=0;
+    wherey=ystatus-1;
+    displaystatus();
+    if (inputlast>cols-8) {
+      inputcursor=cols-9;
+      inputofs=inputlast-cols+9;
+    } else {
+      inputofs=0;
+      inputcursor=inputlast;
+    }
+    ofsredisplay(0);
+  }
+#endif
+}
+
+void allsigs() {
+  signal(SIGHUP, interrupted);
+  signal(SIGINT, interrupted);
+  signal(SIGQUIT, SIG_IGN);
+  signal(SIGPIPE, sigpipe);
+  signal(SIGTSTP, suspend);
+  signal(SIGCONT, sigcont);
+#ifdef TIOCGWINSZ
+  signal(SIGWINCH, sigwinch);
+#endif
+}
+
+#ifdef __GNUC__
+void setstatus(unsigned char *title) {
+#else
+void setstatus(title)
+unsigned char *title; {
+#endif
+  unsigned char *t=title;
+  for (;*t;t++) if (*t<' ') (*t)+='@';
+  memset(statusline, ' ', MAX_COLS-1);
+  memcpy(statusline, title, strlen(title)<MAX_COLS ? strlen(title) : MAX_COLS);
+}
+
+void displaystatus() {
+  normal();
+  fullscroll();
+  gotoxy(0, ystatus);
+  setinv();
+  write(1, statusline, cols-1);
+  if (hold_mode) {
+    gotoxy(cols-4, ystatus);
+    write(1, "(h)", 3);
+  }
+  cursorwhere=2;
+  normal();
+  cleareol();
+}
+
+#ifdef __GNUC__
+int casecmp(unsigned char *s, unsigned char *t) {
+#else
+int casecmp(s, t)
+unsigned char *s, *t; {
+#endif
+  while (((*s>='a' && *s<='z')?(*s)-32:*s)==
+         ((*t>='a' && *t<='z')?(*t)-32:*t)) {
+    if (*s=='\0') return 1;
+    s++; t++;
+  }
+  return 0;
+}
+
+#ifdef __GNUC__
+void addtab(unsigned char *line) {
+#else
+void addtab(line)
+unsigned char *line; {
+#endif
+  struct tabinfo *nt;
+
+  nt=oldest;
+  if (tablines) do {
+    if (casecmp(nt->string, line)) {
+      strcpy(nt->string, line);
+      if (nt==oldest) oldest=nt->prev;
+      else {
+       nt->prev->next=nt->next;
+       nt->next->prev=nt->prev;
+       nt->prev=oldest;
+       nt->next=oldest->next;
+       oldest->next=nt;
+       nt->next->prev=nt;
+      }
+      curtabt=oldest->next;
+      curtabr=oldest;
+      return;
+    }
+    nt=nt->next;
+  } while (nt!=oldest);
+
+  if (!tablines) {
+    nt=(struct tabinfo *)malloc(sizeof (struct tabinfo));
+    nt->prev=nt->next=curtabt=curtabr=oldest=nt;
+    tablines++;
+  } else if (tablines<MAX_TAB_LINES) {
+    nt=(struct tabinfo *)malloc(sizeof (struct tabinfo));
+    nt->prev=oldest;
+    nt->next=oldest->next;
+    oldest->next=nt;
+    nt->next->prev=nt;
+    tablines++;
+  } else {
+    nt=oldest;
+    oldest=nt->prev;
+  }
+  strcpy(nt->string, line);
+  oldest=nt->prev;
+  curtabt=oldest->next;
+  curtabr=oldest;
+}
+
+void doprotcommand() {
+  unsigned char *tmp;
+
+  switch (protcmd[0]) {
+    case 'i' : dispmode=2;     /* set irc mode, ack */
+              bold=inv=under=0;
+              write(writefd, "@ssfe@i\n", 8);
+              break;
+    case 'c' : dispmode=1;     /* set cooked mode, ack */
+              write(writefd, "@ssfe@c\n", 8);
+              break;
+    case 's' : setstatus(protcmd+1); /* set status */
+              displaystatus();
+              break;
+    case 'T' : strncpy(ctrl_t, protcmd+1, 127); /* set ^t's text */
+              ctrl_t[126] = '\0';
+              strcat(ctrl_t, "\n");
+              break;
+    case 't' : addtab(protcmd+1); /* add tabkey entry */
+              break;
+    case 'l' : fullscroll(); /* clear screen */
+              normal();
+              clearscreen();
+              bold=inv=under=wherex=wherey=donl=0;
+              displaystatus();
+              ofsredisplay(0);
+              break;
+
+    case 'P' : no_echo = 1;                /* password prompt */
+    case 'p' : if (strlen(protcmd+1)<=8) {  /* prompt something */
+                fullscroll();
+                if (!specialprompt) {
+                  histcurrent->len=inputlast;
+                  histcurrent->plen=plen;
+                }
+                input=extrainput;
+                strcpy(input, protcmd+1);
+                plen=strlen(input);
+                inputofs=0;
+                modified=specialprompt=1;
+                inputlast=inputcursor=plen;
+                ofsredisplay(0);
+              }
+              break;
+    case 'n' : if (cursorwhere!=1) { /* type text */
+                normal();
+                fullscroll();
+                gotoxy(inputcursor, yinput);
+                cursorwhere=1;
+              }
+              for (tmp=protcmd+1; *tmp; tmp++) {
+                inschar(*tmp);
+              }
+              break;
+    case 'o' : strcpy(o_buffer, protcmd+1);
+              break;
+  }
+}
+
+void newline() {
+  unsigned char t;
+  hold_lines++;
+  if (hold_mode && hold_lines>lines-4) {
+    normal();
+    fullscroll();
+    gotoxy(cols-4, ystatus);
+    setinv();
+    write(1, "(H)", 3);
+    while(1) {
+      read(0, &t, 1);
+      if (t==9) break;
+      dokbdchar(t);
+    }
+    normal();
+    fullscroll();
+    gotoxy(cols-4, ystatus);
+    setinv();
+    write(1, "(h)", 3);
+    hold_lines=0;
+    normal();
+    winscroll();
+    gotoxy(cols-1, wherey);
+    if (bold) setbold();
+    if (under) setunder();
+    if (inv) setinv();
+  }
+}
+
+#ifdef __GNUC__
+void formatter(unsigned char *readbuf, int rc) {
+#else
+void formatter(readbuf, rc)
+unsigned char *readbuf;
+int rc; {
+#endif
+
+  unsigned char t, *r, *lwr, *lww;
+  int lwrc, lwbold, lwunder, lwinv, lwx;
+
+  if (cursorwhere!=0) {
+    winscroll();
+    gotoxy(wherex, wherey);
+    cursorwhere=0;
+  }
+  if (donl) {
+    newline();
+    write(1, "\r\n", 2);
+    normal();
+    wherex=0;
+    bold=inv=under=lwbold=lwinv=lwunder=0;
+    if (wherey<ystatus-1) wherey++;
+  } else if (dispmode>1) {
+    if (bold) setbold();
+    if (under) setunder();
+    if (inv) setinv();
+    lwbold=bold;
+    lwinv=inv;
+    lwunder=under;
+  }
+  if (rc && readbuf[rc-1]=='\n') {
+    rc--;
+    donl=1; cutline=0;
+  } else {
+    donl=0;
+    if (dispmode==0) cutline=1;
+  }
+  if (dispmode==0) {
+    if (rc) write(1, readbuf, rc);
+    normal();
+    return;
+  }
+  lww=w=writebuf;
+  lwr=r=readbuf;
+  lwrc=rc;
+  lwx=wherex;
+  while(rc-->0) {
+    t=(*r++);
+    if (t=='\r') continue;
+    if (wherex>cols-2 || (t==9 && wherex>(cols-2)&0xfff8)) {
+      if (t==' ' || t==9) ;
+      else if (lww>writebuf+cols/2) {
+       wherex=lwx; r=lwr; w=lww; rc=lwrc;
+       bold=lwbold; inv=lwinv; under=lwunder; wherex=lwx;
+      } else {
+       rc++; r--;
+      }
+      write(1, writebuf, w-writebuf);
+      newline();
+      write(1, "\r\n           ", 13);
+      w=writebuf;
+      lwr=r; lww=w; lwrc=rc;
+      lwbold=bold; lwinv=inv; lwunder=under;
+      lwx=wherex=11;
+      if (wherey<ystatus-1) wherey++;
+      rc--; t=(*r++);
+    }
+    if (t=='\n') {
+      if (w!=writebuf) write(1, writebuf, w-writebuf);
+      newline();
+      write(1, "\r\n", 2);
+      normal();
+      w=writebuf;
+      lwr=r; lww=w; lwrc=rc;
+      lwbold=bold=lwinv=inv=lwunder=under=lwx=wherex=0;
+      if (wherey<ystatus-1) wherey++;
+    } else if (dispmode>1 &&
+               ((t==2 && bold) || (t==22 && inv) || (t==31 && under))) {
+      writecap(t_me);
+      bold=under=inv=0;
+    } else if (dispmode>1 && t==2) {
+      writecap(t_md);
+      bold=1;
+    } else if (dispmode>1 && t==22) {
+      writecap(t_mr);
+      inv=1;
+    } else if (dispmode>1 && t==31) {
+      writecap(t_us);
+      under=1;
+    } else if (dispmode>1 && t==15) {
+      if (bold || inv || under) writecap(t_me);
+      bold=under=inv=0;
+    } else if (t==9) {
+      (*w++)=t;
+      wherex=(wherex & 0xfff8)+8;
+    } else if (t<' ' && (t!=7 || !beep)) {
+      wherex++;
+      if (inv) {
+       writecap(t_me);
+       (*w++)=(t+'@');
+      } else {
+       writecap(t_mr);
+       (*w++)=(t+'@');
+       writecap(t_me);
+      }
+      if (bold) writecap(t_md);
+      if (inv) writecap(t_mr);
+      if (under) writecap(t_us);
+    } else {
+      if (t!=7) wherex++;
+      (*w++)=t;
+    }
+    if (t==' ' || t==9) {
+      lwr=r; lww=w; lwrc=rc;
+      lwbold=bold; lwinv=inv; lwunder=under;
+      lwx=wherex;
+    }
+  }
+  if (w!=writebuf) write(1, writebuf, w-writebuf);
+}
+
+#ifdef __GNUC__
+void doprogramline(unsigned char *readbuf, int rc) {
+#else
+void doprogramline(readbuf, rc)
+unsigned char *readbuf;
+int rc; {
+#endif
+
+  unsigned char *w, *r, *r2, t;
+  if (dispmode==0) {
+    formatter(readbuf, rc);
+    return;
+  }
+  w=r=readbuf;
+  while(rc-->0) {
+    t=(*r++);
+    if (idstatus==0)
+      if (*inid=='\0') {
+       idstatus=1;
+       wpc=protcmd;
+       inid=id;
+      } else if (*inid==t && (inid!=id || r==(readbuf+1) || *(r-2)=='\n')) {
+       inid++;
+       (*wpc++)=t;
+      } else {
+        r2=protcmd;
+        while (r2!=wpc) (*w++)=(*r2++);
+       (*w++)=t;
+       wpc=protcmd;
+       inid=id;
+      }
+    if (idstatus==1)
+      if (t=='\n')  {
+        *wpc='\0';
+        doprotcommand();
+       inid=id;
+       wpc=protcmd;
+       idstatus=0;
+      } else (*wpc++)=t;
+  }
+  if (w!=readbuf) formatter(readbuf, w-readbuf);
+}
+
+#ifdef __GNUC__
+void write1(unsigned char t, int pos) {
+#else
+void write1(t, pos)
+unsigned char t;
+int pos; {
+#endif
+  if (no_echo && pos>=plen) {
+    write(1, "*", 1);
+  } else if (t>=' ')
+      write(1, &t, 1);
+  else {
+      setinv();
+      t+='@';
+      write(1, &t, 1);
+      normal();
+  }
+}
+
+#ifdef __GNUC__
+void ofsredisplay(int x) {
+#else
+void ofsredisplay(x) {
+#endif
+/* redisplays starting at x */
+  unsigned char *w;
+  int i;
+  gotoxy(x, yinput);
+  if (inputlast-inputofs>=x) {
+    i=((inputlast-inputofs>cols-1 ? cols-1-x : inputlast-inputofs-x));
+    for (w=input+inputofs+x; i--; w++) write1(*w, w-input);
+  }
+  cleareol();
+  gotoxy(inputcursor, yinput);
+  cursorwhere=1;
+}
+
+#ifdef __GNUC__
+void delempty(struct histinfo *leavealone) {
+#else
+void delempty(leavealone)
+struct histinfo *leavealone; {
+#endif
+  struct histinfo *h, *h2;
+  int cont=0;
+  h=histoldest;
+  do {
+    cont=0;
+    if ((h->len<=h->plen) && (h!=leavealone)) {
+      histlines--;
+      h->next->prev=h->prev;
+      h->prev->next=h->next;
+      h2=h->prev;
+      free(h);
+      if (h==histoldest) {
+        histoldest=h2;
+       cont=1;
+      }
+      h=h2;
+    } else h=h->prev;
+  } while ((h!=histoldest || cont) && histlines>0);
+  if (!histlines) {
+    histoldest=NULL;
+    return;
+  }
+}
+
+struct histinfo *makenew() {
+  struct histinfo *nh;
+  if (!histlines) {
+    nh=(struct histinfo *)malloc(sizeof (struct histinfo));
+    nh->prev=nh->next=histoldest=nh;
+    histlines++;
+  } else if (histlines<MAX_HIST_LINES) {
+    nh=(struct histinfo *)malloc(sizeof (struct histinfo));
+    nh->prev=histoldest;
+    nh->next=histoldest->next;
+    histoldest->next=nh;
+    nh->next->prev=nh;
+    histlines++;
+  } else {
+    nh=histoldest;
+    histoldest=nh->prev;
+  }
+  return nh;
+}
+
+#ifdef __GNUC__
+void sendline(int yank) {
+#else
+void sendline(yank) {
+#endif
+  if (!specialprompt) {
+    histcurrent->len=inputlast;
+    histcurrent->plen=plen;
+  }
+  if (!yank) {
+    input[inputlast]='\n';
+    if (printmode) formatter(input, inputlast+1);
+    if (write(writefd, input+plen, inputlast+1-plen)<inputlast+1-plen)
+      cleanupexit(1, "write error");
+  }
+  input[inputlast]='\0';
+  delempty(NULL);
+  histcurrent=makenew();
+  input=histcurrent->string;
+  strcpy(input, prompt);
+  plen=strlen(prompt);
+  inputofs=specialprompt=0;
+  modified=1;
+  inputcursor=inputlast=plen;
+  ofsredisplay(0);
+  no_echo=0;
+}
+
+void modify() {
+  struct histinfo *h;
+  if (!modified) {
+    if (inputlast>plen) {
+      h=histcurrent;
+      delempty(h);
+      histcurrent=makenew();
+      strcpy(histcurrent->string, h->string);
+      input=histcurrent->string;
+    }
+    modified=1;
+  }
+}
+
+void fixpos() {
+  if (inputcursor<8 && inputofs>0) {
+    inputofs-=cols-16;
+    inputcursor+=cols-16;
+    if (inputofs<0) {
+      inputcursor+=inputofs;
+      inputofs=0;
+    }
+    ofsredisplay(0);
+  } else if (inputcursor>cols-8) {
+    inputofs+=cols-16;
+    inputcursor-=cols-16;
+    ofsredisplay(0);
+  }
+}
+
+void reshow() {
+  if (inputlast>cols-8) {
+    inputcursor=cols-9;
+    inputofs=inputlast-cols+9;
+  } else {
+    inputofs=0;
+    inputcursor=inputlast;
+  }
+  ofsredisplay(0);
+}
+
+#ifdef __GNUC__
+void inschar(unsigned char t) {
+#else
+void inschar(t)
+unsigned char t; {
+#endif
+
+  unsigned char *tmp;
+
+  if (inputlast<BUF_SIZE-4) {
+    modify();
+    if (inputofs+inputcursor==inputlast) {
+      write1(t, inputlast);
+      input[inputlast++]=t;
+      input[inputlast]='\0';
+      inputcursor++;
+    } else {
+      tmp=input+inputlast;
+      while (tmp>=input+inputofs+inputcursor)
+       *(tmp+1)=(*tmp--);
+      input[inputofs+(inputcursor++)]=t;
+      inputlast++;
+      ofsredisplay(inputcursor-1);
+    }
+    fixpos();
+  }
+}
+
+#ifdef __GNUC__
+void dokbdchar(unsigned char t) {
+#else
+void dokbdchar(t)
+unsigned char t; {
+#endif
+
+  unsigned char *tmp;
+
+  if (inarrow==1) {
+    if (t=='[' || t=='O') {
+      inarrow++;
+      return;
+    }
+    inarrow=0;
+  } else if (inarrow==2) {
+    inarrow=0;
+    if (t=='D') t=2;
+    else if (t=='C') t=6;
+    else if (t=='A') t=16;
+    else if (t=='B') t=14;
+    else return;
+  }
+  if (ctrlx && !quote) {
+    ctrlx=0;
+    t|=0x20;
+    if (dispmode>0 && ((t=='h' && !hold_mode) || t=='y')) {
+      hold_mode=1;
+      hold_lines=0;
+      if (cursorwhere!=1) fullscroll();
+      cursorwhere=2;
+      normal();
+      gotoxy(cols-4, ystatus);
+      setinv();
+      write(1, "(h)", 3);
+      normal();
+    } else if (dispmode>0 && ((t=='h' && hold_mode) || t=='n')) {
+      hold_mode=0;
+      if (cursorwhere!=1) fullscroll();
+      cursorwhere=2;
+      normal();
+      gotoxy(cols-4, ystatus);
+      setinv();
+      write(1, "   ", 3);
+      normal();
+    } else if (dispmode>0 && t=='i') {
+      dispmode=3-dispmode;
+      bold=inv=under=0;
+    } else if (dispmode>0 && t=='b') {
+      beep=!beep;
+    } else if (t=='c') cleanupexit(1, "exiting");
+    return;
+  }
+  if (cutline) donl=1;
+  if (cursorwhere!=1) {
+    normal();
+    fullscroll();
+    gotoxy(inputcursor, yinput);
+    cursorwhere=1;
+  }
+  if (t==24 && !quote) {
+    ctrlx=1;
+    return;
+  } else ctrlx=0;
+  if (t==27 && !quote) {
+    inarrow=1;
+  } else if ((t==10 || t==13) && !quote) {  /* return, newline */
+    sendline(0);
+    if (tablines) {
+      curtabr=oldest;
+      curtabt=oldest->next;
+    }
+  } else if (t==25 && !quote) {          /* ^y */
+    if (!specialprompt) {
+      sendline(1);
+      if (tablines) {
+       curtabr=oldest;
+       curtabt=oldest->next;
+      }
+    }
+  } else if (t==21 && !quote) {   /* ^u */
+    modify();
+    input[plen]='\0';
+    inputcursor=inputlast=plen;
+    inputofs=0;
+    ofsredisplay(0);
+  } else if ((t==8 || t==0x7f) && !quote) {  /* ^h, ^? */
+    if (inputcursor>plen) {
+      modify();
+      tmp=input+inputcursor+inputofs;
+      while (tmp<input+inputlast)
+       *(tmp-1)=(*tmp++);
+      input[--inputlast]='\0';
+      gotoxy(--inputcursor, yinput);
+      ofsredisplay(inputcursor);
+      fixpos();
+    }
+  } else if (t==4 && !quote) {  /* ^d */
+    if (inputcursor+inputofs<inputlast) {
+      modify();
+      tmp=input+inputcursor+inputofs+1;
+      while (tmp<input+inputlast)
+       *(tmp-1)=(*tmp++);
+      input[--inputlast]='\0';
+      gotoxy(inputcursor, yinput);
+      ofsredisplay(inputcursor);
+    }
+  } else if (t==11 && !quote) {  /* ^k */
+    if (inputcursor+inputofs<inputlast) {
+      modify();
+      input[inputlast=inputofs+inputcursor]='\0';
+      ofsredisplay(inputcursor);
+    }
+  } else if (t==2 && !quote) {  /* ^b */
+    if (inputcursor>0 && (inputcursor>plen || inputofs>0)) {
+      gotoxy(--inputcursor, yinput);
+      fixpos();
+    }
+  } else if (t==6 && !quote) {  /* ^f */
+    if (inputcursor+inputofs<inputlast) {
+      gotoxy(++inputcursor, yinput);
+      fixpos();
+    }
+  } else if (t==1 && !quote) { /* ^a */
+    if (inputcursor+inputofs>plen) {
+      if (inputofs==0)
+       gotoxy((inputcursor=plen), yinput);
+      else {
+       inputofs=0;
+       inputcursor=plen;
+       ofsredisplay(0);
+      }
+    }
+  } else if (t==5 && !quote) { /* ^e */
+    if (inputcursor+inputofs<inputlast) {
+      if (inputlast-inputofs<cols-3) {
+       gotoxy((inputcursor=inputlast-inputofs), yinput);
+      } else if (inputlast>cols-8) {
+       inputcursor=cols-9;
+       inputofs=inputlast-cols+9;
+       ofsredisplay(0);
+      } else {
+       inputofs=0;
+       inputcursor=inputlast;
+       ofsredisplay(0);
+      }
+    }
+  } else if (t==12 && !quote) { /* ^l */
+    displaystatus();
+    ofsredisplay(0);
+  } else if (t==9 && !quote) { /* TAB */
+    if (tablines) {
+      modify();
+      strcpy(input+plen, curtabt->string);
+      curtabr=curtabt->prev;
+      curtabt=curtabt->next;
+      inputlast=strlen(input);
+      reshow();
+    }
+  } else if (t==18 && !quote) { /* ^r */
+    if (tablines) {
+      modify();
+      strcpy(input+plen, curtabr->string);
+      curtabt=curtabr->next;
+      curtabr=curtabr->prev;
+      inputlast=strlen(input);
+      reshow();
+    }
+  } else if (t==16 && !quote) { /* ^p */
+    if (histlines>1 && !specialprompt) {
+      histcurrent->plen=plen;
+      histcurrent->len=inputlast;
+      histcurrent=histcurrent->next;
+      plen=histcurrent->plen;
+      inputlast=histcurrent->len;
+      input=histcurrent->string;
+      modified=0;
+      reshow();
+    }
+  } else if (t==14 && !quote) { /* ^n */
+    if (histlines>1 && !specialprompt) {
+      histcurrent->plen=plen;
+      histcurrent->len=inputlast;
+      histcurrent=histcurrent->prev;
+      plen=histcurrent->plen;
+      inputlast=histcurrent->len;
+      input=histcurrent->string;
+      modified=0;
+      reshow();
+    }
+  } else if (t==15 &&!quote) { /* ^o */
+    if (strlen(o_buffer)) modify();
+    for (tmp=o_buffer; *tmp; tmp++) inschar(*tmp);
+  } else if (t==20 && !quote) { /* ^t */
+    write(writefd, ctrl_t, strlen(ctrl_t));
+  } else if (t==22 && !quote) { /* ^v */
+    quote++;
+    return;
+#ifdef CONTROL_W
+  } else if (t==23 && !quote) { /* ^w */
+    fullscroll();
+    normal();
+    clearscreen();
+    bold=inv=under=wherex=wherey=donl=0;
+    displaystatus();
+    ofsredisplay(0);
+#endif
+  } else inschar(t);
+  quote=0;
+}
+
+#ifdef __GNUC__
+void barf(unsigned char *m) {
+#else
+void barf(m)
+unsigned char *m; {
+#endif
+  fprintf(stderr, "%s\n", m);
+  exit(1);
+}
+
+char *myname;
+
+void use() {
+  fprintf(stderr, "Use: %s [options] program [program's options]\n", myname);
+  fprintf(stderr, "Options are:\n");
+  fprintf(stderr, "   -raw, -cooked, -irc  : set display mode\n");
+  fprintf(stderr, "   -print               : print your input lines\n");
+  fprintf(stderr, "   -prompt <prompt>     : specify a command-line prompt\n");
+  fprintf(stderr, "   -hold                : pause after each full screen (for cooked/irc mode)\n");
+  fprintf(stderr, "   -beep                : let beeps through (for cooked/irc mode)\n");
+  fprintf(stderr, "   -flow                : leave ^S/^Q alone for flow control\n");
+  exit(1);
+}
+
+#ifdef __GNUC__
+int main(int argc, char *argv[]) {
+#else
+int main(argc, argv)
+int argc;
+char *argv[]; {
+#endif
+
+  char *vr;
+  int pfds0[2], pfds1[2], pfds2[2];
+
+  myname=(*argv);
+  prompt=nullstring;
+  while (argc>1) {
+    if (strcmp(argv[1], "-raw")==0) {
+      dispmode=0;
+      argv++; argc--;
+    } else if (strcmp(argv[1], "-cooked")==0) {
+      dispmode=1;
+      argv++; argc--;
+    } else if (strcmp(argv[1], "-irc")==0) {
+      dispmode=2;
+      argv++; argc--;
+    } else if (strcmp(argv[1], "-hold")==0) {
+      hold_mode=1;
+      argv++; argc--;
+    } else if (strcmp(argv[1], "-print")==0) {
+      argv++; argc--;
+      if (prompt==nullstring) prompt=defprompt;
+      printmode=1;
+    } else if (strcmp(argv[1], "-beep")==0) {
+      beep=1;
+      argv++; argc--;
+    } else if (strcmp(argv[1], "-flow")==0) {
+      flow=1;
+      argv++; argc--;
+    } else if (strcmp(argv[1], "-prompt")==0) {
+      if (argc>2) prompt=(unsigned char *)argv[2];
+      if (strlen(prompt)>8) barf("Prompt too long");
+      argv+=2; argc-=2;
+    } else break;
+  }
+  if (argc<2) use();
+  if (!isatty(0)) barf("I can only run on a tty, sorry");
+  if ((termtype=getenv("TERM"))==NULL) barf("No terminal type set");
+  if (tgetent(termcap, termtype)<1) barf("No termcap info for your terminal");
+  tc=capabilities;
+  if ((t_cm=(char *)tgetstr("cm", &tc))==NULL)
+    barf("Can't find a way to move the cursor around with your terminal");
+  if ((t_cl=(char *)tgetstr("cl", &tc))==NULL)
+    barf("Can't find a way to clear the screen with your terminal");
+  if ((t_ce=(char *)tgetstr("ce", &tc))==NULL)
+    barf("Can't find a way to clear to end of line with your terminal");
+  if ((t_cs=(char *)tgetstr("cs", &tc))==NULL) {
+    if (strncmp(termtype, "xterm", 5)==0 || strncmp(termtype, "vt100", 5)==0)
+      ansi_cs=1;
+    else
+      barf("Can't find a way to set the scrolling region with your terminal");
+  }
+  if ((t_me=(char *)tgetstr("me", &tc))!=NULL) {
+    if ((t_mr=(char *)tgetstr("mr", &tc))==NULL) t_mr=t_me;
+    if ((t_md=(char *)tgetstr("md", &tc))==NULL) t_md=t_me;
+    if ((t_us=(char *)tgetstr("us", &tc))==NULL) t_us=t_me;
+  } else if ((t_me=(char *)tgetstr("se", &tc))!=NULL &&
+            (t_mr=(char *)tgetstr("so", &tc))!=NULL) {
+    t_md=t_mr;
+    t_us=tc;
+    (*tc++)='\0';
+  } else {
+    t_me=t_md=t_mr=t_us=tc;
+    (*tc++)='\0';
+  }
+
+/*
+  if ((ttyfd=open("/dev/tty", O_RDWR))<0 &&
+      (ttyfd=open("/dev/tty", O_RDONLY))<0) barf("Can't open terminal!");
+    */
+    ttyfd = 0;
+
+#ifdef TIOCGWINSZ
+  if (ioctl(ttyfd, TIOCGWINSZ, &wsz)<0 || wsz.ws_row<1 || wsz.ws_col<1) {
+#endif
+    lines=((vr=getenv("LINES"))?atoi(vr):0);
+    cols=((vr=getenv("COLUMNS"))?atoi(vr):0);
+    if (lines<1 || cols<1) {
+      if ((lines=tgetnum("li"))<1 || (cols=tgetnum("co"))<1) {
+       lines=24; cols=80;
+      }
+    }
+#ifdef TIOCGWINSZ
+  } else {
+    lines=wsz.ws_row;
+    cols=wsz.ws_col;
+  }
+#endif
+
+  if (pipe(pfds0)<0 || pipe(pfds1)<0 || pipe(pfds2)<0) {
+    perror("pipe");
+    exit(1);
+  }
+  mypid=getpid();
+  switch (pid=fork()) {
+    case -1:
+      perror("fork");
+      exit(1);
+    case 0:
+      if (pfds0[0]!=0) dup2(pfds0[0], 0);
+      if (pfds1[1]!=1) dup2(pfds1[1], 1);
+      if (pfds2[1]!=2) dup2(pfds2[1], 2);
+      if (pfds0[0]>2) close(pfds0[0]);
+      if (pfds0[1]>2) close(pfds0[1]);
+      if (pfds1[0]>2) close(pfds1[0]);
+      if (pfds1[1]>2) close(pfds1[1]);
+      if (pfds2[0]>2) close(pfds2[0]);
+      if (pfds2[1]>2) close(pfds2[1]);
+      /* okay we can read from 0 and write to 1 and 2, now.. it seems */
+      execvp(argv[1], argv+1);
+      perror("exec");
+      sleep(1);
+      exit(1);
+    default:
+      close(pfds0[0]);
+      close(pfds1[1]);
+      close(pfds2[1]);
+      readfd=pfds1[0];
+      writefd=pfds0[1];
+      errfd=pfds2[0];
+  }
+
+#ifdef USE_SGTTY
+
+  if (ioctl(ttyfd, TIOCGETP, &term)<0 || ioctl(ttyfd, TIOCGETC, &tch)<0 ||
+      ioctl(ttyfd, TIOCGLTC, &lch)<0) {
+    perror("sgtty get ioctl");
+    exit(1);
+  }
+  term0=term;
+  tch0=tch;
+  lch0=lch;
+  term.sg_flags|=CBREAK;
+  term.sg_flags&= ~ECHO & ~CRMOD;
+
+  memset(&tch, -1, sizeof(tch));
+  memset(&lch, -1, sizeof(lch));
+  tch.t_intrc=(char)28;
+  tch.t_quitc=(char)3;
+  if (flow) {
+    tch.t_startc=(char)17;
+    tch.t_stopc=(char)19;
+  }
+  lch.t_suspc=(char)26;
+
+  if (ioctl(ttyfd, TIOCSETP, &term)<0 || ioctl(ttyfd, TIOCSETC, &tch)<0 ||
+      ioctl(ttyfd, TIOCSLTC, &lch)<0) {
+    perror("sgtty set ioctl");
+    exit(1);
+  }
+
+#else
+  if (tcgetattr(ttyfd, &term)<0) {
+    perror("tcgetattr");
+    exit(1);
+  }
+  term0=term;
+
+  term.c_lflag &= ~ECHO & ~ICANON;
+  term.c_cc[VTIME]=(char)0;
+  term.c_cc[VMIN]=(char)1;
+  if (!flow) {
+    term.c_cc[VSTOP]=(char)0;
+    term.c_cc[VSTART]=(char)0;
+  }
+  term.c_cc[VQUIT]=(char)3;
+  term.c_cc[VINTR]=(char)28; /* reverse ^c and ^\ */
+  term.c_cc[VSUSP]=(char)26;
+#ifdef VREPRINT
+  term.c_cc[VREPRINT]=(char)0;
+#endif
+#ifdef VDISCARD
+  term.c_cc[VDISCARD]=(char)0;
+#endif
+#ifdef VLNEXT
+  term.c_cc[VLNEXT]=(char)0;
+#endif
+#ifdef VDSUSP
+  term.c_cc[VDSUSP]=(char)0;
+#endif
+
+  if (tcsetattr(ttyfd, TCSANOW, &term)<0) {
+    perror("tcsetattr");
+    exit(1);
+  }
+#endif
+
+  allsigs();
+
+  ystatus=lines-2;
+  yinput=lines-1;
+
+  if (lines>255) barf("Screen too big");
+  if (ystatus<=2 || cols<20) barf("Screen too small");
+
+  statusline=(unsigned char *)malloc(MAX_COLS);
+  writebuf=(unsigned char *)malloc(20*BUF_SIZE);
+  strcpy(tmpstr, " ");
+  for (i=1; i<argc; i++)
+    if (strlen(tmpstr)+strlen(argv[i])<cols-1) {
+      strcat(tmpstr, argv[i]);
+      strcat(tmpstr, " ");
+    }
+  setstatus(tmpstr);
+
+  if (dispmode==0) wherey=ystatus-1;
+  clearscreen();
+  displaystatus();
+
+  histoldest=histcurrent=(struct histinfo *)malloc(sizeof (struct histinfo));
+  input=histcurrent->string;
+  histcurrent->prev=histcurrent->next=histcurrent;
+  histlines=1;
+  plen=strlen(prompt);
+  inputlast=inputcursor=plen;
+  strcpy(input, prompt);
+  ofsredisplay(0);
+  *protcmd='\0';
+  *o_buffer='\0';
+  cursorwhere=1;
+
+  FD_ZERO(&ready);
+  FD_SET(ttyfd, &ready);
+  FD_SET(readfd, &ready);
+  FD_SET(errfd, &ready);
+
+  while(1) {
+    result=ready;
+    if (select(64, &result, NULL, NULL, NULL)<=0)
+      if (errno==EINTR) continue;
+      else cleanupexit(1, "select error");
+
+    if (FD_ISSET(readfd, &result))
+      if ((rc=read(readfd, readbuf, BUF_SIZE))>0)
+        doprogramline(readbuf, rc);
+      else
+        cleanupexit(1, "program terminated");
+    if (FD_ISSET(errfd, &result))
+      if ((rc=read(errfd, readbuf, BUF_SIZE))>0)
+        doprogramline(readbuf, rc);
+      else
+        cleanupexit(1, "program terminated");
+    if (FD_ISSET(ttyfd, &result))
+      if ((rrc=read(0, readbuf, BUF_SIZE))>0)
+        for (t=readbuf; rrc>0; rrc--) dokbdchar(*(t++));
+      else
+       cleanupexit(1, "read error from keyboard");
+  }
+}
+