#!/bin/sh
# \
exec wish "$0" "$@"

# 
#       xeznet - Tcl/Tk interface for 'eznet'
#       xeznet,v 1.8 1999/02/19 00:55:48 migh Exp
#

option add *borderWidth         1
option add *Label*anchor        w
option add *Label*foreground    DarkBlue

bind Entry <FocusIn>    {%W configure -background gray95; show_help %W}
bind Entry <FocusOut>   {%W configure -background gray85}

set upd_rate 60                 ;# status update rate (see menu settings)
set nquick 0                    ;# number of quick updates for service up/down

set rc_file     ~/.xeznet_rc

#
#       Return a list of selected service names
#
proc get_sel { } {
        msg ""
        set svcs {}
        foreach ix [.lbox.list curselection] {
                set line [.lbox.list get $ix]
                set svc [string trim \
                                [string range $line 1 \
                                        [string wordend $line 1]]]
                if {$svc == ""} {
                        continue
                }
                lappend svcs $svc
        }
        if {[llength $svcs] == 0} {
                msg "Select a service, then click the button."
        }
        return $svcs
}

#
#       Make a new action window for each selected service name
#
proc do_sel {action} {
        if {$action == "add"} {
                msg ""
                svc_window add new
        } {
                foreach svc [get_sel] {
                        svc_window $action $svc
                }
        }
        return
}

#
#       Bring the selected service(s) up or down
#       TODO: we sometimes exit when we bring down a service
#
proc do_updn {dir} {
        set svcs [get_sel]
        foreach svc $svcs {
                msg "Bringing $dir $svc."
                catch {exec eznet $dir $svc >/dev/null &}
        }
        if {[llength $svcs] > 0} {
                global nquick
                set nquick 12
                svc_status
        }
        return
}

#
#       Refresh the current service status
#
proc svc_status {{repeat 0}} {
        set fid 0
        if {[catch {open "|eznet status" r} fid] == 0} {
                set sel [lindex [.lbox.list curselection] 0]
                .lbox.list delete 0 end
                while {[gets $fid line] > 0} {
                        set p [string first ":" $line]
                        set svc  [string range $line 0 [expr {$p - 1}]]
                        set stat [string range $line [expr {$p + 1}] end]
                        .lbox.list insert end [format " %-10s %s" $svc $stat]
                }
                catch {close $fid}
                if {$sel != ""} {
                        .lbox.list selection set $sel
                        .lbox.list activate $sel
                        .lbox.list see $sel
                }
        } {
                msg "Unable to get status from 'eznet'."
        }

        # do we repeat?  how soon?
        set dt 0
        global nquick
        if {$nquick > 0} {
                set dt 5000
                incr nquick -1
        } elseif {$repeat > 0} {
                global upd_rate
                set dt [expr {$upd_rate * 1000}]
        }
        if {$dt > 0} {
                after $dt svc_status 1
        }
        return
}

#
#       Handle the "OK" button on the service information window
#
proc do_svc_ok {w action} {
        set svc [$w.service.e get]
        if {$action == "delete"} {
                msg "Deleted $svc"
                catch {exec eznet delete $svc >/dev/null}
        } elseif {$action != "show"} {
                set cmd "eznet $action $svc"
                set args ""
                global all_entries
                foreach x $all_entries {
                        set val [$w.$x.e get]
                        # TODO: How do we clear an entry???
                        if {$val != ""} {
                                if {$x == "password" && [string index $val 0] == "*"} {
                                        continue
                                } {
                                        append args " \"$x=$val\""
                                }
                        }
                }
                if {$args != ""} {
                        msg "$cmd ..."
                        catch {eval exec $cmd $args >/dev/null}
                }
        }
        destroy $w
        update idletasks
        if {$action != "show"} {
                svc_status
        }
        return
}

array set entry_help {
        phone           "Phone number for modem to dial"
        user            "User login name"
        password        "Login password"
        tty             "Serial port for the modem"
        baud            "Baud rate for the serial port"
        service         "Name of the ISP"
        chat            "`no' to ignore `login:' prompts, etc."
        autostart       "`no' to not start pppd if no `login:'"
        pppversion      "Version of pppd in use"
        debug           "`yes' to turn on pppd debugging"
        expectN         "Extra expect string"
        replyN          "Reply to issue after expect string"
        initN           "Extra modem initialization string"
        diald           "Name of diald executable"
        pppd            "Name of pppd executable"
        local           "Local IP address for diald slip link"
        remote          "Remote IP address for diald slip link"
        idle            "Number of seconds for idle timeout"
        persist         "`yes' for a permanent connection"
        defaultroute    "`no' to not create a default route"
        pppoptN         "Extra parameters to pppd"
        routeN          "Add this route when pppd comes up"
        ip              "Network accessed by this service"
        netmask         "Netmask for the ip network"
        dialtimeout     "Time allowed for modem connect"
        chattimeout     "Time allowed for chat dialog"
}

