summaryrefslogtreecommitdiff
path: root/example/multixterm
diff options
context:
space:
mode:
authorAnas Nashif <anas.nashif@intel.com>2012-11-04 17:21:04 -0800
committerAnas Nashif <anas.nashif@intel.com>2012-11-04 17:21:04 -0800
commite0b431a48cc3ac5d3ec32f06eddd9708ad655fa2 (patch)
treece4c73521220fbb751c2be6a42e85ff6a6cbff97 /example/multixterm
downloadexpect-e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2.tar.gz
expect-e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2.tar.bz2
expect-e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2.zip
Imported Upstream version 5.45upstream/5.45
Diffstat (limited to 'example/multixterm')
-rwxr-xr-xexample/multixterm993
1 files changed, 993 insertions, 0 deletions
diff --git a/example/multixterm b/example/multixterm
new file mode 100755
index 0000000..d0abfe7
--- /dev/null
+++ b/example/multixterm
@@ -0,0 +1,993 @@
+#!/bin/sh
+# -*- tcl -*-
+# The next line is executed by /bin/sh, but not tcl \
+exec tclsh "$0" ${1+"$@"}
+
+package require Expect
+
+#
+# NAME
+# multixterm - drive multiple xterms separately or together
+#
+# SYNOPSIS
+# multixterm [-xa "xterm args"]
+# [-xc "command"]
+# [-xd "directory"]
+# [-xf "file"]
+# [-xn "xterm names"]
+# [-xv] (enable verbose mode)
+# [-xh] or [-x?] (help)
+# [xterm names or user-defined args...]
+#
+# DESCRIPTION
+# Multixterm creates multiple xterms that can be driven together
+# or separately.
+#
+# In its simplest form, multixterm is run with no arguments and
+# commands are interactively entered in the first entry field.
+# Press return (or click the "new xterm" button) to create a new
+# xterm running that command.
+#
+# Keystrokes in the "stdin window" are redirected to all xterms
+# started by multixterm. xterms may be driven separately simply
+# by focusing on them.
+#
+# The stdin window must have the focus for keystrokes to be sent
+# to the xterms. When it has the focus, the color changes to
+# aquamarine. As characters are entered, the color changes to
+# green for a second. This provides feedback since characters
+# are not echoed in the stdin window.
+#
+# Typing in the stdin window while holding down the alt or meta
+# keys sends an escape character before the typed characters.
+# This provides support for programs such as emacs.
+#
+# ARGUMENTS
+# The optional -xa argument indicates arguments to pass to
+# xterm.
+#
+# The optional -xc argument indicates a command to be run in
+# each named xterm (see -xn). With no -xc argument, the command
+# is the current shell.
+#
+# The optional -xd argument indicates a directory to search for
+# files that will appear in the Files menu. By default, the
+# directory is: ~/lib/multixterm
+#
+# The optional -xf argument indicates a file to be read at
+# startup. See FILES below for more info.
+#
+# The optional -xn argument indicates a name for each xterm.
+# This name will also be substituted for any %n in the command
+# argument (see -xc).
+#
+# The optional -xv flag puts multixterm into a verbose mode
+# where it will describe some of the things it is doing
+# internally. The verbose output is not intended to be
+# understandable to anyone but the author.
+#
+# Less common options may be changed by the startup file (see
+# FILES below).
+#
+# All the usual X and wish flags are supported (i.e., -display,
+# -name). There are so many of them that to avoid colliding and
+# make them easy to remember, all the multixterm flags begin
+# with -x.
+#
+# If any arguments do not match the flags above, the remainder
+# of the command line is made available for user processing. By
+# default, the remainder is used as a list of xterm names in the
+# style of -xn. The default behavior may be changed using the
+# .multixtermrc file (see DOT FILE below).
+#
+# EXAMPLE COMMAND LINE ARGUMENTS
+# The following command line starts up two xterms using ssh to
+# the hosts bud and dexter.
+#
+# multixterm -xc "ssh %n" bud dexter
+#
+# FILES
+# Command files may be used to drive or initialize multixterm.
+# The File menu may be used to invoke other files. If files
+# exist in the command file directory (see -xd above), they will
+# appear in the File menu. Files may also be loaded by using
+# File->Open. Any filename is acceptable but the File->Open
+# browser defaults to files with a .mxt suffix.
+#
+# Files are written in Tcl and may change any variables or
+# invoke any procedures. The primary variables of interest are
+# 'xtermCmd' which identifies the command (see -xc) and
+# 'xtermNames' which is a list of names (see -xn). The
+# procedure xtermStartAll, starts xterms for each name in the
+# list. Other variables and procedures may be discovered by
+# examining multixterm itself.
+#
+# EXAMPLE FILE
+# The following file does the same thing as the earlier example
+# command line:
+#
+# # start two xterms connected to bud and dexter
+# set xtermCmd "ssh %n"
+# set xtermNames {bud dexter}
+# xtermStartAll
+#
+# DOT FILE
+# At startup, multixterm reads ~/.multixtermrc if present. This
+# is similar to the command files (see FILES above) except that
+# .multixtermrc may not call xtermStartAll. Instead it is
+# called implicitly, similar to the way that it is implicit in
+# the command line use of -xn.
+#
+# The following example .multixtermrc file makes every xterm run
+# ssh to the hosts named on the command line.
+#
+# set xtermCmd "ssh %n"
+#
+# Then multixterm could be called simply:
+#
+# multixterm bud dexter
+#
+# If any command-line argument does not match a multixterm flag,
+# the remainder of the command line is made available to
+# .multixtermrc in the argv variable. If argv is non-empty when
+# .multixtermrc returns, it is assigned to xtermNames unless
+# xtermNames is non-empty in which case, the content of argv is
+# ignored.
+#
+# Commands from .multixtermrc are evaluated early in the
+# initialization of multixterm. Anything that must be done late
+# in the initialization (such as adding additional bindings to
+# the user interface) may be done by putting the commands inside
+# a procedure called "initLate".
+#
+# MENUS
+# Except as otherwise noted, the menus are self-explanatory.
+# Some of the menus have dashed lines as the first entry.
+# Clicking on the dashed lines will "tear off" the menus.
+#
+# USAGE SUGGESTION - ALIASES AND COMMAND FILES
+# Aliases may be used to store lengthy command-line invocations.
+# Command files can be also be used to store such invocations
+# as well as providing a convenient way to share configurations.
+#
+# Tcl is a general-purpose language. Thus multixterm command
+# files can be extremely flexible, such as loading hostnames
+# from other programs or files that may change from day-to-day.
+# In addition, command files can be used for other purposes.
+# For example, command files may be used to prepared common
+# canned interaction sequences. For example, the command to
+# send the same string to all xterms is:
+#
+# xtermSend "a particularly long string"
+#
+# The File menu (torn-off) makes canned sequences particularly
+# convenient. Interactions could also be bound to a mouse
+# button, keystroke, or added to a menu via the .multixtermrc
+# file.
+#
+# USAGE SUGGESTION - HANDLING MANY XTERMS BY TILING
+# The following .multixtermrc causes tiny xterms to tile across
+# and down the screen. (You may have to adjust the parameters
+# for your screen.) This can be very helpful when dealing with
+# large numbers of xterms.
+#
+# set yPos 0
+# set xPos 0
+#
+# trace variable xtermArgs r traceArgs
+#
+# proc traceArgs {args} {
+# global xPos yPos
+# set ::xtermArgs "-geometry 80x12+$xPos+$yPos -font 6x10"
+# if {$xPos} {
+# set xPos 0
+# incr yPos 145
+# if {$yPos > 800} {set yPos 0}
+# } else {
+# set xPos 500
+# }
+# }
+#
+# The xtermArgs variable in the code above is the variable
+# corresponding to the -xa argument.
+#
+# xterms can be also be created directly. The following command
+# file creates three xterms overlapped horizontally:
+#
+# set xPos 0
+#
+# foreach name {bud dexter hotdog} {
+# set ::xtermArgs "-geometry 80x12+$xPos+0 -font 6x10"
+# set ::xtermNames $name
+# xtermStartAll
+# incr xPos 300
+# }
+#
+# USAGE SUGGESTION - SELECTING HOSTS BY NICKNAME
+# The following .multixtermrc shows an example of changing the
+# default handling of the arguments from hostnames to a filename
+# containing hostnames:
+#
+# set xtermNames [exec cat $argv]
+#
+# The following is a variation, retrieving the host names from
+# the yp database:
+#
+# set xtermNames [exec ypcat $argv]
+#
+# The following hardcodes two sets of hosts, so that you can
+# call multixterm with either "cluster1" or "cluster2":
+#
+# switch $argv {
+# cluster1 {
+# set xtermNames "bud dexter"
+# }
+# cluster2 {
+# set xtermNames "frank hotdog weiner"
+# }
+# }
+#
+# COMPARE/CONTRAST
+# It is worth comparing multixterm to xkibitz. Multixterm
+# connects a separate process to each xterm. xkibitz connects
+# the same process to each xterm.
+#
+# LIMITATIONS
+# Multixterm provides no way to remotely control scrollbars,
+# resize, and most other window system related functions.
+#
+# Multixterm can only control new xterms that multixterm itself
+# has started.
+#
+# As a convenience, the File menu shows a limited number of
+# files. To show all the files, use File->Open.
+#
+# FILES
+# $DOTDIR/.multixtermrc initial command file
+# ~/.multixtermrc fallback command file
+# ~/lib/multixterm/ default command file directory
+#
+# BUGS
+# If multixterm is killed using an uncatchable kill, the xterms
+# are not killed. This appears to be a bug in xterm itself.
+#
+# Send/expect sequences can be done in multixterm command files.
+# However, due to the richness of the possibilities, to document
+# it properly would take more time than the author has at present.
+#
+# REQUIREMENTS
+# Requires Expect 5.36.0 or later.
+# Requires Tk 8.3.3 or later.
+#
+# VERSION
+#! $::versionString
+# The latest version of multixterm is available from
+# http://expect.nist.gov/example/multixterm . If your version of Expect
+# and Tk are too old (see REQUIREMENTS above), download a new version of
+# Expect from http://expect.nist.gov
+#
+# DATE
+#! $::versionDate
+#
+# AUTHOR
+# Don Libes <don@libes.com>
+#
+# LICENSE
+# Multixterm is in the public domain; however the author would
+# appreciate acknowledgement if multixterm or parts of it or ideas from
+# it are used.
+
+######################################################################
+# user-settable things - override them in the ~/.multixtermrc file
+# or via command-line options
+######################################################################
+
+set palette #d8d8ff ;# lavender
+set colorTyping green
+set colorFocusIn aquamarine
+
+set xtermNames {}
+set xtermCmd $env(SHELL)
+set xtermArgs ""
+set cmdDir ~/lib/multixterm
+set inputLabel "stdin window"
+
+set fileMenuMax 30 ;# max number of files shown in File menu
+set tearoffMenuMin 2 ;# min number of files needed to enable the File
+ ;# menu to be torn off
+
+proc initLate {} {} ;# anything that must be done late in initialization
+ ;# such as adding/modifying bindings, may be done by
+ ;# redefining this
+
+######################################################################
+# end of user-settable things
+######################################################################
+
+######################################################################
+# sanity checking
+######################################################################
+
+set versionString 1.8
+set versionDate "2004/06/29"
+
+package require Tcl
+catch {package require Tk} ;# early versions of Tk had no package
+package require Expect
+
+proc exit1 {msg} {
+ puts "multixterm: $msg"
+ exit 1
+}
+
+exp_version -exit 5.36
+
+proc tkBad {} {
+ exit1 "requires Tk 8.3.3 or later but you are using Tk $::tk_patchLevel."
+}
+
+if {$tk_version < 8.3} {
+ tkBad
+} elseif {$tk_version == 8.3} {
+ if {[lindex [split $tk_patchLevel .] 2] < 3} tkBad
+}
+
+######################################################################
+# process args - has to be done first to get things like -xv working ASAP
+######################################################################
+
+# set up verbose mechanism early
+
+set verbose 0
+proc verbose {msg} {
+ if {$::verbose} {
+ if {[info level] > 1} {
+ set proc [lindex [info level -1] 0]
+ } else {
+ set proc main
+ }
+ puts "$proc: $msg"
+ }
+}
+
+# read a single argument from the command line
+proc arg_read1 {var args} {
+ if {0 == [llength $args]} {
+ set argname -$var
+ } else {
+ set argname $args
+ }
+
+ upvar argv argv
+ upvar $var v
+
+ verbose "$argname"
+ if {[llength $argv] < 2} {
+ exit1 "$argname requires an argument"
+ }
+
+ set v [lindex $argv 1]
+ verbose "set $var $v"
+ set argv [lrange $argv 2 end]
+}
+
+proc xtermUsage {{msg {}}} {
+ if {![string equal $msg ""]} {
+ puts "multixtermrc: $msg"
+ }
+ puts {usage: multixterm [flags] ... where flags are:
+ [-xa "xterm args"]
+ [-xc "command"]
+ [-xd "directory"]
+ [-xf "file"]
+ [-xn "xterm names"]
+ [-xv] (enable verbose mode)
+ [-xh] or [-x?] (help)
+ [xterm names or user-defined args...]}
+ exit
+}
+
+while {[llength $argv]} {
+ set flag [lindex $argv 0]
+ switch -- $flag -x? - -xh {
+ xtermUsage
+ } -xc {
+ arg_read1 xtermCmd -xc
+ } -xn {
+ arg_read1 xtermNames -xn
+ } -xa {
+ arg_read1 xtermArgs -xa
+ } -xf {
+ arg_read1 cmdFile -xf
+ if {![file exists $cmdFile]} {
+ exit1 "can't read $cmdFile"
+ }
+ } -xd {
+ arg_read1 cmdDir -xd
+ if {![file exists $cmdDir]} {
+ exit1 "can't read $cmdDir"
+ }
+ } -xv {
+ set argv [lrange $argv 1 end]
+ set verbose 1
+ puts "main: verbose on"
+ } default {
+ verbose "remaining args: $argv"
+ break ;# let user handle remaining args later
+ }
+}
+
+######################################################################
+# determine and load rc file - has to be done now so that widgets
+# can be affected
+######################################################################
+
+# if user has no $DOTDIR, fall back to home directory
+if {![info exists env(DOTDIR)]} {
+ set env(DOTDIR) ~
+}
+# catch bogus DOTDIR, otherwise glob will lose the bogus directory
+# and it won't appear in the error msg
+if {[catch {glob $env(DOTDIR)} dotdir]} {
+ exit1 "$env(DOTDIR)/.multixtermrc can't be found because $env(DOTDIR) doesn't exist or can't be read"
+}
+set rcFile $dotdir/.multixtermrc
+
+set fileTypes {
+ {{Multixterm Files} *.mxt}
+ {{All Files} *}
+}
+
+proc openFile {{fn {}}} {
+ verbose "opening $fn"
+ if {[string equal $fn ""]} {
+ set fn [tk_getOpenFile \
+ -initialdir $::cmdDir \
+ -filetypes $::fileTypes \
+ -title "multixterm file"]
+ if {[string match $fn ""]} return
+ }
+ uplevel #0 source [list $fn]
+ verbose "xtermNames = \"$::xtermNames\""
+ verbose "xtermCmd = $::xtermCmd"
+}
+
+if {[file exists $rcFile]} {
+ openFile $rcFile
+} else {
+ verbose "$rcFile: not found"
+}
+
+if {![string equal "" $argv]} {
+ if {[string equal $xtermNames ""]} {
+ set xtermNames $argv
+ }
+}
+
+######################################################################
+# Describe and initialize some important globals
+######################################################################
+
+# ::activeList and ::activeArray both track which xterms to send
+# (common) keystrokes to. Each element in activeArray is connected to
+# the active menu. The list version is just a convenience making the
+# send function easier/faster.
+
+set activeList {}
+
+# ::names is an array of xterm names indexed by process spawn ids.
+
+set names(x) ""
+unset names(x)
+
+# ::xtermSid is an array of xterm spawn ids indexed by process spawn ids.
+# ::xtermPid is an array of xterm pids indexed by process spawn id.
+
+######################################################################
+# create an xterm and establish connections
+######################################################################
+
+proc xtermStart {cmd name} {
+ verbose "starting new xterm running $cmd with name $name"
+
+ ######################################################################
+ # create pty for xterm
+ ######################################################################
+ set pid [spawn -noecho -pty]
+ verbose "spawn -pty: pid = $pid, spawn_id = $spawn_id"
+ set sidXterm $spawn_id
+ stty raw -echo < $spawn_out(slave,name)
+
+ regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
+ if {[string compare $c1 "/"] == 0} {
+ set c1 0
+ }
+
+ ######################################################################
+ # prepare to start xterm by making sure xterm name is unique
+ # X doesn't care but active menu won't make sense unless names are unique
+ ######################################################################
+ set unique 1
+ foreach oldName [array names ::names] {
+ if {[string match "$name" $::names($oldName)]} {
+ set unique 0
+ }
+ }
+ verbose "uniqueness of $name: $unique"
+
+ set safe [safe $name]
+
+ # if not unique, look at the numerical suffixes of all matching
+ # names, find the biggest and increment it
+ if {!$unique} {
+ set suffix 2
+ foreach oldName [array names ::names] {
+ verbose "regexp ^[set safe](\[0-9]+)$ $::names($oldName) X num"
+ if {[regexp "^[set safe](\[0-9]+)$" $::names($oldName) X num]} {
+ verbose "matched, checking suffix"
+ if {$num >= $suffix} {
+ set suffix [expr $num+1]
+ verbose "new suffix: $suffix"
+ }
+ }
+ }
+ append name $suffix
+ verbose "new name: $name"
+ }
+
+ ######################################################################
+ # start new xterm
+ ######################################################################
+ set xtermpid [eval exec xterm -name [list $name] -S$c1$c2$spawn_out(slave,fd) $::xtermArgs &]
+ verbose "xterm: pid = $xtermpid"
+ close -slave
+
+ # xterm first sends back window id, save in environment so it can be
+ # passed on to the new process
+ log_user 0
+ expect {
+ eof {wait;return}
+ -re (.*)\n {
+ # convert hex to decimal
+ # note quotes must be used here to avoid diagnostic from expr
+ set ::env(WINDOWID) [expr "0x$expect_out(1,string)"]
+ }
+ }
+
+ ######################################################################
+ # start new process
+ ######################################################################
+ set pid [eval spawn -noecho $cmd]
+ verbose "$cmd: pid = $pid, spawn_id = $spawn_id"
+ set sidCmd $spawn_id
+ lappend ::activeList $sidCmd
+ set ::activeArray($sidCmd) 1
+
+ ######################################################################
+ # link everything back to spawn id of new process
+ ######################################################################
+ set ::xtermSid($sidCmd) $sidXterm
+ set ::names($sidCmd) $name
+ set ::xtermPid($sidCmd) $xtermpid
+
+ ######################################################################
+ # connect proc output to xterm output
+ # connect xterm input to proc input
+ ######################################################################
+ expect_background {
+ -i $sidCmd
+ -re ".+" [list sendTo $sidXterm]
+ eof [list xtermKill $sidCmd]
+ -i $sidXterm
+ -re ".+" [list sendTo $sidCmd]
+ eof [list xtermKill $sidCmd]
+ }
+
+ .m.e entryconfig Active -state normal
+ .m.e.active add checkbutton -label $name -variable activeArray($sidCmd) \
+ -command [list xtermActiveUpdate $sidCmd]
+ set ::activeArray($sidCmd) 1
+}
+
+proc xtermActiveUpdate {sid} {
+ if {$::activeArray($sid)} {
+ verbose "activating $sid"
+ } else {
+ verbose "deactivating $sid"
+ }
+ activeListUpdate
+}
+
+proc activeListUpdate {} {
+ set ::activeList {}
+ foreach n [array names ::activeArray] {
+ if {$::activeArray($n)} {
+ lappend ::activeList $n
+ }
+ }
+}
+
+# make a string safe to go through regexp
+proc safe {s} {
+ string map {{[} {\[} {*} {\*} {+} {\+} {^} {\^} {$} {\\$}} $s
+}
+
+# utility to map xterm name to spawn id
+# multixterm doesn't use this but a user might want to
+proc xtermGet {name} {
+ foreach sid [array names ::names] {
+ if {[string equal $name $::names($sid)]} {
+ return $sid
+ }
+ }
+ error "no such term with name: $name"
+}
+
+# utility to activate an xterm
+# multixterm doesn't use this but a user might want to
+proc xtermActivate {sid} {
+ set ::activeArray($sid) 1
+ xtermActiveUpdate $sid
+}
+
+# utility to deactivate an xterm
+# multixterm doesn't use this but a user might want to
+proc xtermDeactivate {sid} {
+ set ::activeArray($sid) 0
+ xtermActiveUpdate $sid
+}
+
+# utility to do an explicit Expect
+# multixterm doesn't use this but a user might want to
+proc xtermExpect {args} {
+ # check if explicit spawn_id in args
+ for {set i 0} {$i < [llength $args]} {incr i} {
+ switch -- [lindex $args $i] "-i" {
+ set sidCmd [lindex $args [incr i]]
+ break
+ }
+ }
+
+ if {![info exists sidCmd]} {
+ # nothing explicit, so get it from the environment
+
+ upvar spawn_id spawn_id
+
+ # mimic expect's normal behavior in obtaining spawn_id
+ if {[info exists spawn_id]} {
+ set sidCmd $spawn_id
+ } else {
+ set sidCmd $::spawn_id
+ }
+ }
+
+ # turn off bg expect, do fg expect, then re-enable bg expect
+
+ expect_background -i $sidCmd ;# disable bg expect
+ eval expect $args ;# fg expect
+ ;# reenable bg expect
+ expect_background {
+ -i $sidCmd
+ -re ".+" [list sendTo $::xtermSid($sidCmd)]
+ eof [list xtermKill $sidCmd]
+ }
+}
+
+######################################################################
+# connect main window keystrokes to all xterms
+######################################################################
+proc xtermSend {A} {
+ if {[info exists ::afterId]} {
+ after cancel $::afterId
+ }
+ .input config -bg $::colorTyping
+ set ::afterId [after 1000 {.input config -bg $colorCurrent}]
+
+ exp_send -raw -i $::activeList -- $A
+}
+
+proc sendTo {to} {
+ exp_send -raw -i $to -- $::expect_out(buffer)
+}
+
+# catch the case where there's no selection
+proc xtermPaste {} {catch {xtermSend [selection get]}}
+
+######################################################################
+# clean up an individual process death or xterm death
+######################################################################
+proc xtermKill {s} {
+ verbose "killing xterm $s"
+
+ if {![info exists ::xtermPid($s)]} {
+ verbose "too late, already dead"
+ return
+ }
+
+ catch {exec /bin/kill -9 $::xtermPid($s)}
+ unset ::xtermPid($s)
+
+ # remove sid from activeList
+ verbose "removing $s from active array"
+ catch {unset ::activeArray($s)}
+ activeListUpdate
+
+ verbose "removing from background handler $s"
+ catch {expect_background -i $s}
+ verbose "removing from background handler $::xtermSid($s)"
+ catch {expect_background -i $::xtermSid($s)}
+ verbose "closing proc"
+ catch {close -i $s}
+ verbose "closing xterm"
+ catch {close -i $::xtermSid($s)}
+ verbose "waiting on proc"
+ wait -i $s
+ wait -i $::xtermSid($s)
+ verbose "done waiting"
+ unset ::xtermSid($s)
+
+ # remove from active menu
+ verbose "deleting active menu entry $::names($s)"
+
+ # figure out which it is
+ # avoid using name as an index since we haven't gone to any pains to
+ # make it safely interpreted by index-pattern code. instead step
+ # through, doing the comparison ourselves
+ set last [.m.e.active index last]
+ # skip over tearoff
+ for {set i 1} {$i <= $last} {incr i} {
+ if {![catch {.m.e.active entrycget $i -label} label]} {
+ if {[string equal $label $::names($s)]} break
+ }
+ }
+ .m.e.active delete $i
+ unset ::names($s)
+
+ # if none left, disable menu
+ # this leaves tearoff clone but that seems reasonable
+ if {0 == [llength [array names ::xtermSid]]} {
+ .m.e entryconfig Active -state disable
+ }
+}
+
+######################################################################
+# create windows
+######################################################################
+tk_setPalette $palette
+
+menu .m -tearoff 0
+.m add cascade -menu .m.f -label "File" -underline 0
+.m add cascade -menu .m.e -label "Edit" -underline 0
+.m add cascade -menu .m.help -label "Help" -underline 0
+set files [glob -nocomplain $cmdDir/*]
+set filesLength [llength $files]
+if {$filesLength >= $tearoffMenuMin} {
+ set filesTearoff 1
+} else {
+ set filesTearoff 0
+}
+menu .m.f -tearoff $filesTearoff -title "multixterm files"
+menu .m.e -tearoff 0
+menu .m.help -tearoff 0
+.m.f add command -label Open -command openFile -underline 0
+
+if {$filesLength} {
+ .m.f add separator
+ set files [lsort $files]
+ set files [lrange $files 0 $fileMenuMax]
+ foreach f $files {
+ .m.f add command -label $f -command [list openFile $f]
+ }
+ .m.f add separator
+}
+
+.m.f add command -label "Exit" -command exit -underline 0
+.m.e add command -label "Paste" -command xtermPaste -underline 0
+.m.e add cascade -label "Active" -menu .m.e.active -underline 0
+.m.help add command -label "About" -command about -underline 0
+.m.help add command -label "Man Page" -command help -underline 0
+. config -m .m
+
+menu .m.e.active -tearoff 1 -title "multixterm active"
+.m.e entryconfig Active -state disabled
+# disable the Active menu simply because it looks goofy seeing an empty menu
+# for consistency, though, it should be enabled
+
+entry .input -textvar inputLabel -justify center -state disabled
+entry .cmd -textvar xtermCmd
+button .exec -text "new xterm" -command {xtermStart $xtermCmd $xtermCmd}
+
+grid .input -sticky ewns
+grid .cmd -sticky ew
+grid .exec -sticky ew -ipadx 3 -ipady 3
+
+grid columnconfigure . 0 -weight 1
+grid rowconfigure . 0 -weight 1 ;# let input window only expand
+
+bind .cmd <Return> {xtermStart $xtermCmd $xtermCmd}
+
+# send all keypresses to xterm
+bind .input <KeyPress> {xtermSend %A ; break}
+bind .input <Alt-KeyPress> {xtermSend \033%A; break}
+bind .input <Meta-KeyPress> {xtermSend \033%A; break}
+bind .input <<Paste>> {xtermPaste ; break}
+bind .input <<PasteSelection>> {xtermPaste ; break}
+
+# arrow keys - note that if they've been rebound through .Xdefaults
+# you'll have to change these definitions.
+bind .input <Up> {xtermSend \033OA; break}
+bind .input <Down> {xtermSend \033OB; break}
+bind .input <Right> {xtermSend \033OC; break}
+bind .input <Left> {xtermSend \033OD; break}
+# Strange: od -c reports these as \033[A et al but when keypad mode
+# is initialized, they send \033OA et al. Presuming most people
+# want keypad mode, I'll go with the O versions. Perhaps the other
+# version is just a Sun-ism anyway.
+
+set colorCurrent [.input cget -bg]
+set colorFocusOut $colorCurrent
+
+# change color to show focus
+bind .input <FocusOut> colorFocusOut
+bind .input <FocusIn> colorFocusIn
+proc colorFocusIn {} {.input config -bg [set ::colorCurrent $::colorFocusIn]}
+proc colorFocusOut {} {.input config -bg [set ::colorCurrent $::colorFocusOut]}
+
+# convert normal mouse events to focusIn
+bind .input <1> {focus .input; break}
+bind .input <Shift-1> {focus .input; break}
+
+# ignore all other mouse events that might make selection visible
+bind .input <Double-1> break
+bind .input <Triple-1> break
+bind .input <B1-Motion> break
+bind .input <B2-Motion> break
+
+set scriptName [info script] ;# must get while it's active
+
+proc about {} {
+ set w .about
+ if {[winfo exists $w]} {
+ wm deiconify $w
+ raise $w
+ return
+ }
+ toplevel $w
+ wm title $w "about multixterm"
+ wm iconname $w "about multixterm"
+ wm resizable $w 0 0
+
+ button $w.b -text Dismiss -command [list wm withdraw $w]
+
+ label $w.title -text "multixterm" -font "Times 16" -borderwidth 10 -fg red
+ label $w.version -text "Version $::versionString, Released $::versionDate"
+ label $w.author -text "Written by Don Libes <don@libes.com>"
+ label $w.using -text "Using Expect [exp_version],\
+ Tcl $::tcl_patchLevel,\
+ Tk $::tk_patchLevel"
+ grid $w.title
+ grid $w.version
+ grid $w.author
+ grid $w.using
+ grid $w.b -sticky ew
+}
+
+proc help {} {
+ if {[winfo exists .help]} {
+ wm deiconify .help
+ raise .help
+ return
+ }
+ toplevel .help
+ wm title .help "multixterm help"
+ wm iconname .help "multixterm help"
+
+ scrollbar .help.sb -command {.help.text yview}
+ text .help.text -width 74 -height 30 -yscroll {.help.sb set} -wrap word
+
+ button .help.ok -text Dismiss -command {destroy .help} -relief raised
+ bind .help <Return> {destroy .help;break}
+ grid .help.sb -row 0 -column 0 -sticky ns
+ grid .help.text -row 0 -column 1 -sticky nsew
+ grid .help.ok -row 1 -columnspan 2 -sticky ew -ipadx 3 -ipady 3
+
+ # let text box only expand
+ grid rowconfigure .help 0 -weight 1
+ grid columnconfigure .help 1 -weight 1
+
+ set script [auto_execok $::scriptName]
+ if {[llength $script] == 0} {
+ set script /depot/tcl/bin/multixterm ;# fallback
+ }
+ if {[catch {open $script} fid]} {
+ .help.text insert end "Could not open help file: $script"
+ } else {
+ # skip to the beginning of the actual help (starts with "NAME")
+ while {-1 != [gets $fid buf]} {
+ if {1 == [regexp "NAME" $buf]} {
+ .help.text insert end "\n NAME\n"
+ break
+ }
+ }
+
+ while {-1 != [gets $fid buf]} {
+ if {0 == [regexp "^#(.?)(.*)" $buf X key buf]} break
+ if {$key == "!"} {
+ set buf [subst -nocommands $buf]
+ set key " "
+ }
+ .help.text insert end $key$buf\n
+ }
+ }
+
+ # support scrolling beyond Tk's built-in Next/Previous
+ foreach w {"" .sb .text .ok} {
+ set W .help$w
+ bind $W <space> {scrollPage 1} ;#more
+ bind $W <Delete> {scrollPage -1} ;#more
+ bind $W <BackSpace> {scrollPage -1} ;#more
+ bind $W <Control-v> {scrollPage 1} ;#emacs
+ bind $W <Meta-v> {scrollPage -1} ;#emacs
+ bind $W <Control-f> {scrollPage 1} ;#vi
+ bind $W <Control-b> {scrollPage -1} ;#vi
+ bind $W <F35> {scrollPage 1} ;#sun
+ bind $W <F29> {scrollPage -1} ;#sun
+ bind $W <Down> {scrollLine 1}
+ bind $W <Up> {scrollLine -1}
+ }
+}
+
+proc scrollPage {dir} {
+ tkScrollByPages .help.sb v $dir
+ return -code break
+}
+
+proc scrollLine {dir} {
+ tkScrollByUnits .help.sb v $dir
+ return -code break
+}
+
+######################################################################
+# exit handling
+######################################################################
+
+# xtermKillAll is not intended to be user-callable. It just kills
+# the processes and that's it. A user-callable version would update
+# the data structures, close the channels, etc.
+
+proc xtermKillAll {} {
+ foreach sid [array names ::xtermPid] {
+ exec /bin/kill -9 $::xtermPid($sid)
+ }
+}
+
+rename exit _exit
+proc exit {{x 0}} {xtermKillAll;_exit $x}
+
+wm protocol . WM_DELETE_WINDOW exit
+trap exit SIGINT
+
+######################################################################
+# start any xterms requested
+######################################################################
+proc xtermStartAll {} {
+ verbose "xtermNames = \"$::xtermNames\""
+ foreach n $::xtermNames {
+ regsub -all "%n" $::xtermCmd $n cmdOut
+ xtermStart $cmdOut $n
+ }
+ set ::xtermNames {}
+}
+
+initLate
+
+# now that xtermStartAll and its accompanying support has been set up
+# run it to start anything defined by rc file or command-line args.
+
+xtermStartAll ;# If nothing has been requested, this is a no-op.
+
+# finally do any explicit command file
+if {[info exists cmdFile]} {
+ openFile $cmdFile
+}
+