#!/usr/bin/env expectk # # 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. # Debian provides expectk as a separate package unlike SuSE! # Requires Tk 8.3.3 or later. # # VERSION #! $::versionString # The latest version of multixterm is available from # http://expect.nist.gov/example/multixterm . However, if your # version of Expect and Tk are too old (see REQUIREMENTS above) # you should download a new version of expect from # http://expect.nist.gov # # DATE #! $::versionDate # # AUTHOR # Don Libes # Update on window logic: Thomas Adam # # LICENSE # Multixterm is in the public domain; however the author would # appreciate acknowledgement if multixterm or parts of 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.7 set versionDate "2004/04/09" package require Tcl package require Tk 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" ###################################################################### # start the 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 ###################################################################### # start the new xterm ###################################################################### set pid [spawn -noecho -pty] verbose "spawn -pty: pid = $pid, spawn_id = $spawn_id" set sidXterm $spawn_id set ::xtermSid($sidCmd) $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 } ###################################################################### # make 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" } set ::names($sidCmd) $name set pid [eval exec xterm -name [list $name] -S$c1$c2$spawn_out(slave,fd) $::xtermArgs &] verbose "xterm: pid = $pid" set ::xtermPid($sidCmd) $pid close -slave # xterm first sends back window id, discard log_user 0 expect { eof {wait;return} \n } ###################################################################### # 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 {xtermStart $xtermCmd $xtermCmd} # send all keypresses to xterm bind .input {xtermSend %A ; break} bind .input {xtermSend \033%A; break} bind .input {xtermSend \033%A; break} bind .input <> {xtermPaste ; break} bind .input <> {xtermPaste ; break} # arrow keys - note that if they've been rebound through .Xdefaults # you'll have to change these definitions. bind .input {xtermSend \033OA; break} bind .input {xtermSend \033OB; break} bind .input {xtermSend \033OC; break} bind .input {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 colorFocusOut bind .input 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 {focus .input; break} # ignore all other mouse events that might make selection visible bind .input break bind .input break bind .input break bind .input 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 " 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 {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 {scrollPage 1} ;#more bind $W {scrollPage -1} ;#more bind $W {scrollPage -1} ;#more bind $W {scrollPage 1} ;#emacs bind $W {scrollPage -1} ;#emacs bind $W {scrollPage 1} ;#vi bind $W {scrollPage -1} ;#vi bind $W {scrollPage 1} ;#sun bind $W {scrollPage -1} ;#sun bind $W {scrollLine 1} bind $W {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} # Woah! This is bad practice and can lead to all kinds of nasties if # the window is deleted. It's much more polite if we at least close it # stops a lot of window managers from complaining that way, as well. # #wm protocol . WM_DELETE_WINDOW exit # wm protocol . WM_CLOSE_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 }