proc show_help {w} {
        global entry_help
        set parts [split $w .]
        set pw  [lindex $parts 1]
        set ent [lindex $parts 2]
        if {[winfo exists .$pw.help]} {
                regsub {[0-9]} $ent N ent
                if {![info exists entry_help($ent)]} {
                        set entry_help($ent) "(unknown)"
                }
                .$pw.help configure -text "$ent - $entry_help($ent)"
        }
        return
}

array set win_txt {
        add     "Create Service" 
        show    "Show Service for $svc" 
        edit    "Edit Service for $svc" 
        delete  "Delete Service for $svc ?"
}

set win_id 0

#
#       Split all the available entries/settings into subsets for display
#
set basic_entries {
        service phone user password tty baud
}

set modem_entries {
        chat
        diald dialtimeout chattimeout idle
        init0 init1 init2 init3 init4
}

set chat_entries {
        expect0 reply0 
        expect1 reply1 
        expect2 reply2 
        expect3 reply3 
        expect4 reply4 
}

set ppp_entries {
        pppd pppversion 
        pppopt0 pppopt1 pppopt2 pppopt3 pppopt4
        autostart debug 
}

set ip_entries {
        local remote ip netmask persist
}

set route_entries {
        defaultroute
        route0 route1 route2 route3 route4
}

set all_entries [concat $basic_entries $modem_entries $chat_entries\
                        $ppp_entries $ip_entries $route_entries]

#
#       List of frame names and titles
#       (names should match above entry lists)
#
set frame_list {
        {basic  "Basic Settings"}
        {modem  "Modem Settings"}
        {chat   "Chat Dialog Settings"}
        {ppp    "PPP Settings"}
        {ip     "IP Settings"}
        {route  "Route Settings"}
}

#
#       Unpack the current selection frame; pack a new one
#
proc newframe {w opt} {
        # unpack the old frame (find it by name "f_xyz")
        foreach slave [pack slaves $w] {
                if {[string first "f_" $slave] > 0} {
                        pack forget $slave
                }
        }
        # new text on menu button
        global frame_defs
        $w.fs configure -text $frame_defs($opt)

        # pack the new frame
        pack $w.f_$opt -side top -fill both -expand yes
        
        # select first entry (to update the help-text, if there)
        global ${opt}_entries
        set x [lindex [set ${opt}_entries] 0]
        $w.$x.e select range 0 end
        focus $w.$x.e
}

#
#       Make a service-information window
#
proc svc_window {action svc} {
        global win_id win_txt

        # make a new window
        set w .w[incr win_id]
        toplevel $w
        wm title $w [subst $win_txt($action)]
        # TODO: wm iconbitmap $w ... ?

        # make a menu button to choose among the frames
        menubutton $w.fs -indicatoron 1 -menu $w.fs.menu -anchor w \
                -text "Basic Settings" -relief raised
        menu $w.fs.menu -tearoff 0

        # make some frames to hold subsets of entries
        global frame_list frame_defs
        foreach x $frame_list {
                foreach {base label} $x { }

                # save name-label association for later
                # add menu button to the menu
                set frame_defs($base) $label
                $w.fs.menu add radiobutton -label $label -indicatoron 0 \
                        -command "newframe $w $base"

                # make the frame and fill it with its labelled entries
                global ${base}_entries
                frame $w.f_$base
                foreach x [set ${base}_entries] {
                        labent $w $x
                        pack $w.$x -in $w.f_$base -side top -fill x
                }
        }

        # make the bottom button row
        buttons $w.b left \
                [list OK        "do_svc_ok $w $action"] \
                [list Cancel    "destroy $w"]

        # put the frame-selector on top, button-row on bottom, frame inbetween
        pack $w.fs      -side top       -fill x
        pack $w.b       -side bottom    -fill x
        pack $w.f_basic -side top       -fill both -expand yes

        # make the 'OK' button look special on the delete-window
        if {$action == "delete"} {
                $w.b.b1 configure -activeforeground Red -fg Red -text Delete
        }

        # for editable windows, add a prompt-line for help messages
        if {$action == "add" || $action == "edit"} {
                label $w.help -text "" -fg Black -relief sunken -padx 1m \
                        -font Helvetica
                pack $w.help -side bottom -fill x
        }

        if {$action == "add"} {
                $w.service.e insert 0 service-name
                $w.service.e select range 0 end
                focus $w.service.e
        } {
                update
                if {[catch {open "|eznet list $svc" r} fid] == 0} {
                        while {[gets $fid buf] > 0} {
                                set p [string first "=" $buf]
                                incr p -1
                                set var [string trim [string range $buf 0 $p]]
                                incr p 2
                                set val [string trim [string range $buf $p end]]
                                if {[winfo exists $w.$var.e]} {
                                        $w.$var.e insert 0 $val
                                }
                                # TODO: else report missing values?
                        }
                        catch {close $fid}
                }
        }

        # make the 'show' and 'delete' windows read-only
        if {$action == "show" || $action == "delete"} {
                global all_entries
                foreach x $all_entries {
                        $w.$x.e configure -state disabled
                }
                if {$action == "show"} {
                        bind $w <Return> [list $w.b.b1 invoke]
                }
        }
        return
}

