$ netstat -i Kernel Interface table Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg eth0 1500 0 23449 0 0 0 19670 0 0 0 BMRU lo 16436 0 4 0 0 0 4 0 0 0 LRU wlan0 1500 0 0 0 0 0 0 0 0 0 BMU wmaster0 0 0 0 0 0 0 0 0 0 0 RUIn particular, I want the Tk GUI to show two meters --- to show the rate of increase of the 'RX-OK' and the 'TX-OK' counts.On Linux systems, there are many commands that show this network activity data. For example, 'ifconfig' could be used as an alternative to 'netstat':
$ ifconfig -a -s Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg eth0 1500 0 23449 0 0 0 19670 0 0 0 BMRU lo 16436 0 4 0 0 0 4 0 0 0 LRU wlan0 1500 0 0 0 0 0 0 0 0 0 BMU wmaster0 0 0 0 0 0 0 0 0 0 0 RUAnd there are newer commands, like 'ip', that are said to be a replacement, eventually, for older commands like 'netstat'.For now, I decided to go with 'netstat'.Like with the 'free' command --- since the 'netstat' command (or a command returning similar data) is probably available on most Linux, Unix, and BSD systems --- and since the Apple Mac operating system is based on a BSD system, this 'network activity' utility is probably usable (with very little change) on Linux-Unix-BSD-Mac systems.Besides showing two 'tachometer style' meters, side by side, on two Tk 'canvas' widgets --- I wanted to provide a 'scale' widget on the GUI, by which the user could specify, at any time, a new 'sampling rate' (actually, a 'wait-time' = 'wave-length', rather than a 'frequency').I was able to implement that ability by use of the Tcl 'after' command, in the form
after $WAITmillisecs update_needlesThis command is issued once to initialize the GUI --- and it is issued within the 'update_needles' proc itself, to continue sampling a network interface with the 'netstat' command.I have also supplied a 'Refresh' button on the GUI, so that the user can request at ANY TIME, a new set of interface RX and TX values with which to update the GUI.In putting together the code for this GUI, I drew heavily on the 'shadow-circle' technique of Marco Maggi to make nice looking meters. And I included a 'red-line' (danger) area on the meters, like he did.One rather unique thing about this implementation of the meters (something not done by Maggi in his demo) is that the window and the canvases and the meters are resizable. In other words, I spent quite a bit of effort in converting Maggi's procs
- FROM using hard-coded numbers for making the meters and their needles
- TO using variables that work off of queries on the current size of frame and canvas widgets.
CAPTURING THE GENERATED IMAGE:When you get an image that you want to save --- say, to send an 'abuse report' to a web master at a site that has web pages that are blasting your interface card with unjustified traffic --- a screen/window capture utility (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG or GIF file, say.If necessary, an image editor (like 'mtpaint' on Linux) can be used to crop the window capture image. The image could also be down-sized --- say to make a smaller image suitable for presentation in an email or on a web page.
A LITTLE NETWORK ACITIVITY EXPERIMENT:Most of the time, my network interface card is experiencing no change to the cumulative counts of RX (received) and TX (transmitted) packets. So the needles are usually resting at zero.To perk up the meters, I opened my Seamonkey web browser and went to the YouTube site and started up a movie. By doing that, I was able to capture the image above, that shows the needles off of their zero positions.It was interesting to note, however, that the movie packets seem to be downloaded in bursts (buffers) --- because the needles would return to zero for periods of many seconds while a movie was running.
The codeBelow, I provide the Tk script code for this 'network activity in tachometers' display utility.I follow my usual 'canonical' structure for Tk code for this Tk script:
0) Set general window & widget parms (win-name, win-position, win-color-scheme, fonts, widget-geometry-parms, win-size-control, text-array-for-labels-etc). 1a) Define ALL frames (and sub-frames, if any). 1b) Pack ALL frames and sub-frames. 2) Define & pack all widgets in the frames, frame by frame. Within each frame, define ALL the widgets. Then pack the widgets. 3) Define keyboard and mouse/touchpad/touch-sensitive-screen action BINDINGS, if needed. 4) Define PROCS, if needed. 5) Additional GUI initialization (typically with one or more of the procs), if needed.This Tk coding structure is discussed in more detail on the page A Canonical Structure for Tk Code --- and variations.This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in other scripts (code re-use).I call your attention to step-zero. One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.
Experimenting with the GUIAs in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on all of the 'pack' commands for the frames and widgets.That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window.I think that I have used a pretty nice choice of the 'pack' parameters. The label and button widgets stay fixed in size and relative-location if the window is re-sized --- while the two canvas areas (without scroll bars) expand/contract whenever the window is re-sized, and the 'Refresh' button is clicked.The meters expand/contract when the window is re-sized --- but probably not always in a way you would expect. Occasionally, you may need to tug the borders of the window to center the meters in a way that suits you.You can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various frames and widgets --- to get the widget behavior that you want.And you can look into the code that is drawing the meters to see if you can devise meter-resizing behavior that pleases you more.___Additional experimentation: You might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families.In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.
Some features in the codeThat said, here's the code --- with plenty of comments to describe what most of the code-sections are doing.You can look at the top of the PROCS section of the code to see a list of the procs used in this script, along with brief descriptions of how they are called and what they do.The main procs are
'make_tachometers' - to draw 2 meters within their 2 Tk (square) canvases. (We allow the 2 canvases to resize according to a resizing of the window. This proc will set the SQUARE size of the 2 canvases according to the current size of the frame containing the 2 canvases.) 'make_one_tachometer' - called by 'make_tachometers', to make each meter. 'draw_rivet' - called by 'make_one_tachometer', 4 times, to put rivets in 4 corners around a meter. 'draw_circle_shadow' - called by 'make_one_tachometer' to put a shadowed edge around the circle that makes the meter. Also called to help make a 'pin' in the center of the meter to hold the needle. Also called to make the 4 rivets. 'update_needles' - to update the needles on the 2 meters. 'update_one_needle' - called by 'update_needles', to draw each needle. 'Refresh' - called by 'Refresh' button. Runs 'make_tachometers' and 'update_needles'. 'popup_msgVarWithScroll' - called by the 'Help' button, to show text in variable $HELPtext.Thanks to Marco Maggi whose 'shadow-circle' drawing technique and code made this script much easier to write.
It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this.Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of 'plump', shirtless guys talking to you while seated in front of their computers.
Code for Tk script 'meters_net_stats.tk' :
#!/usr/bin/wish -f ##+######################################################################## ## ## SCRIPT: meters_net_stats.tk ## ## PURPOSE: This script is meant to show a GUI that holds a pair of ## tachometer-style meters. The needles on the meters can be updated ## periodically to show the rate of network activity --- rate of ## change of RX (packets received) and TX (packets transmitted) data. ## ## This script was developed on Linux and uses the 'netstat' ## command (or an alternative command) to periodically get the ## RX and TX data to determine where to relocate the position of ## the needles. ## ##+################ ## GUI DESCRIPTION: ## ## This script provides a Tk GUI with the following widgets. ## ## 1) There is an 'fRbuttons' frame to hold BUTTONS such as ## 'Exit' and 'Help' buttons --- as well as a 'Refresh' ## button. ## ## There is an ENTRY widget to hold a network interface-ID ## such as eth0 or eth1 or wlan0. ## ## There is a SCALE widget that allows the user to set a ## 'wait-seconds' parameter for auto-refresh of the meters --- ## in seconds --- down to tenths of seconds, and up to multiple ## minutes. ## ## 2) There is an 'fRcanvases' frame to contain 2 CANVAS widgets that ## hold the two meter images, in 2 SQUARE canvases, side by side. ## Also the 'fRcanvases' frame holds some LABEL widgets, to show ## RX-RATE and TX-RATE, as text items. ## ##+################################ ## METHOD USED to update the meters: ## ## A Tcl 'exec' command calls on a separate shell script that uses ## the 'netstat' (or other) command to get the RX,TX data and ## extract-and-format the data for return to this Tk script. ## ##+####################### ## CAPTURING THE GUI IMAGE: ## ## A screen/window capture utility (like 'gnome-screenshot' ## on Linux) can be used to capture the GUI image in a PNG ## or GIF file, say. ## ## If necessary, an image editor (like 'mtpaint' on Linux) ## can be used to crop the window capture image. The image ## could also be down-sized --- say to make a smaller image ## suitable for use in a web page or an email. ## ##+####################################################################### ## 'CANONICAL' STRUCTURE OF THIS CODE: ## ## 0) Set general window parms (win-name, win-position, win-color-scheme, ## fonts, widget-geom-parms, win-size-control, text-array-for-labels-etc). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack the frames. ## ## 2) Define & pack all widgets in the frames, frame by frame. ## After all the widgets for a frame are defined, pack them in the frame. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen 'event' ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or more of ## the procs), if needed. ## ##+################################# ## Some detail of the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : ## '.fRbuttons' - to contain 'Exit', 'Help', 'Refresh' buttons ## as well as an entry widget for interface-ID ## and a label-and-scale pair. ## ## '.fRcanvases' - to contain 2 canvas widgets, which will display ## the two meters, side-by-side. ## ## Sub-frames: ## '.fRcanvases.fRcanvas1' - for 1 label widget & 1 canvas widget ## '.fRcanvases.fRcanvas2' - for 1 label widget & 1 canvas widget ## ## 1b) Pack ALL frames. ## ## 2) Define & pack all widgets in the frames -- basically going through ## frames & their interiors in left-to-right, or top-to-bottom order. ## ## 3) Define BINDINGS: none ## ## 4) Define PROCS: ## ## 'make_tachometers' - to draw 2 meters within their 2 Tk (square) canvases. ## ## (We may allow the 2 canvases to resize according to ## a resizing of the window. This proc will set the ## SQUARE size of the 2 canvases according to the current ## size of the frame containing the 2 canvases.) ## ## 'make_one_tachometer' - called by 'make_tachometers', to make each meter. ## ## 'draw_rivet' - called by 'make_one_tachometer', 4 times, to put ## rivets in 4 corners around a meter. ## ## 'draw_circle_shadow' - called by 'make_one_tachometer' to put a shadowed ## edge around the circle that makes the meter. ## Also called to help make a 'pin' in the center of ## the meter to hold the needle. Also called to make ## the 4 rivets. ## ## 'update_needles' - to update the needles on the 2 meters. ## ## 'update_one_needle' - called by 'update_needles', to draw each needle. ## ## 'Refresh' - called by 'Refresh' button. Runs 'make_tachometers' ## and 'update_needles'. ## ## 'popup_msgVarWithScroll' - called by 'Help' button to show HELPtext var. ## ## ## 5) Additional GUI Initialization: ## - call 'make_tachometers' to put the 2 meters on the canvas ## - call 'update_needles' --- to initialize the needle locations. ## ##+####################################################################### ## DEVELOPED WITH: Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october, 'Karmic Koala') ## ## $ wish ## % puts "$tcl_version $tk_version" ## ## showed ## 8.5 8.5 ## but this script should work in most previous 8.x versions, and probably ## even in some 7.x versions (if font handling is made 'old-style'). ##+####################################################################### ## MAINTENANCE HISTORY: ## Started by: Blaise Montandon 2013sep05 Started the basic code of the ## script based on my previous ## meters script ## meters_mem_and_swap.tk ## which was based on a ## demo script by Marco Maggi ## at http://wiki.tcl.tk/9107 ## Changed by: Blaise Montandon 2013sep06 Added 'Ifaces' button in place ## of an 'Ifaces:' label. ## Initialized the IFACEname var with ## a call to 'netstat' instead of ## using a hardcoded value. ##+######################################################################## ##+###################################################### ## Set WINDOW TITLE and POSITION. ##+###################################################### wm title . "Network Activity - RX and TX Rates" wm iconname . "RX-TX" wm geometry . +15+30 ##+###################################################### ## Set the COLOR SCHEME for the window and its widgets --- ## such as listbox and entry field background color. ##+###################################################### tk_setPalette "#e0e0e0" # set listboxBKGD "#ffffff" set entryBKGD "#ffffff" set scaleBKGD "#f0f0f0" ##+######################################################## ## DEFINE (temporary) FONT NAMES. ## ## We use a VARIABLE-WIDTH font for text on LABEL and ## BUTTON widgets. ## ## We use a FIXED-WIDTH font for LISTBOX lists, ## for Help-text in a TEXT widget, and for ## the text in ENTRY fields, if any. ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## CANVAS widget geom settings: set initCanWidthPx 200 set initCanHeightPx 200 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ## LABEL widget geom settings: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 ## BUTTON widget geom settings: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 ## ENTRY widget geom settings: set BDwidthPx_entry 2 ## SCALE widget geom parameters: set BDwidthPx_scale 2 set scaleThicknessPx 10 ##+###################################################################### ## Set a MIN-SIZE of the window (roughly). ## ## For WIDTH, allow for the min-width of the '.fRbuttons' and '.fRcanvas' ## frames --- at least, the widgets in the 'fRbuttons' frame. ## ## For HEIGHT, allow for the stacked frames: ## 2 chars high for the '.fRbuttons' frame, ## at least 50 pixels high for the '.fRcanvas' frame. ##+##################################################################### ## FOR WIDTH: set minWidthPx [font measure fontTEMP_varwidth \ " Exit Help Refresh Ifaces eth0 Sample rate "] ## We accomodate a label-and-scale pair --- ## add pixels for length of the scale widget, at least 100. ## ## We add some pixels to account for right-left-size of ## window-manager decoration (~8 pixels) and some pixels for ## frame/widget borders (~3 widgets x 4 pixels/widget = 12 pixels). set minWinWidthPx [expr {120 + $minWidthPx}] ## For HEIGHT --- for ## 2 char high for 'fRbuttons' ## 50 pixels high for 'fRcanvas' set charHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {2 * $charHeightPx}] ## Add about 50 pixels for height of the canvas ## AND add about 20 pixels for top-bottom window decoration -- ## and some pixels for top-and-bottom of frame/widget borders ## (~4 widgets x 4 pixels/widget = 16 pixels). set minWinHeightPx [expr {86 + $minWinHeightPx}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We may allow the window to be resizable. We pack the canvases ## (and the frames that contain them) with '-fill both -expand 1' ## so that the canvases can be enlarged by enlarging the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+############################################################## ## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI. ## NOTE: This can aid INTERNATIONALIZATION. This array can ## be set according to a nation/region parameter. ##+############################################################## ## if { "$VARlocale" == "en"} ## For '.fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonREFRESH) "Refresh" set aRtext(buttonIFACES) "Ifaces" set aRtext(labelSCALE) "SampleRate (seconds) :" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' , '.fRcanvases' ## ## Sub-frames: '.fRcanvases.fRcanvas1' '.fRcanvases.fRcanvas2' ##+################################################################ ## FOR TESTING: (to see size of frames as window is resized) # set BDwidth_frame 2 # set RELIEF_frame raised set BDwidth_frame 0 set RELIEF_frame flat frame .fRbuttons -relief $RELIEF_frame -bd $BDwidth_frame frame .fRcanvases -relief $RELIEF_frame -bd $BDwidth_frame frame .fRcanvases.fRcanvas1 -relief raised -bd 2 frame .fRcanvases.fRcanvas2 -relief raised -bd 2 ##+############################## ## PACK the FRAMES. ##+############################## pack .fRbuttons \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvases \ -side top \ -anchor nw \ -fill both \ -expand 1 pack .fRcanvases.fRcanvas1 \ -side left \ -anchor nw \ -fill both \ -expand 1 pack .fRcanvases.fRcanvas2 \ -side right \ -anchor ne \ -fill both \ -expand 1 ##+########################################################## ## The FRAMES ARE PACKED. START PACKING WIDGETS IN THE FRAMES. ##+########################################################## ##+########################################################## ## In FRAME '.fRbuttons' - ## DEFINE-and-PACK 'BUTTON' WIDGETS ## --- Exit, Help, ... --- and a LABEL-AND-SCALE widget pair ## (for changing the 'refresh rate' for the meter needles. ##+########################################################## button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {set loop0or1 0 ; exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext"} button .fRbuttons.buttREFRESH \ -text "$aRtext(buttonREFRESH)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {Refresh} ## Here is the BUTTON for showing installed network interfaces. button .fRbuttons.buttIFACES \ -text "$aRtext(buttonIFACES)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {sho_ifaces} ## Here is the ENTRY field for the network interface-ID. ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set IFACEname "eth0" entry .fRbuttons.entryIFACE \ -textvariable IFACEname \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width 6 \ -relief sunken \ -bd $BDwidthPx_entry ## Here is the LABEL-AND-SCALE pair for the wait-seconds (sample rate). label .fRbuttons.labelSCALE \ -text "$aRtext(labelSCALE)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set WAITseconds 5.0 scale .fRbuttons.scaleSECONDS \ -from 0.1 -to 20.0 \ -resolution 0.1 \ -font fontTEMP_SMALL_varwidth \ -variable WAITseconds \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -length 100 \ -width $scaleThicknessPx ## Here is a label to show the current sample count. label .fRbuttons.labelCOUNT \ -textvariable VARsampcnt \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bd 0 ## Pack the widgets in frame '.fRbutton'. pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttREFRESH \ .fRbuttons.buttIFACES \ .fRbuttons.entryIFACE \ .fRbuttons.labelSCALE \ .fRbuttons.scaleSECONDS \ .fRbuttons.labelCOUNT \ -side left \ -anchor w \ -fill none \ -expand 0 ##+######################################################## ## In FRAME '.fRcanvases.fRcanvas1' - ## DEFINE-and-PACK TWO LABELs and ## ONE CANVAS WIDGET (no scrollbars). ## ## We highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+####################################################### label .fRcanvases.fRcanvas1.labelINFO1 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label label .fRcanvases.fRcanvas1.labelINFO2 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label canvas .fRcanvases.fRcanvas1.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 ## Pack the widgets in frame '.fRcanvases.fRcanvas1'. pack .fRcanvases.fRcanvas1.labelINFO1 \ .fRcanvases.fRcanvas1.labelINFO2 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvases.fRcanvas1.can \ -side top \ -anchor nw \ -fill none \ -expand 0 ##+######################################################## ## In FRAME '.fRcanvases.fRcanvas2' - ## DEFINE-and-PACK TWO LABELs and ## ONE CANVAS WIDGET (no scrollbars). ## ## We highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+####################################################### label .fRcanvases.fRcanvas2.labelINFO1 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label label .fRcanvases.fRcanvas2.labelINFO2 \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief flat \ -padx $PADXpx_label \ -pady $PADYpx_label \ -bd $BDwidthPx_label canvas .fRcanvases.fRcanvas2.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 ## Pack the widgets in frame '.fRcanvases.fRcanvas2'. pack .fRcanvases.fRcanvas2.labelINFO1 \ .fRcanvases.fRcanvas2.labelINFO2 \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvases.fRcanvas2.can \ -side top \ -anchor nw \ -fill none \ -expand 0 ##+################################################## ## END OF DEFINITION of the GUI widgets. ##+################################################## ## Start of BINDINGS, PROCS, Added-GUI-INIT sections. ##+################################################## ##+################################################################## ##+################################################################## ## BINDINGS SECTION: none ##+################################################################## ##+################################################################## ##+################################################################## ## DEFINE PROCS SECTION: ## ## 'make_tachometers' - to draw 2 meters within 2 Tk canvases ## ## (We may allow the Tk canvases to resize according to ## a resizing of the window. This proc will draw the ## 2 meters in proportion to the size of their canvases.) ## ## 'make_one_tachometer' - to draw one tachometer. Called by 'make_tachometers' ## to make the 2 meters. ## ## 'draw_rivet' - called by 'make_tachometers' to put rivets in 4 ## corners around each meter. ## ## 'draw_circle_shadow' - called by 'make_tachometers' to put a shadowed ## edge around the circle that makes each meter. ## Also called by 'make_tachometers' to put ## a shadowed edge on the 'pin' that holds a needle. ## Also called by 'draw_rivet' to put a shadowed ## edge on each rivet. ## ## 'update_needles' - to update the 2 needles on the 2 meters. Called in a ## recursive loop, initiated at the bottom of this script. ## And called in a 'Refresh' proc. ## ## 'update_one_needle' - to draw one needle in a specified canvas. Called by ## 'update_needles' to update the 2 needles on the 2 meters. ## ## 'Refresh' - called by the 'Refresh' button. Runs the procs ## 'make_tachometers' and 'update_needles' --- in particular, ## for the user to force the meters to be resized if ## the user resizes the window --- and whenever the user ## wants a new 'reading'. ## ## 'popup_msgVarWithScroll' - to show the HELPtext var. Called by the 'Help' button. ## ##+################################################################# ##+######################################################################## ## PROC 'make_tachometers' ##+######################################################################## ## PURPOSE: Draws all features of 2 tachometer-style meters (except the ## needles) --- in a 'nice filling-size' according to the ## current canvas dimensions. ## ## (We will allow the canvas to resize according to ## a resizing of the window. This proc will redraw the ## meters in proportion to the new size of the canvas.) ## ## CALLED BY: once, at the 'Additional GUI Initialization' section, ## at the bottom of this script --- and ## in the 'ReDraw...' proc. ##+######################################################################## proc make_tachometers {} { global marginPx ## FOR TESTING: (to dummy out this proc) # return ############################################################ ## Get current '.fRcanvases' dimensions --- in case the user ## has resized the window, and thus the '.fRcanvases' frame, ## which was packed with '-fill both -expand 1'. ############################################################ # set curCanvasesWidthPx [.fRcanvases cget -width] # set curCanvasesHeightPx [.fRcanvases cget -height] set curCanvasesWidthPx [winfo width .fRcanvases] set curCanvasesHeightPx [winfo height .fRcanvases] ############################################################ ## Set a width-and-height to use for a canvas to contain ## each of the 2 meters. (Half the 'fRcanvases' width. Also ## take the height of 'fRcanvases' into account.) ## (We take 8 pixels off the width to account for some ## borderwidths of frames within the 'fRcanvases' frame.) ############################################################ set canvasSizePx [expr {int(($curCanvasesWidthPx - 8) / 2.0)}] if {$curCanvasesHeightPx < $canvasSizePx} {set canvasSizePx $curCanvasesHeightPx} #################################################################### ## Resize the canvases 'fRcanvas1.can' and 'fRcanvas2.can' that ## hold the 2 (square) Tk canvases for the 2 meters. Note that those ## 2 canvases and their parent frames were all packed with ## '-fill both -expand 1' --- so if the canvas widgets ## expand/contract, then the parent frames should do the same. #################################################################### if {0} { .fRcanvases.fRcanvas1 configure -width $canvasSizePx .fRcanvases.fRcanvas1 configure -height $canvasSizePx .fRcanvases.fRcanvas2 configure -width $canvasSizePx .fRcanvases.fRcanvas2 configure -height $canvasSizePx } .fRcanvases.fRcanvas1.can configure -width $canvasSizePx .fRcanvases.fRcanvas1.can configure -height $canvasSizePx ## FOR TESTING: # update .fRcanvases.fRcanvas2.can configure -width $canvasSizePx .fRcanvases.fRcanvas2.can configure -height $canvasSizePx ## FOR TESTING: # update ## Following not needed? The resizing of the '.can' canvas ## widgets should cause the parent frames to resize. # set doubleWidthPx [expr {(2 * $canvasSizePx) + 8}] # .fRcanvases configure -width $doubleWidthPx # .fRcanvases configure -height $canvasSizePx ##################################################### ## NEEDED to force the canvases and frames to update ## according to the new canvas sizes. ##################################################### update ######################################################### ## Draw meter1 (without needle). ######################################################### make_one_tachometer .fRcanvases.fRcanvas1.can ######################################################### ## Draw meter2 (without needle). ######################################################### make_one_tachometer .fRcanvases.fRcanvas2.can } ## END OF proc 'make_tachometers' ##+######################################################################## ## PROC 'make_one_tachometer' ##+######################################################################## ## PURPOSE: Draws all features of a tachometer-style meter (except the ## needle) --- according to the 'marginPx' parameter to set ## top-right and bottom-left coordinates to specify the location ## of the square exactly containing the circular meter on ## the canvas whose ID is passed into this proc. ## ## The features include: ## - white-filled circle for the meter background ## - a gray-shaded (shadowed) edge around the circle ## - a 'pin' in the center of the circle, for the needle ## - 4 decorative rivets at the corners of the canvas ## - an arc with tic-marks ## - a red danger-zone in the last segment of the arc ## (between the last pair of tic-marks) ## - labels for the tic-marks ## ## CALLED BY: proc 'make_tachometers' ##+####################################################################### ## Set an 'indentation' to use for placing the outer-circle of the 2 meters ## from the 4 edges of their respective canvases. set marginPx 12 set pi [expr {4.0 * atan(1.0)}] set radsPERdeg [expr {$pi/180.0}] ## The above variables are set ONCE, for use in the following proc. proc make_one_tachometer {canvas} { global marginPx pi radsPERdeg Nsegs pcentLabels ## FOR TESTING: (to dummy out this proc) # return ################################################################ ## Remove any previously drawn elements in this canvas, if any. ################################################################ catch {$canvas delete all} ################################################################## ## Get the width (= height) of the specified (square) canvas. ################################################################## set curCanvasSizePx [winfo width $canvas] ################################################################## ## Set the corner coords for drawing the meter circle (background). ################################################################## set topleftXpx $marginPx set topleftYpx $marginPx set botrightXpx [expr {$curCanvasSizePx - $marginPx}] set botrightYpx [expr {$curCanvasSizePx - $marginPx}] ################################################ ## Draw basic white-filled circle for the meter. ################################################ $canvas create oval \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ -fill white -outline {} # -width 1 -outline lightgray ## FOR TESTING: (exit this proc before adding more to the meter) # return ####################################################################### ## Draw shadow-circle at the outer circle of the meter. ####################################################################### ## INPUTS: ## - the 4 corner coordinates of the oval/circle box (in pixels) ## - number of segments for the arc (segments of differing color shade) ## - width (in pixels) to draw the arc segments ## - start angle for drawing the (darker) arc segments ## (measured counter-clockwise from the 3 o'clock position) ## (An angle of +135=90+45 means the dark side of the 'shadow-circle' ## is on the north-west side of the circle.) ###################################################################### draw_circle_shadow $canvas \ $topleftXpx $topleftYpx $botrightXpx $botrightYpx \ 40 6 135.0 ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################################################### ## Draw a shadow-circle for the 'pin' of the meter needle. ################################################################### ## INPUTS: ## - the 4 corner coordinates of the oval/circle box (in pixels) ## - number of segments for the arc (segments of differing color shade) ## - width (in pixels) to draw the arc segments ## - start angle for drawing the (darker) arc segments ## (measured counter-clockwise from the 3 o'clock position) ## (An angle of -45 means the dark side of the 'shadow-circle' ## is on the south-east side of the circle.) ################################################################### set centerXpx [expr {int($curCanvasSizePx/2.0)}] set centerYpx $centerXpx set pinOuterRadiusPx 14 set x1 [expr {$centerXpx - $pinOuterRadiusPx}] set y1 [expr {$centerYpx - $pinOuterRadiusPx}] set x2 [expr {$centerXpx + $pinOuterRadiusPx}] set y2 [expr {$centerYpx + $pinOuterRadiusPx}] draw_circle_shadow $canvas $x1 $y1 $x2 $y2 40 6 -45.0 ## FOR TESTING: (exit this proc before adding more to the meter) # return ############################################################ ## Draw a red-filled circle on the 'pin' of the meter needle. ############################################################ set pinRadiusPx 12 set x1 [expr {$centerXpx - $pinRadiusPx}] set y1 [expr {$centerYpx - $pinRadiusPx}] set x2 [expr {$centerXpx + $pinRadiusPx}] set y2 [expr {$centerYpx + $pinRadiusPx}] $canvas create oval \ $x1 $y1 $x2 $y2 -fill red -outline {} # -width 1 -outline lightgray ## FOR TESTING: (exit this proc before adding more to the meter) # return ########################################### ## Draw arc-line on which to put tic marks. ################################################# ## 320 degrees counter-clockwise from -70 degrees ## (based at 3 oclock) is 70 degrees beyond 180. ## I.e. -70 + 320 = 250 = 180 + 70 ################################################# set arcLineIndentPx 10 set x1 [expr {$topleftXpx + $arcLineIndentPx}] set y1 [expr {$topleftYpx + $arcLineIndentPx}] set x2 [expr {$botrightXpx - $arcLineIndentPx}] set y2 [expr {$botrightYpx - $arcLineIndentPx}] $canvas create arc $x1 $y1 $x2 $y2 \ -start -70 -extent 320 -style arc \ -outline black -width 2 ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################################## ## Draw tic-marks and labels around the meter. ################################################## set DEGperTIC [expr {320.0/$Nsegs}] set half $centerXpx ## outer location (radius) of tic marks set l1 [expr {$half - ($arcLineIndentPx + $marginPx)}] ## inner location (radius) of tic marks set l2 [expr {$l1 - $arcLineIndentPx}] ## inner location of tic labels set l3 [expr {$l2 - $arcLineIndentPx}] set angle0 250.0 for {set i 0} {$i <= $Nsegs} {incr i} { set rads [expr {($angle0 - ($DEGperTIC * $i)) * $radsPERdeg}] set x1 [expr {$half + $l1 * cos($rads)}] set y1 [expr {$half - $l1 * sin($rads)}] set x2 [expr {$half + $l2 * cos($rads)}] set y2 [expr {$half - $l2 * sin($rads)}] $canvas create line \ $x1 $y1 $x2 $y2 \ -fill black -width 2 set x1 [expr {$half + $l3 * cos($rads)}] set y1 [expr {$half - $l3 * sin($rads)}] set label [lindex $pcentLabels $i] if { [string length $label] } { $canvas create text \ $x1 $y1 \ -anchor center -justify center -fill black \ -text $label -font { Helvetica 10 } } ## END OF labels loop. } ## END OF i-loop for tic-marks ## FOR TESTING: (exit this proc before adding more to the meter) # return ####################################################### ## Draw red-line arc-segment (danger zone) of the meter. ####################################################### set redLineIndentPx 15 set x1 [expr {$topleftXpx + $redLineIndentPx}] set y1 [expr {$topleftYpx + $redLineIndentPx}] set x2 [expr {$botrightXpx - $redLineIndentPx}] set y2 [expr {$botrightYpx - $redLineIndentPx}] $canvas create arc $x1 $y1 $x2 $y2 \ -start -70 -extent $DEGperTIC -style arc \ -outline red -fill red -width 8 ## FOR TESTING: (exit this proc before adding more to the meter) # return ################################## ## Draw 4 rivets around the meter. ################################## set RIVETindentPx 10 set RIVEToutdentPx [expr {$curCanvasSizePx - $RIVETindentPx}] ## upper-left rivet draw_rivet $canvas $RIVETindentPx $RIVETindentPx ## upper-right rivet draw_rivet $canvas $RIVEToutdentPx $RIVETindentPx ## lower-left rivet draw_rivet $canvas $RIVETindentPx $RIVEToutdentPx ## lower-right rivet draw_rivet $canvas $RIVEToutdentPx $RIVEToutdentPx } ## END OF proc 'make_tachometers' ##+######################################################################## ## PROC 'draw_rivet' ##+######################################################################## ## PURPOSE: Put a rivet at a specified center point. ## The center point is specified in pixels, as a location on ## the canvas of the GUI, relative to the upper left corner. ## ## (We pass the radius of the rivets in a global variable.) ## ## CALLED BY: the 'make_tachometer' proc ##+######################################################################## set rivetRadiusPx 4 proc draw_rivet { canvas centerXpx centerYpx } { global rivetRadiusPx ## FOR TESTING: # return ######################################################## ## Draw a color shaded arc using ## - 5 arc segments around each half of the circle/oval ## - 3 pixels for width of the arc segments ## - -45 degrees for the start angle (darkest shade) ######################################################## draw_circle_shadow $canvas \ [expr {$centerXpx - $rivetRadiusPx}] \ [expr {$centerYpx - $rivetRadiusPx}] \ [expr {$centerXpx + $rivetRadiusPx}] \ [expr {$centerYpx + $rivetRadiusPx}] \ 5 3 -45.0 } ## END OF proc 'draw_rivet' ##+######################################################################## ## PROC 'draw_circle_shadow' ##+######################################################################## ## PURPOSE: Puts a shadowed edge around an oval/circle in a specified 'box'. ## ## INPUTS: - the corner coordinates of the oval/circle box (in pixels) ## - number of segments for the arc (segments of differing color shade) ## - width (in pixels) to draw the arc segments ## - start angle for drawing the arc segments ## ## CALLED BY: the 'make_tachometers' and 'draw_rivets' procs ##+######################################################################## proc draw_circle_shadow {canvas x1 y1 x2 y2 Nsegs ARCwidthPx startDEGREES } { ## FOR TESTING: (dummy out this proc) # return set DEGperSHADE [expr {180.0/$Nsegs}] for {set i 0} {$i <= $Nsegs} {incr i} { set a [expr {($startDEGREES + $i * $DEGperSHADE)}] set b [expr {($startDEGREES - $i * $DEGperSHADE)}] ## Make darker grays for greater angles. set color255 [expr {40 + $i*(200/$Nsegs)}] set hexcolor [format "#%x%x%x" $color255 $color255 $color255] $canvas create arc \ $x1 $y1 $x2 $y2 \ -start $a -extent $DEGperSHADE \ -style arc -outline $hexcolor -width $ARCwidthPx $canvas create arc \ $x1 $y1 $x2 $y2 \ -start $b -extent $DEGperSHADE \ -style arc -outline $hexcolor -width $ARCwidthPx ## FOR TESTING: (show each pair of segments before ## drawing the next pair) # update } ## END OF loop over the arc segments } ## END OF proc 'draw_circle_shadow' ##+######################################################################## ## PROC 'update_needles' ##+######################################################################## ## PURPOSE: Updates the needles on 2 square canvases --- using the ## Linux/Unix/BSD/Mac 'netstat' (or other) command to get ## RX and TX data. ## ## Input is the canvas ID. This proc queries the canvas to ## get its center and to determine an appropriate length for ## the needle. ## ## CALLED BY: the 'Additional GUI Initialization' section at the ## bottom of this script, and ## within this proc itself. ##+######################################################################## proc update_needles {} { global argv0 DIRscripts WAITseconds IFACEname \ PREVrx PREVtx PREVclock_millisecs VARsampcnt # global env ########################################################## ## Get RX and TX data --- and the current clock time, ## in millisecs. ########################################################## foreach {CURrx CURtx} \ [exec $DIRscripts/get_net_stats.sh $IFACEname] {break} incr VARsampcnt set CURclock_millisecs [clock milliseconds] ## FOR TESTING: if {0} { puts "proc update_needles:" puts "PREVrx: $PREVrx PREVtx: $PREVtx" puts "PREVclock_millisecs: $PREVclock_millisecs" puts "CURrx: $CURrx CURtx: $CURtx" puts "CURclock_millisecs: $CURclock_millisecs" } ######################################################### ## Calculate the RX and TX rates. ######################################################### set deltaRX [expr {$CURrx - $PREVrx}] set deltaTX [expr {$CURtx - $PREVtx}] set deltaSECS \ [expr {double($CURclock_millisecs - $PREVclock_millisecs)/1000.0}] set RATErx [expr {$deltaRX/$deltaSECS}] set RATEtx [expr {$deltaTX/$deltaSECS}] ######################################################### ## Update the 2 needles. ######################################################### ## FOR TESTING: (hardcoded meter values) # update_one_needle .fRcanvases.fRcanvas1 123456789 1234 RX # update_one_needle .fRcanvases.fRcanvas2 654321 321 TX update_one_needle .fRcanvases.fRcanvas1 $CURrx $RATErx RX update_one_needle .fRcanvases.fRcanvas2 $CURtx $RATEtx TX ######################################################### ## Load the current counts to the PREV vars. ######################################################### set PREVrx $CURrx set PREVtx $CURtx set PREVclock_millisecs $CURclock_millisecs ############################################################ ## Force the needles to show up on the GUI. (needed???) ############################################################ # update ################################################################ ## 'Pseudo-Recursively' 'fork off' another (delayed) instance of the ## 'update_needles' here to support the wait-seconds scale widget ## --- using the 'after ms cmd arg arg ...' form of the 'after' ## command. ## ## We may need an 'after idle update_needles' somewhere ## in this script to 'register'/'queue' the 'update_needles' ## proc for execution at idle times --- to assure responsiveness ## of the GUI. ################################################################ set WAITmillisecs [expr {int($WAITseconds * 1000)}] after $WAITmillisecs update_needles } ## END OF proc 'update_needles' ##+######################################################################## ## PROC 'update_one_needle' ##+######################################################################## ## PURPOSE: Updates the a needle on a square canvas --- using the ## canvas ID and the rate number and label-name passed as arguments. ## ## Input is the canvas ID. This proc queries the canvas to ## get its center and to determine an appropriate length for ## the needle as a proportion of the (square) canvas size. ## ## CALLED BY: the 'update_needles' proc ##+######################################################################## proc update_one_needle {frame TOT RATE TYPE} { global pi radsPERdeg Nsegs MAXrate ## FOR TESTING: (dummy out this routine) # return set RATEtext "$TYPE Rate = [format "%0.2f" $RATE] packets/second" set TOTtext "$TOT $TYPE pkts so far this session" $frame.labelINFO1 configure -text "$RATEtext" $frame.labelINFO2 configure -text "$TOTtext" ## Set the angle for the zero-point on the arc-of-tic-marks. set angle0 250.0 ## Convert RATE/MAXrate to an angle in radians on the arc. if {$RATE > $MAXrate} {set RATE $MAXrate} set degs [expr {$angle0 - (320.0 * $RATE / $MAXrate)}] set rads [expr {$degs * $radsPERdeg}] ## FOR TESTING: # puts "proc 'update_one_needle' :" # puts "RATE: $RATE MAXrate: $MAXrate degs: $degs rads: $rads" ## Get the coord(s) of the center of the (square) canvas ## and calculate a length of the needle. # set width [$frame.can cget -width] set width [winfo width $frame.can] set half [expr {int($width / 2.0)}] set length [expr {int($half * 0.5)}] ## Calculate the coordinates for the tip and base of the needle. set xtip [expr {$half + $length*cos($rads)}] set ytip [expr {$half - $length*sin($rads)}] # set xbase [expr {$half + 0.2*$length*cos($rads)}] # set ybase [expr {$half - 0.2*$length*sin($rads)}] set xbase $half set ybase $half ## Remove a previous needle, if any. catch {$frame.can delete -tags TAGneedle} #################################################################### ## Draw a red-line needle and a reddish-white line on either side ## --- for an (attempted) anti-aliasing effect. ## ## NOTE: This attempt at anti-aliasing did not work out well. ## This code needs improvement --- or simply one 'create line'. #################################################################### $frame.can create line \ $xbase $ybase $xtip $ytip \ -fill #ff0000 -width 4 -tag TAGneedle $frame.can create line \ [expr {$xbase + 1}] [expr {$ybase + 1}] \ [expr {$xtip + 1}] [expr {$ytip + 1}] \ -fill #ff8888 -width 2 -tag TAGneedle $frame.can create line \ [expr {$xbase - 1}] [expr {$ybase - 1}] \ [expr {$xtip - 1}] [expr {$ytip - 1}] \ -fill #ff8888 -width 2 -tag TAGneedle } ## END OF proc 'update_one_needle' ##+############################################################# ## proc Refresh ## ## PURPOSE: 'Refresh' the two meters and their needles --- ## for when the user wants a new set of values ## and/or when the user resizes the window. ## ## CALLED BY: 'Refresh' button ##+############################################################# proc Refresh {} { ## Cancel pending needle update(s), before redrawing ## the meters and restarting the update-needles cycle. set LISTids [after info] foreach ID $LISTids { after cancel $ID } make_tachometers update_needles } ## END OF proc 'Refresh' ##+############################################################# ## proc sho_ifaces ## ## PURPOSE: Shows available network interfaces, in a ## popup message window. ## ## CALLED BY: 'Ifaces' button ##+############################################################# proc sho_ifaces {} { set IFACES [exec /bin/sh -c {netstat -i | tail -n +3 | awk '{print $1}'}] popup_msgVarWithScroll .topIFACES $IFACES } ## END OF proc 'sho_ifaces' ##+############################################################# ## proc ReDraw_if_canvases_resized ## ## PURPOSE: To handle resizing the meters when the window is ## resized --- IF the <Configure> binding is implemented. ## ## The intent is to avoid too many redraws --- for ## almost every little resize of the window as its ## border(s) are dragged. ## ## CALLED BY: bind .fRcanvas.can <Configure> ## at bottom of this script. ##+############################################################# ## NOT IMPLEMENTED. ## Code is included for possible future development. ##+############################################################# proc ReDraw_if_canvases_resized {} { global PREVcanvasesWidthPx PREVcanvasesHeightPx draw_wait0or1 ## FOR TESTING: (to dummy out this proc) # return if {$draw_wait0or1 == 1} {return} ## Set the wait indicator and delay doing the canvas resize ## check for about 300 milliseconds --- to allow time for the ## user to stop moving the window. After about 300 milliseconds, ## it is unlikely that the window is moving and thus causing ## multiple redraws. set draw_wait0or1 1 after 900 set CURcanvasesWidthPx [winfo width .fRcanvases] set CURcanvasesHeightPx [winfo height .fRcanvases] if { $CURcanvasesWidthPx != $PREVcanvasesWidthPx || \ $CURcanvasesHeightPx != $PREVcanvasesHeightPx} { ## The 'after' could be used to prevent too many ## redraws (and flickering and unnecessary processing) ## as the window is being moved. # after 200 make_tachometers update_needles set PREVcanvasesWidthPx $CURcanvasesWidthPx set PREVcanvasesHeightPx $CURcanvasesHeightPx set draw_wait0or1 0 } } ## END OF proc 'ReDraw_if_canvases_resized' ##+######################################################################## ## PROC 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext } { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 wm geometry $toplevName +100+50 wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################## ## END of PROC definitions. ##+######################## ## Set HELPtext var. ##+######################## set HELPtext "\ \ \ ** HELP for this 'Network Activity' Monitoring Utility ** This utility is meant to show a GUI that holds a pair of tachometer-style meters. The needles on the meters are updated periodically --- according to a 'wait-seconds' setting on a 'scale' widget of the GUI --- OR whenever the user chooses to click on the 'Refresh' button --- to show the RATE of packet receipts and transmissions (RX and TX rates) on this computer. The RX and TX rates (packets per second) are shown on the GUI as numeric-text as well as by the positions of the needles. This Tcl-Tk script was developed on Linux and uses the 'netstat' command to get the RX and TX data to determine where to relocate the position of the needles on the meters. An 'Ifaces' button on the GUI can be clicked to get a list of the names of network interfaces known to the 'netstat' command. Typically, the names are 'eth0', 'eth1', or 'wlan0'. Change the interface name in the entry field if you want to monitor a different interface. An integer on the top right of the GUI is incremented each time a sample is taken with the 'netstat' command. *************************************** WINDOW RESIZE (an experimental feature): We can allow the user to resize the window rather than using a fixed window (and fixed meters) size. If the user resizes the window, the 'Refresh' button can be used to force the meters to be resized according to the new window size. (The meters may be resized such that they are 'too tall' for the new window size. Just pull the bottom border of the window down, to see the entire meters.) ************************************ THE SCRIPT USED to update the meters: A Tcl 'exec' command calls on a separate shell script --- 'get_net_stats.sh' --- that uses the 'netstat' (or other) command to get the RX and TX data --- and extract-and-format the data for return to this Tk script. If the 'netstat' command is not available on your computer, you may have to edit the shell script to use a different command (such as 'ifconfig') to get the RX and TX data. *********************** CAPTURING THE GUI IMAGE: A screen/window capture utility (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG or GIF file, say. If necessary, an image editor (like 'mtpaint' on Linux) can be used to crop the window capture image. The image could also be down-sized --- say to make a smaller image suitable for use in a web page or an email. " ##+################################################################ ##+################################################################ ## Additional GUI INITIALIZATION: Mainly to ## - Put the 2 meters on their 2 canvases, with 'make_tachometers'. ## - Start an execution loop for the 'update_needles' proc. ##+################################################################ ##+######################################################## ## Get a default network interface-ID for the entry widget. ##+######################################################## set IFACEname [exec /bin/sh -c {netstat -i | tail -n +3 | awk '{print $1}' | head -1}] # set IFACEname "eth0" # set IFACEname "eth1" # set IFACEname "wlan0" ##+################################################### ## Set the scale widget var for initial 'refresh rate' ## (actually wait-time = 'wave-length', not 'frequency') ## --- in seconds. ## ## We set this before the following 'update' command, ## so that the slider-button on the scale widget is ## positioned accordingly when the GUI first appears. ##+################################################### set WAITseconds 2.0 # set WAITseconds 5.0 # set WAITseconds 1.0 # set WAITseconds 0.1 ##+##################################################### ## Set a max-rate (packets/sec) to use for the limit ## on the 2 meters. ##+##################################################### set MAXrate 1000 ##+#################################################### ## Set a number of segments and labels for the 2 meters, ## according to the MAXrate setting. ##+#################################################### set Nsegs 10 set pcentLabels "0 100 200 300 400 500 600 700 800 900 1000" ##+################################################# ## Draw the 2 tachometers (without needles). ##+################################################# ## Need 'update' here to set the size of the canvases, ## because 'make_tachometers' uses 'winfo' to get ## the width and height of some frames and canvases ## --- to support resizing the meters if the window ## is resized. ##+################################################# update make_tachometers ##+####################################################### ## Get the directory that this Tk script is in. That should ## be the directory that the utility shell script is in ## --- to get the RX,TX data values. ##+####################################################### ## FOR TESTING: # puts "argv0: $argv0" # set DIRscripts "." # set DIRscripts "[pwd]" # set DIRscripts "$env(HOME)/apps/tkUtils" set DIRscripts "[file dirname $argv0]" ##+#################################################### ## Get an initial sample of the RX,TX data, along with ## the time at which the sample was taken --- in ## 'PREV' variables. ##+#################################################### foreach {PREVrx PREVtx} \ [exec $DIRscripts/get_net_stats.sh $IFACEname] {break} set PREVclock_millisecs [clock milliseconds] ## FOR TESTING: # puts "PREVrx: $PREVrx PREVtx: $PREVtx" # puts "PREVclock_millisecs: $PREVclock_millisecs" ##+#################################################### ## Initialize the variable we use to keep track of ## the sample count. ##+#################################################### set VARsampcnt 0 ##+###################################################### ## Do an initial draw of the needles, after WAITseconds. ## ## NOTE: ## 'update_needles' starts a loop to keep updating the needles. ## ## The proc 'update_needles' called itself --- ## with 'after <millisecs>'. ## ## NOTE: ## I am leery of that kind of 'forked-recursion' --- even though ## it is a 'recursive-like-call-with-FORKED-AND-DELAYED-execution' ## --- which immediately returns to processing after doing the ## 'queue-command-with-delay'. ## ## This could possibly cause ever-increasing memory consumption ## or the generation of an ever-increasing 'stack' of ## processes --- or 'stack' of 'whatever'. I would ## like to take the time to test to make sure that a ## high-percent of CPU and memory are NOT being used. ## ## (Besides the loop structure itself, ## there is the possibility of excessive CPU usage or ## memory leaks due to implementation within the 'wish' ## interpreter. This has happened in the past --- for ## example, with 'tags' on canvas items. So I prefer ## to take the time to test CPU and memory usage --- ## especially for Tk techniques I have not used before.) ## ## I plan to investigate usage of the 'after' command in its ## several forms (including 'after idle'), and try to ## make sure that the use of 'after ms cmd' and/or ## 'after idle cmd' does not lead to 'issues'. ## ## In any case, I provide the user with the 'Refresh' ## button to do the needle updates --- for example, ## if the user resizes the window and wants to force ## a refresh (which runs 'make_tachometers') to resize ## the meters and the variables that the 'update_needles' ## proc uses to draw the needles on the meters. ##+###################################################### set WAITmillisecs [expr {int(1000*$WAITseconds)}] after $WAITmillisecs update_needles ##+################################################# ## Set a resize binding on the canvas --- ## to redraw the tachometers and needles ## if the window is resized. ## ## DE-ACTIVATED, for now. ## (Code is here for future experimentation. ## It is not easy to avoid extraneous redraws of ## the GUI as the window is being dragged/resized.) ##+################################################# if {0} { set draw_wait0or1 0 set PREVcanvasesWidthPx [winfo width .fRcanvases] set PREVcanvasesHeightPx [winfo height .fRcanvases] bind .fRcanvases <Configure> "ReDraw_if_canvases_resized" }
#!/bin/sh ## ## SCRIPT NAME: get_net_stats.sh ## ## PURPOSE: ## Gets 'RX' (received) and 'TX' (transmitted) data from output of ## the 'netstat' command (or some other command supplying the same data). ## ##+############################################ ## Example output from the 'netstat -i' command: ## ## Kernel Interface table ## Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg ## eth0 1500 0 23449 0 0 0 19670 0 0 0 BMRU ## lo 16436 0 4 0 0 0 4 0 0 0 LRU ## wlan0 1500 0 0 0 0 0 0 0 0 0 BMU ## wmaster0 0 0 0 0 0 0 0 0 0 0 RU ## ##+######################################################### ## ALTERNATIVELY, we could use 'ifconfig -a -s', which gives: ## ## Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg ## eth0 1500 0 23449 0 0 0 19670 0 0 0 BMRU ## lo 16436 0 4 0 0 0 4 0 0 0 LRU ## wlan0 1500 0 0 0 0 0 0 0 0 0 BMU ## wmaster0 0 0 0 0 0 0 0 0 0 0 RU ## ## where '-a' stands for 'all' --- and '-s' stand for 'summary'. ##+######################################################################## ## ## This script gets the data from the 'RX-OK' and 'TX-OK' columns. ## ## INPUT: a single argument supplies an interface ID, such as ## 'eth0' or 'eth1' or 'wlan0'. ## ## CALLED BY: a Tk GUI script that shows 'RX' and 'TX' DIFFERENCE data ## --- i.e. RATE data --- ## as needle readings on a couple of meters (dials) drawn ## on a Tk canvas --- Tk script name: ## meters_net_stats.tk ## ## MAINTENANCE HISTORY: ## Updated by: Blaise Montandon 2013sep05 Started this script on Linux, ## using Ubuntu 9.10 (2009 October, ## 'Karmic Koala'). ## Updated by: Blaise Montandon 20....... ##+######################################################################## ## FOR TESTING: # set -x if test "$1" = "" then echo "ERROR: Missing argument. Need a network interface ID." exit fi ## ALTERNATIVE: # ifconfig -a -s | grep "^$1" | awk '{print $4 " " $8}' netstat -i | grep "^$1" | awk '{print $4 " " $8}'
SIMILAR UTILITIES:There are a couple of more 'meter utilities' on my 'to-do' list at the bottom of my 'bio' page at uniquename --- in the 'CME' (Code for MEters) group.In particular, I may make a meter utility that shows CPU activity (preferably for all the CPU's on multiple CPU computers, which are everywhere nowadays).
IN CONCLUSIONAs I have said on several other code-donation pages on this wiki ...There's a lot to like about a utility that is 'free freedom' --- that is, no-cost and open-source so that you can modify/enhance/fix it without having to wait for someone else to do it for you (which may be never).A BIG THANK YOU to Ousterhout for starting Tcl-Tk, and a BIG THANK YOU to the Tcl-Tk developers and maintainers who have kept the simply MAH-velous 'wish' interpreter going.
uniquename UPDATE 2013nov22 :Added a small 'foreach' loop to the 'Refresh' proc of the Tk script. The 'after info' and 'after cancel' commands are used to cancel any pending commands that were issued by a previous 'after' command.