#!/bin/sh
# \
if [ -x $HOME/.roxirc/.wish ]; then exec $HOME/.roxirc/.wish "$0" -- "$@"; else exec wish8.3 "$0" -- "$@"; fi

# Copyright (c) 1997, 1998, 1999, 2000 Aaron Faupell (roxirc@lighter.net)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#    This product includes software developed by Aaron Faupell
# 4. The name of Aaron Faupell may not be used to endorse or promote
#    products derived from this software without specific prior written
#    permission.
#
# THIS SOFTWARE IS PROVIDED BY AARON FAUPELL ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AARON FAUPELL BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

# In addition to the above, please do not remove the version reply.
# Improvments and bug fixes are welcome.
# Version 1.72 (1/29/01)
# Description: main program file

if {[info level] > 0} {
    return
}

wm withdraw .

proc SetDefaults { } {
    global prefs env info
    array set prefs {
        geom,channel 600x350
        geom,status 550x375
        geom,chat 500x300
        font,topic {fixed 10}
        font,cmdline {fixed 10}
        font,menu {fixed 10}
        font,status {fixed 10}
        font,channel {fixed 10}
        font,nicklist {fixed 10}
        font,chat {fixed 10}
        style,nicklist {}
        style,channel "-bg [.0 cget -bg] -fg black"
        style,chat "-bg [.0 cget -bg] -fg black"
        style,status "-bg [.0 cget -bg] -fg black"
        name {My config script is missing}
        away {Finding my config script}
        awayreason {auto away}
        nick {"roxirc" "roxirc_" "roxirc-"}
        chan #freebsd
        quit RoxIRC
        server irc.ef.net:EFnet
        port 6667
        notify {}
        showmotd 1
        autoaway 0
        autounaway 2
        ts 0
        history 30
        scrollback 200
        host ""
        underline 1
        bold 1
        ial 1
        margin 0
        urlcommand {}
        dccpacketsize 512
        dccfileautoclose 0
        dccchatautoclose 0
        defaultdccdir $env(HOME)
        dcchighport 65535
        dcclowport 1025
        defaultlogdir $env(HOME)
        netsplit 1
        urls 1
        maxbeeps 3
        nicklist 1
        unsafedcc 0
        dcctimeout 600
        iconifyqueries 0
    }
    array set info {
        set,history num
        set,showmotd bool
        set,autoaway num
        set,autounaway {num 0 2}
        set,awayreason string
        set,port num
        set,name string
        set,ts bool
        set,bold {bool cmd bold}
        set,underline {bool cmd underline}
        set,margin {num cmd margin}
        set,scrollback num
        set,host string
        set,urlcommand string
        set,dccpacketsize {num 256 16384}
        set,dccfileautoclose bool
        set,dccchatautoclose bool
        set,defaultdccdir string
        set,dcchighport num
        set,dcclowport num
        set,defaultlogdir string
        set,netsplit bool
        set,urls bool
        set,maxbeeps num
        set,ial {bool cmd ial}
        set,nicklist bool
        set,unsafedcc bool
        set,dcctimeout num
        set,iconifyqueries bool
    }
    set info(config) $env(HOME)/.roxirc
    set info(on) "{text 3} {action 3} {notice 3} {join 2} {part 2} {quit 2} {nick 2} {kick 3} {mode 2} {op 3} {deop 3} {voice 3} {ban 2} {unban 2} {devoice 3} {topic 2} {away 0} {unaway 0} {notify 1} {unnotify 1} {connect 0} {invite 2} {dccsendrequest 2} {dccchatrequest 1} {dccchatconnect 1} {dccchattext 2}"
}

proc SetVars { } {
    global notify ume me server away urls info bind
    set notify(on) ""
    set me -
    set ume -
    set server -
    set away 0
    set urls ""
    set info(connect) ""
    set info(send,last) [clock seconds]
    set info(send,num) 0
}

proc ParseCommandline { } {
    global argv argc
    set oargv $argv
    set num 0
    unset argv
    while {[set cur [lrange $oargv $num end]] != ""} {
        switch -glob -- [lindex $cur 0] {
            -h {
                incr num 2
                if [info exists argv(h)] {
                    puts stderr "Only one -h flag allowed"
                    continue
                }
                set argv(h) [lindex $cur 1]
            }
            -f {
                incr num 2
                if [info exists argv(f)] {
                    puts stderr "Only one -f flag allowed"
                    continue
                }
                set argv(f) [lindex $cur 1]
            }
            -* {
                puts stderr "Unknown option [lindex $cur 0]"
                incr num
            }
            default {
                if {[expr $argc - $num] > 2} {
                    puts stderr "Too many arguments"
                    return [array get argv]
                } else {
                    if [string match *.* [lindex $oargv $num]] {
                        set argv(server) [lindex $oargv $num]
                    } else {
                        set argv(nick) [lindex $oargv $num]
                    }
                }
                incr num 1
            }
        }
    }
    return [array get argv]
}

proc LoadFile {file} {
    global procs
    set ft [file tail $file]
    set ns ::scripts::$ft
    if ![catch {namespace eval $ns [list source $file]} msg] {
        namespace import -force ${ns}::*
        if {[info commands ${ns}::*] == ""} {
            namespace delete $ns
        }
        return 1
    }
    foreach proc [array names procs] {
        if {$ft == $procs($proc)} {
            catch {rename ::$proc ""}
            rename ::backup::${ft}::$proc ::$proc
            unset procs($proc)
        }
    }
    catch {namespace delete ::backup::$ft}
    namespace delete $ns
    echo on
    puts stderr "Error loading $file: $msg"
    Echo .0 "\[ error \] Error loading $file: $msg" error default
    return 0
}

proc procs {args} {
    global procs
    set file [file tail [info script]]
    foreach x $args {
        if {[info exists procs($x)] && $procs($x) != $file} {
            error "proc $x conflicts with script $procs($x)"
        }
        set procs($x) $file
        if {[info procs $x] != "" && [info command ::backup::${file}::$x] == ""} {
            namespace eval ::backup::$file {}
            rename $x ::backup::${file}::$x
        }
        namespace eval ::scripts::${file} "namespace export $x"
    }
}

proc FirstRun { } {
    global info
    set installpath /usr/local/share/doc/roxirc
    if [catch {file mkdir $info(config)} err] {
        Echo .0 "\[ error \] Could not create directory $info(config): [geterror $err]" error default
    } else {
        Echo .0 "\[ info \] Created directory $info(config)" info default
        catch {file copy $installpath/prefs-initial $info(config)/prefs}
        if [catch {file copy $installpath/config-example $info(config)/config ; file copy $installpath/menus-example $info(config)/menus ; file copy $installpath/bindings-example $info(config)/bindings} err] {
            Echo .0 "\[ info \] Could not copy example files from $installpath: [geterror $err]" info default
            Echo .0 "\[ info \] Please see http://roxirc.lighter.net/ for example files" info default
        } else {
            catch {file attributes $info(config)/config -permissions 0644}
            catch {file attributes $info(config)/menus -permissions 0644}
            catch {file attributes $info(config)/prefs -permissions 0644}
            catch {file attributes $info(config)/bindings -permissions 0644}
            Echo .0 "\[ info \] Please edit the config file $info(config)/config" info default
        }
        Echo .0 "\[ info \] See /set, /color, and /help for other settings, and http://roxirc.lighter.net/ for more information" info default
    }
}