#
#       Put messages or errors in the message line
#
proc err {msg} {
        .msg configure -text "Error! $msg"
        return
}

proc msg {msg} {
        .msg configure -text $msg
        return
}

# proc ask_yn {msg} {
#       return [tk_messageBox -title Question -message $msg \
#               -icon question -type yesno]
# }

###
### Some GUI-creation helper routines...
###

#
#       Make a menubar with menus
#
proc menu_bar {w args} {
        if {[winfo exists $w] == 0} {
                frame $w -relief raised -borderwidth 2
        }
        set mnum 0              ;# menu counter
        set inum 0              ;# item counter

        foreach arg $args {
                foreach {type name var val v2} $arg { }
                if {$inum == 0 && [string match "menu*" $type] == 0} {
                        err "menu_bar: need menu to hold $type/$name menu-item"
                        return
                }
                switch $type {
                menu {
                        set mbase $w.m[incr mnum]
                        set new $mbase.menu
                        menubutton $mbase -text $name -menu $new
                        menu $new -disabledforeground DarkBlue
                        set inum 1
                }
                sep {
                        $new add sep
                }
                label {
                        $new add command -label $name -state disabled
                        incr inum       
                }
                cmd {
                        $new add command -label $name -command $var
                        incr inum
                }
                radio {
                        global $var
                        $new add radiobutton -label $name \
                                -variable $var -value $val
                        incr inum
                }
                check {
                        global $var
                        $new add checkbutton -label $name \
                                -variable $var -offvalue $val -onvalue $v2
                        incr inum
                }
                end {
                        # pack this menu on the given side
                        pack $mbase -side $name -padx 1m
                        set inum 0
                }
                default {
                        err "menu_bar: unknown type '$type' for '$name'"
                        return
                }
                }
        }
        return
}

#
#       Make a button row from names and commands
#
proc buttons {w dir args} {
        if {[winfo exists $w] == 0} {
                frame $w
        }
        set i 0
        set btns ""
        foreach arg $args {
                foreach {name cmd} $arg { }
                set btn $w.b[incr i]
                append btns " $btn"
                button $btn -text $name -command $cmd -width 4
        }
        eval pack $btns -side $dir -fill x -expand yes
        return
}

#
#       Make a labeled entry
#
proc labent {w x} {
        if {[winfo exists $w.$x] == 0} {
                frame $w.$x
        }
        label $w.$x.l -text $x -anchor e -width 10 -padx 3m
        entry $w.$x.e -width 25
        pack  $w.$x.l -side left -fill x
        pack  $w.$x.e -side left -fill x -expand yes
        return
}

#
#       Make a listbox/scrollbar pair, connect them
#
proc list_box {w title action} {
        if {[winfo exists $w] == 0} {
                frame $w
        }
        label $w.hdr -text $title -font fixed -anchor w
        scrollbar $w.sb -command "$w.list yview"
        listbox $w.list -yscroll "$w.sb set" \
                -font fixed -setgrid 1 -height 5

        pack $w.hdr  -side top   -fill x
        pack $w.sb   -side right -fill y
        pack $w.list -side left  -fill both -expand yes
        if {$action != ""} {
                bind $w.list    <Double-1>      $action
                bind $w.list    <Return>        $action
        }
        bind $w.list    <Control-p>     {tkListboxUpDown %W -1}
        bind $w.list    <Control-n>     {tkListboxUpDown %W  1}
        bind $w.list    <k>             {tkListboxUpDown %W -1}
        bind $w.list    <j>             {tkListboxUpDown %W  1}
        return
}

