# # Copyright 2005-2013 the Boeing Company. # See the LICENSE file included in this distribution. # set g_imageFileTypes {{"images" {.gif}} {"images" {.jpg}} {"images" {.png}} {"images" {.bmp}} {"images" {.pcx}} {"images" {.tga}} {"images" {.tif}} {"images" {.ps}} {"images" {.ppm}} {"images" {.xbm}} {"All files" {*} }} global execMode if { $execMode == "interactive"} { if { [catch { package require Img }] } { puts "warning: Tcl/Tk Img package not found" puts " Thumbnails and other image types (JPG, PNG, etc.) will not be supported." puts " Please install it with:" puts " yum install tkimg (RedHat/Fedora)" puts " sudo apt-get install libtk-img (Debian/Ubuntu)" puts " pkg_add -r libimg (FreeBSD)\n" set g_imageFileTypes {{"images" {.gif}} {"All files" {*} }} } } # # Set the global systype variable using checkOS, if it hasn't been set yet proc setSystype { } { global systype if { [info exists systype] } { return } ;# global already set set systype [lindex [checkOS] 0] } # # Return a string identifying the operating system, # and a string of verbose OS info (for user display) proc checkOS {} { global tk_patchLevel tk_library set ret {} set tcl_ver [info patchlevel] set tcl_libpath [info library] if { [info exists tk_patchLevel] } { set tk_ver $tk_patchLevel set tk_libpath $tk_library } else { set tk_ver 0 set tk_libpath 0 } set os_name [exec uname] set os_ver [exec uname -r] set machine [exec uname -m] set kernel [exec uname -v] set x11 0 catch { set x11 [winfo server .c] } set os_ident "$os_name $os_ver" lappend ret $os_ident set os_verbose "$os_ident on $machine\nkernel build $kernel\nTCL version $tcl_ver ($tcl_libpath)\nTk version $tk_ver ($tk_libpath)\nX11 server $x11\n" lappend ret $os_verbose return $ret } # search a .imn topology file for old statements, and upgrade them proc upgradeOldConfig { cfg_ref } { global execMode # modify config directly! upvar 1 $cfg_ref cfg set msg "config line updated:" # s/circle/annotation/ set match {circle circle([0-9]+) (.)(.)} set subs {annotation a\1 \2\3 type oval\3} set r [regsub -all $match $cfg $subs cfg] if { $r > 0 } { puts "$msg circle -> oval annotation ($r)" } # s/label/annotation/ set match {label lab([0-9]+) (.)(.)} set subs {annotation a1\1 \2\3 type text\3} set r2 [regsub -all $match $cfg $subs cfg] if { $r2 > 0 } { puts "$msg label -> text annotation ($r2)" } # s/size/fontsize/ set r3 0 if { $r2 > 0 } { set match {size ([0-9]+)} set subs {fontsize \1} set r3 [regsub -all $match $cfg $subs cfg] if { $r3 > 0 } { puts "$msg size -> fontsize ($r3)" } } # s/model quagga/model router/ set match { model quagga} set subs { model router} set r4 [regsub -all $match $cfg $subs cfg] if { $r4 > 0 } { puts "$msg model quagga -> model router ($r4)" } # s/type pc/type router\n model PC/ set match { type pc} set subs " type router\n model PC" set r5 [regsub -all $match $cfg $subs cfg] if { $r5 > 0 } { puts "$msg type pc -> model pc ($r5)" } # s/type host/type router\n model host/ set match { type host} set subs " type router\n model host" set r6 [regsub -all $match $cfg $subs cfg] if { $r6 > 0 } { puts "$msg type host -> model host ($r6)" } incr r [expr {$r2 + $r3 + $r4 + $r5 + $r6}] #puts "$cfg" set choice "" if { $execMode == "interactive" && $r > 0 } { puts "$msg $r substitutions made" set choice [tk_messageBox -type yesno -icon question -title "CORE" \ -message "This configuration file contains old syntax, \ would you like to upgrade it to the new format?"] } return $choice } # renumber n0 if it exists proc upgradeConfigRemoveNode0 { } { global node_list set i [lsearch $node_list n0] if { $i < 0 } { return } set new [newObjectId node] puts "changing node n0 to $new" global $new n0 set $new $n0 renumberNodesIfcs n0 $new set node_list [lreplace $node_list $i $i] lappend node_list $new } # update network-config blocks from old imn file configs proc upgradeNetworkConfigToServices { } { global node_list # XXX this is a hack to populate zebra service values # really, we should query for service config items... set vals "{('/usr/local/etc/quagga', '/var/run/quagga')}" set vals "$vals {('/usr/local/etc/quagga/Quagga.conf', 'quaggaboot.sh', '/usr/local/etc/quagga/vtysh.conf')}" set vals "$vals 35 {('sh quaggaboot.sh zebra',)} {('killall zebra',)} {} {}" set statvals "{} {('staticroutes.sh', )} 35 {('sh staticroutes.sh', )}" set statvals "$statvals {} {} {}" foreach node $node_list { # # build Quagga services config from network-config block # set ospfv2 [netconfFetchSection $node "router ospf"] set ospfv3 [netconfFetchSection $node "router ospf6"] set rip [netconfFetchSection $node "router rip"] set ripng [netconfFetchSection $node "router ripng"] set bgp [netconfFetchSection $node "router bgp"] if { $ospfv2 != "" || $ospfv3 != "" || $rip != "" || $ripng != "" } { set cfg "" set services "zebra vtysh IPForward" foreach ifc [ifcList $node] { lappend cfg "interface $ifc" set ifccfg [netconfFetchSection $node "interface $ifc"] set cfg "$cfg $ifccfg" lappend cfg "!" } if { $ospfv2 != "" } { netconfClearSection $node "router ospf" lappend cfg "router ospf"; set cfg "$cfg $ospfv2 !" lappend services "OSPFv2" } if { $ospfv3 != "" } { netconfClearSection $node "router ospf6" lappend cfg "router ospf6"; set cfg "$cfg $ospfv3 !" lappend services "OSPFv3" } if { $rip != "" } { netconfClearSection $node "router rip" lappend cfg "router rip"; set cfg "$cfg $rip !" lappend services "RIP" } if { $ripng != "" } { netconfClearSection $node "router ripng" lappend cfg "router ripng"; set cfg "$cfg $ripng !" lappend services "RIPNG" } if { $bgp != "" } { netconfClearSection $node "router bgp" # AS number is lost here lappend cfg "router bgp"; set cfg "$cfg $bgp !" lappend services "BGP" } setCustomConfig $node "service:zebra" "zebra" $vals 0 set cfgname "/usr/local/etc/quagga/Quagga.conf" setCustomConfig $node "service:zebra:$cfgname" $cfgname $cfg 0 set cfgname "/usr/local/etc/quagga/vtysh.conf" setCustomConfig $node "service:zebra:$cfgname" $cfgname \ "service integrated-vtysh-config" 0 setNodeServices $node $services puts "updating Quagga services on node $node" } ;# end quagga services # # convert static model to router # if { [getNodeModel $node] == "static" } { setNodeModel $node "router" setNodeServices $node "IPForward" puts "changing model static to router on node $node" } # # convert static routes to a custom service # if { [getStatIPv4routes $node] != "" || \ [getStatIPv6routes $node] != "" } { set cfg {} lappend cfg "# custom static route service generated by util.tcl" addStaticRoutesToConfig $node cfg setStatIPv4routes $node ""; setStatIPv6routes $node "" ;# clear old set cfgname "staticroutes.sh" setCustomConfig $node "service:UserDefined" "UserDefined" \ $statvals 0 setCustomConfig $node "service:UserDefined:$cfgname" $cfgname $cfg 0 set services [getNodeServices $node true] lappend services "UserDefined" setNodeServices $node $services puts "adding user-defined static routing service on node $node" } ;# end static services } } # get CPU usage from /proc/stat jiffies proc getCPUUsage { } { global lastcpu if { [catch {set f [open "/proc/stat" r]} ] } { return ""; # unable to open /proc/stat } array set cpu {} while { [ gets $f line ] >= 0 } { set cpun [lindex $line 0] set user [lindex $line 1]; set nice [lindex $line 2] set sys [lindex $line 3]; set idle [lindex $line 4] if { [string range $cpun 0 2] != "cpu" } { continue } set cpu($cpun) "$user $nice $sys $idle" } close $f if { ![info exists cpu(cpu)] } { return "" } set cpuusages "" foreach cpun [lsort -dictionary [array names cpu]] { if { ![info exists lastcpu($cpun)] } { set lastcpu($cpun) "0 0 0 0" } set lu [lindex $lastcpu($cpun) 0]; set ln [lindex $lastcpu($cpun) 1] set ls [lindex $lastcpu($cpun) 2]; set li [lindex $lastcpu($cpun) 3] set u [lindex $cpu($cpun) 0]; set n [lindex $cpu($cpun) 1] set s [lindex $cpu($cpun) 2]; set i [lindex $cpu($cpun) 3] set lastcpu($cpun) "$u $n $s $i" set usage_time [expr {($u-$lu) + ($n-$ln) + ($s-$ls)}] set total_time [expr {$usage_time + ($i-$li)}] if { $total_time <= 0 } { set cpuusage "" ;# avoid div by zero } else { set cpuusage [expr { 100 * $usage_time / $total_time }] } lappend cpuusages $cpuusage } return $cpuusages } # Node selection dialog display given message 'msg' with initial node selection # set to the 'initsel' list, and calls the callback using the selected nodes proc popupSelectNodes { msg initsel callback } { global node_list set wi .nodeselect catch {destroy $wi} toplevel $wi -takefocus 1 wm resizable $wi 0 0 wm title $wi "Select Nodes" grab $wi frame $wi.nodes -borderwidth 4 if { $msg == "" } { set msg "Select one or more nodes:" } label $wi.nodes.label -text $msg frame $wi.nodes.fr listbox $wi.nodes.fr.nodelist -width 40 \ -listvariable node_list -yscrollcommand "$wi.nodes.fr.scroll set" \ -activestyle dotbox -selectmode extended scrollbar $wi.nodes.fr.scroll -command "$wi.nodes.fr.nodelist yview" pack $wi.nodes.fr.nodelist -fill both -expand true -side left pack $wi.nodes.fr.scroll -fill y -expand true -side left pack $wi.nodes.label $wi.nodes.fr -side top -padx 4 -pady 4 \ -anchor w -fill both -expand true pack $wi.nodes -fill both -expand true -side top frame $wi.fbot -borderwidth 4 button $wi.fbot.apply -text "OK" \ -command "selectNodesHelper $wi {$callback}" button $wi.fbot.cancel -text "Cancel" -command "destroy $wi" pack $wi.fbot.cancel $wi.fbot.apply -side right -padx 4 -pady 4 pack $wi.fbot -side bottom #set n [$wi.nodes.fr.from get $i] #itemconfigure $i -foreground blue set idx 0 foreach node $node_list { foreach sel $initsel { if { $node == $sel } { $wi.nodes.fr.nodelist selection set $idx break } } incr idx } } proc selectNodesHelper { wi callback } { set selected_indices [$wi.nodes.fr.nodelist curselection] set selected_nodes {} foreach idx $selected_indices { set node [$wi.nodes.fr.nodelist get $idx] lappend selected_nodes $node } destroy $wi lappend callback $selected_nodes eval $callback } # Boeing: node renumbering dialog proc popupRenumberNodes { } { set wi .renumbernode catch {destroy $wi} toplevel $wi -takefocus 1 wm transient $wi . wm resizable $wi 0 0 wm title $wi "Renumber Nodes" grab $wi frame $wi.nodes -borderwidth 4 frame $wi.nodes.left label $wi.nodes.left.label -text "Change this node:" frame $wi.nodes.left.fr listbox $wi.nodes.left.fr.from -selectmode single -width 20 \ -listvariable node_list -yscrollcommand "$wi.nodes.left.fr.scroll set" \ -activestyle dotbox scrollbar $wi.nodes.left.fr.scroll -command "$wi.nodes.left.fr.from yview" pack $wi.nodes.left.fr.from $wi.nodes.left.fr.scroll -fill y -side left pack $wi.nodes.left.label $wi.nodes.left.fr -side top -padx 4 -pady 4 \ -anchor w pack $wi.nodes.left -side left bind $wi.nodes.left.fr.from <> "selectRenumberNodes $wi from" frame $wi.nodes.right label $wi.nodes.right.label -text "to this node:" frame $wi.nodes.right.fr listbox $wi.nodes.right.fr.to -selectmode single -width 20 \ -listvariable node_list -yscrollcommand "$wi.nodes.right.fr.scroll set" scrollbar $wi.nodes.right.fr.scroll -command "$wi.nodes.right.fr.to yview" pack $wi.nodes.right.fr.to $wi.nodes.right.fr.scroll -fill y -side left pack $wi.nodes.right.label $wi.nodes.right.fr -side top -padx 4 -pady 4 \ -anchor w pack $wi.nodes.right -side left bind $wi.nodes.right.fr.to <> "selectRenumberNodes $wi to" pack $wi.nodes -side top frame $wi.fbot -borderwidth 4 button $wi.fbot.apply -text "Renumber Node" -command "renumberNodes $wi" \ -state disabled button $wi.fbot.cancel -text "Close" -command "destroy $wi" pack $wi.fbot.cancel $wi.fbot.apply -side right -padx 4 -pady 4 pack $wi.fbot -side bottom #set n [$wi.nodes.left.fr.from get $i] #itemconfigure $i -foreground blue } # helper for highlighting nodes in the node renumbering list boxes proc selectRenumberNodes { wi l } { global renumber_node_from renumber_node_to; # may be undefined if { $l == "to" } { set nlist $wi.nodes.right.fr.to set other "from" } else { set nlist $wi.nodes.left.fr.from set other "to" } if { [info exists renumber_node_$other] } { ;# do we have two selections? $wi.fbot.apply configure -state normal } set sel [$nlist curselection] if { [info exists renumber_node_${l}] } {; # color prev selected item black $nlist itemconfigure [set renumber_node_${l}] -foreground black } $nlist itemconfigure $sel -foreground blue; # highlight curr selection blue set renumber_node_${l} $sel; # this value is used by the apply button } # perform node renumbering, e.g. n1 becomes n3 and n3 is now n1 proc renumberNodes { wi } { global renumber_node_from renumber_node_to set from_idx $renumber_node_from set to_idx $renumber_node_to if { $from_idx < 0 || $to_idx < 0 } { return } set from [$wi.nodes.left.fr.from get $from_idx] set to [$wi.nodes.right.fr.to get $to_idx] set from_name [getNodeName $from] set to_name [getNodeName $to] set tmp [newObjectId node] global $to $from $tmp set $tmp [set $to] renumberNodesIfcs $to $tmp renumberNodesIfcs $from $to set $to [set $from] set $from [set $tmp] renumberNodesIfcs $tmp $from setNodeName $from $from_name setNodeName $to $to_name redrawAll destroy $wi } # helper to change interface-peers and links from one node to another proc renumberNodesIfcs { from to } { foreach ifc [ifcList $from] { set peer [peerByIfc $from $ifc] set peerifc [ifcByPeer $peer $from] set link [linkByPeers $from $peer] global $peer $link # modify the peer to point to the new node set i [lsearch [set $peer] "interface-peer {$peerifc $from}"] set $peer [lreplace [set $peer] $i $i "interface-peer {$peerifc $to}"] # update the link variable with the new node if { [lindex [linkPeers $link] 0] == $from } { set newnodes "nodes {$to $peer}" } else { set newnodes "nodes {$peer $to}" } set $link [lreplace [set $link] 0 0 $newnodes] } } proc addAddressesToConfig { node cfg_ref } { global $node systype upvar 1 $cfg_ref cfg foreach ifc [ifcList $node] { set addr [getIfcIPv4addr $node $ifc] set addr6 [getIfcIPv6addr $node $ifc] if {[lindex $systype 0] == "Linux" } { ;# Linux if { $addr != "" } { lappend cfg "/usr/local/sbin/addip.sh $ifc $addr" } if { $addr6 != "" } { lappend cfg "/usr/local/sbin/addip.sh $ifc $addr6" } } else { if { $addr != "" } { lappend cfg "ifconfig $ifc inet $addr" } if { $addr6 != "" } { lappend cfg "ifconfig $ifc inet6 $addr6" } } } } # support for legacy static routing config # called by upgradeNetworkConfigToServices proc addStaticRoutesToConfig { node cfg_ref } { global $node systype upvar 1 $cfg_ref cfg foreach statrte [getStatIPv4routes $node] { if {[lindex $systype 0] == "Linux" } { ;# Linux set net [lindex [split $statrte] 0] set gw [lindex [split $statrte] 1] lappend cfg "/sbin/ip -4 route add $net via $gw" } else { ;# FreeBSD lappend cfg "route -q add -inet $statrte" } } foreach statrte [getStatIPv6routes $node] { if { [lindex $systype 0] == "Linux" } { ;# Linux set net [lindex [split $statrte] 0] set gw [lindex [split $statrte] 1] if { $net == "::/0" } { set net "default" } lappend cfg "/sbin/ip -6 route add $net via $gw" } else { ;# FreeBSD lappend cfg "route -q add -inet6 $statrte" } } } proc getServiceStartString { } { global systype setSystype if { [lindex $systype 0] == "Linux" } { ;# Linux return "/etc/init.d/core-daemon start" } else { ;# FreeBSD return "/usr/local/etc/rc.d/core onestart" } } proc popupBuildHostsFile { } { global node_list set wi .buildhostsdialog catch {destroy $wi} toplevel $wi wm transient $wi . wm resizable $wi 1 1 wm title $wi "Build hosts File" # help text at top frame $wi.top set helptext "The entries below can be added to your /etc/hosts file.\n" set helptext "$helptext Use the append button to write it now." label $wi.top.help -text $helptext pack $wi.top.help -side top -fill both -expand true pack $wi.top -padx 4 -pady 4 -side top # text box frame $wi.mid text $wi.mid.hosts -relief sunken -bd 2 \ -yscrollcommand "$wi.mid.scroll set" -setgrid 1 -height 30 -undo 1 \ -autosep 1 -background white scrollbar $wi.mid.scroll -command "$wi.mid.hosts yview" pack $wi.mid.hosts -side left -fill both -expand true pack $wi.mid.scroll -side right -fill y pack $wi.mid -side top -fill both -expand true $wi.mid.hosts insert end "### begin CORE auto-generated hosts entries\n" foreach node $node_list { set hostname [getNodeName $node] foreach ifc [ifcList $node] { foreach addr [getIfcIPv4addr $node $ifc] { set ip [lindex [split $addr /] 0] $wi.mid.hosts insert end "$ip $hostname\n" } } } $wi.mid.hosts insert end "### end CORE auto-generated hosts entries\n" # file selection frame $wi.fil entry $wi.fil.filename -width 30 -bg white button $wi.fil.filebtn -text "..." -command { set wi .buildhostsdialog set f [$wi.fil.filename get] set f [tk_getSaveFile -initialfile $f] if { $f != "" } { $wi.fil.filename delete 0 end $wi.fil.filename insert 0 $f } } pack $wi.fil.filename -expand true -fill x -side left pack $wi.fil.filebtn -side left pack $wi.fil -side top $wi.fil.filename insert 0 "/etc/hosts" # buttons on the bottom frame $wi.btm button $wi.btm.apply -text "Append file" -command { set wi .buildhostsdialog set hosts [string trim [$wi.mid.hosts get 0.0 end]] set filename [$wi.fil.filename get] set fileId [open $filename a] puts $fileId $hosts close $fileId destroy $wi } button $wi.btm.cancel -text "Close" -command "destroy $wi" pack $wi.btm.apply $wi.btm.cancel -side left pack $wi.btm focus $wi.mid.hosts } proc popupAddressConfig { } { global plugin_img_add plugin_img_del g_prefs set wi .addrconfig catch {destroy $wi} toplevel $wi wm transient $wi . wm resizable $wi 1 1 wm title $wi "IP Addresses" # help text at top frame $wi.top set helptext "New interfaces are automatically assigned IP addresses from\n" set helptext "$helptext the range selected below." label $wi.top.help -text $helptext pack $wi.top.help -side top -fill both -expand true pack $wi.top -padx 4 -pady 4 -side top frame $wi.f foreach fam [list "IPv4" "IPv6"] { set faml [string tolower $fam] set f $wi.f.$faml labelframe $f -text "$fam" # list of address prefixes frame $f.l listbox $f.l.list -bg white -yscrollcommand "$f.l.scroll set" \ -exportselection 0 scrollbar $f.l.scroll -command "$f.l.list yview" -bd 1 -width 10 pack $f.l.list $f.l.scroll -side left -fill y -expand true pack $f.l -side top -fill y -expand true # controls for editing list frame $f.edit entry $f.edit.addr -bg white -width 20 button $f.edit.new -image $plugin_img_add \ -command "addressConfigHelper $wi $fam add" button $f.edit.del -image $plugin_img_del \ -command "addressConfigHelper $wi $fam del" pack $f.edit.addr $f.edit.new $f.edit.del -side left pack $f.edit -side top frame $f.butt label $f.butt.l -text "Remove $fam:" button $f.butt.delall -text "all" -command "delAddrs all $fam" button $f.butt.delsel -text "selected" -command "delAddrs sel $fam" pack $f.butt.l $f.butt.delall $f.butt.delsel -side left pack $f.butt -side top pack $f -side left # populate list and select appropriate entry if {![info exists g_prefs(gui_${faml}_addr)]} { setDefaultAddrs $faml } set idx -1; set i 0 foreach addr $g_prefs(gui_${faml}_addrs) { $f.l.list insert end $addr if { $addr == $g_prefs(gui_${faml}_addr) } { set idx $i } incr i } if { $idx < 0 } { $f.l.list insert end $g_prefs(gui_${faml}_addr) $f.l.list selection set end } else { $f.l.list selection set $idx } } ;# end foreach fam pack $wi.f -side top # buttons on the bottom frame $wi.btm button $wi.btm.apply -text "OK" \ -command "addressConfigHelper $wi \"\" apply" button $wi.btm.def -text "Defaults" \ -command "setDefaultAddrs ipv4; setDefaultAddrs ipv6; destroy $wi; popupAddressConfig" button $wi.btm.cancel -text "Cancel" -command "destroy $wi" pack $wi.btm.apply $wi.btm.def $wi.btm.cancel -side left pack $wi.btm #focus $wi.mid.hosts } # listbox helper for adding and removing entries, and applying them proc addressConfigHelper { wi fam cmd } { global g_prefs set f $wi.f.[string tolower $fam] switch -exact -- $cmd { add { set addr [$f.edit.addr get] $f.l.list insert end $addr } del { set i [$f.l.list curselection] if { $i == "" } { return } $f.l.list delete $i } apply { foreach fam [list "ipv4" "ipv6"] { set f $wi.f.$fam set i [$f.l.list curselection] if { $i == "" } { set i 0 } set addr [$f.l.list get $i] set addrs [$f.l.list get 0 end] array set g_prefs [list gui_${fam}_addr $addr] array set g_prefs [list gui_${fam}_addrs $addrs] } destroy $wi } } } # set the default addresses for automatic allocation in the g_prefs array # for the given address family proc setDefaultAddrs { fam } { global g_prefs if { $fam == "ipv4" } { set addrs [getDefaultIPv4Addrs] } elseif { $fam == "ipv6" } { set addrs [getDefaultIPv6Addrs] } else { return } array set g_prefs [list gui_${fam}_addr [lindex $addrs 0]] array set g_prefs [list gui_${fam}_addrs $addrs] } proc popupMacAddressConfig { } { set wi .macaddrconfig global mac_addr_start catch {destroy $wi} toplevel $wi wm transient $wi . wm resizable $wi 1 1 wm title $wi "MAC Addresses" # help text at top frame $wi.top set helptext "MAC addresses are automatically assigned starting with\n" set helptext "$helptext 00:00:00:aa:00:nn, where nn starts with the below" set helptext "$helptext value.\n You should change this value when tunneling" set helptext "$helptext between \nemulations to prevent MAC address conflicts." label $wi.top.help -text $helptext pack $wi.top.help -side top -fill both -expand true pack $wi.top -padx 4 -pady 4 -side top if { ![info exists mac_addr_start] } { set mac_addr_start 0 } frame $wi.f label $wi.f.maclab -text "Starting MAC number:" entry $wi.f.mac -width 5 -bg white -textvariable mac_addr_start pack $wi.f.maclab $wi.f.mac $wi.f -side left pack $wi.f -side top frame $wi.btm button $wi.btm.apply -text "close" -command "destroy $wi" pack $wi.btm.apply -side left pack $wi.btm } # # Capture a window into an image # Author: David Easton # proc captureWindow { win } { regexp {([0-9]*)x([0-9]*)\+([0-9]*)\+([0-9]*)} [winfo geometry $win] - w h x y # Make the base image based on the window set image [image create photo -format window -data $win] foreach child [winfo children $win] { captureWindowSub $child $image 0 0 } return $image } proc captureWindowSub { win image px py } { if {![winfo ismapped $win]} { return } regexp {([0-9]*)x([0-9]*)\+([0-9]*)\+([0-9]*)} [winfo geometry $win] - w h x y incr px $x incr py $y # Make an image from this widget set tempImage [image create photo -format window -data $win] # Copy this image into place on the main image $image copy $tempImage -to $px $py image delete $tempImage foreach child [winfo children $win] { captureWindowSub $child $image $px $py } } proc writeCanvasThumbnail { c fn } { global execMode set r false if [ catch { set img [captureWindow $c] set imgthumb [image create photo] # resize image to height 100 set w [image height $img] $imgthumb copy $img -subsample [expr { int($w / 100)} ] $imgthumb write $fn -format jpeg image delete $img image delete $imgthumb set r true } e ] { if { $execMode == "interactive" } { puts "warning: failed to create canvas thumbnail" } } return $r } # contributed code from CL from http://wiki.tcl.tk/557 fetched 6/11/10 proc _launchBrowser url { global tcl_platform # It *is* generally a mistake to switch on $tcl_platform(os), particularly # in comparison to $tcl_platform(platform). For now, let's just regard it # as a stylistic variation subject to debate. switch $tcl_platform(os) { Darwin { set command [list open $url] } HP-UX - Linux - SunOS { foreach executable {firefox mozilla netscape iexplorer opera lynx w3m links epiphany galeon konqueror mosaic amaya browsex elinks} { set executable [auto_execok $executable] if [string length $executable] { # Do you want to mess with -remote? How about other browsers? set command [list $executable $url &] break } } } {Windows 95} - {Windows NT} { set command "[auto_execok start] {} [list $url]" } } if [info exists command] { if [catch {eval exec $command } err] { tk_messageBox -icon error -message "error '$err' with '$command'" } } else { tk_messageBox -icon error -message \ "Please tell CL that ($tcl_platform(os), $tcl_platform(platform)) is not yet ready for browsing." } } # helper for registering a callback with a tk_optionMenu variable, when a user # clicks on the menu and changes the value; if the global variable var is # cleared, this callback is cancelled # NOTE: when closing the window that calls this, ensure that var is cleared proc tkOptionMenuCallback { ctl var cb args } { if { ![winfo exists $ctl] } { return } global $var tkwait variable $var # cancel callback when var is cleared if { [set $var] == "" } { return } # here is a hack to remove the outer list if {[llength $args] == 1} { set args [lindex $args 0] } $cb [set $var] $args tkOptionMenuCallback $ctl $var $cb $args } # split a string on commas, while respecting single quote "'" quoting # items should be in single quotes which are omitted from the resulting list # e.g. 'foo', 'ba\'r', 'baz' ==> {foo, ba'r, baz} proc safeCommaSplit { str } { set item "" set r [] set inquotes false for {set i 0} {$i < [string length $str]} {incr i} { set c [string index $str $i] if { $c == "'" } { # check for escaped quote and skip it if { $i > 0 && [string index $str [expr $i-1]] == "\\" } { set item "$item$c" continue } if { !$inquotes } { set inquotes true ;# begin quoted block } else { set inquotes false ;# end quoted block } continue } if { $c == "," && !$inquotes } { ;# comma outside quoted block lappend r $item set item "" continue } set item "$item$c" } if { $item != "" } { lappend r $item } ;# last item in list return $r } # convert a string in the Python tuple format into a TCL list proc tupleStringToList { str } { set str [string trim $str] ;# remove whitespace set str [string trim $str "()"] ;# remove (...) set quotedlist [safeCommaSplit $str] ;# [split $str ","] doesn't work here set r "" foreach item $quotedlist { set item [string trim $item] ;# remove whitespace # un-escape double and single quotes set item [string map {\\\" \"} $item] set item [string map {\\' '} $item] if { $item != "" } { lappend r $item } } return $r } # convert a TCL list to the Python tuple format proc listToTupleString { l } { if { [llength $l] == 0 } { return {} } ;# don't return empty tuples "()" set str "(" foreach item $l { # escape double and single quotes set item [string map {\" \\\"} $item] set item [string map {' \\'} $item] # enclose each item in single quotes set str "$str'$item', " } set str "$str)" return $str } proc exportPython { } { global node_list link_list foreach n $node_list { set name [getNodeName $n] set xy [getNodeCoords $n] puts " $name = session.addobj(cls = pycore.nodes.class, name=\"$name\")" puts " $name.setposition(x=[lindex $xy 0],y=[lindex $xy 1])" } foreach l $link_list { set lnode1 [lindex [linkPeers $l] 0] set lnode2 [lindex [linkPeers $l] 1] set ifc1 [ifcByPeer $lnode1 $lnode2] set ifc2 [ifcByPeer $lnode2 $lnode1] set net "ptpnet" if { [[typemodel $lnode1].layer] == "LINK" } { set net [getNodeName $lnode1] } if { [[typemodel $lnode2].layer] == "LINK" } { set net [getNodeName $lnode2] } if { [[typemodel $lnode1].layer] == "NETWORK" } { set ipv4 [getIfcIPv4addr $lnode1 $ifc1] set if1n [ifcNameToNum $ifc1] puts -nonewline " [getNodeName $lnode1].newnetif(net=$net, " puts "addrlist=\[\"$ipv4\"\], ifindex=$if1n)" } if { [[typemodel $lnode2].layer] == "NETWORK" } { set ipv4 [getIfcIPv4addr $lnode2 $ifc2] set if2n [ifcNameToNum $ifc2] puts -nonewline " [getNodeName $lnode2].newnetif(net=$net, " puts "addrlist=\[\"$ipv4\"\], ifindex=$if2n)" } } } # ask the daemon to execute the selected file proc execPython { } { set fn [tk_getOpenFile -filetypes {{ "CORE Python scripts" {.py} }} ] if { $fn == "" } { return } set flags 0x10 ;# status request flag sendRegMessage -1 $flags [list "exec" $fn] addFileToMrulist $fn } # from Practical Programming in Tcl and Tk, page 190 proc Call_Trace {{file stdout}} { puts $file "*** Tcl Call Trace:" for {set x [expr [info level]-1]} {$x > 0} {incr x -1} { puts $file " $x: [info level $x]" } } # toggle a boolean variable proc toggle { v } { upvar 1 $v var set var [expr {!$var}] } # return the name of the text editor to use # - the preference g_prefs(gui_text_editor) overrides any default # - if want_default is specified, first return EDITOR environment variable, # then find the first in the list of editors that exists on the system set EDITORS "vim emacs gedit nano vi" proc get_text_editor { want_default } { global g_prefs env EDITORS set ed "" if { [info exists env(EDITOR)] } { set ed $env(EDITOR) } if { !$want_default && [info exists g_prefs(gui_text_editor)] } { set edpref $g_prefs(gui_text_editor) # preference can be EDITOR, to use environment variable EDITOR if { $edpref == "EDITOR" } { set edpref $ed } if { $edpref != "" } { return $edpref } # fall through since environment variable or pref not set } # first use any EDITOR variable if { $ed != "" } { if { $want_default } { return "EDITOR" } else { return $ed } } # return the first installed editor from EDITORS global foreach ed $EDITORS { if { [auto_execok $ed] != "" } { return $ed } } # none of the editors were found, just return the first one return [lindex $EDITORS 0] } # return the name of the terminal program to use # - the preference g_prefs(gui_term_prog) overrides any default # - if want_default is specified, first return COLORTERM or TERM environment # variable, then find the first in the list of terminals that exists on the # system set TERMS "{gnome-terminal -x} {lxterminal -e} {konsole -e} {xterm -e}" set TERMS "$TERMS {aterm -e} {eterm -e} {rxvt -e} {xfce4-terminal -e}" proc get_term_prog { want_default } { global g_prefs env TERMS # initialize term = COLORTERM or TERM environment variables set term "" if { [info exists env(COLORTERM)] } { if { ![string is integer $env(COLORTERM) ] } { # under OpenSUSE, COLORTERM=1 set term [auto_execok $env(COLORTERM)] } } if { $term == "" && [info exists env(TERM)] } { set term [auto_execok $env(TERM)] } if { $term != "" } { set arg "-e" # gnome-terminal has problem w/subsequent arguments after -e, needs -x if { [file tail $term] == "gnome-terminal" } { set arg "-x" } set term "$term $arg" } if { !$want_default && [info exists g_prefs(gui_term_prog)] } { set termpref $g_prefs(gui_term_prog) # preference can be TERM, to adopt environment variable TERM if { $termpref == "TERM" } { set termpref $term } if { $termpref != "" } { return $termpref ;# pre-configured preference or expanded TERM } # fall through since environment variable or preference not set } # first use any TERM variable if { $term != "" } { if { $want_default } { return "TERM" } else { return $term } } # return the first installed terminal from TERMS global foreach term $TERMS { if { [auto_execok [lindex $term 0]] != "" } { return $term } } # none of the terminals were found, just return the first one return [lindex $TERMS 0] } # short session ID used by Python daemon for interface names proc shortSessionID { sid } { return [ expr { ($sid >> 8) ^ ($sid & ((1<<8) - 1)) } ] } proc delAddrs { mode fam } { global node_list if { $mode == "all" } { delAddrsFromNodes $fam $node_list } else { set msg "Remove all $fam addresses from these nodes:" popupSelectNodes $msg "" "delAddrsFromNodes $fam" } } proc delAddrsFromNodes { fam nodes } { foreach node $nodes { foreach ifc [ifcList $node] { if { $fam == "IPv4" } { setIfcIPv4addr $node $ifc "" } elseif { $fam == "IPv6" } { setIfcIPv6addr $node $ifc "" } } } redrawAll } # fix for Tcl/Tk 8.5.8 and lower which doesn't have ttk::spinbox # set spinbox [getspinbox] # $spinbox $var -justify right -width 10 ... # proc getspinbox {} { if { [info command ttk::spinbox] == "" } { return spinbox } else { return ttk::spinbox } } # find dialog for searching for nodes and links proc popupFind {} { set msg "find" set initsel "" set callback "" global node_list set w .find catch {destroy $w} toplevel $w -takefocus 1 wm transient $w . wm title $w "Find" ttk::frame $w.find -borderwidth 4 ttk::label $w.find.lab -text "Find:" ttk::entry $w.find.text -width 40 pack $w.find.lab -side left pack $w.find.text -side left -fill x -expand 1 pack $w.find -fill x -side top -padx 4 -pady 4 bind $w.find.text "findButton $w" ttk::frame $w.mid ttk::treeview $w.mid.tree -columns {number name location details} \ -show headings -yscroll "$w.mid.vsb set" -xscroll "$w.mid.hsb set" ttk::scrollbar $w.mid.vsb -orient vertical -command "$w.mid.tree yview" ttk::scrollbar $w.mid.hsb -orient horizontal -command "$w.mid.tree xview" pack $w.mid -side top -fill both -expand true -padx 4 -pady 4 grid $w.mid.tree $w.mid.vsb -in $w.mid -sticky nsew grid $w.mid.hsb -in $w.mid -sticky nsew grid column $w.mid 0 -weight 1 grid row $w.mid 0 -weight 1 bind $w.mid.tree <> "findTreeSelect $w.mid.tree" set closecmd "drawNodeCircle {} {} {} {} findhi; destroy $w" bind $w.find.text $closecmd bind $w $closecmd ttk::frame $w.fbot -borderwidth 4 ttk::button $w.fbot.find -text "Find" -command "findButton $w" ttk::button $w.fbot.close -text "Close" -command $closecmd pack $w.fbot.find $w.fbot.close -side left -padx 4 -pady 4 pack $w.fbot -side bottom findTreeHeader $w.mid.tree focus $w.find.text } # helper for find button, implements searching of nodes, links, and node names # TODO: search IPv4/IPv6/MAC addresses, services, annotations, EMANE configs? proc findButton { w } { global node_list link_list set terms [$w.find.text get] set tree $w.mid.tree findTreeHeader $tree . config -cursor watch; update set first "" set nodename_list "" foreach n $node_list { lappend nodename_list [getNodeName $n] } foreach search [list node nodename link] { set ${search}_results [lsearch -nocase -all -glob \ [set ${search}_list] "*$terms*"] # populate results foreach result [set ${search}_results] { if { $result == -1 } { continue } set search_list ${search}_list if { ${search} == "nodename" } { set search_list node_list } set obj [lindex [set $search_list] $result] if { $first == "" } { set first $obj } set num $obj if { $search == "link" } { set peers [linkPeers $obj] set name "[lindex $peers 0]-[lindex $peers 1]" set coords [getNodeCoords [lindex $peers 0]] set details [getLinkBandwidthString $obj] set details "$details [getLinkDelayString $obj]" } else { set name [getNodeName $obj] set coords [getNodeCoords $obj] set details [ipv4List $obj false] set details "$details [ipv6List $obj false]" } set coords "<[lindex $coords 0], [lindex $coords 1]>" if { ![$tree exists $obj] } { $tree insert {} end -id $obj \ -values [list $num $name $coords $details] } } } if { $first == "" } { $tree insert {} end -id none -values [list "" "" "" "no results found"] } else { $tree selection set $first } . config -cursor left_ptr } # helper clears treeview and populates column header row proc findTreeHeader { tree } { $tree delete [$tree children {}] set widths {75 75 125 350}; set i 0 foreach col {number name location details} { $tree heading $col -text $col set width [lindex $widths $i]; incr i $tree column $col -width $width } drawNodeCircle "" "" "" "" findhi } # helper handles treeview selection changes in the Find dialog proc findTreeSelect { ctl } { global curcanvas drawNodeCircle "" "" "" "" findhi set obj [$ctl selection] if { $obj == "none" } { return } if { [string range $obj 0 0] == "l" } { lassign [linkPeers $obj] node node2 } else { set node $obj set node2 "" } # highlight node(s) and reposition canvas view so items are visible set target_canvas [getNodeCanvas $node] if { $target_canvas != $curcanvas } { set curcanvas $target_canvas switchCanvas none } drawNodeCircle $node 30 red findhi "" if { $node2 != "" } { drawNodeCircle $node2 30 red findhi "" } canvasSee .c $node }