diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-11-04 17:21:04 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-11-04 17:21:04 -0800 |
commit | e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2 (patch) | |
tree | ce4c73521220fbb751c2be6a42e85ff6a6cbff97 /example/multixterm | |
download | expect-e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2.tar.gz expect-e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2.tar.bz2 expect-e0b431a48cc3ac5d3ec32f06eddd9708ad655fa2.zip |
Imported Upstream version 5.45upstream/5.45
Diffstat (limited to 'example/multixterm')
-rwxr-xr-x | example/multixterm | 993 |
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 +} + |