#
#       Make a text-box/scrollbar pair, connect them
#
proc text_box {w {title ""}} {
        if {[winfo exists $w] == 0} {
                frame $w
        }
        if {$title != ""} {
                label $w.hdr -text $title -font fixed
                pack $w.hdr -side top -fill x
        }
        scrollbar $w.sb -command "$w.text yview"
        text $w.text -yscroll "$w.sb set" -setgrid 1
#               -font fixed 

        pack $w.sb -side right -fill y
        pack $w.text -side left -fill both -expand yes

        $w.text insert end "Please wait..."
        return
}

#
#       Given some text, fill a text-box
#
proc fill_textbox {tb ro txt} {
        set t $tb.text
        $t configure -state normal
        $t delete 1.0 end
        $t mark set insert 0.0
        $t insert end $txt

        if {$ro == "ro"} {
                $t configure -state disabled
                $t mark set curs 0.0
        } {
                $t mark set insert 0.0
        }
        return
}

#
#       Make a simple text window
#
proc simple_text {w title} {
        if {[winfo exists $w] == 0} {
                toplevel $w
                # TODO: wm iconbitmap .   ...
                if {$title != ""} {
                        wm title $w $title
                }
                text_box $w.tbox
                buttons $w.b left [list Done "destroy $w"]
                pack $w.tbox -side top -fill both -expand yes
                pack $w.b -side bottom -fill x
                bind $w <Return> "destroy $w"
                focus $w.tbox.text
        } {
                wm deiconify $w
        }
        return
}

###
### End of GUI helpers
###

#
#       Save our current settings
#
proc save_settings { } {
        global rc_file
        if {[catch {open $rc_file w} fid] == 0} {
                puts $fid "wm geometry . [wm geometry .]"
                set sel [lindex [.lbox.list curselection] 0]
                if {$sel != ""} {
                        puts $fid "set lbsel $sel"
                } {
                        puts $fid "set lbsel -1"
                }
                catch {close $fid}
        }
}

#
#       Are we 'root'?
#
proc as_root { } {
        global env
        set rt 0
        if {[info exists env(TESTING_XEZNET)]} {
                set rt 1
        } elseif {[catch {open "|id" r} fid] == 0} {
                set id [read $fid]
                catch {close $fid}
                if {[string first "(root)" $id] > 0} {
                        set rt 1
                }
        }
        return $rt
}

#
#       Make a new window, fill it with 'eznet log'
#
proc see_log { } {
        simple_text .w_log "Log file from eznet"
        update
        if {[catch {open "|eznet log" r} fid] == 0} {
                set txt [read $fid]
                catch {close $fid}
        } {
                set txt "Problems running 'eznet log'!"
        }
        fill_textbox .w_log.tbox ro $txt
        return
}

set eznet_icon {
#define eznet_width 32
#define eznet_height 32
static unsigned char eznet_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0xfe, 0x1f, 0xf8, 0x3f, 0xfe, 0x1f,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c,
   0x18, 0x00, 0x00, 0x0e, 0x18, 0x00, 0x00, 0x07, 0x18, 0x00, 0x80, 0x03,
   0x18, 0x1f, 0xc0, 0x01, 0x18, 0x1f, 0xe0, 0x00, 0x18, 0x00, 0x70, 0x00,
   0x18, 0x00, 0x38, 0x00, 0x18, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0e, 0x00,
   0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, 0xfe, 0x3f,
   0xf8, 0x3f, 0xfe, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
}

#
#       Work-around a Tcl/Tk "feature":
#       - bitmaps work OK on buttons, but are not accepted by the 'wm' command
#
proc wm_set_icon {w icon} {
        set iconFile /tmp/.eznet_icon.[pid]
        if {[catch {open $iconFile w} fid] == 0} {
                puts $fid $icon
                close $fid
                wm iconbitmap $w @$iconFile
                catch {file delete $iconFile}
        }
        return
}