proc SourceFiles { } {
    global prefs notify env menu ignore on info argv
    set autoload 1
    set startup 1
    SetDefaults
    WindowMenu
    if [info exists argv(f)] {
        set info(config) [abspath $argv(f)]
    } elseif ![file isdirectory $info(config)] {
        FirstRun
    }
    if [file isdirectory $info(config)] {
        set files [glob -nocomplain -types f $info(config)/*]
        foreach file [lsort $files] {
            LoadFile $file
        }
    } elseif [file isfile $info(config)] {
        LoadFile $info(config)
    } else {
        Echo .0 "\[ info \] No such file or directory: $info(config)" info default
    }
    if [info exists argv(h)] {
        set prefs(host) $argv(h)
    }
    if [info exists argv(nick)] {
        set prefs(nick) "[escape [escape $argv(nick)]] $prefs(nick)"
    }
    if [info exists argv(server)] {
        command_server .0 $argv(server)
    }
}

proc startup { } {
    upvar 3 startup start
    if !$start {
        return -code return
    }
}

proc noautoload { } {
    upvar 3 autoload auto
    if $auto {
        return -code return
    }
}

proc !xresources { } {
    option readfile [info script] userDefault
    return -code return
}

proc echo {cmd} {
    switch $cmd {
        on {
            if {[info args [namespace origin Echo]] == "off off args"} {
                set ns [namespace qualifiers [namespace origin Echooff]]
                rename [namespace origin Echo] ""
                rename ${ns}::Echooff ${ns}::Echo
                if {$ns != ""} {
                    namespace import ${ns}::Echo
                }
            }
        }
        off {
            if {[info args [namespace origin Echo]] == "window line args"} {
                set ns [namespace qualifiers [namespace origin Echo]]
                rename ${ns}::Echo ${ns}::Echooff
                if {$ns != ""} {
                    namespace eval ${ns} {namespace export Echooff}
                    namespace import ${ns}::Echooff
                }
                proc Echo {off off args} {}
            }
        }
    }
}

proc configfile {name} {
    global $name
    set data [read [set fh [open [info script] r]]]
    close $fh
    array set $name $data
    catch {unset ${name}(configfile) ${name}(#)}
    return -code return
}

proc WindowMenu { } {
    global menu
    set menu(window) {
        command "New Window" {CreateChannel ""}
        command "New RoxIRC" "global argv0 ; exec $argv0 &"
        command "Save Settings" "SaveSettings"
        separator
        menu Extras
         command "Url List" /url
         command "Notify List" NotifyWindow
        end
        menu Position
         command Remember "Position s"
         command Forget "Position f"
         command Reset "Position r"
        end
        menu Options
         checkbutton Timestamp {-variable options(ts,$i) -command "ts $i"}
         checkbutton Logging... {-variable options(log,$i) -command "LogMenu $i"}
         checkbutton Popup "-variable options(popup,$i)"
         checkbutton Nicklist {-variable options(nicklist,$i) -command "NickMenu $i"}
        end
        menu Buffer
         command Save... /savebuf
         command Clear /clear
        end
        separator
        command Hide /hide
        command Close /close
        menu Disconnect
         foreach quit $x "/disconnect $x"
        end
        menu Quit
         foreach quit $x "/quit $x"
        end
        command Exit /quit
    }
}

proc SaveSettings {args} {
    global prefs info
    set file $info(config)/prefs
    if {$args != ""} {
        set file [lindex $args 0]
    }
    if [catch {open $file w} fh] {
        Echo .0 "\[ error \] Could not open $file for writing: [geterror $fh]" error default
        return
    }
    puts $fh {configfile prefs

# "do not edit this file!"}
    foreach var [lsort [array names prefs geom,*]] {
        if {$var != "geom,status" && $var != "geom,channel" && $var != "geom,chat"} {
            puts $fh "$var \{$prefs($var)\}"
        }
    }
    foreach x [array names info set,*] {
        set x [lindex [split $x ,] 1]
        puts $fh "$x \{$prefs($x)\}"
    }
    foreach x [array names prefs font,*] {
        puts $fh "$x \{$prefs($x)\}"
    }
    foreach x "notify color,* options,*" {
        foreach var [array names prefs $x] {
            puts $fh "$var \{$prefs($var)\}"
        }
    }
    close $fh
    Echo .0 "\[ info \] Settings saved to $file" info default
}

proc AddToPrefs {var type default} {
    global prefs info
    if ![info exists prefs($var)] {
        set prefs($var) $default
    }
    if ![info exists info(set,$var)] {
        set info(set,$var) $type
    } elseif {$info(set,$var) != $type} {
        error "Cannot redefine preferences: $var"
    }
}

proc RemoveFromPrefs {var} {
    global prefs info
    catch {unset info(set,$var)}
    catch {unset prefs($var)}
}

proc Position {cmd} {
    global prefs info options
    upvar window window
    set win ""
    if [info exists info(channel,$window)] {
        set win $info(channel,$window)
    } elseif [info exists dcc([string trimleft $window .],nick)] {
        set win =[string tolower $dcc([string trimleft $window .],nick)]
    } elseif [info exists info(query,$window)] {
        set win $info(query,$nick)
    }
    if {$cmd != "r" && $win == ""} {
        return
    }
    switch -exact $cmd {
        s {
            if [info exists info(window,$win)] {
                set prefs(geom,$win) [list [winfo geometry $window] [winfo geometry $window.n] $options(nicklist,$window) [wm state $window.n]]
            } else {
                set prefs(geom,$win) [winfo geometry $window]
            }
        }
        f {
            catch {unset prefs(geom,$win)}
            catch {unset prefs(options,$win)}
        }
        r {
            if ![info exists prefs(geom,$win)] {
                set win channel
                if [string match .\[cd\]* $window] {
                    set win chat
                } elseif {$window == ".0"} { 
                    set win status
                }
            }
            wm geometry $window [lindex $prefs(geom,$win) 0]
            if [info exists info(window,$win)] {
                set n [lindex $prefs(geom,$win) 2]
                set s [lindex $prefs(geom,$win) 3]
                set g [lindex $prefs(geom,$win) 1]
                catch {$info(window,$win).top.user configure -state normal}
                if {$g != "1x1+0+0"} {
                    wm geometry $info(window,$win).n $g
                }
                if {$s == "withdrawn"} {
                    wm withdraw $info(window,$win).n
                } elseif {$s == "iconic"} {
                    wm iconify $info(window,$win).n
                } else {
                    wm deiconify $info(window,$win).n
                }
                if {$n && $s == "withdrawn"} {
                    reattachnick $info(window,$win)
                } else {
                    pack forget $info(window,$win).middle.right
                    if !$n {
                        catch {$info(window,$win).top.user configure -state disabled}
                    }
                }
            }
        }
    }
}

proc command_admin {window line} {
    Send "ADMIN $line"
}

proc command_alias {window line} {
    set line [split $line]
    set name [string tolower [lindex $line 0]]
    if {[info commands command_$name] != "" && [lindex [split [info body command_$name] "\n"] 2] != "#alias"} {
        Echo $window "\[ error \] Cannot add alias $name: command exists" error default
        return
    }
    if {[llength $line] < 2} {
        return
    }
    proc command_$name {window line} {
#alias
        global prefs away me server info names style
        set channel ""
        set nicks ""
        set nick ""
        if [info exists info(channel,$window)] {
            set channel $info(channel,$window)
        }
        if [info exists info(nick,$window)] {
            set nick $info(nick,$window)
        }
        if [winfo exists $window.middle.right.nicks] {
            set win $window.middle.right.nicks
            if {[wm state $window.n] != "withdrawn"} {
                set win $window.n.nicks
            }
            foreach x [$win curselection] {
                lappend nicks [string trimleft [string trimleft [$win get $x] @] +]
            }
            set nick [lindex $nicks 0]
        }
    }
    eval "proc command_$name {window line} {
        [info body command_$name]
        if \[catch {
            [escape2 [join [lrange $line 1 end]]]
        } err\] {
            Echo \$window \"\\\[ error \\\] Error while executing alias $name: \[geterror \$err\]\" error default
        }
    }"
    Echo $window "+alias $name: [join [lrange $line 1 end]]" none
}

proc command_ame {window line} {
    foreach x [GetActiveChannelWindows] {
        command_me $x $line
    }
}

proc command_amsg {window line} {
    global info me
    foreach x [GetActiveChannelWindows] {
        command_msg $window "$info(channel,$x) $line"
    }
}

proc command_away {window line} {
    global autoaway
    catch {unset autoaway}
    Send "AWAY :$line"
}

proc rspaces {list} {
    while {[set pos [lsearch $list ""]] > -1} {
        set list [lreplace $list $pos $pos]
    }
    return $list
}

proc command_ban {window line} {
    global info userhost
    set line [rspaces [split $line]]
    set chan [lindex $line 0]
    if {[info exists info(channel,$window)] && [string index $chan 0] != "#" && [string index $chan 0] != "&"} {
        set chan $info(channel,$window)
        set nick [lindex $line 0]
        set mask [lindex $line 1]
    } else {
        set nick [lindex $line 1]
        set mask [lindex $line 2]
    }
    if {$mask == ""} {
        set mask 3
    }
    if {[set address [address $nick $mask]] != ""} {
        Send "MODE $chan +b $address"
    } else {
        Echo $window "\[ info \] Getting users address..." info default
        set userhost([string tolower $nick]) "Send \"MODE $chan +b \[AddressMask \"\$address\" \"$mask\"\]\""
        Send "USERHOST $nick"
    }
}

proc command_beep {window line} {
    set line [rspaces [split $line]]
    if {$line == ""} {
        bell
        return
    }
    set delay 0
    set times 1
    set delay [lindex $line 1]
    set times [lindex $line 0]
    if {[IsNum $delay] && [IsNum $times]} {
        if {$times > 1} {
            after $delay [list command_beep $window "[expr $times - 1] $delay"]
        }
        bell
    }
}

proc command_bind {window line} {
    if {[string trim $line] == ""} {
        foreach x [lsort [bind cmdline]] {
            if {[lindex [split [bind cmdline $x]] 0] == "DoBinding"} {
                Echo $window "binding $x: [lindex [bind cmdline $x] 2]" none
            }
        }
        return
    }
    set line [split $line]
    set keysym [lindex $line 0]
    set command [join [lrange $line 1 end]]
    set keysym [replace $keysym ^ Control-]
    if {$command == ""} {
        set tmp ""
        if {[lindex [bind cmdline <$keysym>] 0] == "DoBinding"} {
            set tmp [lindex [bind cmdline <$keysym>] 2]
        }
        Echo $window "binding <$keysym>: $tmp" none
        return
    }
    if {[bind cmdline <$keysym>] != "" && [lindex [bind cmdline <$keysym>] 0] != "DoBinding"} {
        Echo $window "\[ error \] Error creating binding $keysym: cannot replace builtin binding" error default
        return
    }
    if {$command == "\"\""} {
        bind cmdline <$keysym> ""
        Echo $window "-binding <$keysym>" none
        return
    }
    if [catch {bind cmdline <$keysym> [list DoBinding %W $command]} err] {
        Echo $window "\[ error \] Error creating binding $keysym: [geterror $err]" error default
        return
    }
    Echo $window "+binding <$keysym>: $command" none
}

proc DoBinding {window command} {
    global away server me info prefs
    set window [winfo toplevel $window]
    switch -exact [string index $window 1] {
        c {
            if ![info exists info(nick,$window)] {
                return
            }
            set nick $info(nick,$window)
        }
        d {
            set nick $dcc(nick,$window)
        }
        0 {
        }
        default {
            set nicks ""
            if ![info exists info(channel,$window)] {
                set channel ""
            } else {
                set win $window.middle.right.nicks
                if {[wm state $window.n] != "withdrawn"} {
                    set win $window.n.nicks
                }
                foreach x [$win curselection] {
                    lappend nicks [string trimleft [string trimleft [$win get $x] @] +]
                }
                set nick [lindex $nicks 0]
                set channel $info(channel,$window)
            }
        }
    }
    if [catch {eval $command} err] {
        Echo $window "\[ error \] Error in binding: [geterror $err] while executing $command" error default
    }
}

proc command_bk {window line} {
    global info
    set line [rspaces [split $line]]
    set chan [lindex $line 0]
    if {[info exists info(channel,$window)] && [string index [lindex $line 0] 0] != "#" && [string index [lindex $line 0] 0] != "&"} {
        set chan $info(channel,$window)
        set nick [lindex $line 0]
    } else {
        set chan [lindex $line 0]
        set nick [lindex $line 1]
    }
    if {[info exists info(window,$chan)] && [IsOn $chan $nick]} {
        KickWindow $info(window,$chan) $nick
    }
}

proc command_clear {window line} {
    global info
    set line [rspaces [split $line]]
    if {[set tmp [GetWindow [lindex $line 0]]] != ""} {
        set window $tmp
        set line [lrange $line 1 end]
    } elseif {[string tolower [lindex $line 0]] == "all"} {
        set line [join [lrange $line 1 end]]
        foreach x [GetAllTextWindows] {
            command_clear $x $line
        }
        return
    }
    $info(text,$window) configure -state normal
    if {$line == ""} {
        $info(text,$window) delete 0.0 end
    } elseif [IsNum $line] {
        $info(text,$window) delete end-${line}l end
    } elseif [IsNum [trim $line -]] {
        $info(text,$window) delete 0.0 0.0+[trim $line -]l
    }
    $info(text,$window) configure -state disabled
}

proc command_close {window line} {
    set line [rspaces [split $line]]
    set close ""
    if {$line == ""} {
        set close $window
    } elseif {[set tmp [GetWindow [lindex $line 0]]] != ""} {
        set close $tmp
    } else {
        set line [join $line]
        foreach x [winfo children .] {
            if {$x != ".0" && [match *$line* [wm title $x]]} {
                set close $x
                break
            }
        }
    }
    if {$close == "" || $close == ".0"} {
        return
    }
    if {[wm protocol $close WM_DELETE_WINDOW] != ""} {
        eval [wm protocol $close WM_DELETE_WINDOW]
    } else {
        destroy $close
    }
}

proc command_color {window line} {
    global info prefs
    if {$line == ""} {
        return
    }
    set line [rspaces [split $line]]
    set tag [string tolower [lindex $line 0]]
    if {[lindex $line 1] == "\"\""} {
        set color ""
    } elseif {[lindex $line 1] == ""} {
        if [info exists prefs(color,$tag)] {
            set color [tk_chooseColor -initialcolor $prefs(color,$tag) -title "RoxIRC $tag color"]
        } elseif [info exists prefs(color,default)] {
            set color [tk_chooseColor -initialcolor $prefs(color,default) -title "RoxIRC $tag color"]
        } else {
            set color [tk_chooseColor -initialcolor [$info(text,$window) cget -foreground] -title "RoxIRC $tag color"]
        }
        if {$color == ""} {
            return
        }
    } else {
        set color [lindex $line 1]
    }
    if {$color == ""} {
        catch {unset prefs(color,$tag)}
        foreach x [GetAllTextWindows] {
            $info(text,$x) tag configure $tag -foreground ""
        }
        return
    }
    if [catch {.0.middle.text tag configure $tag -foreground $color} err] {
        Echo $window "\[ error \] Error configuring color: [geterror $err]" error default
        return
    }
    foreach x [GetAllTextWindows] {
        $info(text,$x) tag configure $tag -foreground $color
        set prefs(color,$tag) $color
    }
}

proc command_ctcp {window line} {
    if {$line != ""} {
        set line [split $line]
        if {[string tolower [lindex $line 1]] == "ping" && [lindex $line 2] == ""} {
            command_ping $window [lindex $line 0]
            return
        }
        Echo .0 "\[ ctcp \] -> [lindex $line 0] [string toupper [lindex $line 1]] [join [lrange $line 2 end]]" ctcp default
        Send "PRIVMSG [lindex $line 0] :\001[string toupper [lindex $line 1]] [join [lrange $line 2 end]]\001"
    }
}

proc abspath {file} {
    if {[file pathtype $file] != "absolute"} {
        set file [pwd]/$file
    }
    return $file
}

proc command_dcc {window line} {
    global irc info dcc prefs env
    set line [rspaces [split $line]]
    switch -- [string tolower [lindex $line 0]] {
        chat {
            set id c[lindex [split [expr abs([clock clicks] * rand())] .] 1]
            if {[set nick [lindex $line 1]] == ""} {
                return
            }
            set tmp [split [lindex [fconfigure $irc -sockname] 0] .]
            set ip [format %u 0x[format %02X%02X%02X%02X [lindex $tmp 0] [lindex $tmp 1] [lindex $tmp 2] [lindex $tmp 3]]]
            
            if {[set tmp [GetId c "nick $nick"]] != ""} {
                if [info exists dcc($tmp,pending)] {
                    Send "PRIVMSG $dcc($tmp,nick) :\001DCC CHAT chat $ip $dcc($tmp,port)\001"
                    Echo .0 "\[ dcc \] Sent DCC Chat request to $dcc($tmp,nick)" dcc default
                    Echo .$tmp "\[ dcc \] Sent DCC Chat request to $dcc($tmp,nick)" dcc default
                    wm deiconify .$tmp
                    raise .$tmp
                } else {
                    
                }
                return
            }
            
            set dcc($id,nick) $nick
            set dcc($id,port) [expr round(rand() * ($prefs(dcchighport) - $prefs(dcclowport)) + $prefs(dcclowport))]
            CreateDccChat $id
            if [catch {socket -server [list AcceptDccChat $id] -myaddr $ip $dcc($id,port)} sock] {
                Echo .$id "\[ error \] Could not create listening socket: [geterror $sock]" error default
                ClearDcc $id
                return
            }
            set dcc($id,pending) ""
            set dcc($id,sock) $sock
            Send "PRIVMSG $dcc($id,nick) :\001DCC CHAT chat $ip $dcc($id,port)\001"
            after [expr $prefs(dcctimeout) * 1000] [list DeadDccChat $id]
            Echo .0 "\[ dcc \] Sent DCC Chat request to $dcc($id,nick)" dcc default
            Echo .$id "\[ dcc \] Sent DCC Chat request to $dcc($id,nick)" dcc default
        }
        send {
            if {[set nick [lindex $line 1]] == ""} {
                return
            }
            if {[lindex $line 2] == ""} {
                set fn [tk_getOpenFile -initialdir $env(HOME) -title "RoxIRC Choose File"]
            } else {
                set fn [lindex $line 2]
            }
            if {$fn == ""} {
                return
            }
            set fn [abspath $fn]
            if [catch {open $fn r} fh] {
                Echo $window "\[ error \] Cannot open $fn for reading: [geterror $fh]" error default
                return
            }
            fconfigure $fh -translation binary
            set id f[lindex [split [expr abs([clock clicks] * rand())] .] 1]
            set dcc($id,fh) $fh
            set dcc($id,file) $fn
            set dcc($id,nick) $nick
            set dcc($id,size) [file size $dcc($id,file)]
            set tmp [split [lindex [fconfigure $irc -sockname] 0] .]
            set ip [format %u 0x[format %02X%02X%02X%02X [lindex $tmp 0] [lindex $tmp 1] [lindex $tmp 2] [lindex $tmp 3]]]
            set dcc($id,port) [expr round(rand() * ($prefs(dcchighport) - $prefs(dcclowport)) + $prefs(dcclowport))]
            CreateDccFile send $id
            if [catch {socket -server [list AcceptDccSend $id] -myaddr $ip $dcc($id,port)} sock] {
                Echo $window "\[ error \] Could not create listening socket: [geterror $sock]" error default
                close $dcc($id,fh)
                ClearDcc $id
                return
            }
            set dcc($id,sock) $sock
            set dcc($id,pending) ""
            Send "PRIVMSG $dcc($id,nick) :\001DCC SEND [file tail $dcc($id,file)] $ip $dcc($id,port) $dcc($id,size)\001"
            after [expr $prefs(dcctimeout) * 1000] [list DeadDccSend $id]
            Echo .0 "\[ dcc \] Sent DCC Send request to $dcc($id,nick): $dcc($id,file) ([kb $dcc($id,size)])" dcc default
        }
        accept {
            if {[set nick [lindex $line 1]] == ""} {
                return
            }
            if {[set id [GetId c "nick $nick"]] != ""} {
                if $prefs(unsafedcc) {
                    set host $dcc($id,ip)
                } else {
                    set host [lindex [split $dcc($id,who) @] 1]
                }
                if [info exists dcc($id,sock)] {
                    wm deiconify .$id
                    raise .$id
                    return
                }
                if [catch {socket -async $host $dcc($id,port)} sock] {
                    Echo $window "\[ dcc \] Could not connect to $host: [geterror $sock]" dcc default
                    return
                }
                if [winfo exists .dialog$id] {
                    destroy .dialog$id
                }
                if ![winfo exists .$id] {
                    CreateDccChat $id
                } elseif {$window != ".$id"} {
                    wm deiconify .$id
                    raise .$id
                }
                fconfigure $sock -blocking 0 -buffering none -translation lf
                fileevent $sock readable [list DccChat $id]
                fileevent $sock writable [list DccChatConnect $id]
                set dcc($id,sock) $sock
            } else {
                Echo $window "\[ dcc \] No chats from $nick found" dcc default
            }
        }
        get {
            set nick [lindex $line 1]
            set file [lindex $line 2]
            if {$nick == "" || $file == ""} {
                return
            }
            if {[lindex $line 3] != ""} {
                set fn [lindex $line 3]
            } elseif {[file dirname $file] == "."} {
                set fn $prefs(defaultdccdir)/$file
            } else {
                set fn $file
            }
            set fn [abspath $fn]
            if {[set id [GetId f "nick $nick" "file $file"]] != ""} {
                set id [lindex $id 0]
                if [info exists dcc($id,sock)] {
                    wm deiconify .$id
                    raise .$id
                    return
                }
                if [catch {open $fn w} fh] {
                    Echo $window "\[ error \] Cannot open $fn for writing: [geterror $fh]" error default
                    return
                }
                if $prefs(unsafedcc) {
                    set host $dcc($id,ip)
                } else {
                    set host [lindex [split $dcc($id,who) @] 1]
                }
                if [catch {socket -async $host $dcc($id,port)} sock] {
                    Echo $window "\[ dcc \] Could not connect to $host: [geterror $sock]" dcc default
                    return
                }
                if [winfo exists .dialog$id] {
                    destroy .dialog$id
                }
                set dcc($id,fh) $fh
                set dcc($id,sock) $sock
                if ![winfo exists .$id] {
                    CreateDccFile get $id
                } elseif {$window != ".$id"} {
                    wm deiconify .$id
                    raise .$id
                }
                .$id.top.1.size configure -text "size: [kb $dcc($id,size)]"
                fconfigure $sock -blocking 0 -buffering none -translation binary
                fileevent $sock readable [list DccFileGet $id]
                fileevent $sock writable [list DccFileConnect $id]
            } else {
                Echo $window "\[ dcc \] No send of $file from $nick found" dcc default
            }
        }
        close {
            switch -- [string tolower [lindex $line 1]] {
                send {
                    set nick [lindex $line 2]
                    if {$nick == ""} {
                        return
                    }
                    set file [lindex $line 3]
                    if {$file == ""} {
                        set file <any>
                        set found [GetId f "nick $nick"]
                    } else {
                        set found [GetId f "nick $nick" "file $file"]
                    }
                    if {$found == ""} {
                        Echo $window "\[ dcc \] No DCC sends of $file to $nick found" dcc default
                        return
                    }
                    foreach x $found {
                        Echo .0 "\[ dcc \] Send of $dcc($x,file) to $dcc($x,nick) closed ($dcc($x,scale)%)" dcc default
                        eval [wm protocol .$x WM_DELETE_WINDOW]
                    }
                }
                chat {
                    if {[set tmp [GetId c "nick [lindex $line 2]"]] != ""} {
                        foreach id $tmp {
                            catch {close $dcc($id,sock)}
                            bind .$id <<command>> {Echo %W "\[ info \] This dcc is not connected"}
                            set title [lindex [split [wm title .$id]] 3]
                            if [DccChatAutoClose $id] {
                                Echo .0 "\[ dcc \] Chat connection with $title closed" dcc default
                            } else {
                                Echo .$id "\[ dcc \] Chat connection closed" dcc default
                            }
                        }
                    } else {
                        Echo $window "\[ dcc \] No chat connections with [lindex $line 2] found" dcc default
                    }
                }
                get {
                    set nick [lindex $line 2]
                    if {$nick == ""} {
                        return
                    }
                    set file [lindex $line 3]
                    if {$file == ""} {
                        set file <any>
                        set found [GetId f "nick $nick"]
                    } else {
                        set found [GetId f "nick $nick" "file $file"]
                    }
                    if {$found == ""} {
                        Echo $window "\[ dcc \] No DCC gets of $file from $nick found" dcc default
                        return
                    }
                    foreach x $found {
                        Echo .0 "\[ dcc \] Get of $dcc($x,file) from $dcc($x,nick) closed ($dcc($x,scale)%)" dcc default
                        eval [wm protocol .$x WM_DELETE_WINDOW]
                    }
                }
                default {
                    Echo $window "\[ dcc \] Unknown dcc type \"[lindex $line 1]\"" dcc default
                }
            }
        }
        resume {
            set nick [lindex $line 1]
            set fn [lindex $line 2]
            if {$nick == "" || $fn == ""} {
                return
            }
            if {[set id [GetId f "nick $nick" "file $fn"]] != ""} {
                set fn $prefs(defaultdccdir)/$fn
                set fn [abspath $fn]
                if [catch {open $fn a} fh] {
                    Echo .0 "\[ error \] Cannot open $fn for writing: [geterror $fh]" error default
                    return
                }
                set dcc($id,fh) $fh
                if [winfo exists .dialog$id] {
                    destroy .dialog$id
                }
                if ![winfo exists .$id] {
                    CreateDccFile get $id
                } elseif {$window != ".$id"} {
                    wm deiconify .$id
                    raise .$id
                }
                .$id.top.1.size configure -text "size: [kb $dcc($id,size)]"
                .$id.bottom.status configure -text "Requesting resume"
                Send "PRIVMSG $dcc($id,nick) :\001DCC RESUME $dcc($id,file) $dcc($id,port) [file size $fn]\001"
            } else {
                Echo $window "\[ dcc \] No send of $fn from $nick found" dcc default
            }
        }
        "" {
            lappend tmp "Nick IP Type File % Rate"
            foreach x [array names dcc *,nick] {
                set id [lindex [split $x ,] 0]
                if {[string index $id 0] == "c"} {
                    if {[info exists dcc($id,ip)] && [info exists dcc($id,sock)]} {
                        set i $dcc($id,ip)
                    } else {
                        set i ?.?.?.?
                    }
                    lappend tmp [list $dcc($id,nick) $i Chat - - -]
                } elseif {[string index $id 0] == "f"} {
                    set title [split [wm title .$id]]
                    if {[info exists dcc($id,ip)] && [info exists dcc($id,sock)]} {
                        set i $dcc($id,ip)
                    } else {
                        set i ?.?.?.?
                    }
                    lappend tmp [list $dcc($id,nick) $i [lindex $title 2] [file tail $dcc($id,file)] [expr int($dcc($id,scale))] 0]% [lindex [.$id.top.2.kbps cget -text] 1]kbps]
                }
            }
            foreach x $tmp {
                Echo $window "\[ dcc \] [format "%-11s %-15s %-4s %-20s %-4s %s" [lindex $x 0] [lindex $x 1] [lindex $x 2] [lindex $x 3] [lindex $x 4] [lindex $x 5]]" dcc default
            }
        }
        default {
            Echo $window "\[ dcc \] Unknown dcc type \"[lindex $line 0]\"" dcc default
        }
    }
}

proc multiline {arg line} {
    if {[string first "\n" $line] != "-1"} {
        set cmd [info level -1]
        set win [lindex $cmd 1]
        set cmd [lindex $cmd 0]
        if {$arg != ""} {
            set arg "$arg "
        }
        foreach x [rspaces [split $line "\n"]] {
            $cmd $win "$arg$x"
        }
        return -code return
    }
}

proc DccChatExists {nick} {
    global dcc
    set id [GetId c "nick $nick"]
    if {![info exists dcc($id,pending)] && [info exists dcc($id,sock)]} {
        return $id
    }
    return ""
}

proc command_describe {window line} {
    global ume info dcc
    set line [split $line " "]
    set to [lindex $line 0]
    set line [join [lrange $line 1 end]]
    multiline $to $line
    set to2 [string tolower $to]
    if [info exists info(window,$to2)] {
        Echo $info(window,$to2) "* $ume $line" me action
    } elseif {[string index $to2 0] == "=" && [set tmp [DccChatExists [string range $to2 1 end]]] != ""} {
        DccSend .$tmp "\001ACTION $line\001"
        Echo .$tmp "* $ume $line" action me
        return
    } elseif [info exists info(query,$to2)] {
        Echo $info(query,$to2) "* $ume $line" me action
        wm deiconify $info(query,$to2)
        raise $info(query,$to2)
    } else {
        Echo .0 "-> **$to $line" me action
    }
    Send "PRIVMSG $to :\001ACTION $line\001"
}

proc command_disconnect {window line} {
    global irc info server me ume away
    if {$line == ""} {
        set line "<insert witty, contrived message here>"
    }
    Send "QUIT :$line"
    if [info exists irc] {
        catch {close $irc}
        unset irc
        after cancel autoaway
        Echo all "\[ server \] Disconnected" default server
        if [info exists info(time,server)] {
            Echo .0 "\[ info \] Connected to server for: [dur [expr [clock seconds] - $info(time,server)]]" info default
            unset info(time,server)
        }
        Echo all "\[ server \] You are not connected to a server" default server
    }
    foreach x [after info] {
        if {[lindex [lindex [after info $x] 0] 0] == "command_server"} {
            after cancel $x
        }
    }
    foreach x [GetActiveChannelWindows] {
        DeleteChannel $info(channel,$x) $x
    }
    set server -
    set me -
    set ume -
    set away 0
    .0.top.modes configure -text -
    UpdateAllTitles
}

proc command_echo {window line} {
    set line2 [split [string trimleft $line]]
    if {[set tmp [GetWindow [lindex $line2 0]]] != ""} {
        set window $tmp
        set line [join [lrange $line2 1 end]]
    } elseif {[string tolower [lindex $line2 0]] == "all"} {
        set window all
        set line [join [lrange $line2 1 end]]
    } elseif {[string tolower [lindex $line2 0]] == "current"} {
        set window [current]
        set line [join [lrange $line2 1 end]]
    }
    Echo $window $line
}

proc command_exec {window line} {
    global info ume errorInfo
    set line [split $line]
    switch -exact -- [lindex $line 0] {
        -o {
            set line [join [lrange $line 1 end]]
            if [catch {open |$line} exec] {
                Echo $window "\[ error \] Error executing command: [geterror $exec]"
                return
            }
            if [info exists info(channel,$window)] {
                fileevent $exec readable [list execcallback $exec $window "Echo $window \"<$ume> \$line\" me ; Send \"PRIVMSG $info(channel,$window) :\$line\""]
            } elseif [info exists info(query,$window)] {
                fileevent $exec readable [list execcallback $exec $window "Echo $window \"<$ume> \$line\" me ; Send \"PRIVMSG $info(query,$window) :\$line\""]
            } else {
                fileevent $exec readable [list execcallback $exec $window "Echo $window \$line none"]
            }
        }
        -m {
            set nick [lindex $line 1]
            set line [join [lrange $line 2 end]]
            if [catch {open |$line} exec] {
                Echo $window "\[ error \] Error executing command: [geterror $exec]"
                return
            }
            fileevent $exec readable [list execcallback $exec $window "Echo .0 \"-> *$nick* \$line\" me ; Send \"PRIVMSG $nick :\$line\""]
        }
        default {
            set line [join $line]
            if [catch {open |$line} exec] {
                Echo $window "\[ error \] Error executing command: [geterror $exec]"
                return
            }
            fileevent $exec readable [list execcallback $exec $window "Echo $window \$line"]
        }
    }
}

proc execcallback {h window cmd} {
    if [eof $h] {
        catch {close $h} out
        if {$out == "child process exited abnormally"} {
            return
        }
    } elseif [catch {gets $h} out] {
        global errorInfo
    	Echo $window "\[ error \] Error executing command: $errorInfo"
    	catch {close $h}
    	return
    }
    if {$out != ""} {
        foreach line [split $out "\n"] {
            eval $cmd
        }
    }
}

proc command_font {window line} {
    global info prefs
    set line [rspaces [split $line]]
    set win [string tolower [lindex $line 0]]

    if [info exists prefs(font,$win)] {
        set line [lrange $line 1 end]
        if {$line == ""} {
            Echo $window "\[ info \] Default $win font is \"$prefs(font,$win)\"" info default
            return
        }
        if {[llength $line] > 1 && [IsNum [lindex $line end]]} {
            set line [list [lrange $line 0 end-1] [lindex $line end]]
        } else {
            set line [list $line]
        }
        set prefs(font,$win) $line
        switch -exact -- $win {
            status {
                set tmp [lindex [$info(text,.0) yview] 1]
                ConfigureFont .0 $line
                if {$tmp == "1"} {
                    $info(text,.0) see end
                }
            }
            channel {
                foreach x [GetAllChannelWindows] {
                    set tmp [lindex [$info(text,$x) yview] 1]
                    ConfigureFont $x $line
                    if {$tmp == "1"} {
                        $info(text,$x) see end
                    }
                }
            }
            chat {
                foreach x "[GetQueryWindows] [GetDccWindows]" {
                    set tmp [lindex [$info(text,$x) yview] 1]
                    ConfigureFont $x $line
                    if {$tmp == "1"} {
                        $info(text,$x) see end
                    }
                }
            }
            cmdline {
                foreach x [GetAllTextWindows] {
                    $x.bottom.cmdline configure -font $line
                }
            }
            topic {
                foreach x [GetAllChannelWindows] {
                    $x.middle.left.topic configure -font $line
                }
            }
            nicklist {
                foreach x [GetAllChannelWindows] {
                    $x.middle.right.nicks configure -font $line
                    $x.n.nicks configure -font $line
                }
            }
            menu {
                foreach m "window personal server misc" {
                    catch {destroy .0.top.$m}
                }
                MakeMenu .0 "window personal server misc"
                foreach x [GetAllChannelWindows] {
                    foreach m "window user channel personal server misc" {
                        catch {destroy $x.top.$m}
                    }
                    MakeMenu $x "window user channel personal server misc"
                    $x.top.modes configure -font $line
                }
                foreach x [GetQueryWindows] {
                    foreach m "window query personal misc" {
                        catch {destroy $x.top.$m}
                    }
                    MakeMenu $x "window query personal misc"
                    $x.top.window.menu.3 delete 0
                }
                foreach x [GetDccWindows] {
                    foreach m "window dcc personal misc" {
                        catch {destroy $x.top.$m}
                    }
                    MakeMenu $x "window dcc personal misc"
                    $x.top.window.menu.3 delete 0
                }
                .0.top.modes configure -font $line
            }
        }
        return
    }
    if {[set tmp [GetWindow $win]] != ""} {
        set win $tmp
    } elseif {$win == "all"} {
        set win [GetAllTextWindows]
    } else {
        set win $window
    }
    if {$line == "" && [llength $win] == "1"} {
        set font [font actual f[string trimleft $window .]]
        Echo $window "\[ info \] Font is currently \"[lindex $font 1] [lindex $font 3]\"" info default
        return
    } elseif {$line == ""} {
        return
    }
    if {[llength $line] > 1 && [IsNum [lindex $line end]]} {
        set line [list [lrange $line 0 end-1] [lindex $line end]]
    } else {
        set line [list $line]
    }
    if {$win == ".0"} {
        set prefs(font,status) $line
    }
    foreach x $win {
        set tmp [lindex [$info(text,$x) yview] 1]
        ConfigureFont $x $line
        if {$tmp == "1"} {
            $info(text,$x) see end
        }
    }
}

proc command_help {window line} {
    global info
    set line [string tolower $line]
    set startup 0
    set autoload 0
    if [file isfile $info(config)/help] {
        catch {source $info(config)/help}
    } elseif [file isfile /usr/local/share/doc/roxirc/help] {
        catch {source /usr/local/share/doc/roxirc/help}
    }
    if [info exists help($line)] {
        Echo $window "Help on $line:" help default
        foreach x [rspaces [split $help($line) "\n"]] {
            Echo $window "$x" help default
        }
        return
    }
    if {$line != ""} {
        Echo $window "No help on \"$line\"" help default
        return
    }
    Echo $window "Available commands:" help default
    Echo $window [lsort [replace [info commands command_*] command_ ""]] help default
    Echo $window "Type \"/help <command>\" for more information" help default
}

proc command_hide {window line} {
    if {[string trim [string tolower $line]] == "all"} {
        foreach x [GetAllTextWindows] {
            if {$x != $window} {
                command_hide $x ""
            }
        }
        return
    }
    set hide ""
    if {[string trim $line] == ""} {
        set hide $window
    } else {
        foreach x [split $line] {
            if {[set win [GetWindow $x]] != ""} {
                lappend hide $win
            }
        }
    }
    foreach a $hide {
        foreach x [GetAllTextWindows] {
            if {[wm state $x] != "withdrawn" && $x != $a} {
                wm withdraw $a
                continue
            }
        }
    }
}

proc command_ial {window line} {
    global ial prefs
    set line [split $line]
    switch -regexp -- [lindex $line 0] {
        search|find {
            set line [join [lrange $line 1 end]]
            set num 0
            foreach x [array names ial] {
                if [match $line $ial($x)] {
                    Echo $window "\[ info \] [lindex [split $x ,] 0] $ial($x)" info default
                    incr num
                }
            }
            Echo $window "\[ info \] Found $num matches for \"$line\" out of [array size ial] in IAL" info default
        }
        clear {
            catch {unset ial}
            Echo $window "\[ info \] IAL cleared" info default
        }
        on {
            Echo $window "\[ info \] IAL is now ON" info default
            set prefs(ial) 1
        }
        off {
            Echo $window "\[ info \] IAL is now OFF" info default
            catch {unset ial}
            set prefs(ial) 0
        }
        "" {
            if $prefs(ial) {
                Echo $window "\[ info \] IAL is ON, [array size ial] entries" info default
            } else {
                Echo $window "\[ info \] IAL is OFF" info default
            }
        }
    }
}

proc command_ignore {window line} {
    global ignore
    set line [rspaces [split $line]]
    set mask [lindex $line 0]
    if {$mask == ""} {
        set found 0
        foreach type "ALL MSGS NOTICES PUBLIC INVITES CTCP" {
            if [info exists ignore($type)] {
                Echo $window "\[ ignore \] Ignoring $type from [join $ignore($type)]" ignore default
                set found 1
            }
        }
        if !$found {
            Echo $window "\[ ignore \] Ignore list is empty" ignore default
        }
        return
    }
    set type [string toupper [lrange $line 1 end]]
    if {[string match *.* $mask] && ![string match *@* $mask]} {
        set mask *!*@*$mask
    } elseif ![string match *!* $mask] {
        set mask $mask!*@*
    }
    if {$type == "NONE"} {
        set found 0
        foreach type "ALL MSGS NOTICES PUBLIC INVITES CTCP" {
            if [info exists ignore($type)] {
                for {set i 0} {$i < [llength $ignore($type)]} {incr i} {
                    set found 1
                    if [mnc $mask [lindex $ignore($type) $i]] {
                        Echo $window "\[ ignore \] Removed ignore of [lindex $ignore($type) $i] from $type" ignore default
                        set ignore($type) [lreplace $ignore($type) $i $i]
                        incr i -1
                    }
                }
            }
        }
        foreach type "ALL MSGS NOTICES PUBLIC INVITES CTCP" {
            if {[info exists ignore($type)] && [string trim $ignore($type)] == ""} {
                unset ignore($type)
            }
        }
        if !$found {
            Echo $window "\[ ignore \] $mask is not in ignore list" ignore default
        }
        return
    }
    if {$type == ""} {
        set type ALL
    }
    foreach x $type {
        if {$x != "MSGS" && $x != "NOTICES" && $x != "PUBLIC" && $x != "INVITES" && $x != "CTCP" && $x != "ALL"} {
            Echo $window "\[ ignore \] Unknown type $type" ignore default
            return
        }
    }
    foreach x $type {
        if {[info exists ignore($x)] && [lsearch -exact $ignore($x) $mask] != "-1"} {
            Echo $window "\[ ignore \] Already ignoring $x from $mask" ignore default
            return
        }
        lappend ignore($x) $mask
    }
    Echo $window "\[ ignore \] Now ignoring $type from $mask" ignore default
}

proc Ignore {type nick} {
    global ignore
    if [info exists ignore($type)] {
        foreach x $ignore($type) {
            if [match $x $nick] {
                return -code return
            }
        }
    }
    if [info exists ignore(ALL)] {
        foreach x $ignore(ALL) {
            if [match $x $nick] {
                return -code return
            }
        }
    }
}

proc command_info {window line} {
    global info
    Echo $window "\[ info \] RoxIRC 1.72 by RockShox (1/29/01)" info default
    Echo $window "\[ info \] roxirc@lighter.net - http://roxirc.lighter.net/" info default
    Echo $window "\[ info \] Client uptime: [dur [expr [clock seconds] - $info(time,client)]]" info default
    if [info exists info(time,server)] {
        Echo $window "\[ info \] Connected to server for: [dur [expr [clock seconds] - $info(time,server)]]" info default
    }
}

proc command_invite {window line} {
    global info
    set line [rspaces [split $line]]
    if {[info exists info(channel,$window)] && [string index [lindex $line 1] 1] != "#" && [string index [lindex $line 1] 1] != "&"} {
        Send "INVITE [join $line] $info(channel,$window)"
    } else {
        Send "INVITE [join $line]"
    }
}

proc command_join {window line} {
    global info
    set chan [string tolower [lindex [split $line] 0]]
    if {[info exists info(window,$chan)] && [winfo exists $info(window,$chan)]} {
        wm deiconify $info(window,$chan)
        raise $info(window,$chan)
    }
    Send "JOIN $line"
}

proc command_kick {window line} {
    global info
    set line [split $line]
    if {[info exists info(channel,$window)] && [string index $line 0] != "#" && [string index $line 0] != "&"} {
        Send "KICK $info(channel,$window) [lindex $line 0] :[join [lrange $line 1 end]]"
    } else {
        Send "KICK [lindex $line 0] [lindex $line 1] :[join [lrange $line 2 end]]"
    }
}

proc command_links {window line} {
    Send "LINKS $line"
}

proc command_list {window line} {
    Send "LIST $line"
}

proc command_load {window line} {
    global away prefs me server notify env info
    set autoload 0
    set startup 0
    set files ""
    foreach file [split $line] {
        if [file isfile $file] {
            lappend files [abspath $file]
        } elseif {[file dirname $file] == "." && [file isfile $info(config)/$file]} {
            lappend files $info(config)/$file
        } elseif [file isdirectory $file] {
            append files " [join [glob -nocomplain $file/*]]"
        } else {
            Echo $window "\[ info \] No such file or directory: $file" info default
        }
    }
    foreach file $files {
        if [LoadFile $file] {
            if {$window != ".0"} {
                Echo .0 "\[ info \] Loaded $file" info default
            }
            Echo $window "\[ info \] Load: $file loaded succesfully" info default
        }
    }
}

proc command_log {window line} {
    global options prefs env
    set line [rspaces [split $line]]
    if {[set tmp [GetWindow [lindex $line 0]]] != ""} {
        set window $tmp
        set line [lrange $line 1 end]
    }
    switch -exact -- [string tolower [lindex $line 0]] {
        on {
            if $options(log,$window) {
                command_log $window off
            }
            if {[lindex $line 1] != ""} {
                set fn [lindex $line 1]
                if {[file dirname $fn] == "."} {
                    set fn $prefs(defaultlogdir)/$fn
                }
                set fn [abspath $fn]
            } else {
                set fn [tk_getSaveFile -title "RoxIRC Logfile" -initialdir $prefs(defaultlogdir) -filetypes {{All *} {Logs *.log} {Text *.txt}}]
                if {$fn == ""} {
                    return
                }
            }
            if [catch {open $fn a} fh] {
                Echo $window "\[ error \] Cannot open $fn for writing: [geterror $fh]" error default
                return
            } else {
                fconfigure $fh -buffersize 2048
                Echo $window "\[ info \] Now logging to $options(ln,$window)" info default
                array set options [list ln,$window $fn lfh,$window $fh log,$window 1]
                puts $fh "Logging started on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"
            }
        }
        off {
            if !$options(log,$window) {
                Echo $window "\[ info \] Logging is already OFF" info default
                return
            }
            puts $options(lfh,$window) "Logging stopped on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"
            close $options(lfh,$window)
            unset options(ln,$window) options(lfh,$window)
            set options(log,$window) 0
            Echo $window "\[ info \] Logging stopped" info default
        }
        flush {
            if $options(log,$window) {
                flush $options(lfh,$window)
                Echo $window "\[ info \] Flushed log to $options(ln,$window)" info default
            } else {
                Echo $window "\[ info \] Logging is OFF" info default
            }
        }
        "" {
            if $options(log,$window) {
                Echo $window "\[ info \] Logging to $options(ln,$window)" info default
            } else {
                Echo $window "\[ info \] Logging is OFF" info default
            }
        }
    }
}

proc command_lusers {window line} {
    Send "LUSERS $line"
}

proc command_me {window line} {
    global ume info dcc
    multiline "" $line
    if [info exists info(channel,$window)] {
        Send "PRIVMSG $info(channel,$window) :\001ACTION $line\001"
        Echo $window "* $ume $line" me action
    } elseif [info exists info(nick,$window)] {
        Send "PRIVMSG $info(nick,$window) :\001ACTION $line\001"
        Echo $window "* $ume $line" me action
    }  elseif [info exists dcc(socket,$window)] {
        DccSend $window "\001ACTION $line\001"
        Echo $window "* $ume $line" me action
        return
     } else {
        Echo $window "\[ info \] You have no channel joined in this window" info default
    }
}

proc command_mode {window line} {
    global info
    set line [rspaces [split $line]]
    if {[llength $line] == "1" && [info exists info(window,[lindex $line 0])]} {
        ModeWindow $window
    } elseif {[info exists info(channel,$window)] && [string index [lindex $line 0] 0] != "#" && [string index [lindex $line 0] 0] != "&"} {
        Send "MODE $info(channel,$window) [join $line]"
    } else {
        Send "MODE [join $line]"
    }
}

proc command_motd {window line} {
    Send "MOTD $line"
}

proc command_msg {window line} {
    global info ume dcc
    set line [split $line " "]
    set to [join [lindex [split $line] 0]]
    set line [join [lrange $line 1 end]]
    multiline $to $line
    set to2 [string tolower $to]
    if [info exists info(query,$to2)] {
        Echo $info(query,$to2) "<$ume> $line" me
        wm deiconify $info(query,$to2)
        raise $info(query,$to2)
    } elseif {[string index $to2 0] == "=" && [set tmp [DccChatExists [string range $to2 1 end]]] != ""} {
        DccSend .$tmp $line
        Echo .$tmp "<$ume> $line" me
        return
    } elseif [info exists info(window,$to2)] {
        Echo $info(window,$to2) "<$ume> $line" me
    } else {
        Echo .0 "-> *$to* $line" me
    }
    Send "PRIVMSG $to :$line"
}

proc command_names {window line} {
    Send "NAMES $line"
}

proc command_newwin {window line} {
    CreateChannel ""
}

proc command_nick {window line} {
    Send "NICK $line"
}

proc command_notice {window line} {
    global info ume
    set line [split $line " "]
    set to [lindex $line 0]
    set line [join [lrange $line 1 end]]
    multiline $to $line
    set to2 [string tolower $to]
    if [info exists info(query,$to2)] {
        Echo $info(query,$to2) "+$ume+ $line" me
        raise $info(query,$to2)
    } elseif [info exists info(window,$to2)] {
        Echo $info(window,$to2) "-$ume- $line" me
    } else {
        Echo .0 "-> +$to+ $line" me
    }
    Send "NOTICE $to :$line"
}

proc command_notify {window line} {
    global prefs notify
    if {[string trim $line] == ""} {
        if {$prefs(notify) != ""} {
            foreach x $notify(on) {
                Echo $window "\[ notify \] Online: $x [lindex $notify([string tolower $x]) 0] ([dur [expr [clock seconds] - [lindex $notify([string tolower $x]) 1]]])" notify default
            }
            set off ""
            foreach x [lsort -command Sort $prefs(notify)] {
                if {[lsearch -exact [string tolower $notify(on)] [string tolower $x]] == "-1"} {
                    lappend off $x
                }
            }
            Echo $window "\[ notify \] Offline: [join [lsort -command Sort $off]]" notify default
        } else {
            Echo $window "\[ notify \] Notify list is empty" notify default
        }
    } else {
        foreach x [rspaces [split $line]] {
            switch -exact -- [string index $x 0] {
                - {
                    set x [string range $x 1 end]
                    if {[set index [lsearch -exact [string tolower $prefs(notify)] [string tolower $x]]] != "-1"} {
                        Echo .0 "\[ notify \] [lindex $prefs(notify) $index] removed from notification list" notify default
                        set prefs(notify) [lreplace $prefs(notify) $index $index]
                        catch {unset notify([string tolower $x])}
                        if {[set index [lsearch -exact [string tolower $notify(on)] [string tolower $x]]] != "-1"} {
                            set notify(on) [lreplace $notify(on) $index $index]
                        }
                        DoNotifyWindow refresh
                    }
                }
                default {
                    if {[lsearch -exact [string tolower $prefs(notify)] [string tolower $x]] == "-1"} {
                        lappend prefs(notify) $x
                        Echo .0 "\[ notify \] $x added to notification list" notify default
                        DoNotifyWindow refresh
                    }
                }
            }
        }
    }
    if {$prefs(notify) != ""} {
        Send "ISON [join $prefs(notify)]"
    }
}

proc command_on {window line} {
    global on info
    switch -glob -- $line {
        "" {
            foreach x $info(on) {
                if [info exists on([lindex $x 0])] {
                    foreach a $on([lindex $x 0]) {
                        Echo $window "on [lindex $x 0] $a" none
                    }
                }
            }
        }
        -* {
            set type [string range [lindex [split [split $line]] 0] 1 end]
            if {$type == "all"} {
                foreach x $info(on) {
                    command_on $window -[lindex $x 0]
                }
                return
            }
            if [info exists on($type)] {
                set num 0
                set line [string range $line [expr [string length $type] + 2] end]
                foreach x $on($type) {
                    if [match [replace [replace $line * \\\*] ? \\\?]* [join $x]] {
                        Echo $window "-on $type $x" none
                        set on($type) [lreplace $on($type) $num $num]
                        incr num -1
                    }
                    incr num
                }
                if {[string trim $on($type)] == ""} {
                    unset on($type)
                }
            }
        }
        default {
            set found 0
            foreach x $info(on) {
                set t [lindex $x 0]
                if [string match $t* $line] {
                    set l [string length $t]
                    set found 1
                    if {[string length $line] == $l} {
                        continue
                    }
                    switch -exact [lindex $x 1] {
                        0 {
                            set a [string range $line [expr $l + 1] end]
                            if {$a == ""} {
                                return
                            }
                            lappend on($t) [list $a]
                            Echo $window "+on $t {$a}" none
                        }
                        1 {
                            set tmp [split $line [string index $line $l]]
                            set a [lindex $tmp 1]
                            set b [join [lrange $tmp 2 end]]
                            if {$b == ""} {
                                return
                            }
                            lappend on($t) [list $a $b]
                            Echo $window "+on $t {$a} {$b}" none
                        }
                        2 {
                            set tmp [split $line [string index $line $l]]
                            set a [lindex $tmp 1]
                            set b [lindex $tmp 2]
                            set c [join [lrange $tmp 3 end]]
                            if {$c == ""} {
                                return
                            }
                            lappend on($t) [list $a $b $c]
                            Echo $window "+on $t {$a} {$b} {$c}" none
                        }
                        3 {
                            set tmp [split $line [string index $line $l]]
                            set a [lindex $tmp 1]
                            set b [lindex $tmp 2]
                            set c [lindex $tmp 3]
                            set d [join [lrange $tmp 4 end]]
                            if {$d == ""} {
                                return
                            }
                            lappend on($t) [list $a $b $c $d]
                            Echo $window "+on $t {$a} {$b} {$c} {$d}" none
                        }
                    }
                }
            }
            if {$found && [llength [rspaces [split $line]]] == "1" && [info exists on($line)]} {
                foreach a $on($line) {
                    Echo $window "on $line $a" none
                }
                return
            } elseif !$found {
                Echo $window "on: unknown event" none
            }
        }
    }
}

proc command_part {window line} {
    global info
    if {$line == "all"} {
        Send "JOIN 0"
    } elseif {$line == ""} {
        if [info exists info(channel,$window)] {
            Send "PART $info(channel,$window)"
        }
    } else {
        Send "PART $line"
    }
}

proc command_ping {window line} {
    foreach x [rspaces [split $line]] {
        Echo .0 "\[ ctcp \] -> $x PING" ctcp default
        Send "PRIVMSG $x :\001PING [clock clicks -milliseconds]\001"
    }
}

proc command_qbk {window line} {
    global prefs info
    set line [rspaces [split $line]]
    if {[info exists info(channel,$window)] && [string index [lindex $line 0] 0] != "#" && [string index [lindex $line 0] 0] != "&"} {
        set chan $info(channel,$window)
    } else {
        set chan [lindex $line 0]
        set line [lrange $line 1 end]
    }
    foreach x $line {
        command_ban $window "$chan $x"
        command_kick $window "$chan $x [lindex $prefs(kick) 0]"
    }
}

proc command_query {window line} {
    global prefs
    set line [split $line]
    if {[lindex $line 0] != ""} {
        CreateChat [lindex $line 0]
    }
    if {[join [lrange $line 1 end]] != ""} {
        command_msg $window [join $line]
    }
}

proc command_quit {window line} {
    CloseClient $line
}

proc command_quote {window line} {
    Send $line
}

proc command_reload {window line} {
    global prefs env info menu argv info
    foreach x [info commands event_*] {
        rename $x ""
    }
    set autoload 1
    set startup 0
    SetDefaults
    catch {unset menu}
    WindowMenu
    if [info exists argv(f)] {
        set info(config) [abspath $argv(f)]
    }
    if [file isdirectory $info(config)] {
        set files [glob -nocomplain -types f $info(config)/*]
        foreach file [lsort $files] {
            LoadFile $file
        }
    } elseif [file isfile $info(config)] {
        LoadFile $info(config)
    } else {
        Echo .0 "\[ info \] No such file or directory: $info(config)" info default
    }
    if [info exists argv(h)] {
        set prefs(host) $argv(h)
    }
    if [info exists argv(nick)] {
         set prefs(nick) "[escape [escape $argv(nick)]] $prefs(nick)"
    }
    foreach win [GetAllTextWindows] {
        foreach x [$info(text,$win) tag names] {
            if {![info exists prefs(color,$x)] && $x != "none" && $x != "bold" && $x != "underline"} {
                $info(text,.0) tag delete $x
            }
        }
        foreach m "window user channel personal server dcc query misc" {
            catch {destroy $win.top.$m}
        }
        $win.bottom.cmdline configure -font $prefs(font,cmdline)
    }
    eval $info(text,.0) configure $prefs(style,status)
    ConfigureFont .0 $prefs(font,status)
    MakeMenu .0 "window personal server misc"
    .0.top.window.menu delete 10
    .0.top.window.menu.3 delete 0
    foreach win [GetAllChannelWindows] {
        eval $info(text,$win) configure $prefs(style,channel)
        ConfigureFont $win $prefs(font,channel)
        $win.middle.left.topic configure -font $prefs(font,topic)
        eval $win.middle.right.nicks configure $prefs(style,nicklist)
        eval $win.n.nicks configure $prefs(style,nicklist)
        MakeMenu $win "window user channel personal server misc"
    }
    foreach win [GetQueryWindows] {
        eval $info(text,$win) configure $prefs(style,chat)
        ConfigureFont $win $prefs(font,chat)
        MakeMenu $win "window query personal misc"
        $win.top.window.menu.3 delete 0
    }
    foreach win [GetDccWindows] {
        eval $info(text,$win) configure $prefs(style,chat)
        ConfigureFont $win $prefs(font,chat)
        MakeMenu $win "window dcc personal misc"
        $win.top.window.menu.3 delete 0
    }
    foreach win [GetAllTextWindows] {
        ConfigureTags $win
    }
}

proc command_reply {window line} {
    set line [split $line]
    Send "NOTICE [lindex $line 0] :\001[string toupper [lindex $line 1]] [join [lrange $line 2 end]]\001"
}

proc command_save {window line} {
    if {[string trim $line] != ""} {
        SaveSettings $line
    } else {
        SaveSettings
    }
}

proc command_savebuf {window line} {
    global info prefs
    set line [rspaces [split $line]]
    if {[set tmp [GetWindow [lindex $line 0]]] != ""} {
        set window $tmp
        set line [lrange $line 1 end]
    }
    if {[lindex $line 0] == ""} {
        set file [tk_getSaveFile -title "RoxIRC Save Buffer" -initialdir $prefs(defaultlogdir) -filetypes {{All *} {Logs *.log} {Text *.txt}}]
        if {$file == ""} {
            return
        }
    } else {
        set file [lindex $line 0]
    }
    if {[file dirname $file] == "."} {
        set file $prefs(defaultlogdir)/$file
    }
    if {$file == ""} {
        return
    }
    set file [abspath $file]
    if [catch {open $file w} fn] {
        Echo $window "\[ error \] Cannot open $file for writing: [geterror $fn]" error default
        return
    }
    puts -nonewline $fn [$info(text,$window) get 0.0 end]
    close $fn
    Echo $window "\[ info \] Buffer saved to $file" info default
}

proc SaveListbox {path} {
    global env
    set file [tk_getSaveFile -title "RoxIRC Save Listbox" -initialdir $env(HOME) -filetypes {{All *} {Text *.txt}}]
    if {$file == ""} {
        return
    }
    if [catch {open $file w} fn] {
        Echo .0 "\[ error \] Cannot open $file for writing: [geterror $fn]" error default
        return
    }
    puts $fn [join [$path get 0 end] "\n"]
    close $fn
    Echo .0 "\[ info \] [lrange [split [wm title [winfo toplevel $path]]] 1 end] saved to $file" info default
}

proc command_say {window blah} {
     global info ume line
     set line $blah
     event generate $window <<command>>
}

proc command_search {window line} {
    global info
    $info(text,$window) tag remove search 0.0 end
    if {$line == ""} {
        return
    }
    set blah 0.0
    set pos 0.0
    while {$blah != ""} {
        set blah [$info(text,$window) search -nocase -elide -count len -- $line $pos end]
        if {$blah == ""} {
            break
        }
        set pos [$info(text,$window) index $blah+${len}c]
        $info(text,$window) tag add search $blah $pos
    }
    $info(text,$window) tag raise search
    if {[lindex [$info(text,$window) yview] 1] == "1"} {
        $info(text,$window) yview search.first
    } else {
        set a [expr int([$info(text,$window) index @0,0])].0
        $info(text,$window) yview [lindex [$info(text,$window) tag nextrange search $a+1l] 1]
    }
}

proc command_server {window line} {
    global prefs info
    set tmp ""
    set line [rspaces [split $line " :"]]
    set server [string tolower [lindex $line 0]]
    if {$server == ""} {
        return
    }
    set port [lindex $line 1]
    set pass [join [lrange $line 2 end]]
    foreach x [split [string tolower $prefs(server)] "\n"] {
        set x [split $x :]
        lappend serv([string trim [lindex $x 1]]) $x
        lappend grp([string trim [lindex $x 0]]) [lindex $x 1]
    }
    if [info exists serv($server)] {
        set tmp [lindex $serv($server) [expr round(rand() * ([llength $serv($server)] - 1))]]
    }
    if {$tmp != ""} {
        set info(connect) $grp([string trim [lindex $tmp 0]])
        OpenSock [lindex $tmp 0]:[lindex $tmp 2]:[lindex $tmp 3]
    } else {
        if [info exists grp([lindex [split $server :] 0])] {
            set info(connect) $grp([lindex [split $server :] 0])
        } else {
            set info(connect) $server:$port:$pass
        }
        OpenSock $server:$port:$pass
    }

}

proc command_set {window line} {
    global prefs info
    set line [split $line]
    set tmp [string tolower [lindex $line 0]]
    if {[string trim [join $line]] == ""} {
        foreach x [lsort [array names info set,*]] {
            set x [lindex [split $x ,] 1]
            Echo $window "\[ info \] [string toupper $x] set to \"$prefs($x)\"" info default
        }
        return
    }
    if {[llength [rspaces $line]] == "1"} {
        if {[info exists prefs($tmp)] && [info exists info(set,$tmp)]} {
            Echo $window "\[ info \] [string toupper $line] set to \"$prefs($tmp)\"" info default
        }
        return
    }
    if ![info exists info(set,$tmp)] {
        Echo $window "\[ error \] No such variable \"[string toupper $tmp]\"" error default
        return
    }
    set type [split $info(set,$tmp)]
    set val [join [lrange $line 1 end]]
    for {set i 0} {$i < [llength $type]} {incr i} {
        switch [lindex $type $i] {
            cmd {
                [lindex $type [expr $i + 1]] $window [join [lrange $line 1 end]]
            }
            bool {
                if {[string tolower $val] == "off" || [string tolower $val] == "false"} {
                    set val 0
                } elseif {[string tolower $val] == "on" || [string tolower $val] == "true"} {
                    set val 1
                }
                if {$val != "0" && $val != "1"} {
                    Echo $window "\[ error \] Invalid value for [string toupper [lindex $line 0]], must be boolean" error default
                    return
                }
            }
            num {
                if ![IsNum $val] {
                    Echo $window "\[ error \] Invalid value for [string toupper [lindex $line 0]], must be a number" error default
                    return
                }
                if {[IsNum [lindex $type [expr $i + 1]]] && [IsNum [lindex $type [expr $i + 2]]]} {
                    if {$val < [lindex $type [expr $i + 1]] || $val > [lindex $type [expr $i + 2]]} {
                        Echo $window "\[ error \] Invalid value for [string toupper [lindex $line 0]], must be from [lindex $type [expr $i + 1]] to [lindex $type [expr $i + 2]]" error default
                        return
                    }
                    incr i
                }
            }
        }
    }
    if {$val == "\"\""} {
        Echo $window "\[ info \] [string toupper $tmp] \"$prefs($tmp)\" -> \"\"" info default
        set prefs($tmp) ""
    } else {
        Echo $window "\[ info \] [string toupper $tmp] \"$prefs($tmp)\" -> \"$val\"" info default
        set prefs($tmp) $val
    }
}

proc command_show {window line} {
    if {[string trim $line] == ""} {
        set num 1
        foreach x [GetAllTextWindows] {
            if {[wm state $x] == "withdrawn"} {
                set title [split [wm title $x]]
                if [string match ".q*" $x] {
                    set title [lrange $title 1 2]
                } elseif [string match ".c*" $x] {
                    set title [lrange $title 1 3]
                } else {
                    set title [lindex $title 1]
                }
                Echo $window "$num $title" none
                incr num
            }
        }
        return
    }
    if {[string tolower [string trim $line]] == "all"} {
        foreach x [GetAllTextWindows] {
            if {[wm state $x] == "withdrawn"} {
                wm geometry $x [winfo geometry $x]
                wm deiconify $x
            }
        }
        return
    }
    set show ""
    foreach x [split $line] {
        if {[set win [GetWindow $x]] != ""} {
            lappend show $win
        } elseif [IsNum $x] {
            set num 1
            foreach a [GetAllTextWindows] {
                if {[wm state $a] == "withdrawn"} {
                    if {$num == $x} {
                        lappend show $a
                        continue
                    }
                    incr num
                }
            }
        }
    }
    foreach x $show {
        wm geometry $x [winfo geometry $x]
        wm deiconify $x
    }
}

proc command_sping {window line} {
    global server
    if {[string trim $line] == ""} {
        set line $server
    }
    Send "PING [clock clicks -milliseconds] :$line"
}

proc command_stats {window line} {
    Send "STATS $line"
}

proc command_tcl {window line} {
    global info prefs dcc ignore help server me irc env notify userhost last ial names history options on away tcl_traceExec errorInfo errorCode
    if [catch {set out [eval $line]} msg] {
        Echo $window "\[ error \] Error while executing tcl command: $msg" error default
    } elseif {[string trim $out] != ""} {
        Echo $window $out none
    }
}

proc command_timer {window line} {
    set line [split $line]
    if {[string trim [join $line]] == ""} {
        foreach x [after info] {
            if {[lindex [set tmp [lindex [after info $x] 0]] 0] == "DoTimer"} {
                Echo $window "timer[lindex $tmp 1] [lindex $tmp 3]/[lindex $tmp 4] [lindex $tmp 2] [lindex $tmp 5]" none
            }
        }
    } elseif {[lindex $line 0] == "stop" || [lindex $line 0] == "cancel" || [lindex $line 0] == "off"} {
        foreach x [after info] {
            if {[lindex [set tmp [lindex [after info $x] 0]] 0] == "DoTimer"} {
                if {[lindex $tmp 1] == [lindex $line 1] || [lindex $line 1] == "all"} {
                    after cancel $x
                    Echo $window "-timer[lindex $tmp 1] [lindex $tmp 3]/[lindex $tmp 4] [lindex $tmp 2] [lindex $tmp 5]" none
                }
            }
        }
    } else {
        set delay [lindex $line 1]
        set times [lindex $line 0]
        set command [join [lrange $line 2 end]]
        if [string match *s $delay] {
            set delay [expr 1000 * [string trimright $delay s]]
        } elseif [string match *m $delay] {
            set delay [expr 60000 * [string trimright $delay m]]
        } elseif [string match *h $delay] {
            set delay [expr 360000 * [string trimright $delay h]]
        }
        if {[IsNum $delay] && [IsNum $times]} {
            set num 1
            foreach x [after info] {
                if {[lindex [lindex [after info $x] 0] 0] == "DoTimer"} {
                    set temp([lindex [lindex [after info $x] 0] 1]) ""
                }
            }
            while {[info exists temp($num)]} {
                incr num
            }
            if {$times > 0} {
                after $delay [list DoTimer $num $delay $times $times $command]
            } else {
                after $delay [list DoTimer $num $delay 0 0 $command]
            }
            Echo $window "+timer$num $times $delay $command" none
        }
    }
}

proc command_topic {window line} {
    global info
    set line [split $line]
    if {[string index [lindex $line 0] 0] != "#" && [string index [lindex $line 0] 0] != "&" && [info exists info(channel,$window)]} {
        set chan $info(channel,$window)
        set line [join $line]
    } else {
        set chan [lindex $line 0]
        set line [join [lrange $line 1 end]]
    }
    if {[string trim $line] == ""} {
        Send "TOPIC $chan"
        return
    }
    if {[string trim $line] == ":"} {
        Send "TOPIC $chan :"
    } else {
        Send "TOPIC $chan :$line"
    }
}

proc command_ts {window line} {
    global options
    set line [rspaces [split $line]]
    if {[set tmp [GetWindow [lindex $line 0]]] != ""} {
        set window $tmp
        set win $tmp
        set to [lindex $line 1]
    } elseif {[string tolower [lindex $line 0]] == "channels"} {
        set win [GetAllChannelWindows]
        set to [lindex $line 1]
    } elseif {[string tolower [lindex $line 0]] == "chats"} {
        set win "[GetQueryWindows] [GetDccWindows]"
        set to [lindex $line 1]
    } elseif {[string tolower [lindex $line 0]] == "all"} {
        set win [GetAllTextWindows]
        set to [lindex $line 1]
    } else {
        set win $window
        set to [lindex $line 0]
    }
    if {$to == ""} {
        set to 3
    } elseif {$to == "true" || $to == "1" || $to == "on"} {
        set to 1
    } elseif {$to == "false" || $to == "0" || $to == "off"} {
        set to 0
    } else {
#        Echo $window "\[ info \] Bad timestamp value, must be default, true, or false" info default
        return
    }
    foreach x $win {
        if {$to == 3} {
            if {$options(ts,$x) == 1} {
                set options(ts,$x) 0
            } elseif {$options(ts,$x) == 0} {
                set options(ts,$x) 1
            }
        } else {
            set options(ts,$x) $to
        }
        ts $x
    }
}

proc command_trace {window line} {
    Send "TRACE $line"
}

proc command_umode {window line} {
    global ume
    Send "MODE $ume $line"
}

proc command_unalias {window line} {
    set line [string tolower [string trim $line]]
    foreach x [split $line] {
        if {[info procs command_$x] == "" || [lindex [split [info body command_$x] "\n"] 2] != "#alias"} {
            Echo $window "\[ error \] Cannot unalias $x: no such alias" error default
            continue
        }
        catch {rename command_$x ""}
        Echo $window "-alias $x" none
    }
}

proc command_unload {window line} {
    global procs
    if {[string trim $line] == ""} {
        set s ""
        foreach x [namespace children ::scripts] {
            if {[set t [info commands ${x}::unload]] != ""} {
                lappend s [namespace tail $x]
            }
        }
        if {$s != ""} {
            Echo $window "\[ info \] Currently unloadable scripts: [join $s]" info default
        } else {
            Echo $window "\[ info \] There are no unloadable scripts" info default
        }
        return
    }
    foreach x [rspaces [split $line]] {
        if {[info commands ::scripts::${x}::unload] != ""} {
           if [catch {::scripts::${x}::unload} err] {
                Echo $window "\[ error \] Could not unload $x: [geterror $err]" error default
           } else {
                foreach proc [array names procs] {
                    if {$x == $procs($proc)} {
                        catch {rename ::$proc ""}
                        if {[info procs ::backup::${x}::$proc] != ""} {
                            rename ::backup::${x}::$proc ::$proc
                        }
                        unset procs($proc)
                    }
                }
                catch {namespace delete ::backup::${x}}
                namespace delete ::scripts::${x}
                if {$window != ".0"} {
                    Echo .0 "\[ info \] Unloaded $x"
                }
                Echo $window "\[ info \] Unload: $x unloaded succesfully" info default
                
            }
        } else {
            Echo $window "\[ error \] Could not unload $x: no such script" error default
        }
    }
}

proc command_url {window line} {
    global urls prefs
    switch -exact -- [lindex [split $line] 0] {
        on {
            set prefs(urls) 1
            Echo $window "\[ info \] Url catcher is now ON" info default
        }
        off {
            set prefs(urls) 0
            Echo $window "\[ info \] Url catcher is now OFF" info default
        }
        clear {
            set urls ""
            catch {.urls.top.list delete 0 end}
            Echo $window "\[ info \] Url list cleared" info default
        }
        last {
            global prefs
            set url [lindex [lindex $urls end] 2]
            eval exec [replace $prefs(urlcommand) \$url $url] &
        }
        "" {
            UrlWindow
        }
    }
}

proc command_users {window line} {
    Send "USERS $line"
}

proc command_wall {window line} {
    global info names ume
    set line [split $line]
    if {[info exists info(channel,$window)] && [string index [lindex $line 0] 0] != "#" && [string index [lindex $line 0] 0] != "&"} {
        set chan $info(channel,$window)
    } else {
        set chan [lindex $line 0]
        set line [lrange $line 1 end]
    }
    if ![info exists names(o,$chan)] {
        return
    }
    set line [join $line]
    set index [lsearch $names(o,$chan) $ume]
    Send "NOTICE [join [lreplace $names(o,$chan) $index $index] ,] :\[$chan/Wall\] $line"
    Echo $window "-> +@$chan+: $line" me
}

proc command_who {window line} {
    Send "WHO $line"
}

proc command_whois {window line} {
    Send "WHOIS $line"
}

proc command_whowas {window line} {
    Send "WHOWAS $line"
}

proc raw_001 {header line} {
    global me ume server prefs info showmotd notify away connecting irc autoaway
    unset connecting
    foreach x [after info] {
        if {[lindex [lindex [after info $x] 0] 0] == "command_server"} {
            after cancel $x
        }
    }
    set info(time,server) [clock seconds]
    set notify(on) ""
    set me [escape [lindex $header end]]
    set ume [lindex $header end]
    set server [lindex $header 0]
    if !$prefs(showmotd) {
        set showmotd 1
    }
    UpdateAllTitles
    catch {unset autoaway}
    if $away {
        set autoaway 1
        Send "AWAY :$prefs(awayreason)"
    }
    set away 0
    foreach x [array names info channel,*] {
        if [winfo exists $info(window,$info($x))] {
            Send "JOIN $info($x)"
        } else {
            DeleteChannel $info($x)
        }
    }
    if {$prefs(autoaway) > 0} {
        after [expr $prefs(autoaway) * 60000] autoaway
    }
    if {$prefs(notify) != ""} {
        after 1000 [list Send "ISON [join $prefs(notify)]"]
    }
    Echo .0 "\[ server \] $line" server default
    Event connect -1 "a a"
}

proc raw_004 {header line} {
    Echo .0 "\[ server \] User modes available: [lindex $header 5] Channel modes: $line" server default
}

proc raw_221 {header line} {
    if {$line == "+"} {
        .0.top.modes configure -text "-"
    } else {
        .0.top.modes configure -text [string trimleft $line +]
    }
}

proc raw_301 {header line} {
    global info
    set nick [lindex $header end]
    if ![info exists info(query,[string tolower $nick])] {
        Echo .0 "\[ away \] $nick is away: $line" away default
    }
}

proc raw_302 {header line} {
    global userhost
    foreach x [split $line] {
        set nick [string trimright [lindex [split $x =] 0] *]
        set address [string range [lindex [split $x =] 1] 1 end]
        if {[set tmp [common $nick]] != ""} {
            foreach x $tmp {
                ialadd $x $nick $address
            }
        }
        if [info exists userhost([string tolower $nick])] {
            set address $nick!$address
            eval $userhost([string tolower $nick])
            unset userhost([string tolower $nick])
        }
    }
}

proc raw_303 {header line} {
    global prefs notify away userhost on
    foreach x $notify(on) {
        if {[lsearch -exact [split [string tolower $line]] [string tolower $x]] == "-1"} {
            set address [lindex $notify([string tolower $x]) 0]
            Echo .0 "\[ notify \] Signoff by $x ($address) at [clock format [clock seconds] -format "%H:%M"] ([dur [expr [clock seconds] - [lindex $notify([string tolower $x]) 1]]])" notify default
            unset notify([string tolower $x])
            set index [lsearch -exact [string tolower $notify(on)] [string tolower $x]]
            set notify(on) [lreplace $notify(on) $index $index]
            DoNotifyWindow refresh
            Event unnotify 0 "x nick address address" $x!$address
        }
    }
    foreach x [split $line] {
        if {$x == ""} {
            continue
        }
        if {[lsearch -exact [string tolower $notify(on)] [string tolower $x]] == "-1"} {
            set userhost([unescape [string tolower $x]]) "notifyon [escape $x] \$address [clock seconds]"
            Send "USERHOST $x"
            lappend notify(on) $x
        }
    }
}

proc raw_305 {header line} {
    global away prefs on me server options autoaway
    Echo .0 "\[ away \] $line" away default
    set away 0
    catch {unset autoaway}
    if {$prefs(autoaway) > 0} {
        after [expr $prefs(autoaway) * 60000] autoaway
    }
    UpdateAllTitles
    Event unaway -1 "a a"
}

proc raw_306 {header line} {
    global away on me server options autoaway
    set away 1
    after cancel autoaway
    if [info exists autoaway] {
        Echo .0 "\[ away \] $line (auto)" away default
    } else {
        Echo .0 "\[ away \] $line" away default
    }
    UpdateAllTitles
    Event away -1 "a a"
}

proc raw_311 {header line} {
    Echo .0 "\[ whois \] [lindex $header 3] is [lindex $header 4]@[lindex $header 5] ($line)" whois default
}

proc raw_312 {header line} {
    global last
    if {$last == "314"} {
        Echo .0 "\[ whowas \] [lindex $header 3] was on server [lindex $header end] until $line" whowas default
    } else {
        Echo .0 "\[ whois \] [lindex $header 3] on server [lindex $header end] ($line)" whois default
    }
}

proc raw_313 {header line} {
    Echo .0 "\[ whois \] [lindex $header end] $line" whois default
}

proc raw_314 {header line} {
    Echo .0 "\[ whowas \] [lindex $header 3] was [lindex $header 4]@[lindex $header 5] ($line)" whowas default
}

proc raw_315 {header line} {
    Echo .0 "\[ who \] [lindex $header end] $line" who default
}

proc raw_317 {header line} {
    if {[lindex $header 5] != ""} {
        Echo .0 "\[ whois \] [lindex $header 3] has been idle [dur [lindex $header 4]], signed on [clock format [lindex $header end] -format "%m/%d/%y at %H:%M:%S"]" whois default
    } else {
        Echo .0 "\[ whois \] [lindex $header 3] has been idle [dur [lindex $header 4]]" whois default
    }
}

proc raw_318 {header line} {
}

proc raw_319 {header line} {
    Echo .0 "\[ whois \] [lindex $header end] on channels [string trim $line]" whois default
}

proc raw_324 {header line} {
    global info
    set chan [string tolower [lindex $header 3]]
    set mode [string trimleft [string trim "[join [lrange $header 4 end]] $line"] +]
    if {[info exists info(window,$chan)] && [winfo exists $info(window,$chan)]} {
        if {$line != "+"} {
            $info(window,$chan).top.modes configure -text $mode
        } else {
            $info(window,$chan).top.modes configure -text "-"
        }
    } else {
        Echo .0 "\[ channel \] $chan modes: $line" channel default
    }
}

proc raw_329 {header line} {
    global info
    set chan [string tolower [lindex $header 3]]
    global join$chan
    if ![info exists join$chan] {
        return
    }
    unset join$chan
    if [info exists info(window,$chan)] {
        Echo $info(window,$chan) "\[ channel \] $chan was created on [clock format $line -format "%m/%d/%y at %H:%M:%S"]" channel default
    } else {
        Echo .0 "\[ channel \] $chan was created on [clock format $line -format "%m/%d/%y at %H:%M:%S"]" channel default
    }
}

proc raw_332 {header line} {
    global info
    set chan [string tolower [lindex $header end]]
    Echo $info(window,$chan) "\[ topic \] $line" topic default
    $info(window,$chan).middle.left.topic configure -state normal
    $info(window,$chan).middle.left.topic delete 0 end
    $info(window,$chan).middle.left.topic insert 0 $line
    $info(window,$chan).middle.left.topic configure -state disabled
}

proc raw_331 {header line} {
    global info
    set chan [string tolower [lindex $header end]]
    if [info exists info(window,$chan)] {
        Echo $info(window,$chan) "\[ topic \] No topic is set" topic default
    } else {
        Echo .0 "\[ topic \] $chan no topic is set" topic default
    }
}

proc raw_333 {header line} {
    global info
    Echo $info(window,[string tolower [lindex $header 3]]) "\[ topic \] set by [lindex $header 4] on [clock format $line -format "%m/%d/%y at %H:%M:%S"]" topic default
}

proc raw_341 {header line} {
    Echo .0 "\[ invite \] Invited [lindex $header 3] to $line" invite default
}

proc raw_352 {header line} {
    set blah ""
    set hops [lindex $line 0]
    if [string match *@* [lindex $header end]] {
        lappend blah @[lindex $header 3]
    } elseif [string match *+* [lindex $header end]] {
        lappend blah +[lindex $header 3]
    } elseif {[lindex $header 3] != "*"} {
        lappend blah [lindex $header 3]
    }
    if {[string index [lindex $header end] 0] == "G"} {
        lappend blah Away
    } elseif {[string index [lindex $header end] 0] == "H"} {
        lappend blah Here
    }
    lappend blah "$hops hops"
    if [string match "*\\\**" [lindex $header end]] {
        lappend blah Oper
    }
    set line [string range $line [expr [string length $hops] + 1] end]
    Echo .0 "\[ who \] [lindex $header 7]![lindex $header 4]@[lindex $header 5] ($line) on [lindex $header 6], [join $blah ", "]" who default
}

proc raw_353 {header line} {
    global names info last
    set chan [string tolower [lindex $header end]]
    if [info exists info(window,$chan)] {
        if {$last != "353"} {
            array unset names $chan,*
        }
        foreach nick [split $line] {
            if {$nick == ""} {
                continue
            }
            if {[string index $nick 0] == "@"} {
                set nick [string trimleft $nick @]
                set names($chan,[string tolower $nick],o) $nick
            } elseif {[string index $nick 0] == "+"} {
                set nick [string trimleft $nick +]
                set names($chan,[string tolower $nick],v) $nick
            } else {
                set names($chan,[string tolower $nick],n) $nick
            }
            lappend names($chan,[string tolower $nick],a) $nick
        }
    } else {
        Echo .0 "( 353 ) $chan: $line" numeric
    }
}

proc raw_365 {header line} {
}

proc raw_364 {header line} {
    if ![winfo exists .links] {
        global prefs
        toplevel .links -bd 1 -relief raised
        wm geom .links [expr round([winfo width .0] * .70)]x[expr round([winfo height .0] * .80)]
        wm title .links "RoxIRC Links"
        wm iconname .links "Links [lindex $header end] \[RoxIRC\]"
        frame .links.top
        frame .links.buttons -bd 1 -relief raised
        pack .links.buttons -side bottom -fill x -ipady 3
        pack .links.top -ipadx 1 -ipady 1 -side top -fill both -expand 1
        scrollbar .links.top.scrolly -orient v -command ".links.top.list yview"
        scrollbar .links.top.scrollx -orient h -command ".links.top.list xview"
        listbox .links.top.list -bd 1 -relief sunken -font fixed -yscrollcommand ".links.top.scrolly set" -xscrollcommand ".links.top.scrollx set"
        bind .links.top.list <Double-Button-1> "command_server .0 \[lindex \[split \[.links.top.list get \[.links.top.list curselection\]\]\] 0\]"
        bind .links <Escape> ".links.buttons.done invoke"
        pack .links.top.scrolly -side right -fill y
        pack .links.top.scrollx -side bottom -fill x
        pack .links.top.list -fill both -expand 1
        button .links.buttons.save -text Save -command "SaveListbox .links.top.list" -bd 1
        button .links.buttons.done -text Done -command "destroy .links" -bd 1
        pack .links.buttons.save -side left -padx 5
        pack .links.buttons.done -side right -padx 5
    }
    set num [expr [lindex [split $line] 0] * 3]
    set line [join [string range [split $line] 2 end]]
    set blah ""
    while {$num > [string length $blah]} {
        append blah "   "
    }
    .links.top.list insert 0 " $blah[lindex $header 3] ($line)"
}

proc raw_366 {header line} {
    global info
    set chan [string tolower [lindex $header end]]
    if [info exists info(window,$chan)] {
        ListFill $info(window,$chan)
        UpdateTitle $info(window,$chan)
    }
}

proc raw_367 {header line} {
    global banlist
    lappend banlist "[lrange $header 4 end] $line b"
}

proc raw_348 {header line} {
    global banlist
    lappend banlist "[lrange $header 4 end] $line e"
}

proc BanWindow {chan} {
    global banlist info prefs
    if ![info exists banlist] {
        if [info exists info(window,$chan)] {
            Echo $info(window,$chan) "\[ channel \] Banlist is empty" channel default
        } else {
            Echo .0 "\[ channel \] $chan banlist is empty" channel default
        }
        return
    }
    if [winfo exists .bans] {
        if {$chan == [lindex [split [wm title .bans]] 2]} {
            set geom [winfo geometry .bans]
        }
        destroy .bans
    }
    toplevel .bans -bd 1 -relief raised
    wm title .bans "RoxIRC Banlist $chan"
    wm iconname .bans "Banlist $chan \[RoxIRC\]"
    bind .bans <Return> ".bans.buttons.ok invoke"
    bind .bans <Escape> ".bans.buttons.cancel invoke"
    frame .bans.top
    frame .bans.mid
    frame .bans.buttons -bd 1 -relief raised
    pack .bans.buttons -side bottom -fill x -ipady 3
    pack .bans.mid -side bottom -fill x -ipady 3
    pack .bans.top -ipadx 1 -ipady 1 -side top -fill both -expand 1
    scrollbar .bans.top.scrolly -orient v -command ".bans.top.list yview"
    scrollbar .bans.top.scrollx -orient h -command ".bans.top.list xview"
    listbox .bans.top.list -bd 1 -relief sunken -font fixed -yscrollcommand ".bans.top.scrolly set" -xscrollcommand ".bans.top.scrollx set" -selectmode extended
    listbox .bans.top.old
    label .bans.mid.num -bd 1 -relief sunken -text " [llength $banlist] bans "
    button .bans.mid.del -text "Remove" -command "DoBanWindow 0" -bd 1
    pack .bans.top.scrolly -side right -fill y
    pack .bans.top.scrollx -side bottom -fill x
    pack .bans.top.list -fill both -expand 1
    pack .bans.mid.num -side left -padx 5
    pack .bans.mid.del -side left -padx 10
    button .bans.buttons.ok  -default active -text "Ok" -command "DoBanWindow $chan" -bd 1
    button .bans.buttons.cancel -text "Cancel" -command "destroy .bans" -bd 1
    pack .bans.buttons.ok -side left -padx 5
    pack .bans.buttons.cancel -side right -padx 5
    set l1 0
    set l2 0
    foreach x $banlist {
        if {[string length [lindex $x 0]] > $l1} {
            set l1 [string length [lindex $x 0]]
        }
        if {[string length [lindex $x 1]] > $l2} {
            set l2 [string length [lindex $x 1]]
        }
        .bans.top.old insert end [lindex $x 0]
    }
    foreach x $banlist {
        .bans.top.list insert end [format " %s  %-${l1}s  %-${l2}s  %s"  [lindex $x 3] [lindex $x 0] [lindex $x 1] [clock format [lindex $x 2] -format "%m/%d/%y %H:%M:%S"]]
    }
    unset banlist
    wm withdraw .bans
    update idletasks
    if [info exists geom] { 
        wm geom .bans $geom
    } elseif [info exists info(window,$chan)] {
        set win $info(window,$chan)
        set cw [winfo width $win]
        set ch [winfo height $win]
        set bw [expr round($cw * .666)]
        set bh [expr round($ch * .666)]
        set x [expr (($cw / 2) + [winfo rootx $win]) - ($bw / 2)]
        set y [expr (($ch / 2) + [winfo rooty $win]) - ($bh / 2)]
        wm geom .bans ${bw}x$bh+$x+$y
    } else {
        wm geom .bans [expr round([winfo width .0] * .666)]x[expr round([winfo height .0] * .666)]
    }
    wm deiconify .bans
}

proc raw_349 {header line} {
    BanWindow [string tolower [lindex $header end]]
}

proc raw_368 {header line} {
    BanWindow [string tolower [lindex $header end]]
}

proc raw_369 {header line} {
}

proc raw_372 {header line} {
    global showmotd
    if ![info exists showmotd] {
        Echo .0 "\[ motd \] $line" motd default
    }
}

proc raw_375 {header line} {
    global showmotd
    if ![info exists showmotd] {
        Echo .0 "\[ motd \] $line" motd default
    }
}

proc raw_376 {header line} {
    global showmotd
    if [info exists showmotd] {
        unset showmotd
    } else {
        Echo .0 "\[ motd \] $line" motd default
    }
}

proc raw_377 {header line} {
    global showmotd
    if ![info exists showmotd] {
        Echo .0 "\[ motd \] $line" motd default
    }
}

proc raw_401 {header line} {
    Echo .0 "( 401 ) [lindex $header end] $line" numeric
    Send "WHOWAS [lindex $header end]"
}

proc raw_404 {header line} {
    global info
    set chan [lindex $header end]
    if [info exists info(window,$chan)] {
        if [string match *m* [lindex [$info(window,$chan).top.modes cget -text] 0]] {
            Echo $info(window,$chan) "\[ channel \] Cannot send to channel" channel default
        } else {
            Echo $info(window,$chan) "\[ channel \] [lindex $header 0] is desynched (cannot send to channel)" channel default
        }
    } else {
        Echo .0 "\[ channel \] $chan cannot send to channel" channel default
    }
}

proc raw_422 {header line} {
    global showmotd
    if [info exists showmotd] {
        unset showmotd
    }
    Echo .0 "\[ motd \] No MOTD" motd default
}

proc raw_431 {header line} {
    global info connecting
    if {[info exists connecting] && [lindex $header 2] == "*" || [lindex $header 2] == ""} {
        Echo .0 "\[ info \] All your nicks are in use, choose a new one" info default
        .0.bottom.cmdline delete 0 end
        .0.bottom.cmdline insert 0 "/nick "
    } else {
        Echo .0 "( 431 ) $line" numeric
    }
}

proc raw_433 {header line} {
    global info prefs ume me connecting
    if {[lindex $header 2] == "*"} {
        if {$ume != "-" && [info exists connecting] && [lindex $header end] != $ume} {
            Send "NICK $ume"
        } elseif {[set index [lsearch -exact $prefs(nick) [escape [lindex $header end]]]] != "-1"} {
            Send "NICK [unescape [lindex $prefs(nick) [expr $index + 1]]]"
        } else {
            Send "NICK [unescape [lindex $prefs(nick) 0]]"
        }
    } else {
        Echo .0 "( 433 ) [lindex $header end] $line" numeric
    }
}

proc raw_437 {header line} {
    global info
    set chan [lindex $header end]
    if [info exists info(window,$chan)] {
        Echo $info(window,$chan) "\[ channel \] $line"
        after 30000 [list Send "JOIN $chan"]
    } else {
        Echo .0 "\[ channel \] $chan $line"
    }
}

proc raw_443 {header line} {
    Echo .0 "\[ invite \] [lindex $header 3] $line [lindex $header 4]" invite default
}

proc raw_471 {header line} {
    global info
    set channel [lindex $header end]
    Echo .0 "\[ channel \] Cannot join $channel: full" channel default
    if [info exists info(window,$channel)] {
        DeleteChannel $channel $info(window,$channel)
    }
}

proc raw_473 {header line} {
    global info
    set channel [lindex $header end]
    Echo .0 "\[ channel \] Cannot join $channel: not invited" channel default
    if [info exists info(window,$channel)] {
        DeleteChannel $channel $info(window,$channel)
    }
}

proc raw_474 {header line} {
    global info
    set channel [lindex $header end]
    Echo .0 "\[ channel \] Cannot join $channel: banned" channel default
    if [info exists info(window,$channel)] {
        DeleteChannel $channel $info(window,$channel)
    }
}

proc raw_475 {header line} {
    global info
    set channel [lindex $header end]
    Echo .0 "\[ channel \] Cannot join $channel: wrong key" channel default
    if [info exists info(window,$channel)] {
        DeleteChannel $channel $info(window,$channel)
    }
}

proc raw_JOIN {header line} {
    global ume info names
    set channel [string tolower $line]
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    if [mnc $ume $nick] {
        if ![info exists info(window,$channel)] {
            CreateChannel $channel
        } else {
            $info(window,$channel).middle.left.topic configure -state normal
            $info(window,$channel).middle.left.topic delete 0 end
            $info(window,$channel).middle.left.topic configure -state disabled
        }
        Send "MODE $channel"
        global join$channel
        set join$channel ""
        after 30000 [list catch "global join$channel ; unset join$channel"]
    } elseif [winfo exists $info(window,$channel)] {
        set names($channel,[string tolower $nick],a) $nick
        ListAdd $info(window,$channel) $nick
        ialadd $channel $nick $address
    }
    Echo $info(window,$channel) "\[ join \] $nick ($address)" join default
    Event join 1 "nick nick address address channel channel" $channel $nick!$address
}

proc Event {name num upvars args} {
    global on
    if ![info exists on($name)] {
        return
    }
    eval upvar $upvars
    global server me away prefs
    foreach x $on($name) {
        if [catch {
            set eval 1
            for {set i 0} {$i <= $num} {incr i} {
                if ![match [subst -nobackslashes [lindex $x $i]] [unescape [lindex $args $i]]] {
                    set eval 0
                    break
                }
            }
            if $eval {
                set t [subst -nobackslashes -nocommands [lindex $x end]]
                after 1 [list DoEvent $name $t]
            }
        } err] {
            Echo .0 "\[ error \] Error processing $name event: $err" error default
        }
    }
}

proc DoEvent {name line} {
    if [catch {eval $line} err] {
        Echo .0 "\[ error \] Error executing $name event: $err" error default
        #global errorInfo
        #puts $errorInfo
    }
}

proc raw_PART {header line} {
    global ume info
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    set channel [string tolower $line]
    Echo $info(window,$channel) "\[ part \] $nick ($address)" part default
    Event part 1 "nick nick address address channel channel" $channel $nick!$address
    if [mnc $nick $ume] {
        DeleteChannel $channel $info(window,$channel)
    } elseif [winfo exists $info(window,$channel)] {
        DeleteUser $channel $nick
    }
}

proc raw_QUIT {header line} {
    global info netsplit prefs
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    Event quit 1 "nick nick address address line line" "$nick!$address $line"
    if {$prefs(netsplit) && [string match "*?.??*.??* ?*.??*.??*" $line] && [llength $line] == "2"} {
        catch {after cancel netsplit}
        after 4000 netsplit
        foreach x [common $nick] {
            if ![info exists netsplit($info(window,$x))] {
                Echo $info(window,$x) "\[ quit \] Netsplit: [lindex $line 0] -> [lindex $line 1]" netsplit quit default
            }
            lappend netsplit($info(window,$x)) $nick
            catch {DeleteUser $x $nick}
        }
        return
    }
    foreach x [common $nick] {
        Echo $info(window,$x) "\[ quit \] $nick ($address) ($line)" quit default
        catch {DeleteUser $x $nick}
    }
}

proc netsplit { } {
    global netsplit
    foreach x [array names netsplit] {
        Echo $x "\[ quit \] Netsplit: [join [lsort -command Sort $netsplit($x)]] ([llength $netsplit($x)])" netsplit quit default
    }
    unset netsplit
}

proc raw_PRIVMSG {header line} {
    global ume info away
    set to [string tolower [lindex $header 2]]
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    if [string match \001*\001 $line] {
        global flood
        if [info exists flood] {
            return
        }
        Ignore CTCP $nick!$address
        set line [string trim $line "\001"]
        set ctcp [lindex [rspaces [split $line]] 0]
        set line [string range $line [expr [string length $ctcp] + 1] end]
        if {[info commands ctcp_$ctcp] != ""} {
            ctcp_$ctcp $header $line
        } else {
            if [mnc $to $ume] {
                Echo .0 "\[ ctcp \] Unknown ctcp [string trim "$ctcp $line"] from $nick!$address" ctcp default
            } else {
                Echo $info(window,$to) "\[ ctcp \] Unknown ctcp [string trim "$ctcp $line"] from $nick!$address" ctcp default
            }
        }
        return
    }
    if [info exists info(window,$to)] {
        ialadd $to $nick $address
        Ignore PUBLIC $nick!$address
        Echo $info(window,$to) "<$nick> $line" none
    } elseif [mnc $ume $to] {
        Ignore MSGS $nick!$address
        if {$away && ![info exists info(query,[string tolower $nick])]} {
            Echo .0 "*$nick!$address* $line" privmsg
        } else {
            Echo [UpdateChat $nick!$address] "<$nick> $line" none
        }
    } else {
        Echo .0 "*$nick/$to* $line" privmsg
    }
    Event text 2 "nick nick address address to channel line line" $to $nick!$address $line
}

proc raw_NOTICE {header line} {
    global ume info server connecting
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    set channel [string tolower [lindex $header 2]]
    if [string match \001*\001 $line] {
        set line [string trim $line "\001"]
        set reply [lindex [rspaces [split $line]] 0]
        set line [string range $line [expr [string length $reply] + 1] end]
        if {[info commands reply_$reply] != ""} {
            reply_$reply $header $line
        } else {
            Echo .0 "\[ ctcp \] $reply reply from $nick: $line" ctcp default
        }
        return
    }
    if {[string index $channel 0] != "#" && [string index $channel 0] != "&" && [string index $channel 0] != "\$"} {
        if [string match *.* $nick] {
            if {[string range $line 0 13] == "*** Notice -- "} {
                set line [string range $line 14 end]
            } elseif {[string range $line 0 3] == "*** "} {
                set line [string range $line 4 end]
            }
            if [info exists connecting] {
                Echo .0 "\[ server \] $line" server default
            } elseif [mnc $server $nick] {
                Echo .0 "\[ snotice \] $line" snotice default
            } else {
                Echo .0 "\[ snotice \] from $nick: $line" snotice default
            }
        } elseif [info exists info(query,[string tolower $nick])] {
            Ignore NOTICES $nick!$address
            Echo $info(query,[string tolower $nick]) "+$nick+ $line" privmsg
        } else {
            Ignore NOTICES $nick!$address
            Echo .0 "+$nick+ $line" privmsg
        }
    } elseif [info exists info(window,$channel)] {
        Ignore NOTICES $nick!$address
        Echo $info(window,$channel) "-$nick- $line" none
        ialadd $channel $nick $address
    } else {
        Ignore NOTICES $nick!$address
        Echo .0 "+$nick/$channel+ $line" privmsg
    }
    Event notice 2 "nick nick address address channel channel line line" $channel $nick!$address $line
}

proc raw_MODE {header line} {
    global info ume me names
    if [mnc $ume [lindex $header 2]] {
        Echo .0 "\[ mode \] $ume sets umode $line" mode default
        Send "MODE $ume"
        return
    }
    set line "$header [list $line]"
    set channel [string tolower [lindex $line 2]]
    if ![winfo exists $info(window,$channel)] {
        return
    }
    set nick [lindex [split [lindex $line 0] !] 0]
    set address [lindex [split [lindex $line 0] !] 1]
    set mode [lindex $line 3]
    set line [lrange $line 4 end]
    Echo $info(window,$channel) "\[ mode \] $nick sets mode $mode [join $line]" mode default
    if ![string match *.* $nick] {
        ialadd $channel $nick $address
    }
    if [string match *\[sntmpikl\]* $mode] {
        Send "MODE $channel"
        set key ""
        set limit ""
        if [string match *k $mode] {
            set key [lindex $line end]
        }
        if [string match *l $mode] {
            set limit [lindex $line end]
        }
        set tmp [join [split $mode obv] ""]
        Event mode 1 "nick nick address address channel channel tmp mode key key limit limit" $channel $nick!$address
    }
    if [string match *\[ovb\]* $mode] {
        set tmp [join [split $mode +-] ""]
        set l [llength $line]
        set t [string length $tmp]
        for {set i 0} {$i < $l} {incr i} {
            set arg [lindex $line $i]
            set char [string index $tmp [expr $t - $l + $i]]
            set string [place $mode $char $i]
            if {[string last + $string] > [string last - $string]} {
                set val +
            } else {
                set val -
            }
            switch -exact -- ${val}${char} {
                +o {
                    if ![IsOp $channel $arg] {
                        set names($channel,[string tolower $arg],o) $arg
                        ListChange $info(window,$channel) $arg $arg
                        Event op 2 "nick nick address address channel channel arg onick" $channel $nick!$address $arg
                    }
                }
                -o {
                    if [IsOp $channel $arg] {
                        catch {unset names($channel,[string tolower $arg],o)}
                        ListChange $info(window,$channel) $arg $arg
                        Event deop 2 "nick nick address address channel channel arg onick" $channel $nick!$address $arg
                    }
                }
                +v {
                    if ![IsVoice $channel $arg] {
                        set names($channel,[string tolower $arg],v) $arg
                        ListChange $info(window,$channel) $arg $arg
                        Event voice 2 "nick nick address address channel channel arg vnick" $channel $nick!$address $arg
                    }
                }
                -v {
                    if [IsVoice $channel $arg] {
                        catch {unset names($channel,[string tolower $arg],v)}
                        ListChange $info(window,$channel) $arg $arg
                        Event devoice 2 "nick nick address address channel channel arg vnick" $channel $nick!$address $arg
                    }
                }
                +b {
                    Event ban 1 "nick nick address address channel channel arg ban" $channel $nick!$address
                }
                -b {
                    Event unban 2 "nick nick address address channel channel arg ban" $channel $nick!$address
                }
            }
        }
    }
    if [match *$me* [unescape $line]] {
        UpdateTitle $info(window,$channel)
    }
}

proc raw_NICK {header line} {
    global ume info names me
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    Event nick 1 "nick nick address address line newnick" $nick!$address $line
    set nick2 [string tolower $nick]
    foreach x [common $nick] {
        if [IsOp $x $nick] {
            unset names($x,$nick2,o)
            set names($x,[string tolower $line],o) $line
        }
        if [IsVoice $x $nick] {
            unset names($x,$nick2,v)
            set names($x,[string tolower $line],v) $line
        }
        unset names($x,$nick2,a)
        set names($x,[string tolower $line],a) $line
        Echo $info(window,$x) "\[ nick \] $nick is now known as $line" nick default
        catch {ListChange $info(window,$x) $nick $line}
        ialdel $x $nick
        ialadd $x $line $address
    }
    if [mnc $nick $ume] {
        set me [escape $line]
        set ume $line
        UpdateAllTitles
        Echo .0 "\[ nick \] Your nick is now $line" nick default
    }
    if [info exists info(query,$nick2)] {
        set win $info(query,$nick2)
        unset info(query,$nick2)
        set info(query,[string tolower $line]) $win
        set info(nick,$win) $line
        wm title $win "RoxIRC Query $line"
        wm iconname $win "Query $line \[RoxIRC\]"
    }
}

proc raw_KICK {header line} {
    global ume info away
    set channel [string tolower [lindex $header 2]]
    if ![winfo exists $info(window,$channel)] {
        return
    }
    set knick [lindex $header end]
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    Event kick 2 "nick nick address address channel channel knick knick" $channel $nick!$address $knick
    if [mnc $knick $ume] {
        set win $info(window,$channel)
        Echo $win "\[ kick \] You were kicked from $channel by $nick!$address ($line)" kick default
        DeleteChannel $channel $win
        if !$away {
            dialog ${win}kick "RoxIRC Rejoin?" "You were kicked from $channel\nDo you want to rejoin?" kickrejoin 1 "Yes [list 1 $channel]" "No 0"
        }
    } else {
        DeleteUser $channel $knick
        Echo $info(window,$channel) "\[ kick \] $nick kicked $knick ($line)" kick default
    }
}

proc kickrejoin {args} {
    if {[lindex $args 0] == "1"} {
        Send "JOIN [lindex $args 1]"
    }
}

proc raw_TOPIC {header line} {
    global info on
    set channel [string tolower [lindex $header end]]
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    if {$line == ""} {
        Echo $info(window,$channel) "\[ topic \] $nick has removed the topic" topic default
    } else {
        Echo $info(window,$channel) "\[ topic \] $nick has changed the topic to \"$line\"" topic default
    }
    catch {
        $info(window,$channel).middle.left.topic configure -state normal
        $info(window,$channel).middle.left.topic delete 0 end
        $info(window,$channel).middle.left.topic insert end $line
        $info(window,$channel).middle.left.topic configure -state disabled
    }
    Event topic 1 "nick nick address address channel channel line line" $channel $nick!$address
}

proc raw_INVITE {header line} {
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    Ignore INVITES $nick!$address
    Echo .0 "\[ invite \] $nick!$address invites you to $line" invite default
    Event invite 1 "nick nick address address line channel" $nick!$address $line
}

proc raw_WALLOPS {header line} {
    Echo .0 "\[ wallops \] $line" wallops default
}

proc raw_AUTH {header line} {
    if {[string range $line 0 3] == "*** "} {
        set line [string range $line 4 end]
    }
    Echo .0 "\[ server \] $line" server default
}

proc raw_KILL {header line} {
    Echo all "\[ kill \] You were killed by [lindex $header 0] $line" kill default
}

proc raw_PONG {header line} {
    Echo .0 "\[ server \] Ping reply from [lindex $header 0]: [format %0.2f [expr ([clock clicks -milliseconds] - $line) / 1000.000]]s" server default
}

proc raw_* {header line} {
    global ume
    Parse "[lindex [split $header] 0] $ume :$line"
}

proc LogMenu {win} {
    global options prefs env
    if $options(log,$win) {
        set options(log,$win) 0
        set fn [tk_getSaveFile -title "RoxIRC Logfile" -initialdir $prefs(defaultlogdir) -filetypes {{All *} {Logs *.log} {Text *.txt}}]
        if {$fn == ""} {
            return
        }
        if [catch {open $fn a} fh] {
            Echo $win "\[ error \] Cannot open $fn for writing: [geterror $fh]" error default
            return
        } else {
            fconfigure $fh -buffersize 2048
            set options(ln,$win) $fn
            set options(lfh,$win) $fh
            set options(log,$win) 1
            puts $fh "Logging started on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"
            Echo $win "\[ info \] Now logging to $options(ln,$win)" info default
        }
    } else {
        puts $options(lfh,$win) "Logging stopped on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"
        close $options(lfh,$win)
        unset options(lfh,$win)
        unset options(ln,$win)
        Echo $win "\[ info \] Logging stopped" info default
    }
}

proc DoTimer {num delay left times command} {
    global me server away info prefs
    if [catch {eval $command} error] {
        Echo .0 "\[ error \] Error in timer $num: $error" error default
        return
    }
    if {$times == "0"} {
        incr left
        after $delay [list DoTimer $num $delay $left $times $command]
    } elseif {$left > 1} {
        incr left -1
        after $delay [list DoTimer $num $delay $left $times $command]
    }
}

proc DoBanWindow {type} {
    switch -exact -- $type {
        0 {
            set tmp ""
            foreach x [.bans.top.list curselection] {
                set tmp [linsert $tmp 0 $x]
            }
            foreach x $tmp {
                .bans.top.list delete $x
            }
        }
        default {
            set bans ""
            set tmp [.bans.top.list get 0 end]
            set old [.bans.top.old get 0 end]
            set new ""
            foreach x $tmp {
                lappend new [lindex [split $x] 3]
            }
            foreach x $old {
                if {[lsearch -exact $new $x] == "-1"} {
                    lappend bans $x
                }
                if {[llength $bans] == "4"} {
                    Send "MODE $type -bbbb [join $bans]"
                    set bans ""
                }
            }
            if {[llength $bans] == "3"} {
                Send "MODE $type -bbb [join $bans]"
            } elseif {[llength $bans] == "2"} {
                Send "MODE $type -bb [join $bans]"
            } elseif {[llength $bans] == "1"} {
                Send "MODE $type -b [join $bans]"
            }
            destroy .bans
        }
    }
}

proc KickWindow {win address} {
    global prefs info userhost
    set nick [lindex [split $address !] 0]
    if {$nick == $address} {
        set address [address $nick]
    }
    if {$address == ""} {
        Echo $win "\[ info \] Getting users address..." info default
        set userhost([string tolower $nick]) "KickWindow $win \$address"
        Send "USERHOST $nick"
        return
    }
    if [winfo exists .kb] {
        destroy .kb
    }
    toplevel .kb -bd 1 -relief raised
    wm title .kb "RoxIRC Kick/Ban $nick from $info(channel,$win)"
    wm iconname .kb "Kick/Ban \[RoxIRC\]"
    bind .kb <Escape> ".kb.buttons.cancel invoke"
    bind .kb <Return> ".kb.buttons.bk invoke"
    frame .kb.buttons -relief raised -bd 1
    frame .kb.left
    frame .kb.right
    frame .kb.left.top
    frame .kb.right.top
    frame .kb.left.bottom
    frame .kb.right.bottom
    pack .kb.buttons -side bottom -fill x -ipady 3
    pack .kb.left -side left -fill both -expand 1
    pack .kb.right -side right -fill both -expand 1
    pack .kb.left.bottom -fill x -side bottom
    pack .kb.right.bottom -fill x -side bottom
    pack .kb.left.top -fill both -expand 1 -side top
    pack .kb.right.top -fill both -expand 1 -side top
    listbox .kb.left.top.list -exportselection 0
    listbox .kb.right.top.list -exportselection 0
    foreach x "1 2 3 4 5 6" {
        .kb.left.top.list insert end " [AddressMask $address $x]"
    }
    foreach x $prefs(kick) {
        .kb.right.top.list insert end " $x "
    }
    label .kb.left.bottom.label -text "Ban: "
    label .kb.right.bottom.label -text "Kick: "
    entry .kb.left.bottom.entry
    entry .kb.right.bottom.entry
    button .kb.buttons.ban -text "Ban" -command [list DoKickWindow $info(channel,$win) $nick 0] -bd 1
    button .kb.buttons.bk -default active -text "Ban/Kick" -command [list DoKickWindow $info(channel,$win) $nick 1] -bd 1
    button .kb.buttons.kick -text "Kick" -command [list DoKickWindow $info(channel,$win) $nick 2] -bd 1
    #checkbutton .kb.buttons.ignore -text "Ignore"
    button .kb.buttons.cancel -text "Cancel" -command "destroy .kb" -bd 1
    pack .kb.buttons.ban -side left -padx 5
    pack .kb.buttons.bk -side left
    pack .kb.buttons.kick -side left -padx 5
    #pack .kb.buttons.ignore -side left
    pack .kb.buttons.cancel -side right -padx 5
    pack .kb.left.top.list -fill both -expand 1
    pack .kb.right.top.list -fill both -expand 1
    pack .kb.left.bottom.label -side left
    pack .kb.left.bottom.entry -side left -expand 1 -fill x
    pack .kb.right.bottom.label -side left
    pack .kb.right.bottom.entry -side left -expand 1 -fill x
    bind .kb.left.top.list <ButtonRelease> ".kb.left.bottom.entry delete 0 end ; .kb.left.bottom.entry insert end \[string trim \[.kb.left.top.list get \[.kb.left.top.list curselection\] \] \]"
    bind .kb.right.top.list <ButtonRelease> ".kb.right.bottom.entry delete 0 end ; .kb.right.bottom.entry insert end \[string trim \[.kb.right.top.list get \[.kb.right.top.list curselection\] \] \]"
    wm withdraw .kb
    update idletasks
    set cw [winfo width $win]
    set ch [winfo height $win]
    set bw [expr round($cw * .6)]
    set bh [expr round($ch * .6)]
    set x [expr (($cw / 2) + [winfo rootx $win]) - ($bw / 2)]
    set y [expr (($ch / 2) + [winfo rooty $win]) - ($bh / 2)]
    wm geom .kb ${bw}x$bh+$x+$y
    wm deiconify .kb
}

proc DoKickWindow {chan nick type} {
    global info
    set ban [.kb.left.bottom.entry get]
    set kick [.kb.right.bottom.entry get]
    destroy .kb
    if {$type == "0" || $type == "1"} {
        if {$ban != ""} {
            Send "MODE $chan -o+b $nick $ban"
        }
    }
    if {$type == "1" || $type == "2"} {
        Send "KICK $chan $nick :$kick"
    }
    #if {$ignore && $ban != ""} {
    #    command_ignore .0 $ban
    #}
}

proc ModeWindow {win} {
    global info prefs mmode
    if ![info exists info(channel,$win)] {
        return
    }
    set chan $info(channel,$win)
    set blah [lindex [split [$win.top.modes cget -text]] 0]
    if [winfo exists .mode] {
        destroy .mode
    }
    toplevel .mode -bd 1 -relief raised
    wm title .mode "RoxIRC Modes $chan"
    wm iconname .mode "Modes $chan \[RoxIRC\]"
    frame .mode.left -bd 1 -relief sunken
    frame .mode.right -bd 1 -relief sunken
    frame .mode.kl
    frame .mode.buttons -bd 1 -relief raised
    pack .mode.buttons -side bottom -fill x
    pack .mode.kl -side bottom -fill x
    pack .mode.left -side left -fill both -padx 3 -pady 3 -expand 1
    pack .mode.right -side right -fill both -padx 3 -pady 3 -expand 1
    foreach x "n t s" {
        frame .mode.left.$x -relief raised -bd 1
        pack .mode.left.$x -padx 3 -pady 3 -side top -fill x
        checkbutton .mode.left.$x.cb -font fixed -text " $x   " -variable mmode($x)
        if {[string first $x $blah] != "-1"} {
            .mode.left.$x.cb select
        }
        pack .mode.left.$x.cb -side left -padx 2 -pady 2
    }
    foreach x "i m p" {
        frame .mode.right.$x -relief raised -bd 1
        pack .mode.right.$x -padx 3 -pady 3 -side top -fill x
        checkbutton .mode.right.$x.cb -font fixed -text " $x   " -variable mmode($x)
        if {[string first $x $blah] != "-1"} {
            .mode.right.$x.cb select
        }
        pack .mode.right.$x.cb -side left -padx 2 -pady 2
    }
    label .mode.kl.k -text "k"
    label .mode.kl.l -text "l"
    entry .mode.kl.le -width 10
    entry .mode.kl.ke -width 10
    set mmode(k) ""
    set mmode(l) ""
    switch -exact -- [string index $blah [expr [string length $blah] - 1]] {
        k {
            .mode.kl.ke insert end [lindex [split [$win.top.modes cget -text]] end]
            set mmode(k) [lindex [split [$win.top.modes cget -text]] end]
        }
        l {
            .mode.kl.le insert end [lindex [split [$win.top.modes cget -text]] end]
            set mmode(l) [lindex [split [$win.top.modes cget -text]] end]
        }
    }
    switch -exact -- [string index $blah [expr [string length $blah] - 2]] {
        k {
            .mode.kl.ke insert end [lindex [split [$win.top.modes cget -text]] 1]
            set mmode(k) [lindex [split [$win.top.modes cget -text]] 1]
        }
        l {
            .mode.kl.le insert end [lindex [split [$win.top.modes cget -text]] 1]
            set mmode(l) [lindex [split [$win.top.modes cget -text]] 1]
        }
    }
    pack .mode.kl.k -side left -pady 3 -padx 2
    pack .mode.kl.ke -side left -pady 3 -padx 4 -expand 1 -fill x
    pack .mode.kl.le -side right -pady 3 -padx 4 -expand 1 -fill x
    pack .mode.kl.l -side right -pady 3 -padx 2
    button .mode.buttons.ok -default active -text "Ok" -command [list DoModeWindow $chan] -bd 1
    button .mode.buttons.cancel -text "Cancel" -command "DoModeWindow 0" -bd 1
    pack .mode.buttons.ok -side left -padx 5 -pady 2
    pack .mode.buttons.cancel -side right -padx 5 -pady 2
    bind .mode <Return> ".mode.buttons.ok invoke"
    bind .mode <Escape> ".mode.buttons.cancel invoke"
    wm withdraw .mode
    update idletasks
    set cw [winfo width $win]
    set ch [winfo height $win]
    set x [expr (($cw / 2) + [winfo rootx $win]) - ([winfo reqwidth .mode] / 2)]
    set y [expr (($ch / 2) + [winfo rooty $win]) - ([winfo reqheight .mode] / 2)]
    wm geom .mode +$x+$y
    wm deiconify .mode
}

proc DoModeWindow {chan} {
    global mmode
    if {$chan == "0"} {
        destroy .mode
        unset mmode
        return
    }
    set l [.mode.kl.le get]
    set k [.mode.kl.ke get]
    destroy .mode
    set tmp(-) ""
    set tmp(+) ""
    set blah ""
    foreach x "n t m i s p" {
        if $mmode($x) {
            append tmp(+) $x
        } else {
            append tmp(-) $x
        }
    }
    if {$l != "" && $mmode(l) != $l} {
        append blah "+l $l"
    } elseif {$l == ""} {
        append tmp(-) l
    }
    if {$mmode(k) != "" && $mmode(k) != $k} {
        Send "MODE $chan -k $mmode(k)"
    }
    if {$k != "" && $mmode(k) != $k} {
        Send "MODE $chan +k $k"
    }
    if {$blah != ""} {
        Send "MODE $chan $blah"
    }
    Send "MODE $chan -$tmp(-)"
    Send "MODE $chan +$tmp(+)"
    unset mmode
}

proc NotifyWindow { } {
    if [winfo exists .notify] {
        wm deiconify .notify
        raise .notify
        return
    }
    global notify
    toplevel .notify -bd 1 -relief raised
    wm title .notify "RoxIRC Notify List"
    wm iconname .notify "Notify List \[RoxIRC\]"
    frame .notify.bottom -bd 1 -relief raised
    button .notify.bottom.delete -bd 1 -text Remove -command "DoNotifyWindow remove"
    entry .notify.bottom.entry -width 10
    bind .notify.bottom.entry <Return> "DoNotifyWindow add"
    label .notify.bottom.label -text "Add:"
    pack .notify.bottom -side bottom -fill x -ipadx 4 -ipady 3
    pack .notify.bottom.label -side left -padx 5
    pack .notify.bottom.entry -side left
    pack .notify.bottom.delete -side right -padx 4
    frame .notify.top
    scrollbar .notify.top.scrollx -orient h -command ".notify.top.list xview"
    scrollbar .notify.top.scrolly -orient v -command ".notify.top.list yview"
    listbox .notify.top.list -bd 1 -yscrollcommand ".notify.top.scrolly set" -xscrollcommand ".notify.top.scrollx set" -font fixed
    bind .notify.top.list <Double-Button-1> "DoNotifyWindow double"
    bind .notify <Escape> "destroy .notify"
    pack .notify.top -side top -fill both -expand 1
    pack .notify.top.scrolly -side right -fill y
    pack .notify.top.scrollx -side bottom -fill x
    pack .notify.top.list -expand 1 -fill both
    DoNotifyWindow refresh
    wm withdraw .notify
    update idletasks
    wm geom .notify [expr round([winfo width .0] * .700)]x[expr round([winfo height .0] * .700)]
    wm deiconify .notify
}

proc DoNotifyWindow {cmd} {
    if ![winfo exists .notify] {
        return
    }
    switch -exact -- $cmd {
        refresh {
            global notify prefs
            .notify.top.list delete 0 end
            set l1 0
            foreach x $notify(on) {
                if {[string length $x] > $l1} {
                    set l1 [string length $x]
                }
            }
            .notify.top.list insert end "   Online:"
            foreach x [lsort -command Sort $notify(on)] {
                .notify.top.list insert end [format " %s   %-${l1}s  %s" \[[clock format [lindex $notify([string tolower $x]) 1] -format "%H:%M"]\] $x [lindex $notify([string tolower $x]) 0]]
            }
            .notify.top.list insert end "" "   Offline:"
            foreach x [lsort -command Sort $prefs(notify)] {
                if {[lsearch -exact [string tolower $notify(on)] [string tolower $x]] == "-1"} {
                    .notify.top.list insert end " \[--:--\]   [unescape $x]"
                }
            }
        }
        add {
            set nick [.notify.bottom.entry get]
            if {$nick == ""} {
                return
            }
            .notify.bottom.entry delete 0 end
            command_notify .0 $nick
        }
        remove {
            set tmp [.notify.top.list curselection]
            if {$tmp == ""} {
                return
            }
            set line [.notify.top.list get $tmp]
            if ![string match "   *line:" $line] {
                command_notify .0 -[lindex [split $line] 4]
            }
        }
        double {
            set tmp [.notify.top.list curselection]
            if {$tmp == ""} {
                return
            }
            set nick [.notify.top.list get $tmp]
            if ![string match "   *line:" $nick] {
                command_query .0 [lindex [split $nick] 4]
            }
        }
    }
}

proc UrlWindow { } {
    if [winfo exists .urls] {
        wm deiconify .notify
        raise .urls
        return
    }
    global urls
    toplevel .urls -bd 1 -relief raised
    wm title .urls "RoxIRC URL List"
    wm iconname .urls "URLs \[RoxIRC\]"
    frame .urls.bottom -bd 1 -relief raised
    button .urls.bottom.save -bd 1 -text Save -command "SaveListbox .urls.top.list"
    button .urls.bottom.last -bd 1 -text Last -command "DoUrlWindow last"
    button .urls.bottom.delete -bd 1 -text Delete -command "DoUrlWindow delete"
    button .urls.bottom.clear -bd 1 -text Clear -command "DoUrlWindow clear"
    pack .urls.bottom.save -side left -padx 4
    pack .urls.bottom.last -side left -padx 4
    pack .urls.bottom -side bottom -fill x -ipadx 4 -ipady 3
    pack .urls.bottom.delete -side right -padx 4
    pack .urls.bottom.clear -side right -padx 4
    frame .urls.top
    scrollbar .urls.top.scrollx -orient h -command ".urls.top.list xview"
    scrollbar .urls.top.scrolly -orient v -command ".urls.top.list yview"
    listbox .urls.top.list -bd 1 -yscrollcommand ".urls.top.scrolly set" -xscrollcommand ".urls.top.scrollx set" -selectmode extended -font fixed
    bind .urls.top.list <Double-Button-1> "DoUrlWindow double"
    bind .urls <Escape> "destroy .urls"
    pack .urls.top -side top -fill both -expand 1
    pack .urls.top.scrolly -side right -fill y
    pack .urls.top.scrollx -side bottom -fill x
    pack .urls.top.list -expand 1 -fill both
    foreach x $urls {
        .urls.top.list insert end [format " %s  %-15s  %s" [clock format [lindex $x 0] -format "%m/%d/%y %H:%M"] [lindex $x 1] [lindex $x 2]]
    }
    wm withdraw .urls
    update idletasks
    wm geom .urls [expr round([winfo width .0] * .900)]x[expr round([winfo height .0] * .700)]
    wm deiconify .urls
}

proc DoUrlWindow {cmd args} {
    global prefs urls
    switch -exact -- $cmd {
        double {
            set tmp [.urls.top.list curselection]
            if {$tmp == ""} {
                return
            }
            set url [lindex [.urls.top.list get [lindex $tmp 0]] 3]
            eval exec [replace $prefs(urlcommand) \$url $url] &
        }
        last {
            .urls.top.list selection clear 0 end
            .urls.top.list selection set end
            .urls.top.list see end
            DoUrlWindow double
        }
        clear {
            set urls ""
            .urls.top.list delete 0 end
        }
        delete {
            set tmp ""
            foreach x [.urls.top.list curselection] {
                set tmp [linsert $tmp 0 $x]
            }
            foreach x $tmp {
                .urls.top.list delete $x
                set urls [lreplace $urls $x $x]
            }
        }
        add {
            set url [lindex $urls end]
            .urls.top.list insert end [format " %s  %-15s  %s" [clock format [lindex $url 0] -format "%m/%d/%y %H:%M"] [lindex $url 1] [lindex $url 2]]
        }
    }
}

proc notifyon {nick address time} {
    global notify
    Echo .0 "\[ notify \] Signon by $nick ($address) at [clock format $time -format "%H:%M"]" notify default
    set notify([string tolower $nick]) "$address $time"
    DoNotifyWindow refresh
    Event notify 0 "nick nick address address" $nick!$address
}

proc raw_ {header line} {
    switch -- $header {
        ERROR {Echo .0 "\[ server \] $line" server default}
    }
}

proc auth {fh pass} {
    global prefs ume tcl_platform
    fileevent $fh writable {}
    if {$pass != ""} {
        catch {puts $fh "PASS $pass"}
    }
    if {$ume != "-"} {
         set nick "NICK $ume"
    } else {
        set nick "NICK [unescape [lindex $prefs(nick) 0]]"
    }
    catch {
        puts $fh "USER $tcl_platform(user) host domain :$prefs(name)"
        puts $fh $nick
    }
}

proc reply_PING {header line} {
    upvar nick nick address address
    if [catch {expr ([clock clicks -milliseconds] - $line) / 1000.000} time] {
         Echo .0 "\[ ctcp \] Invalid PING reply from $nick!$address: $line" ctcp default
    } else {
         Echo .0 "\[ ctcp \] PING reply from $nick: [format %0.2f $time]s" ctcp default
    }
}

proc ctcp_PING {header line} {
    global ume info
    set who [lindex $header 0]
    set nick [lindex [split $who !] 0]
    if {[string length $line] > 25} {
        return
    }
    if [mnc [lindex $header 2] $ume] {
        Echo .0 "\[ ctcp \] PING from $who" ctcp default
    } else {
        Echo $info(window,[string tolower [lindex $header 2]]) "\[ ctcp \] PING by $nick" ctcp default
    }
    Send "NOTICE $nick :\001PING $line\001"
}

proc ctcp_VERSION {header line} {
    global ume info tcl_platform
    set nick [lindex [split [lindex $header 0] !] 0]
    set to [string tolower [lindex $header 2]]
    if [mnc $to $ume] {
        Echo .0 "\[ ctcp \] VERSION from [lindex $header 0]" ctcp default
    } else {
        Echo $info(window,$to) "\[ ctcp \] VERSION by $nick" ctcp default
        return
    }
#    Send "NOTICE $nick :\001VERSION RoxIRC 1.72 $tcl_platform(os) $tcl_platform(osVersion)\001"
    Send "NOTICE $nick :\001VERSION RoxIRC 1.72\001"
}

proc ctcp_CLIENTINFO {header line} {
    global ume info
    set to [lindex $header 2]
    set nick [lindex [split [lindex $header 0] !] 0]
    set ci CLIENTINFO
    set tmp [rspaces [split $line]]
    if {[lindex $tmp 0] != ""} {
        append ci " ([lindex $tmp 0])"
    }
    if [mnc $to $ume] {
        Echo .0 "\[ ctcp \] $ci from [lindex $header 0]" ctcp default
    } else {
        Echo $info(window,[string tolower $to]) "\[ ctcp \] $ci by $nick" ctcp default
        return
    }
    switch -- [string toupper [lindex $tmp 0]] {
        ACTION     {Send "NOTICE $nick :\001CLIENTINFO ACTION contains action descriptions for atmosphere\001"}
        CLIENTINFO {Send "NOTICE $nick :\001CLIENTINFO CLIENTINFO gives information about available CTCP commands\001"}
        PING       {Send "NOTICE $nick :\001CLIENTINFO PING returns the arguments it receives\001"}
        VERSION    {Send "NOTICE $nick :\001CLIENTINFO VERSION shows client type and version\001"}
        DCC        {Send "NOTICE $nick :\001CLIENTINFO DCC requests a direct_client_connection\001"}
        ""         {Send "NOTICE $nick :\001CLIENTINFO [string toupper [replace [info commands ctcp_*] ctcp_ ""]]\001"}
    }
}

proc ctcp_DCC {header line} {
    global away dcc info
    set tmp [rspaces [split $line]]
    switch -- [string tolower [lindex $tmp 0]] {
        send    {IncomingDccFile $header $line}
        chat    {IncomingDccChat $header $line}
        resume  {ResumeDccSend $header $line}
        accept  {AcceptDccResume $header $line}
        default {Echo .0 "\[ dcc \] Unknown DCC command [lindex $tmp 0] from [lindex $header 0]" dcc default}
    }
}

proc ctcp_ACTION {header line} {
    global info ume away on ial
    set nick [lindex [split [lindex $header 0] !] 0]
    set address [lindex [split [lindex $header 0] !] 1]
    set channel [string tolower [lindex $header 2]]
    if [mnc $channel $ume] {
        if {$away && ![info exists info(query,[string tolower $nick])]} {
            Echo .0 "** $nick $line" privmsg
        } else {
            Echo [UpdateChat $nick!$address] "* $nick $line" action
        }
    } else {
        Echo $info(window,$channel) "* $nick $line" action
        ialadd $channel $nick $address
    }
    Event action 2 "nick nick address address channel channel line line" $channel $nick!$address $line
}

proc ialadd {chan nick address} {
    global ial prefs
    if $prefs(ial) {
        set ial($chan,[string tolower $nick]) $nick!$address
    }
}

proc ialdel {chan nick} {
    global ial prefs
    if $prefs(ial) {
        catch {unset ial($chan,[string tolower $nick])}
    }
}

proc periodic { } {
    global prefs irc at
    if {$prefs(notify) != "" && [info exists irc]} {
        Send "ISON [join $prefs(notify)]"
    }
    after 60000 periodic
}

proc IsOp {channel nick} {
    global names info
    if {[info exists info(window,$channel)] && [info exists names($channel,[string tolower $nick],o)]} {
        return 1
    }
    return 0
}

proc IsVoice {channel nick} {
    global names info
    if {[info exists info(window,$channel)] && [info exists names($channel,[string tolower $nick],v)]} {
        return 1
    }
    return 0
}

proc IsOn {channel nick} {
    global names info ial
    if {[info exists info(window,$channel)] && [info exists names($channel,[string tolower $nick],a)]} {
        return 1
    }
    return 0
}

proc IsNum {line} {
    if [regexp ^\[0-9\]+$ $line] {
        return 1
    }
    return 0
}

proc channels { } {
    global info
    set blah ""
    foreach x [GetActiveChannelWindows] {
        lappend blah $info(channel,$x)
    }
    return $blah
}

proc common {nick} {
    global info
    set chans ""
    foreach x [GetActiveChannelWindows] {
        if [IsOn $info(channel,$x) $nick] {
            lappend chans $info(channel,$x)
        }
    }
    return $chans
}

proc address {args} {
    global ial userhost
    set nick [string tolower [lindex $args 0]]
    if {[set mask [lindex $args 1]] == ""} {
        set mask 5
    }
    set nick [replace [replace $nick \\ \\\\] \[ \\\[]
    if {[set tmp [lindex [array names ial *,$nick] 0]] != ""} {
        return [AddressMask $ial($tmp) $mask]
    }
    return ""
}

proc AddressMask {address mask} {
    switch -exact -- $mask {
        1 {
            set ident [string trimleft [lindex [split [lindex [split $address @] 0] !] 1] ~]
            set ident [string range $ident [expr [string length $ident] - 9] end]
            return *!*$ident@[lindex [split $address @] 1]
        }
        2 {
            return *!*@[lindex [split $address @] 1]
        }
        3 {
            set tmp [lindex [split $address @] 1]
            set ident [string trimleft [lindex [split [lindex [split $address @] 0] !] 1] ~]
            set ident [string range $ident [expr [string length $ident] - 9] end]
            if [regexp "^\[0-9\]+\\\.\[0-9\]+\\\.\[0-9\]+\\\.\[0-9\]+$" $tmp] {
                set domain [join "[lrange [split $tmp .] 0 2] *" .]
            } elseif {[llength [split $tmp .]] > 2} {
                set domain *.[lindex [split $tmp .] [expr [llength [split $tmp .]] - 2]].[lindex [split $tmp .] end]
            } else {
                set domain $tmp
            }
            return *!*$ident@$domain
        }
        4 {
            set tmp [lindex [split $address @] 1]
            if [regexp "^\[0-9\]+\\\.\[0-9\]+\\\.\[0-9\]+\\\.\[0-9\]+$" $tmp] {
                set domain [join "[lrange [split $tmp .] 0 2] *" .]
            } elseif {[llength [split $tmp .]] > 2} {
                set domain *.[lindex [split $tmp .] [expr [llength [split $tmp .]] - 2]].[lindex [split $tmp .] end]
            } else {
                set domain $tmp
            }
            return *!*@$domain
        }
        5 {
            return $address
        }
        6 {
            return [lindex [split $address !] 0]!*@*
        }
    }
}

proc kb {bytes} {
    if {$bytes < 1024} {
        return "$bytes bytes"
    }
    if {$bytes >= 1048576} {
        return [format %3.2f [expr $bytes / 1048576.0000]]mb
    }
    return [format %3.2f [expr $bytes / 1024.0000]]kb
}

proc dur {in} {
    set m [expr ($in - ($in % 60)) / 60]
    set s [expr $in - ($m * 60)]
    set h [expr ($m - ($m % 60)) / 60]
    set m [expr $m - ($h * 60)]
    set d [expr ($h - ($h % 24)) / 24]
    set h [expr $h - ($d * 24)]
    foreach x "d h m s" {
        if {[set $x] > 0} {
            append return [set $x]$x
        }
    }
    if ![info exists return] {
        return 0s
    }
    return $return
}

proc place {string char num} {
    # returns the string up to the $num occurance of $char in $string
    set a 0
    set tmp $string
    while {$a <= $num} {
        set blah [string first $char $string]
        incr a
        set tmp [string range $tmp [expr $blah + 1] end]
    }
    return [string range $string 0 [expr [string length $string] - [string length $tmp] - 2]]
}

proc inttoquad {in args} {
    upvar [lindex $args 0] return
    if ![catch {
        set ip [format %08X $in]
        set ip [format %u 0x[string range $ip 0 1]].[format %u 0x[string range $ip 2 3]].[format %u 0x[string range $ip 4 5]].[format %u 0x[string range $ip 6 7]]
    } err] {
        set return $ip
        return 1
    }
    set return $err
    return 0
}

proc IncomingDccChat {header line} {
    global away dcc prefs
    set who [lindex $header 0]
    set nick [lindex [split $who !] 0]
    if {[set id [GetId c "nick $nick"]] != ""} {
        if [info exists dcc($id,sock)] {
            return
        }
        ClearDcc $id
    } else {
        set id c[lindex [split [expr abs([clock clicks] * rand())] .] 1]
    }
    set dcc($id,who) $who
    set dcc($id,nick) $nick
    set address [lindex [split $dcc($id,who) !] 1]
    if ![inttoquad [lindex $line 2] ip] {
        Echo .0 "\[ dcc \] Invalid CHAT request from $dcc($id,who): bad ip" dcc default
        ClearDcc $id
        return
    }
    set dcc($id,ip) $ip
    set dcc($id,port) [lindex $line 3]
    Echo .0 "\[ dcc \] Chat request from $dcc($id,who) \[$dcc($id,ip):$dcc($id,port)\]" dcc default
    Event dccchatrequest 0 "dcc($id,nick) nick dcc($id,ip) ip address address dcc($id,port) port" $dcc($id,who)
    after [expr $prefs(dcctimeout) * 1000] [list DccIncomingChatTimedout $id]
    if $away {
        Echo .0 "\[ dcc \] To accept it, type \"/dcc accept $dcc($id,nick)\"" dcc default
        return
    }
    dialog .dialog$id "RoxIRC DCC Request" "Accept DCC Chat from\n$dcc($id,who)?" IncomingDccChat2 0 "Yes [list 1 $id]" "No [list 0 $id]"
}

proc IncomingDccChat2 {choice id} {
    global dcc prefs
    if $prefs(unsafedcc) {
        set host $dcc($id,ip)
    } else {
        set host [lindex [split $dcc($id,who) @] 1]
    }
    if !$choice {
        Send "NOTICE $dcc($id,nick) :\001DCC REJECT chat <any>\001"
    } else {
        if [catch {socket -async $host $dcc($id,port)} sock] {
            Echo .0 "\[ dcc \] Could not connect to $host: [geterror $sock]" dcc default
            return
        }
        if ![winfo exists .$id] {
            CreateDccChat $id
        }
        fconfigure $sock -blocking 0 -buffering none -translation lf
        fileevent $sock readable [list DccChat $id]
        fileevent $sock writable [list DccChatConnect $id]
        set dcc($id,sock) $sock
    }
}

proc DccIncomingChatTimedout {id} {
    global dcc
    if [winfo exists .dialog$id] {
        destroy .dialog$id
    }
    ClearDcc $id
}

proc DccSend {window line} {
    global dcc
    set id [string trimleft $window .]
    if ![info exists dcc($id,sock)] {
        Echo $window "\[ error \] Error writing to socket: no socket" error default
    } elseif [catch {puts $dcc($id,sock) $line} err] {
        Echo $window "\[ error \] Error writing to socket: [geterror $err]" error default
    }
}

proc AcceptDccChat {id sock host port} {
    global dcc
    close $dcc($id,sock)
    set dcc($id,sock) $sock
    set dcc($id,ip) $host
    fconfigure $sock -blocking 0 -buffering none -translation lf
    fileevent $sock readable [list DccChat $id]
    Echo .$id "\[ dcc \] Chat connection to $host established" dcc default
    bind .$id <<command>> {foreach x [rspaces [split $line "\n"]] {DccSend %W $x ; Echo %W "<$ume> $x" me none}}
    wm title .$id "RoxIRC DCC Chat $dcc($id,nick)@$host"
    unset dcc($id,pending)
    Event dccchatconnect 0 "dcc($id,nick) nick host ip" $dcc($id,nick)
}

proc DccChatConnect {id} {
    global info dcc
    if {[set err [fconfigure $dcc($id,sock) -error]] != ""} {
        close $dcc($id,sock)
        if [DccChatAutoClose $id] {
            Echo .0 "\[ dcc \] Could not open connection: [geterror $err]" dcc default
        } else {
            Echo .$id "\[ dcc \] Could not open connection: [geterror $err]" dcc default
        }
        return
    }
    fileevent $dcc($id,sock) writable ""
    set peer [fconfigure $dcc($id,sock) -peername]
    bind .$id <<command>> {foreach x [rspaces [split $line "\n"]] {DccSend %W $x ; Echo %W "<$ume> $x" me none}}
    set dcc($id,ip) [lindex $peer 0]
    set host [lindex $peer 1]
    Echo .$id "\[ dcc \] Chat connection to $host established" dcc default
    wm title .$id "RoxIRC DCC Chat $dcc($id,nick)@$dcc($id,ip)"
    Event dccchatconnect 0 "dcc($id,nick) nick dcc($id,ip) ip" $dcc($id,nick)
}

proc DccChat {id} {
    global dcc
    if [eof $dcc($id,sock)] {
        close $dcc($id,sock)
        if [DccChatAutoClose $id] {
            Echo .0 "\[ dcc \] Chat connection to $dcc($id,nick)@$dcc($id,ip) closed" dcc default
        } else {
            Echo .$id "\[ dcc \] Chat connection closed" dcc default
        }
        return
    }
    if [catch {gets $dcc($id,sock)} tmp] {
        if [DccChatAutoClose $id] {
            Echo .0 "\[ dcc \] Chat connection to $dcc($id,nick)@$dcc($id,ip) lost: [geterror $tmp]" dcc default
        } else {
            Echo .$id "\[ dcc \] Chat connection lost: [geterror $tmp]" dcc default
        }
    } elseif {$tmp != ""} {
        if [string match "\001ACTION *\001" $tmp] {
            Echo .$id "* $dcc($id,nick) [string range [string trim $tmp "\x01"] 7 end]" action none
        } else {
            Echo .$id "<$dcc($id,nick)> $tmp" none
            Event dccchattext 1 "dcc($id,nick) nick tmp line" $dcc($id,nick) $tmp
        }
    }
}

proc DccChatAutoClose {id} {
    global dcc
    if $dcc($id,close) {
        CloseDccChatWindow $id
        return 1
    }
    bind .$id <<command>> {Echo %W "\[ info \] This dcc is not connected"}
    return 0
}

proc DeadDccChat {id} {
    global dcc
    if [info exists dcc($id,sock)] {
        return
    }
    catch {close $dcc($id,sock)}
    catch {unset dcc($id,pending)}
    if [winfo exists .$id] {
        if [DccChatAutoClose $id] {
            Echo .0 "\[ dcc \] Timeout waiting for chat connection from $dcc($id,nick)" dcc default
        } else {
            Echo .$id "\[ dcc \] Timeout waiting for connection" dcc default
        }
    }
}

proc DccCleanupIncomingFile {id} {
    global dcc
    if [winfo exists .dialog$id] {
        destroy .dialog$id
        ClearDcc $id
    } elseif ![info exists dcc($id,sock)] {
        ClearDcc $id
    }
}

proc IncomingDccFile {header line} {
    global dcc away prefs
    set id f[lindex [split [expr abs([clock clicks] * rand())] .] 1]
    set dcc($id,who) [lindex $header 0]
    set line [rspaces [split $line]]
    if ![IsNum [lindex $line end]] {
        set line [lrange $line 0 end-1]
    }
    set nick [lindex [split $dcc($id,who) !] 0]
    set file [string trimleft [file tail [string trim [join [lrange $line 1 end-3] _] \"]] .]
    if {[set tmp [GetId f "nick $nick" "file $file"]] != ""} {
        if [info exists dcc($tmp,sock)] {
            return
        }
        ClearDcc $tmp
    }
    set dcc($id,nick) $nick
    set dcc($id,file) $file
    set dcc($id,size) [lindex $line end]
    if ![IsNum $dcc($id,size)] {
        Echo .0 "\[ dcc \] Invalid SEND request from $dcc($id,who): bad filesize" dcc default
        ClearDcc $id
        return
    }
    set dcc($id,port) [lindex $line end-1]
    set ip [lindex $line end-2]
    if ![inttoquad $ip ip] {
        Echo .0 "\[ dcc \] Invalid SEND request from $dcc($id,who): bad remote address" dcc default
        ClearDcc $id
        return
    }
    set dcc($id,ip) $ip
    Echo .0 "\[ dcc \] Send request from $dcc($id,who) \[$dcc($id,ip):$dcc($id,port)\] $dcc($id,file) ([kb $dcc($id,size)])" dcc default
    after [expr $prefs(dcctimeout) * 1000] [list DccCleanupIncomingFile $id]
    Event dccsendrequest 1 "dcc($id,nick) nick dcc($id,ip) ip dcc($id,file) file dcc($id,size) size address [lindex [split $dcc($id,who) !] 1] dcc($id,port) port" $dcc($id,who) $dcc($id,file)
    if $away {
        Echo .0 "\[ dcc \] To accept it, type \"/dcc get $dcc($id,nick) $dcc($id,file) \[newname\]\"" dcc default
        if {[file exists $prefs(defaultdccdir)/$dcc($id,file)] && [file size $prefs(defaultdccdir)/$dcc($id,file)] < $dcc($id,size)} {
            Echo .0 "\[ dcc \] File exists and is smaller, use /dcc resume $dcc($id,nick) $dcc($id,file)" dcc default
        } elseif [file exists $prefs(defaultdccdir)/$dcc($id,file)] {
            Echo .0 "\[ dcc \] WARNING: file exists" dcc default
        }
        return
    }
    if {[file exists $prefs(defaultdccdir)/$dcc($id,file)] && [file size $prefs(defaultdccdir)/$dcc($id,file)] < $dcc($id,size)} {
        dialog .dialog$id "RoxIRC DCC Request" "Accept DCC Send of\n$dcc($id,file)\nfrom\n$dcc($id,who)?\nWARNING file exists" IncomingDccFile2 2 "Yes [list 2 $id]" "Rename [list 1 $id]" "Resume [list 3 $id]" "No [list 0 $id]"
    } elseif [file exists $prefs(defaultdccdir)/$dcc($id,file)] {
        dialog .dialog$id "RoxIRC DCC Request" "Accept DCC Send of\n$dcc($id,file)\nfrom\n$dcc($id,who)?\nWARNING file exists" IncomingDccFile2 1 "Yes [list 2 $id]" "Rename [list 1 $id]" "No [list 0 $id]"
    } else {
        dialog .dialog$id "RoxIRC DCC Request" "Accept DCC Send of\n$dcc($id,file)\nfrom\n$dcc($id,who)?" IncomingDccFile2 0 "Yes [list 2 $id]" "Rename [list 1 $id]" "No [list 0 $id]"
    }
}

proc ClearDcc {id} {
    global dcc
    array unset dcc $id,*
}

proc IncomingDccFile2 {choice id} {
    global dcc prefs
    set open w
    if {$choice == "0"} {
        Send "NOTICE $dcc($id,nick) :\001DCC REJECT send $dcc($id,file)\001"
        ClearDcc $id
        return
    } elseif {$choice == "1"} {
        set fn [tk_getSaveFile -initialdir $prefs(defaultdccdir) -title "RoxIRC Save As" -initialfile $dcc($id,file)]
        if {$fn == ""} {
            Send "NOTICE $dcc($id,nick) :\001DCC REJECT send $dcc($id,file)\001"
            ClearDcc $id
            return
        }
    } elseif {$choice == "2"} {
        set fn $dcc($id,file)
    } elseif {[lindex $tmp 0] == "3"} {
        set fn $dcc($id,file)
        set open a
    } else {
        ClearDcc $id
        return
    }
    if {[file dirname $fn] == "."} {
        set fn $prefs(defaultdccdir)/$fn
    }
    set fn [abspath $fn]
    while {[catch {open $fn $open} fh]} {
        Echo .0 "\[ error \] Cannot open $fn for writing: [geterror $fh]" error default
        set fn [tk_getSaveFile -initialdir $prefs(defaultdccdir) -title "RoxIRC Save As" -initialfile $dcc($id,file)]
        if {$fn == ""} {
            Send "NOTICE $dcc($id,nick) :\001DCC REJECT send $dcc($id,file)\001"
            ClearDcc $id
            return
        }
        if {[file dirname $fn] == "."} {
            set fn $prefs(dccdefaultdir)/$fn
        }
        set fn [abspath $fn]
    }
    CreateDccFile get $id
    if {$choice == "3"} {
        .$id.bottom.status configure -text "Requesting resume"
        Send "PRIVMSG $dcc($id,nick) :\001DCC RESUME $dcc($id,file) $dcc($id,port) [file size $dcc($id,file)]\001"
        return
    }
    if $prefs(unsafedcc) {
        set host $dcc($id,ip)
    } else {
        set host [lindex [split $dcc($id,who) @] 1]
    }
    if [catch {socket -async $host $dcc($id,port)} sock] {
        Echo .0 "\[ dcc \] Could not connect to $host: [geterror $sock]" dcc default
        .$id.bottom.status configure -text "Could not connect to $host: [geterror $sock]"
        DccFileDone $id
        return
    }
    set dcc($id,fh) $fh
    set dcc($id,sock) $sock
    set dcc($id,file) $fn
    fconfigure $sock -blocking 0 -buffering none -translation binary
    fileevent $sock readable [list DccFileGet $id]
    fileevent $sock writable [list DccFileConnect $id]
}

proc geterror {err} {
    if {[string first ": " $err] == "-1"} {
        return $err
    }
    return [string trimleft [join [lrange [split $err :] 1 end] :]]
}

proc AcceptDccResume {header line} {
    global dcc
    set port [lindex $line 2]
    set nick [lindex [split [lindex $header 0] !] 0]
    set id [GetId f "port $port" "nick $nick" "file [lindex $line 1]"]
    if {$id != ""} {
        if [catch {socket -async [lindex [split $dcc($id,who) @] 1] $dcc($id,port)} sock] {
            Echo .0 "\[ dcc \] Could not connect to [lindex [split $dcc($id,who) @] 1]: [geterror $sock]" dcc default
            .$id.bottom.status configure -text "Could not connect to [lindex [split $dcc($id,who) @] 1]: [geterror $sock]"
            DccFileDone $id
            return
        }
        set dcc($id,sock) $sock
        fconfigure $sock -blocking 0 -buffering none -translation binary
        fileevent $sock readable [list DccFileGet $id]
        fileevent $sock writable [list DccFileConnect $id]
    }
}

proc ResumeDccSend {header line} {
    global dcc
    set port [lindex $line 2]
    set nick [lindex [split [lindex $header 0] !] 0]
    set fh ""
    set id [GetId f "port $port" {pending ""} "nick $nick" "file [lindex $line 1]]
    if {$id != ""} {
        seek $dcc($id,fh) [lindex $line 3]
        Send "PRIVMSG $dcc($id,nick) :\001DCC ACCEPT $dcc($id,file) $dcc($id,port) [tell $dcc($id,fh)]\001"
    }
}

proc DccFileDone {id} {
    global dcc
    catch {unset dcc($id,last)}
    catch {close $dcc($id,fh)}
    catch {close $dcc($id,socket)}
    if {[file size $dcc($id,file)] == "0"} {
        file delete $dcc($id,file)
    }
    if $dcc($id,close) {
        CloseDccFileWindow $id
        return 1
    }
    return 0
}

proc DccFileConnect {id} {
    global dcc
    fileevent $dcc($id,sock) writable {}
    if {[set err [fconfigure $dcc($id,sock) -error]] != ""} {
        Echo .0 "\[ dcc \] Get of [file tail $dcc($id,file)] from $dcc($id,nick) failed: [geterror $err]" dcc default
        .$id.bottom.status configure -text "Get failed: [geterror $err]"
        DccFileDone $id
        return
    }
    set blah [fconfigure $dcc($id,sock) -peername]
    .$id.top.1.host configure -text "ip: [lindex $blah 0]"
    .$id.bottom.status configure -text "Receiving..."
    set dcc($id,start) [clock seconds]
    TimerDccUpdate $id
}

proc DccFileGet {id} {
    global dcc
    if [eof $dcc($id,sock)] {
        if {[tell $dcc($id,fh)] < $dcc($id,size)} {
            .$id.bottom.status configure -text "Get failed: connection lost"
            Echo .0 "\[ dcc \] Get of [file tail $dcc($id,file)] from $dcc($id,nick) failed: connection lost" dcc default
        } else {
            .$id.bottom.status configure -text "Received succesfully"
            set el [expr [clock seconds] - $dcc($id,start)]
            if {$el <= 0} {
                Echo .0 "\[ dcc \] Sucessfully recieved [kb $dcc($id,size)] of [file tail $dcc($id,file)] from $dcc($id,nick) in < 1 second" dcc default
            } else {
                Echo .0 "\[ dcc \] Sucessfully recieved [kb $dcc($id,size)] of [file tail $dcc($id,file)] from $dcc($id,nick) in [dur $el] ([format %3.2f [expr ($dcc($id,size) / 1024.00) / $el]]kbps)" dcc default
            }
        }
        DccFileDone $id
        return
    }
    if [catch {read $dcc($id,sock)} tmp] {
        $win.bottom.status configure -text "Get failed: [geterror $tmp]"
        Echo .0 "\[ dcc \] Get of [file tail $dcc($id,file)] from $dcc($id,nick) failed: [geterror $tmp]" dcc default
        DccFileDone $id
        return
    }
    puts -nonewline $dcc($id,fh) $tmp
    set recv [tell $dcc($id,fh)]
    if [eof $dcc($id,sock)] {
        return
    }
    puts -nonewline $dcc($id,sock) [binary format I* $recv]
    UpdateDccFileWindow $id $recv
    if {$recv > $dcc($id,size)} {
        .$id.bottom.status configure -text "WARNING: received > filesize"
        return
    }
    .$id.bottom.status configure -text "Receiving..."
}

proc GetId {type args} {
    global dcc
    set list ""
    set name [lindex [lindex $args 0] 0]
    set match [string tolower [lindex [lindex $args 0] 1]]
    foreach x [array names dcc ${type}*,$name] {
        if {[string tolower $dcc($x)] == $match} {
            lappend list [lindex [split $x ,] 0]
        }
    }
    foreach arg [lrange $args 1 end] {
        set name [lindex $arg 0]
        set match [string tolower [lindex $arg 1]]
        for {set i 0} {$i < [llength $list]} {incr i} {
            if {![info exists dcc([lindex $list $i],$name)] || [string tolower $dcc([lindex $list $i],$name)] != $match} {
                set list [lreplace $list $i $i]
                break
            }
        }
    }
    return $list
}

proc TimerDccUpdate {id} {
    global dcc
    if ![winfo exists .$id] {
        return
    }
    if ![info exists dcc($id,last)] {
        after 2500 TimerDccUpdate $id
        return
    }
    if ![info exists dcc($id,sock)] {
        return
    }
    if {[expr [clock seconds] - $dcc($id,last)]  > 3} {
        set el [expr [clock seconds] - $dcc($id,start)]
        if {$el != "0"} {
            .$id.top.2.kbps configure -text "kbps: ?"
            .$id.top.2.elapsed configure -text "elapsed: [dur $el]"
            .$id.middle.graph configure -label "Time remaining: ?"
            .$id.bottom.status configure -text "Stalled"
        }
        update idletasks
    }
    after 2500 TimerDccUpdate $id
}

proc UpdateDccFileWindow {id recv} {
    global dcc
    .$id.top.2.r configure -text "received: [kb $recv]"
    set dcc($id,last) [clock seconds]
    set el [expr [clock seconds] - $dcc($id,start)]
    if {$el != "0"} {
        .$id.top.2.kbps configure -text "kbps: [format %3.2f [expr ($recv / 1024.00) / $el]]"
        .$id.top.2.elapsed configure -text "elapsed: [dur $el]"
        set per [expr ($recv.0 / $dcc($id,size).0) * 100.0]
        .$id.middle.graph configure -state normal
        set dcc($id,scale) $per
        if {$per > 100} {
            .$id.middle.graph configure -state disabled -label "Time remaining: ?"
        } else {
            .$id.middle.graph configure -state disabled -label "Time remaining: [dur [expr round(($el / ($per / 100.00)) - $el)]]"
        }
    }
    update idletasks
}

proc DccFileSend {id} {
    global dcc
    if [eof $dcc($id,sock)] {
        .$id.bottom.status configure -text "Send failed: connection reset"
        Echo .0 "\[ dcc \] Send of $dcc($id,file) to $dcc($id,nick) failed: connection reset" dcc default
        DccFileDone $id
        return
    }
    if [catch {binary scan [read $dcc($id,sock)] I* ack} err] {
        .$id.bottom.status configure -text "Send failed: read error"
        Echo .0 "\[ dcc \] Send of $dcc($id,file) to $dcc($id,nick) failed: read error: [geterror $err]" dcc default
        DccFileDone $id
        return
    }
    if {$ack != ""} {
        set ack [lindex $ack end]
        UpdateDccFileWindow $id $ack
        if [eof $dcc($id,fh)] {
            if {$ack == [tell $dcc($id,fh)]} {
                .$id.bottom.status configure -text "Sent sucessfully"
                set el [expr [clock seconds] - $dcc($id,start)]
                if {$el <= 0} {
                    Echo .0 "\[ dcc \] Sucessfully sent [kb $dcc($id,size)] of $dcc($id,file) to $dcc($id,nick) in < 1 second" dcc default
                } else {
                    Echo .0 "\[ dcc \] Sucessfully sent [kb $dcc($id,size)] of $dcc($id,file) to $dcc($id,nick) in [dur $el] ([format %3.2f [expr ($dcc($id,size) / 1024.00) / $el]]kbps)" dcc default
                }
                DccFileDone $id
            }
            return
        }
        if {$ack < [tell $dcc($id,fh)]} {
            return
        } elseif {$ack > [tell $dcc($id,fh)]} {
            .$id.bottom.status configure -text "Send failed: last ack > sent"
            Echo .0 "\[ dcc \] Send of $dcc($id,file) to $dcc($id,nick) failed: last ack > sent" dcc default
            DccFileDone $id
            return
        } else {
            .$id.bottom.status configure -text "Sending..."
            DccSendPacket $id
        }
    }
}

proc DccSendPacket {id args} {
    global prefs dcc
    if {$args != ""} {
        seek $dcc($id,fh) $args
    }
    fcopy $dcc($id,fh) $dcc($id,sock) -size $prefs(dccpacketsize) -command DccSendPacketCallback
}

proc DccSendPacketCallback {args} {
}

proc AcceptDccSend {id sock ip port} {
    global dcc
    close $dcc($id,sock)
    fconfigure $sock -blocking 0 -buffering none -translation binary
    fileevent $sock readable [list DccFileSend $id]
    set dcc($id,sock) $sock
    set dcc($id,ip) $ip
    DccSendPacket $id
    .$id.top.1.host configure -text "ip: $ip"
    .$id.bottom.status configure -text "Sending..."
    set dcc($id,start) [clock seconds]
    unset dcc($id,pending)
    TimerDccUpdate $id
}

proc DeadDccSend {id} {
    global dcc
    if ![info exists dcc($id,pending)] {
        return
    }
    if [winfo exists .$id] {
        .$id.bottom.status configure -text "Timeout waiting for connection"
        Echo .0 "\[ dcc \] Timeout waiting for connection from $dcc($id,nick) for $dcc($id,file)" dcc default
        DccFileDone $id
        ClearDcc $id
    }
}

proc GetActiveChannelWindows { } {
    global info
    set chans ""
    foreach x [array names info window,*] {
        lappend chans $info($x)
    }
    return $chans
}

proc GetAllChannelWindows { } {
    global info
    set chans ""
    for {set i 1} {$i <= 15} {incr i} {
        if [winfo exists .$i] {
            lappend chans .$i
        }
    }
    return $chans
}

proc GetAllTextWindows { } {
    global info
    set win ""
    foreach x [array names info text,*] {
        lappend win [lindex [split $x ,] 1]
    }
    return $win
}

proc GetQueryWindows { } {
    global info
    set chans ""
    foreach x [winfo children .] {
       if [string match .q* $x] {
           lappend chans $x
       }
    }
    return $chans
}

proc GetDccWindows { } {
    global info
    set chans ""
    foreach x [array names dcc c*,nick] {
       lappend chans .[lindex [split $x ,] 0]
    }
    return $chans
}

proc GetWindow {in} {
    global info dcc
    set win ""
    set in [string tolower $in]
    if [info exists info(window,$in)] {
        set win $info(window,$in)
    } elseif [info exists info(query,$in)] {
       set win $info(query,$in)
    } elseif {$in == "status"} {
        set win .0
    } elseif {[string index $in 0] == "=" && [set tmp [DccChatExists [string range $in 1 end]]] != ""} {
        set win .$tmp
    }
    return $win
}

proc DeleteUser {chan nick} {
    global names info ial
    set nick2 [string tolower $nick]
    catch {unset names($chan,$nick2,a)}
    catch {unset names($chan,$nick2,v)}
    catch {unset names($chan,$nick2,o)}
    ListDelete $info(window,$chan) $nick
    ialdel $chan $nick
}

proc Complete {win} {
    global names info
    set line [$win get]
    set index [$win index insert]
    set win [winfo toplevel $win]
    set match ""
    if {[set a [string range $line 0 [expr $index - 1]]] == ""} {
        return
    }
    if {[string trim $a] == ""} {
        return
    }
    set b [string range $line $index end]
    set word [lindex [split $a] end]
    if {[string trim $word] == ""} {
        return
    }
    if {[string index $word 0] == "/"} {
        set match [split [replace [info commands command_*] command_ /]]
    } elseif [info exists info(channel,$win)] {
        set match [replace [replace [$win.middle.right.nicks get 0 end] @ ""] + ""]
        lappend match $info(channel,$win)
    } elseif [info exists info(nick,$win)] {
        lappend match $info(nick,$win)
    }
    set found ""
    set word [replace [replace $word \\ \\\\] \[ \\\[]
    while {[set index [lsearch -glob [string tolower $match] [string tolower $word]*]] != "-1"} {
        lappend found [lindex $match $index]
        set match [lrange $match [expr $index + 1] end]
    }
    set word [replace [replace $word \\\\ \\] \\\[ \[]
    set break 0
    if {[llength $found] > 1} {
        set o [lindex $found 0]
        for {set i [string length $word]} {$i < 31} {incr i} {
            foreach x $found {
                if {[string tolower [string index $x $i]] != [string tolower [string index $o $i]]} {
                    set found [string range $x 0 [expr $i - 1]]
                    set break 1
                }
            }
            if $break {
                break
            }
        }
    } else {
        set found [lindex $found 0]
    }
    if {$found != ""} {
        $win.bottom.cmdline delete 0 end
        $win.bottom.cmdline insert end [string range $a 0 [expr [string length $a] - [string length $word] - 1]]
        $win.bottom.cmdline insert end $found
        if {!$break && [llength [split [$win.bottom.cmdline get]]] == "1"} {
            if {[string index [$win.bottom.cmdline get] 0] != "/" && [string index $found 0] != "#"} {
                $win.bottom.cmdline insert end ":"
            }
        }
        if !$break {
            $win.bottom.cmdline insert end " "
        }
        $win.bottom.cmdline icursor end
        $win.bottom.cmdline insert end $b
    } elseif {![catch {$info(text,$win) search -backwards -nocase -regexp -elide -- "( |^)$word" @65535,65535 @0,0} index] && $index != ""} {
        $win.bottom.cmdline delete 0 end
        $win.bottom.cmdline insert end [string range $a 0 [expr [string length $a] - [string length $word] - 1]]
        $win.bottom.cmdline insert end [lindex [split [$info(text,$win) get $index+1c "$index lineend"]] 0]
        if {[llength [split [$win.bottom.cmdline get]]] == "1"} {
            $win.bottom.cmdline insert end " "
        }
        $win.bottom.cmdline icursor end
        $win.bottom.cmdline insert end $b
    }
}

proc UpdateAllTitles { } {
    global ume server info away
    if $away {
        set ta " (away)"
    } else {
        set ta ""
    }
    wm title .0 "RoxIRC Status ${ume}${ta} on $server"
    wm iconname .0 "$ume Status \[RoxIRC\]"
    foreach x [GetAllChannelWindows] {
        if [info exists info(channel,$x)] {
            if [IsOp $info(channel,$x) $ume] {
                set blah @$info(channel,$x)
            } elseif [IsVoice $info(channel,$x) $ume] {
                set blah +$info(channel,$x)
            } else {
                set blah $info(channel,$x)
            }
        } else {
            set blah -none-
        }
        wm title $x "RoxIRC ${blah}$ta \[$ume on $server\]"
        wm title $x.n "RoxIRC $blah nicklist"
        wm iconname $x "$blah \[RoxIRC\]"
        wm iconname $x.n "$blah nicklist \[RoxIRC\]"
    }
}

proc UpdateTitle {win} {
    global ume server info away
    if $away {
        set ta " (away)"
    } else {
        set ta ""
    }
    if [info exists info(channel,$win)] {
        if [IsOp $info(channel,$win) $ume] {
            set blah @$info(channel,$win)
        } elseif [IsVoice $info(channel,$win) $ume] {
            set blah +$info(channel,$win)
        } else {
            set blah $info(channel,$win)
        }
    } else {
        set blah -none-
    }
    wm title $win "RoxIRC ${blah}$ta \[$ume on $server\]"
    wm title $win.n "RoxIRC $blah nicklist"
    wm iconname $win "$blah \[RoxIRC\]"
    wm iconname $win.n "$blah nicklist \[RoxIRC\]"
}

proc ListFill {window} {
    global info names
    set chan $info(channel,$window)
    $window.middle.right.nicks delete 0 end
    $window.n.nicks delete 0 end
    foreach x [lsort [array names names $chan,*,o]] {
        $window.middle.right.nicks insert end @$names($x)
        $window.n.nicks insert end @$names($x)
    }
    foreach x [lsort [array names names $chan,*,v]] {
        $window.middle.right.nicks insert end +$names($x)
        $window.n.nicks insert end +$names($x)
    }
    foreach x [lsort [array names names $chan,*,n]] {
        $window.middle.right.nicks insert end $names($x)
        $window.n.nicks insert end $names($x)
    }
    array unset names $chan,*,n
    ListUpdateLabel $window
}

proc ListDelete {win nick} {
    global names info
    ListUpdateLabel $win
    set list [$win.middle.right.nicks get 0 end]
    if {[set index [lsearch -exact $list $nick]] != "-1"} {
        $win.middle.right.nicks delete $index
        $win.n.nicks delete $index
    } elseif {[set index [lsearch -exact $list @$nick]] != "-1"} {
        $win.middle.right.nicks delete $index
        $win.n.nicks delete $index
    } elseif {[set index [lsearch -exact $list +$nick]] != "-1"} {
        $win.middle.right.nicks delete $index
        $win.n.nicks delete $index
    }
}

proc ListAdd {win nick} {
    global info names
    ListUpdateLabel $win
    set num [ListSearch [GetList $win n] $nick]
    set index [expr $num + [$win.middle.right.nicks index end] - [llength [GetList $win n]]]
    $win.middle.right.nicks insert $index $nick
    $win.n.nicks insert $index $nick
    return $index
}

proc ListSearch {list nick} {
    set lo -1
    set hi [llength $list]
    set test [expr $hi / 2]
    while {$lo != $test} {
        set res [string compare -nocase [lindex $list $test] $nick]
        if {$res < 0} {
            set lo $test
        } elseif {$res > 0} {
            set hi $test
        } else {
            return $test
        }
        set test [expr ($hi + $lo) / 2]
    }
    return $hi
}

proc ListUpdateLabel {win} {
    global info names
    set tmp "@[llength [array names names $info(channel,$win),*,o]] +[llength [array names names $info(channel,$win),*,v]] [llength [array names names $info(channel,$win),*,a]]"
    $win.middle.right.label configure -text $tmp
    $win.n.label configure -text $tmp
}

proc GetList {win mode} {
    global names
    set list [join [$win.middle.right.nicks get 0 end]]
    switch -exact -- $mode {
        o {
            return [lrange [split $list] 0 [expr [llength [split $list @]] - 2]]
        }
        v {
            set l [split $list +]
            set 1 [llength [lindex $l 0]]
            set 2 [expr [llength $l] - 2]
            return [lrange $list $1 [expr $1 + $2]]
        }
        n {
            return [lrange [split [lindex [split $list @+] end]] 1 end]
        }
    }
}

proc ListChange {win old new} {
    global info names
    ListUpdateLabel $win
    set list [$win.middle.right.nicks get 0 end]
    set sel1 0
    set sel2 0
    if {[set index [lsearch -exact $list $old]] != "-1"} {
        if [$win.middle.right.nicks selection includes $index] {
            set sel1 1
        }
        if [$win.n.nicks selection includes $index] {
            set sel2 1
        }
        $win.middle.right.nicks delete $index
        $win.n.nicks delete $index
    } elseif {[set index [lsearch -exact $list @$old]] != "-1"} {
        if [$win.middle.right.nicks selection includes $index] {
            set sel1 1
        }
        if [$win.n.nicks selection includes $index] {
            set sel2 1
        }
        $win.middle.right.nicks delete $index
        $win.n.nicks delete $index
    } elseif {[set index [lsearch -exact $list +$old]] != "-1"} {
        if [$win.middle.right.nicks selection includes $index] {
            set sel1 1
        }
        if [$win.n.nicks selection includes $index] {
            set sel2 1
        }
        $win.middle.right.nicks delete $index
        $win.n.nicks delete $index
    }
    if [IsOp $info(channel,$win) $new] {
        set index [ListSearch [GetList $win o] @$new]
        $win.middle.right.nicks insert $index @$new
        $win.n.nicks insert $index @$new
    } elseif [IsVoice $info(channel,$win) $new] {
        set index [ListSearch [GetList $win v] +$new]
        incr index [llength [array names names $info(channel,$win),*,o]]
        $win.middle.right.nicks insert $index +$new
        $win.n.nicks insert $index +$new
    } else {
        set index [ListAdd $win $new]
    }
    if $sel1 {
        $win.middle.right.nicks selection set $index
    }
    if $sel2 {
        $win.n.nicks selection set $index
    }
}

proc UpdateChat {who} {
    global info prefs
    set nick [string tolower [lindex [split $who !] 0]]
    if [info exists info(query,$nick)] {
        wm title $info(query,$nick) "RoxIRC Query $who"
        return $info(query,$nick)
    }
    set oldfocus [focus]
    set win [CreateChat $who]
    if $prefs(iconifyqueries) {
        wm iconify $win
        focus $oldfocus
    }
    return $win
}

proc CreateChat {who} {
    global info prefs history menu options
    set nick [string tolower [lindex [split $who !] 0]]
    if [info exists info(query,$nick)] {
        wm deiconify $info(query,$nick)
        raise $info(query,$nick)
        return
    }
    set i 0
    while {[winfo exists .q$i]} {
        incr i
    }
    set i .q$i
    toplevel $i -class Query
    wm protocol $i WM_DELETE_WINDOW "CloseChat $i"
    bind $i <<command>> {foreach x [rspaces [split $line "\n"]] {Send "PRIVMSG $info(nick,%W) :$x" ; Echo %W "<$ume> $x" me none}}
    wm title $i "RoxIRC Query $who"
    wm iconname $i "Query [lindex [split $who !] 0] \[RoxIRC\]"
    if [info exists prefs(geom,$nick)] {
        wm geometry $i $prefs(geom,$nick)
    } else {
        wm geometry $i $prefs(geom,chat)
    }
    set info(text,$i) $i.middle.text
    set options(ts,$i) $prefs(ts)
    frame $i.top -bd 1 -relief raised
    frame $i.middle
    frame $i.bottom
    pack $i.top -side top -fill x
    pack $i.bottom -side bottom -fill x
    pack $i.middle -side top -expand 1 -fill both
    eval text $i.middle.text -wrap word -state disabled -bd 1 $prefs(style,chat) -cursor left_ptr -yscrollcommand \"$i.middle.scroll set\"
    ConfigureFont $i $prefs(font,chat)
    scrollbar $i.middle.scroll -orient v -command "$i.middle.text yview"
    entry $i.bottom.cmdline -font $prefs(font,cmdline)
    bindtags $i.bottom.cmdline "cmdline $i.bottom.cmdline Entry $i all"
    bind $i.middle.text <Double-Button-1> "Double $i query ; break"
    focus $i.bottom.cmdline
    MakeMenu $i "window query personal misc"
    bind $i.middle.text <Button-3> "OtherPopup $i.top.query.menu %X %Y"
    $i.top.window.menu.3 delete 3
    pack $i.middle.scroll -side right -fill y
    pack $i.middle.text -expand 1 -fill both
    pack $i.bottom.cmdline -fill x
    set info(nick,$i) $nick
    set info(query,$nick) $i
    set history($i,list) ""
    set history($i,cur) -1
    ConfigureTags $i
    return $i
}

proc CreateChannel {chan} {
    global info server me prefs history menu options
    set i1 ""
    for {set i 1} {$i <= 10} {incr i} {
        if {[winfo exists .$i] && ![info exists info(channel,.$i)]} {
            set i1 $i
            break
        }
    }
    if {$i1 == ""} {
        set i 1
        while {[winfo exists .$i]} {
            incr i
        }
        set i1 $i
    }
    set i .$i1
    if [winfo exists $i] {
        wm deiconify $i
        raise $i
    } else {
        toplevel $i -class Channel
        wm protocol $i WM_DELETE_WINDOW "CloseChannel $i"
        bind $i <<command>> {Echo %W "\[ info \] You have no channel joined in this window" info default}
        wm geometry $i $prefs(geom,channel)

        toplevel $i.n -class Channel
        wm withdraw $i.n
        wm protocol $i.n WM_DELETE_WINDOW "reattachnick $i"
        bind $i.n <Escape> "wm iconify $i.n"
        bind $i.n <Control-Tab> "FocusNext $i.n"
#        wm protocol $i.n WM_TAKE_FOCUS "lower $i $i.n"
        label $i.n.label -bd 1 -relief raised -text "@- +- -" -font $prefs(font,topic)
        scrollbar $i.n.scroll -orient v -command "$i.n.nicks yview"
        eval listbox $i.n.nicks -bd 2 -relief flat -selectmode extended -width 12 -exportselection 0 $prefs(style,nicklist)
        bind $i.n.nicks <Double-Button-1> "Double $i nick"
        $i.n.nicks configure -yscrollcommand "$i.n.scroll set" -font $prefs(font,nicklist)
        pack $i.n.label -side top -fill x
        pack $i.n.scroll -side right -fill y
        pack $i.n.nicks -fill both -expand 1

#        wm protocol $i WM_TAKE_FOCUS "raise $i.n"
        set options(nicklist,$i) $prefs(nicklist)
        set options(ts,$i) $prefs(ts)
        set info(text,$i) $i.middle.left.text
        frame $i.top -bd 1 -relief raised
        frame $i.middle
        frame $i.middle.left
        frame $i.middle.right
        frame $i.middle.right.move -width 3 -cursor sb_h_double_arrow
        frame $i.bottom
        pack $i.top -side top -fill x
        pack $i.bottom -side bottom -fill x
        pack $i.middle -side top -expand 1 -fill both
        if $prefs(nicklist) {
            pack $i.middle.right -side right -fill y
        }
        pack $i.middle.left -side left -expand 1 -fill both
        label $i.top.modes -relief sunken -bd 1 -text "none" -font $prefs(font,menu)
        bind $i.top.modes <Double-Button-1> "ModeWindow $i"
        entry $i.middle.left.topic -bd 1 -relief sunken -state disabled -font $prefs(font,topic)
        bind $i.middle.left.topic <Double-Button-1> "DCTopic $i ; break"
        scrollbar $i.middle.left.scroll -orient v -command "$i.middle.left.text yview"
        eval text $i.middle.left.text -wrap word -state disabled -bd 1 $prefs(style,channel) -cursor left_ptr -yscrollcommand \"$i.middle.left.scroll set\"
        ConfigureFont $i $prefs(font,channel)
        eval listbox $i.middle.right.nicks -bd 2 -relief flat -selectmode extended -width 12 -exportselection 0 -takefocus 0 $prefs(style,nicklist)
        scrollbar $i.middle.right.scroll -orient v -command "$i.middle.right.nicks yview"
        bind $i.middle.right.nicks <Double-Button-1> "Double $i nick"
        $i.middle.right.nicks configure -yscrollcommand "$i.middle.right.scroll set" -font $prefs(font,nicklist)
        label $i.middle.right.label -bd 1 -relief flat -text "@- +- -" -font $prefs(font,topic) -cursor fleur
        entry $i.bottom.cmdline -font $prefs(font,cmdline)
        bindtags $i.bottom.cmdline "cmdline $i.bottom.cmdline Entry $i all"
        MakeMenu $i "window user channel personal server misc"
        if !$prefs(nicklist) {
            catch {$i.top.user configure -state disabled}
        }
        bind $i.middle.right.nicks <Button-3> "NickPopup $i.top.user $i.middle.right.nicks %X %Y %y"
        bind $i.n.nicks <Button-3> "NickPopup $i.top.user $i.n.nicks %X %Y %y"
        bind $i.middle.left.text <Button-3> "ChanPopup $i %X %Y"
        bind $i.middle.left.text <Double-Button-1> "Double $i channel ; break"
        pack $i.top.modes -side right -padx 2 -ipadx 3
        pack $i.middle.left.topic -side top -fill x
        pack $i.middle.left.scroll -side right -fill y
        pack $i.middle.left.text -expand 1 -fill both
        pack $i.middle.right.label -side top -fill x
        bind $i.middle.right.label <ButtonPress-1> "NicksMove press $i %x %y %X %Y"
        pack $i.middle.right.scroll -side right -fill y
        pack $i.middle.right.move -side left -fill y -expand 1
        bind $i.middle.right.move <ButtonPress-1> "NicksResize press $i %X"
        pack $i.middle.right.nicks -side bottom -fill y -expand 1
        pack $i.bottom.cmdline -fill x
        set history($i,list) ""
        set history($i,cur) -1
        ConfigureTags $i
    }
    if {$chan != ""} {
        set info(window,$chan) $i
        set info(channel,$i) $chan
        bind $i <<command>> {foreach x [rspaces [split $line "\n"]] {Send "PRIVMSG $info(channel,%W) :$x" ; Echo %W "<$ume> $x" me none}}
        if [info exists prefs(geom,$chan)] {
            catch "Position r $i"
        }
    }
    focus $i.bottom.cmdline
    UpdateTitle $i
    return $i
}

proc CreateDccChat {id} {
    global info server me prefs away history menu dcc
    set i .$id
    toplevel $i -class Chat
    wm protocol $i WM_DELETE_WINDOW "CloseDccChatWindow $id"
    bind $i <<command>> {Echo %W "\[ info \] This dcc is not connected"}
    wm title $i "RoxIRC DCC Chat $dcc($id,nick)"
    wm iconname $i "DCC Chat $dcc($id,nick) \[RoxIRC\]"
    if [info exists prefs(geom,=[string tolower $dcc($id,nick)])] {
        wm geometry $i $prefs(geom,=[string tolower $dcc($id,nick)])
    } else {
        wm geometry $i $prefs(geom,chat)
    }
    set options(ts,$i) $prefs(ts)
    frame $i.top -bd 1 -relief raised
    frame $i.middle
    frame $i.bottom
    pack $i.top -side top -fill x
    pack $i.bottom -side bottom -fill x
    pack $i.middle -side top -expand 1 -fill both
    scrollbar $i.middle.scroll -orient v -command "$i.middle.text yview"
    eval text $i.middle.text -wrap word -state disabled -bd 1 $prefs(style,chat) -cursor left_ptr -yscrollcommand \"$i.middle.scroll set\"
    set info(text,$i) $i.middle.text
    ConfigureFont $i $prefs(font,chat)
    bind $i.middle.text <Double-Button-1> "Double $i dcc ; break"
    entry $i.bottom.cmdline -font $prefs(font,cmdline)
    bindtags $i.bottom.cmdline "cmdline $i.bottom.cmdline Entry $i all"
    checkbutton $i.top.close -font $prefs(font,menu) -text Autoclose -variable dcc($id,close)
    bind $i.middle.text <Double-Button-1> "Double $i dcc ; break"
    focus $i.bottom.cmdline
    MakeMenu $i "window dcc personal misc"
    bind $i.middle.text <Button-3> "OtherPopup $i.top.dcc.menu %X %Y"
    $i.top.window.menu.3 delete 0
    set dcc($id,close) $prefs(dccchatautoclose)
    pack $i.middle.scroll -side right -fill y
    pack $i.middle.text -expand 1 -fill both
    pack $i.bottom.cmdline -fill x
    pack $i.top.close -side right
    set history($i,list) ""
    set history($i,cur) -1
    ConfigureTags $i
    return $i
}

proc CreateDccFile {type id} {
    global dcc prefs
    set window .$id
    toplevel $window -bd 1 -relief raised -class File
    wm protocol $window WM_DELETE_WINDOW "CloseDccFileWindow $id"
    frame $window.top
    frame $window.middle
    frame $window.bottom
    frame $window.top.1 -bd 1 -relief sunken
    frame $window.top.2 -bd 1 -relief sunken
    if {$type == "send"} {
        wm title $window "RoxIRC DCC Send to $dcc($id,nick) ([file tail $dcc($id,file)])"
        wm iconname $window "DCC Send $dcc($id,nick) ([file tail $dcc($id,file)]) \[RoxIRC\]"
        label $window.top.1.file -font $prefs(font,menu) -text "file: $dcc($id,file)"
    } elseif {$type == "get"} {
        wm title $window "RoxIRC DCC Get from $dcc($id,nick) ([file tail $dcc($id,file)])"
        wm iconname $window "DCC Get $dcc($id,nick) ([file tail $dcc($id,file)]) \[RoxIRC\]"
        label $window.top.1.file -font $prefs(font,menu) -text "file: [file tail $dcc($id,file)]"
    }
    pack $window.top -side top -fill x
    pack $window.middle -side top -fill x
    pack $window.bottom -side bottom -fill both
    pack $window.top.1 -side top -fill x -padx 3 -pady 3
    pack $window.top.2 -side top -fill x -padx 3
    label $window.bottom.status -font $prefs(font,menu) -bd 1 -relief sunken -text "Connecting..." -anchor w
    checkbutton $window.bottom.close -font $prefs(font,menu) -text "Autoclose" -variable dcc($id,close)
    set dcc($id,close) $prefs(dccfileautoclose)
    pack $window.bottom.close -side right -padx 3
    pack $window.bottom.status -side left -pady 3 -padx 3 -ipadx 3 -ipady 1 -fill x -expand 1
    set dcc($id,scale) 0
    scale $window.middle.graph -width 20 -from 0 -to 100 -resolution 1 -variable dcc($id,scale) -state disabled -orient h -tickinterval 25 -sliderlength 10 -sliderrelief flat -label "Time remaining: "
    pack $window.middle.graph -padx 5 -expand 1 -fill x
    label $window.top.1.host -font $prefs(font,menu) -text "ip: ?.?.?.?"
    label $window.top.1.size -font $prefs(font,menu) -text "size: 0"
    label $window.top.2.elapsed -font $prefs(font,menu) -text "elapsed: 0"
    label $window.top.2.r -font $prefs(font,menu) -text "recieved: 0"
    label $window.top.2.kbps -font $prefs(font,menu) -text "kbps: 0.00"
    pack $window.top.1.host -side left -pady 2 -ipadx 3 -ipady 1
    pack $window.top.1.size -side left -pady 2 -ipadx 3 -ipady 1
    pack $window.top.1.file -side left -pady 2 -ipadx 3 -ipady 1
    pack $window.top.2.elapsed -side left -pady 2 -ipadx 3 -ipady 1
    pack $window.top.2.r -side left -pady 2 -ipadx 3 -ipady 1
    pack $window.top.2.kbps -side left -pady 2 -ipadx 3 -ipady 1
    if {$type == "send"} {
        $window.top.1.size configure -text "size: [kb [file size $dcc($id,file)]]"
        $window.bottom.status configure -text "Waiting for connection"
    } elseif {$type == "get"} {
        $window.top.1.size configure -text "size: [kb $dcc($id,size)]"
    }
    return $window
}

proc CreateStatus { } {
    global info prefs history menu
    wm title .0 "RoxIRC \[Status\] - on -"
    wm protocol .0 WM_DELETE_WINDOW {CloseClient ""}
    bind .0 <<command>> {Echo .0 "\[ info \] You have no channel joined in this window"}
    wm geometry .0 $prefs(geom,status)
    wm iconname .0 "- Status \[RoxIRC\]"
    ConfigureFont .0 $prefs(font,status)
    frame .0.top -bd 1 -relief raised
    frame .0.bottom
    pack .0.top -side top -fill x
    pack .0.bottom -side bottom -fill x
    pack .0.middle -side top -expand 1 -fill both
    label .0.top.modes -text "-" -relief sunken -bd 1 -font $prefs(font,menu)
    entry .0.bottom.cmdline -font $prefs(font,cmdline)
    bindtags .0.bottom.cmdline "cmdline .0.bottom.cmdline Entry .0 all"
    scrollbar .0.middle.scroll -orient v -command ".0.middle.text yview"
    eval .0.middle.text configure $prefs(style,status)
    .0.middle.text configure -yscrollcommand ".0.middle.scroll set" -wrap word -state disabled -cursor left_ptr
    MakeMenu .0 "window personal server misc"
    .0.top.window.menu delete 10
    .0.top.window.menu.2 delete 0 1
    .0.top.window.menu.3 delete 3
    bind .0.middle.text <Button-3> "OtherPopup .0.top.server.menu %X %Y"
    pack .0.top.modes -side right -padx 2 -ipadx 3
    pack .0.middle.scroll -side right -fill y
    pack .0.bottom.cmdline -fill x
    pack .0.middle.text -side left -fill both -expand 1
    set info(text,.0) .0.middle.text
    set history(.0,list) ""
    set history(.0,cur) -1
    bind all <Tab> ""
    DefaultKeyBindings
    bind .0.middle.text <Double-Button-1> "Double .0 status ; break"
    focus .0.bottom.cmdline
    ConfigureTags .0
    UpdateAllTitles
}

proc ConfigureFont {win font} {
    global prefs info option
    set f f[string trimleft $win .]
    catch {font create $f}
    catch {font create ${f}b}
    lappend font 14
    font configure $f -family [lindex $font 0] -size [lindex $font 1] -weight normal -slant roman
    $info(text,$win) configure -font $f
    if $prefs(bold) {
        font configure ${f}b -family [lindex $font 0] -size [lindex $font 1] -weight bold -slant roman
    } else {
        font configure ${f}b -family [lindex $font 0] -size [lindex $font 1] -weight normal -slant roman
    }
    $info(text,$win) tag configure bold -font ${f}b
}

proc ConfigureTags {win} {
    global prefs info options
    foreach x [array names prefs color,*] {
        $info(text,$win) tag configure [lindex [split $x ,] 1] -foreground $prefs($x) -lmargin2 $prefs(margin)
    }
    catch {$info(text,$win) tag lower default}
    catch {$info(text,$win) tag raise hilight}
    catch {$info(text,$win) tag raise me}
    catch {$info(text,$win) tag raise search}
    if {$options(ts,$win) || $prefs(ts)} {
        $info(text,$win) tag configure ts -elide 0
    } else {
        $info(text,$win) tag configure ts -elide 1
    }
    if $prefs(underline) {
        $info(text,$win) tag configure underline -underline 1
    }
    $info(text,$win) tag configure none -lmargin2 $prefs(margin)
    $info(text,$win) tag raise sel
    $info(text,$win) tag bind url <Enter> "$info(text,$win) configure -cursor hand2"
    $info(text,$win) tag bind url <Leave> "$info(text,$win) configure -cursor left_ptr"
    $info(text,$win) tag bind url <Double-Button-1> "UrlEvent double %W %X %Y %x %y"
    $info(text,$win) tag bind url <Button-3> "UrlEvent menu %W %X %Y %x %y"
}

proc FocusNext { } {
    set wins [winfo children .]
    set p1 [lsearch $wins [winfo toplevel [focus]]]
    set wins "[lrange $wins [expr $p1 + 1] end] [lrange $wins 0 [expr $p1 - 1]]"
    for {set i 0} {$i < [llength $wins]} {incr i} {
        set next [lindex $wins $i]
        if {[wm state $next] == "normal" && [winfo exists $next.bottom.cmdline]} {
            break
        }
    }
    if {$next == ""} {
        return
    }
    wm deiconify $next
    raise $next
    focus $next.bottom.cmdline
}

proc margin {window line} {
    global info
    foreach x [GetAllTextWindows] {
        foreach tag [$info(text,$x) tag names] {
            $info(text,$x) tag configure $tag -lmargin2 $line
        }
    }
}

proc ts {win} {
    global options info
    set temp [lindex [$info(text,$win) yview] 1]
    if $options(ts,$win) {
        $info(text,$win) tag configure ts -elide 0
    } else {
        $info(text,$win) tag configure ts -elide 1
    }
    if {$temp == "1"} {
        $info(text,$win) see end
    }
}

proc ial {window line} {
    global ial
    if !$line {
        catch {unset ial}
    }
}

proc bold {window line} {
    if $line {
        foreach x [GetAllTextWindows] {
            font configure f[string trimleft $x .]b -weight bold
        }
    } else {
        foreach x [GetAllTextWindows] {
            font configure f[string trimleft $x .]b -weight normal
        }
    }
}

proc underline {window line} {
    if $line {
        foreach x [GetAllTextWindows] {
            $info(text,$x) tag configure underline -underline 1
        }
    } else {
        foreach x [GetAllTextWindows] {
            $info(text,$x) tag configure underline -underline 0
        }
    }
}

proc MakeMenu {i types} {
    global prefs menu options
    foreach blah $types {
        if ![info exists menu($blah)] {
            continue
        }
        menubutton $i.top.$blah -text [string toupper [string index $blah 0]][string range $blah 1 end] -menu $i.top.$blah.menu -underline 0 -font $prefs(font,menu)
        set num 0
        set tmenu $i.top.$blah.menu
        set mtmp 0
        menu $tmenu -tearoff 0 -font $prefs(font,menu)
        while {[set tmp [lrange $menu($blah) $num end]] != ""} {
            switch -exact -- [lindex $tmp 0] {
                command {
                    $tmenu add command -label [lindex $tmp 1] -command [list DoMenu $i [lindex $tmp 2]]
                    incr num 3
                }
                separator {
                    $tmenu add separator
                    incr num
                }
                menu {
                    set temp $tmenu
                    incr mtmp
                    append tmenu .$mtmp
                    $temp add cascade -label [subst [lindex $tmp 1]] -menu $tmenu
                    menu $tmenu -tearoff 0
                    $tmenu configure -font $prefs(font,menu)
                    $tmenu delete 0 end
                    incr num 2
                }
                end {
                    set tmenu [string range $tmenu 0 [expr [string last . $tmenu] - 1]]
                    incr num
                }
                foreach {
                    foreach x $prefs([lindex $tmp 1]) {
                        $tmenu add command -label [unescape [subst [lindex $tmp 2]]] -command [list DoMenu $i [subst -nocommands -nobackslashes [lindex $tmp 3]]]
                    }
                    incr num 4
                }
                checkbutton {
                    eval $tmenu add checkbutton -label {[lindex $tmp 1]} [lindex $tmp 2]
                    incr num 3
                }
                radiobutton {
                    eval $tmenu add radiobutton -label {[lindex $tmp 1]} [lindex $tmp 2]
                    incr num 3
                }
                builtin {
                    switch -exact [lindex $tmp 1] {
                        servers {
                            foreach x [split $prefs(server) "\n"] {
                                set x [split $x :]
                                lappend serv([lindex $x 1]) $x
                            }
                            $tmenu delete 0 end
                            foreach x [lsort [array names serv]] {
                                if {$x == ""} {
                                    continue
                                }
                                $tmenu add cascade -label $x -menu $tmenu.[string tolower $x]
                                menu $tmenu.[string tolower $x] -tearoff 0
                                $tmenu.[string tolower $x] delete 0 end
                                foreach a $serv($x) {
                                    set name [lindex $a 4]
                                    if {$name == ""} {
                                        set name [string trim [lindex $a 0]]
                                    }
                                    $tmenu.[string tolower $x] add command -font $prefs(font,menu) -label $name -command "command_server $i [lindex $a 0]:[lindex $a 2] [lindex $a 3]"
                                }
                                $tmenu.[string tolower $x] add separator
                                $tmenu.[string tolower $x] add command -label "Random" -command "command_server $i [string tolower $x]" -font $prefs(font,menu)
                            }
                        }
                    }
                    incr num 2
                }
                default {
                    Echo .0 "\[ info \] Unknown menu option \"[lindex $tmp 0]\" in $blah menu" info default
                    return
                }
            }
        }
        pack $i.top.$blah -side left
    }
}

proc DoMenu {window command} {
    global info server away me prefs names dcc
    switch -exact [string index $window 1] {
        q {
            if ![info exists info(nick,$window)] {
                return
            }
            set nick $info(nick,$window)
        }
        c {
            set nick $dcc([string trimleft $window .],nick)
        }
        0 {
        }
        default {
            set nicks ""
            if ![info exists info(channel,$window)] {
                set channel ""
            } else {
                set win $window.middle.right.nicks
                if {[wm state $window.n] != "withdrawn"} {
                    set win $window.n.nicks
                }
                foreach x [$win curselection] {
                    lappend nicks [string trimleft [string trimleft [$win get $x] @] +]
                }
                set nick [lindex $nicks 0]
                set channel $info(channel,$window)
            }
        }
    }
    if [catch {eval $command} msg] {
        Echo .0 "\[ error \] Error in menu command $command: $msg" error default
    }
}

proc DefaultKeyBindings { } {
    bind cmdline <Escape> {wm iconify [winfo toplevel %W]}
    bind cmdline <Control-Tab> "FocusNext"
    bind cmdline <Tab> "Complete %W"
    bind cmdline <Return> "Command %W"
    bind cmdline <Up> "HistoryUp %W"
    bind cmdline <Down> "HistoryDown %W"
    bind cmdline <Prior> {$info(text,[winfo toplevel %W]) yview scroll -1 pages}
    bind cmdline <Next> {$info(text,[winfo toplevel %W]) yview scroll 1 pages}
    catch {bind cmdline <KP_Prior> {$info(text,[winfo toplevel %W]) yview scroll -1 pages}}
    catch {bind cmdline <KP_Next> {$info(text,[winfo toplevel %W]) yview scroll 1 pages}}
    bind Text <Button-5> "%W yview scroll 5 units"
    bind Text <Button-4> "%W yview scroll -5 units"
#    bind cmdline <KeyPress> "puts %K"
}

proc reattachnick {i} {
    wm withdraw $i.n
    pack forget $i.middle.left
    pack $i.middle.right -side right -fill y
    pack $i.middle.left -side left -expand 1 -fill both
    if {[lindex [$i.middle.left.text yview] 1] == "1"} {
        update
        $i.middle.left.text see end
    }
}

proc NickMenu {win} {
    global options
    if $options(nicklist,$win) {
        reattachnick $win
        catch {$win.top.user configure -state normal}
    } else {
        pack forget $win.middle.right
        wm withdraw $win.n
        catch {$win.top.user configure -state disabled}
    }
}

proc NicksMove {type win args} {
    switch $type {
        press {
            bind $win.middle.right.label <ButtonRelease-1> "NicksMove release $win"
            bind $win.middle.right.label <Motion> "NicksMove motion $win $args %X %Y"
        }
        motion {
            set xchange [expr [lindex $args 4] - [lindex $args 2]]
            set ychange [expr [lindex $args 5] - [lindex $args 3]]
            if {[expr abs($xchange)] > 10 || [expr abs($ychange)] > 10} {
                bind $win.middle.right.label <Motion> ""
                bind $win.middle.right.label <ButtonRelease-1> "NicksMove release $win [lindex $args 0] [lindex $args 1] %X %Y"
            }
        }
        release {
            if {$args != ""} {
                pack forget $win.middle.right
                set height [winfo height $win.n]
                if {$height == "1"} {
                    set height [winfo height $win.middle.right]
                }
                wm geometry $win.n [winfo width $win.middle.right]x$height+[expr [lindex $args 2] - [lindex $args 0]]+[expr [lindex $args 3] - [lindex $args 1]]
                wm deiconify $win.n
            }
            bind $win.middle.right.label <Motion> ""
            bind $win.middle.right.label <ButtonRelease-1> ""
        }
    }
}

proc NicksResize {type win args} {
    global info
    switch $type {
        press {
            if {[lindex [$info(text,$win) yview] 1] == "1"} {
                bind $win.middle.right.move <ButtonRelease-1> "NicksResize release $win 1"
            } else {
                bind $win.middle.right.move <ButtonRelease-1> "NicksResize release $win"
            }
            bind $win.middle.right.move <Motion> "NicksResize motion $win [$win.middle.right.nicks cget -width] [expr [winfo width $win.middle.right.nicks] / [$win.middle.right.nicks cget -width]] $args %X"
        }
        motion {
            set new [expr (([lindex $args 2] - [lindex $args 3]) / [lindex $args 1]) + [lindex $args 0]]
            if {[$win.middle.right.nicks cget -width] != $new && $new > 0} {
                $win.middle.right.nicks configure -width $new
                update
            }
        }
        release {
            bind $win.middle.right.move <Motion> ""
            bind $win.middle.right.move <ButtonRelease-1> ""
            if {$args == "1"} {
                update
                $info(text,$win) see end
            }
        }
    }
}

proc Double {window type} {
    global info prefs server me away dcc event
    if [info exists event] {
        unset event
        return
    }
    if [info exists prefs(click,$type)] {
        switch -exact $type {
            nick {
                if ![info exists info(channel,$window)] {
                    return
                }
                set channel $info(channel,$window)
                set win $window.middle.right.nicks
                if {[wm state $window.n] != "withdrawn"} {
                    set win $window.n.nicks
                }
                set nick [string trimleft [string trimleft [$win get [lindex [$win curselection] 0]] +] @]
            }
            query {
                set nick $info(nick,$window)
            }
            channel {
                if ![info exists info(channel,$window)] {
                    return
                }
                set nicks ""
                foreach x [$window.middle.right.nicks curselection] {
                    lappend nicks [string trimleft [string trimleft [$window.middle.right.nicks get $x] +] @]
                }
                set nick [lindex $nicks 0]
                set channel $info(channel,$window)
            }
            dcc {
                set nick $dcc(nick,$window)
            }
        }
        eval $prefs(click,$type)
        return
    }
}

proc DCTopic {window} {
    global info
    if [info exists info(channel,$window)] {
        Send "TOPIC $info(channel,$window)"
    }
}

proc CloseChat {name} {
    global info options history
    destroy $name
    catch {unset info(query,$info(nick,$name))}
    unset info(nick,$name)
    unset info(text,$name)
    catch {puts $options(lfh,$name) "Logging stopped on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"}
    catch {close $options(lfh,$name)}
    foreach var [array names options *,$name] {
        unset options($var)
    }
    unset history($name,cur)
    catch {unset history($name,tmp)}
    unset history($name,list)
    font delete f[string trimleft $name .]
    font delete f[string trimleft $name .]b
    foreach x [GetAllTextWindows] {
        if {[wm state $x] != "withdrawn" && $x != $name} {
            return
        }
    }
    wm deiconify .0
}

proc CloseChannel {name} {
    global info history options
    destroy $name
    if [info exists info(channel,$name)] {
        Send "PART $info(channel,$name)"
    }
    array unset options *,$name
    unset info(text,$name)
    unset history($name,cur)
    catch {unset history($name,tmp)}
    unset history($name,list)
    font delete f[string trimleft $name .]
    font delete f[string trimleft $name .]b
    catch {puts $options(lfh,$name) "Logging stopped on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"}
    catch {close $options(lfh,$name)}
    foreach x [GetAllTextWindows] {
        if {[wm state $x] != "withdrawn" && $x != $name} {
            return
        }
    }
    wm deiconify .0
}

proc DeleteChannel {chan win} {
    global info names ial me server
    unset info(channel,$win)
    unset info(window,$chan)
    array unset names $chan,*
    array unset ial $chan,*
    if [winfo exists $win] {
        bind $win <<command>> {Echo %W "\[ info \] You have no channel joined in this window"}
        $win.middle.right.label configure -text "@- +- -"
        $win.n.label configure -text "@- +- -"
        $win.middle.right.nicks delete 0 end
        $win.n.nicks delete 0 end
        $win.middle.left.topic configure -state normal
        $win.middle.left.topic delete 0 end
        $win.middle.left.topic configure -state disabled
        $win.top.modes configure -text "none"
        UpdateTitle $win
    }
}

proc CloseDccFileWindow {id} {
    global dcc
    destroy .$id
    catch {close $dcc($id,fh)}
    catch {close $dcc($id,sock)}
    ClearDcc $id
}

proc CloseDccChatWindow {id} {
    global dcc info history options
    destroy .$id
    catch {close $dcc($id,sock)}
    ClearDcc $id
    catch {puts $options(lfh,.$id) "Logging stopped on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"}
    catch {close $options(lfh,.$id)}
    foreach var [array names options *,.$id] {
        unset options($var)
    }
    catch {unset history(.$id,tmp)}
    unset history(.$id,cur)
    unset history(.$id,list)
    unset info(text,.$id)
    font delete f[string trimleft .$id .]
    font delete f[string trimleft .$id .]b
    foreach x [GetAllTextWindows] {
        if {[wm state $x] != "withdrawn"} {
            return
        }
    }
    wm deiconify .0
}

proc CloseClient {line} {
    global options
    if {$line == ""} {
        set line "<insert witty, contrived message here>"
    }
    Send "QUIT :$line"
    foreach x [array names options lfh,*] {
        puts $options($x) "Logging stopped on [clock format [clock seconds] -format "%m/%d/%y at %H:%M"]"
    }
    exit
}

proc NickPopup {win path x y y2} {
    global info event
    if [info exists event] {
        unset event
        return
    }
    if [info exists info(channel,[winfo toplevel $win])] {
        if ![$path selection includes [$path nearest $y2]] {
            $path selection clear 0 end
            $path selection set [$path nearest $y2]
        }
        if [winfo exists $win.menu] {
            tk_popup $win.menu $x $y
        }
    }
}

proc ChanPopup {win x y} {
    global info event
    if [info exists event] {
        unset event
        return
    }
    if {[info exists info(channel,$win)] && [winfo exists $win.top.channel.menu]} {
        tk_popup $win.top.channel.menu $x $y
    }
}

proc OtherPopup {menu x y} {
    global info event
    if [info exists event] {
        unset event
        return
    }
    if [winfo exists $menu] {
        tk_popup $menu $x $y
    }
}

proc UrlEvent {type win x y x2 y2} {
    global event prefs
    set event 1
    switch $type {
        double {
            set blah [$win tag prevrange url [$win index @$x2,$y2]]
            set url [$win get [lindex $blah 0] [lindex $blah 1]]
            eval exec [replace $prefs(urlcommand) \$url $url] &
        }
        menu {
            
        }
    }
}

proc Echo {window line args} {
    global info away me options prefs
    if [winfo exists $window] {
        set temp [lindex [$info(text,$window) yview] 1]
        if {$args != ""} {
            set tag $args
        } elseif {[string range $line 0 1] == "\[ "} {
            set tag "default [string range $line 2 [expr [string first \] $line] - 2]]"
        } elseif [regexp -- "^(-> |<$me>|\\\* $me |\\\+$me\\\+|-$me-)" $line] {
            set tag me
        } elseif {[string index $line 0] == "("} {
            set tag numeric
        } elseif [regexp "^(\\\*|\\\+)\[^ \]+(\\\*|\\\+)" $line] {
            set tag privmsg
        } elseif {[string range $line 0 1] == "* "} {
            set tag action
        } else {
            set tag none
        }
        if [match *$me* $line] {
            lappend tag hilight
        }
        set ts \[[clock format [clock seconds] -format "%H:%M"]\]
        $info(text,$window) configure -state normal
        $info(text,$window) insert end \n $tag "$ts " "ts $tag" $line $tag
        $info(text,$window) delete 0.0 end-$prefs(scrollback)l
        styleparse $window
        if $prefs(urls) {
            urls $window
        }
        $info(text,$window) configure -state disabled
        beeps
        if $options(log,$window) {
            puts $options(lfh,$window) "$ts $line"
        }
        if $options(popup,$window) {
            wm geometry $window [winfo geometry $window]
            wm deiconify $window
            raise $window
        }
        if {$temp == "1"} {
            $info(text,$window) see end
        }
    } elseif {$window == "all"} {
        foreach x [GetAllTextWindows] {
            eval Echo $x {$line} $args
        }
    }
}

proc urls {window} {
    global info urls
    set pos [$info(text,$window) index end-1l]
    while {[set blah [$info(text,$window) search -elide "http://" $pos end]] != ""} {
        set url [string trim [$info(text,$window) get $blah end]]
        set url [string trimright [string range $url 0 [expr [string first " " "$url "] - 1]] \")]
        set pos [$info(text,$window) index $blah+[string length $url]c]
        if [string match "http://?*.??*" $url] {
            $info(text,$window) tag add url $blah $pos
            set chan -
            if [info exists info(channel,$window)] {
                set chan $info(channel,$window)
            } elseif [info exists info(nick,$window)] {
                set chan $info(nick,$window)
            }
            lappend urls "[clock seconds] $chan $url"
            if [winfo exists .urls] {
                DoUrlWindow add
            }
        }
    }
}

proc styleparse {win} {
    global info
    set line [$info(text,$win) get end-1l end]
    set blah ""
    set blah2 ""
    set pos2 0
    while {[set pos [string first "\x02" $line]] != "-1"} {
        set line [string range $line [expr $pos + 1] end]
        incr pos $pos2
        lappend blah "end-1l+${pos}c"
        lappend blah2 $pos
        set pos2 $pos
        incr pos2
    }
    if {$blah != ""} {
        eval $info(text,$win) tag add bold $blah end
        set num 0
        foreach x $blah2 {
            $info(text,$win) delete end-1l+[expr $x - $num]c
            incr num
        }
    }
    set line [$info(text,$win) get end-1l end]
    set blah ""
    set blah2 ""
    set pos2 0
    while {[set pos [string first "\x1f" $line]] != "-1"} {
        set line [string range $line [expr $pos + 1] end]
        incr pos $pos2
        lappend blah "end-1l+${pos}c"
        lappend blah2 $pos
        set pos2 $pos
        incr pos2
    }
    if {$blah != ""} {
        eval $info(text,$win) tag add underline $blah end
        set num 0
        foreach x $blah2 {
            $info(text,$win) delete end-1l+[expr $x - $num]c
            incr num
        }
    }
}

proc beeps { } {
    global prefs
    upvar line line
    set num [llength [split $line "\a"]]
    incr num -1
    if {$num > $prefs(maxbeeps)} {
        set num $prefs(maxbeeps)
    }
    if {$num > 0} {
        command_beep .0 "$num 300"
    }
}

proc HistoryUp {window} {
    global history
    set window [winfo toplevel $window]
    if {[lindex $history($window,list) [expr $history($window,cur) + 1]] != ""} {
        if {$history($window,cur) == "-1"} {
            set history($window,tmp) [$window.bottom.cmdline get]
        }
        $window.bottom.cmdline delete 0 end
        incr history($window,cur)
        $window.bottom.cmdline insert end [lindex $history($window,list) $history($window,cur)]
    } else {
        bell
    }
}

proc HistoryDown {window} {
    global history
    set window [winfo toplevel $window]
    if {$history($window,cur) != "-1"} {
        $window.bottom.cmdline delete 0 end
        if {$history($window,cur) == "0"} {
            $window.bottom.cmdline insert end $history($window,tmp)
            set history($window,cur) -1
        } else {
            incr history($window,cur) -1
            $window.bottom.cmdline insert end [lindex $history($window,list) $history($window,cur)]
        }
    } else {
        bell
    }
}

proc autoaway { } {
    global prefs away autoaway
    if {!$away && $prefs(autoaway) > 0} {
        Send "AWAY :$prefs(awayreason)"
    }
    set autoaway 1
}

proc AddToHistory {window line} {
    global history prefs
    if {$history($window,cur) > -1 && [lindex $history($window,list) $history($window,cur)] == $line} {
        set history($window,list) [lreplace $history($window,list) $history($window,cur) $history($window,cur)]
    }
    set history($window,list) [linsert $history($window,list) 0 $line]
    set history($window,list) [lrange $history($window,list) 0 $prefs(history)]
    set history($window,cur) -1
}

proc Command {window} {
    global away prefs autoaway line
    set line [$window get]
    $window delete 0 end
    set window [winfo toplevel $window]
    after cancel autoaway
    if {!$away && $prefs(autoaway) > 0} {
        after [expr $prefs(autoaway) * 60000] autoaway
    }
    if {$away && ($prefs(autounaway) == "1" || ($prefs(autounaway) == "2" && [info exists autoaway]))} {
        Send "AWAY"
    }
    if {$line == ""} {
        unset line
        return
    }
    AddToHistory $window $line
    set command [trim [lindex [split $line] 0] /]
    if {[string index $line 0] == "/"} {
        set line [string range $line [expr [string length $command] + 2] end]
        if {[info commands command_$command] != ""} {
            command_$command $window $line
        } else {
            set tmp [info commands command_$command*]
            switch [llength $tmp] {
                1 {$tmp $window $line}
                0 {Echo $window "\[ info \] Unknown command /$command $line" info default}
                default {Echo $window "\[ info \] Ambigous command /$command $line" info default}
            }
        }
    } else {
        event generate $window <<command>>
    }
    catch {unset line}
}

proc sendq { } {
    global sendq flood irc
    if {[llength $sendq] > 0} {
        set line [lindex $sendq 0]
        #puts "Out: $line"
        catch {puts $irc $line}
        set sendq [lreplace $sendq 0 0]
        after 600 sendq
    } else {
        unset flood
        unset sendq
        Echo .0 "\[ info \] Flood protection deactivated" info default
    }
}

proc Send {line} {
    global irc connecting info sendq flood info
    if [info exists flood] {
        lappend sendq $line
        return
    }
    if {[expr [clock seconds] - $info(send,last)] < 1} {
        incr info(send,num)
    } else {
        set info(send,last) [clock seconds]
        set info(send,num) 0
    }
    if {$info(send,num) >= 5} {
        set flood 1
        Echo .0 "\[ info \] Flood protection activated" info default
        Send $line
        sendq
        return
    }
    #puts "Out: $line"
    if [info exists irc] {
        if {[catch {puts $irc $line} err] && ![info exists connecting]} {
            Echo .0 "\[ error \] Error sending to server: [geterror $err]" error default
        }
    } else {
        Echo all "\[ server \] You are not connected to a server" server default
    }
}

proc Sort {one two} {
    return [string compare [string tolower $one] [string tolower $two]]
}

proc bgerror {error} {
    global errorInfo
    set tmp [split $errorInfo "\n"]
    Echo .0 "\[ error \] Error: $error [string trim [lindex $tmp 1]] [lindex $tmp 2] [string trim [lindex $tmp 3]]" error default
    puts stderr "$errorInfo"
}

proc unknown {args} {
    global env unknown_pending errorCode errorInfo
    set savedErrorCode $errorCode
    set savedErrorInfo $errorInfo
    set name [lindex $args 0]
    if [info exists unknown_pending($name)] {
        return -code error "self-referential recursion in command \"$name\"";
    }
    set unknown_pending($name) pending;
    if {[string index $name 0] == "/"} {
        set cmd [trim $name /]
        if {[info commands command_$cmd] != ""} {
            set args [lrange $args 1 end]
            if {[info level] > 1} {
                upvar window win
            }
            if {![info exists win] || $win == ""} {
                set win .0
            }
            set code [catch {eval [list command_$cmd $win [join $args]]} msg]
            unset unknown_pending($name);
            if {$code != 0} {
                return -code $code -errorcode $errorCode "error executing command \"$name\": $msg"
            }
            set errorCode $savedErrorCode
            set errorInfo $savedErrorInfo
            return -code $code $msg
        }
        return -code error "invalid command name \"$name\""
    }
    set ret [catch {auto_load $name [uplevel 1 {namespace current}]} msg]
    unset unknown_pending($name);
    if {$ret != 0} {
        return -code $ret -errorcode $errorCode  "error while autoloading \"$name\": $msg"
    }
    if ![array size unknown_pending] {
        unset unknown_pending
    }
    if $msg {
        set errorCode $savedErrorCode
        set errorInfo $savedErrorInfo
        set code [catch {uplevel 1 $args} msg]
        if {$code ==  1} {
            set new [split $errorInfo \n]
            set new [join [lrange $new 0 [expr [llength $new] - 6]] \n]
            return -code error -errorcode $errorCode  -errorinfo $new $msg
        } else {
            return -code $code $msg
        }
    }
    return -code error "invalid command name \"$name\""
}

proc dialog {w title text cmd default args} {
    global prefs
    catch {destroy $w}
    set oldfocus [focus]
    toplevel $w -class Dialog
    wm title $w $title
    wm iconname $w $title
    wm transient $w [winfo toplevel [winfo parent $w]]
    wm protocol $w WM_DELETE_WINDOW {}
    frame $w.bot -relief raised -bd 1
    frame $w.top -relief raised -bd 1
    pack $w.bot -side bottom -fill both -ipady 2
    pack $w.top -side top -fill both -expand 1
    option add *Dialog.msg.wrapLength 3i widgetDefault
    label $w.msg -justify center -text $text
    $w.msg configure -font $prefs(font,menu)
    pack $w.msg -in $w.top -side right -expand 1 -fill both -padx 3m -pady 3m
    set i 0
    foreach but $args {
        button $w.button$i -bd 1 -text [lindex $but 0] -command "dialogcallback $w $cmd [join [lrange $but 1 end]]"
        if {$i == $default} {
             $w.button$i configure -default active
             bind $w <Return> "$w.button$i invoke"
        }
        grid $w.button$i -in $w.bot -column $i -row 0 -sticky ew -padx 10
        grid columnconfigure $w.bot $i
        incr i
    }
    bind $w <Destroy> {return -code return}
    bind $w <Escape> "destroy $w"
    wm withdraw $w
    update idletasks
    set x [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 - [winfo vrootx [winfo parent $w]]]
    set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 - [winfo vrooty [winfo parent $w]]]
    wm geom $w +$x+$y
    wm deiconify $w
    update
    focus $oldfocus
    return ""
}

proc dialogcallback {win cmd args} {
    bind $win <Destroy> {}
    destroy $win
    eval $cmd $args
}

proc mnc {one two} {
    return [string equal -nocase $one $two]
}

proc current { } {
    set cur [focus]
    if {$cur != ""} {
        return [winfo toplevel $cur]
    }
    return .0
}

proc match {pattern string} {
    return [string match -nocase $pattern $string]
}

proc trim {line char} {
    if {[string index $line 0] == $char} {
        return [string range $line 1 end]
    }
    return $line
}

proc replace {line find rep} {
    return [string map -nocase [list $find $rep] $line]
}

proc escape {line} {
    return [string map {\\ \\\\ \" \\\" \$ \\\$ \{ \\\{ \} \\\} \[ \\\[} $line]
}

proc escape2 {line} {
    return [string map {\\\\ \\\\\\ \\\" \\\\\" \\\$ \\\\\$ \\\{ \\\\\{ \\\} \\\\\} \\\[ \\\\\[} $line]
}

proc unescape {line} {
    return [subst -nocommands -novariables $line]
}

proc Connecting {serv fh pass} {
    global irc server connecting
    if {[set line [fconfigure $fh -error]] != ""} {
        close $fh
        Echo .0 "\[ server \] Could not connect to $serv: [geterror $line]" server default
        if ![info exists irc] {
            global info
            if {$server != $info(connect) && $server != "-"} {
                command_server .0 $info(connect)
            } else {
                Echo all "\[ server \] You are not connected to a server" server default
            }
        }
        return
    }
    fileevent $fh writable [list auth $fh $pass]
    gets $fh line
    if [info exists connecting] {
        set name [lindex [fconfigure $fh -peername] 1]
        Echo .0 "\[ server \] Connected to $name" server default
        set server $serv
        if [info exists irc] {
            catch {puts $irc "QUIT :changing servers"}
            catch {close $irc}
        }
        set irc $fh
        fileevent $irc readable GetLine
        if {$line != ""} {
            Parse $line
        }
    } else {
        set name [lindex [fconfigure $fh -peername] 1]
        Echo .0 "\[ server \] Connected to $name" server default
        Echo .0 "\[ server \] Closing connection to $name because of existing connection to $server" server default
        close $fh
    }
}

proc Parse {line} {
    global ume last
    #puts "In: $line"
    set line [trim $line :]
    if {[set pos [string first " :" $line]] > -1} {
        set header [string range $line 0 [expr $pos - 1]]
        set line [string range $line [expr $pos + 2] end]
    } else {
        set line [string trim $line]
        set pos [string last " " $line]
        set header [string range $line 0 [expr $pos - 1]]
        set line [string range $line [expr $pos + 1] end]
    }
    set header [split $header]
    set numeric [lindex $header 1]
    if {$header == "PING"} {
        Send "PONG :$line"
        return
    }
    if {[info commands raw_$numeric] != ""} {
        raw_$numeric $header $line
        set last $numeric
    } elseif [mnc $ume $numeric] {
        if {[string range $line 0 13] == "*** Notice -- "} {
            set line [string range $line 14 end]
        } elseif {[string range $line 0 3] == "*** "} {
            set line [string range $line 4 end]
        }
        Echo .0 "\[ server \] $line" server default
    } else {
        Echo .0 "( $numeric ) [string trim "[join [lrange $header 3 end]] $line"]" numeric
    }
    if {[info commands event_$numeric] != ""} {
        if [catch {event_$numeric $header $line} err] {
            Echo .0 "\[ error \] Error in event_$numeric: $err" error default
        }
    }
}

proc GetLine { } {
    global irc info server
    set line ""
    if [catch {gets $irc} line] {
        Echo all "\[ server \] Disconnected from $server: [geterror $line]" server default
        close $irc
        unset irc
        if [info exists info(time,server)] {
            Echo .0 "\[ info \] Connected to server for: [dur [expr [clock seconds] - $info(time,server)]]" info default
            unset info(time,server)
        }
        if {$server != $info(connect)} {
            after 5000 [list command_server .0 $info(connect)]
        } else {
            global away me ume
            Echo all "\[ server \] You are not connected to a server" server default
            set server -
            set me -
            set ume -
            set away 0
            .0.top.modes configure -text -
            foreach x [GetActiveChannelWindows] {
               DeleteChannel $info(channel,$x) $x
            }
            UpdateAllTitles
        }
        return
    }
    if [eof $irc] {
        global server
        Echo all "\[ server \] Disconnected from $server: connection reset by peer" server default
        close $irc
        unset irc
        if [info exists info(time,server)] {
            Echo .0 "\[ info \] Connected to server for: [dur [expr [clock seconds] - $info(time,server)]]" info default
            unset info(time,server)
        }
        after 10000 [list command_server .0 $info(connect)]
        return
    }
    if {$line != ""} {
        Parse $line
    }
}

proc getport {num} {
    global prefs
    if {$num == ""} {
        return $prefs(port)
    }
    set ports ""
    foreach x [split $num ,] {
        lappend ports $x
    }
    for {set i 0} {$i < [llength $ports]} {incr i} {
        set x [lindex $ports $i]
        if [string match ?*-?* $x] {
            set tmp1 [lindex [split $x -] 0]
            set tmp2 [lindex [split $x -] 1]
            set ports [lreplace $ports $i $i]
            for {set a $tmp1} {$a <= $tmp2} {incr a} {
                lappend ports $a
            }
        }
    }
    return [lindex $ports [expr round(rand() * ([llength $ports] - 1))]]
}

proc OpenSock {serv} {
    global irc prefs info
    set port [getport [lindex [split $serv :] 1]]
    set pass [join [lrange [split $serv :] 2 end]]
    set serv [string trim [lindex [split $serv :] 0]]
    if {$pass != ""} {
        Echo .0 "\[ server \] Connecting to $serv on port $port, pass ****" server default
    } else {
        Echo .0 "\[ server \] Connecting to $serv on port $port" server default
    }
    if {$prefs(host) != ""} {
        set catch [catch {set new [socket -async -myaddr $prefs(host) $serv $port]} err]
    } else {
        set catch [catch {set new [socket -async $serv $port]} err]
    }
    if $catch {
        Echo .0 "\[ server \] Could not connect to $serv: [geterror $err]" server default
        if {![info exists irc] && [lindex [split $info(connect) :] 0] != $serv} {
            after 5000 [list command_server .0 $info(connect)]
        } elseif ![info exists irc] {
            Echo all "\[ server \] You are not connected to a server" server default
        }
    } else {
        fconfigure $new -blocking 0 -buffering none
        fileevent $new readable [list Connecting $serv $new $pass]
        global connecting
        set connecting $serv
    }
}

proc Start { } {
    global info options prefs
    SetVars
    ParseCommandline
    toplevel .0 -class Status
    frame .0.middle
    text .0.middle.text
    .0.middle.text insert end {[ info ] RoxIRC 1.72 by RockShox} "default info"
    set info(text,.0) .0.middle.text
    array set options "popup,.0 0 log,.0 0 ts,.0 0"
    SourceFiles
    set info(time,client) [clock seconds]
    CreateStatus
    update idletasks
    after 60000 {periodic}
}

proc tkEntryAutoScan {w} {
    global tkPriv
    set x $tkPriv(x)
    if {![winfo exists $w]} return
    if {$x >= [winfo width $w]} {
        $w xview scroll 2 units
        tkEntryMouseSelect $w $x
        set tkPriv(afterId) [after 50 tkEntryAutoScan $w]
    } elseif {$x < 0} {
        $w xview scroll -2 units
        tkEntryMouseSelect $w $x
        set tkPriv(afterId) [after 50 tkEntryAutoScan $w]
    }
}

Start