#
#       Make the main control and status panel
#
proc make_ui { } {
        global upd_rate eznet_icon
        wm title        . "EZNET Interface"
        wm iconname     . "xeznet"
        wm command      . "xeznet"
        wm_set_icon     . $eznet_icon

        menu_bar .mbar                                          \
                {menu   File}                                   \
                {cmd    "See log..."    see_log}                \
                {cmd    Save            save_settings}          \
                {sep}                                           \
                {cmd    Quit            exit}                   \
                {end    left}                                   \
                \
                {menu   Update}                                 \
                {cmd    Now             svc_status}             \
                {radio  "10 sec"        upd_rate        10}     \
                {radio  "30 sec"        upd_rate        30}     \
                {radio  " 1 min"        upd_rate        60}     \
                {radio  " 5 min"        upd_rate        300}    \
                {end    left}                                   \
                \
                {menu   Help}                                   \
                {cmd    About           do_about}               \
                {cmd    Usage           do_use}                 \
                {end    right}

        list_box .lbox " Service     Status" {do_sel show}
        bind . <Any-Enter> {focus .lbox.list}

        buttons .cmd left                       \
                {Show   {do_sel show}}          \
                {Edit   {do_sel edit}}          \
                {Add    {do_sel add}}           \
                {Delete {do_sel delete}}        \
                {Up     {do_updn up}}           \
                {Down   {do_updn down}}         \
                {Quit   exit}

        # if not root, pull off Edit/Add/Delete buttons
        if {[as_root] == 0} {
                destroy .cmd.b2
                destroy .cmd.b3
                destroy .cmd.b4
        }

        label .msg -text ""

        pack .mbar      -side top       -fill x 
        pack .lbox      -side top       -fill both      -expand yes
        pack .cmd .msg  -side bottom    -fill x 
        return
}

proc do_about { } {
        simple_text .w_about "About xeznet"
        .w_about.tbox.text configure -height 9
        fill_textbox .w_about.tbox ro {
xeznet is a graphical front-end for the 'eznet' command.
This is version 1.8 made on 1999/02/19 00:55:48.

Mike Hall <mghall@enteract.com> is the author of 'xeznet'.

D. Richard Hipp" <drh@acm.org> is the author of 'eznet',
and provided essential suggestions to improve 'xeznet'.
}
        return
}

proc do_use { } {
        simple_text .w_usage "How to use 'xeznet'"
        fill_textbox .w_usage.tbox ro {
OPERATION:
----------

NOTE: The Edit, Add, and Delete buttons will appear only for the 'root' user!

To create a new service, you must be 'root': 
        Click on the Add button.
        A new window will appear; fill in the forms.
        Click on the OK button to have 'eznet' create the new service.

To edit or delete an existing service, you must be 'root':
        Select an entry, then click on the Edit (or Delete) button.
        A new window will appear, showing you the service.
        Click on the OK button to post your edits to 'eznet'
        (or click on the Delete button to delete the service).

To show the settings for a service:
        Method 1: select an entry, then click on the Show button.
        Method 2: select an entry, then press the Return key
        Method 3: double-click on an entry

        A new window will appear, showing you the service parameters.
        Click on the OK button to close the window.

To bring a service up or down:
        Select the service, then click on the Up or Down button.
        The listbox will track the progress of the connection.


SERVICE INFORMATION WINDOW:
-------------------------------
There are several sections in the service popup window:
        - Selection menu, to select groups of settings
        - List of entries, to examine or change 'eznet' settings
        - Optional setting description, to explain the meaning of a setting
        - OK and Cancel buttons, to accept the settings or cancel

One of several groups of entries can be selected from the menu:
        Basic Settings
        Modem Settings
        Chat Dialog Settings
        PPP Settings
        IP Settings
        Route Settings


MENUS:
------
File / 
        See log ...     Show the 'eznet' log file.
        Save            Save the current position, size, and service.
        Quit            Exit the 'xeznet' program.

Update /
        Now             Update the status panel immediately.
        10 sec          Update the status panel every 10 seconds.
        30 sec          ..    ..    30 seconds
         1 min          ..    ..    minute
         5 min          ..    ..    5 minutes

        Note: when bringing a service up or down,
        the status is automatically updated every 5 seconds for one minute.

Help /
        About           Pops up a small version panel.
        Usage           Brings up this text panel.
} 
        return
}

#
#       Make the interface, start status updates
#
make_ui

# no entries to highlight; read in the RC file for position/settings
set lbsel -1
if {[file exists $rc_file]} {
        source $rc_file
}

# draw the interface
update

# start the updates (fill the listbox)
svc_status 1

# highlight an entry if one was saved last time
if {$lbsel >= 0} {
        .lbox.list selection set $lbsel
        .lbox.list activate $lbsel
}
