Fossil

Check-in [e202319e]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fossil chat client (Initial commit)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:e202319ebbdb39853a6905d98422e719011dbba9
User & Date: mjanssen 2007-09-24 21:03:40
Context
2007-09-24
21:05
Use tcl_platform to determine username, this is platform independent check-in: e0232ce1 user: mjanssen tags: trunk
21:03
Fossil chat client (Initial commit) check-in: e202319e user: mjanssen tags: trunk
20:50
Timeline calculated localtime incorrectly so it didn't display latest changes if user is in TZ UTC+X check-in: aad573b3 user: mjanssen tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added tools/fossil_chat.tcl.









































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/home/drh/bin/tobe
#
# Simple chat client for Tcl/Tk.
#
package require Tk

set SERVERHOST fossil-scm.hwaci.com
# set SERVERHOST 127.0.0.1
#set SERVERHOST 64.5.53.192
set SERVERPORT 8615

# Setup the user interface
wm title . Fossil-Chat
wm iconname . [wm title .]

set ::PRIVATE 0

menu .mb -type menubar
if {$tcl_platform(platform)=="unix"} {
  pack .mb -side top -fill x
} else {
  . config -menu .mb
}
.mb add cascade -label File -underline 0 -menu .mb.file
menu .mb.file -tearoff 0
.mb.file add command -label Send -command send_message
.mb.file add command -label {Remove older messages} -command cleanup_record
.mb.file add checkbutton -label {Private} -variable PRIVATE
.mb.file add separator
.mb.file add command -label {Exit} -command exit

frame .who
pack .who -side right -anchor n -fill y
label .who.title -text {Users:      }
pack .who.title -side top -anchor nw
label .who.list -anchor w -justify left -text {}
pack .who.list -side top -anchor nw -expand 1 -padx 5
label .who.time -text {} -justify right
proc update_time {} {
  after 1000 update_time
  set now [clock seconds]
  set time1 [clock format [expr {$now-4*3600}] -format {%H:%M} -gmt 1]
  set time2 [clock format [expr {$now+10*3600}] -format {%H:%M} -gmt 1]
  set time3 [clock format $now -format %H:%M -gmt 1]
  .who.time config -text "AEST: $time2\nUTC: $time3\nEST: $time1"
}
update_time
pack .who.time -side bottom -anchor sw

frame .input
pack .input -side bottom -fill x
text .input.t -bd 1 -relief sunken -bg white -fg black -width 60 -height 3 \
   -wrap word -yscrollcommand [list .input.sb set] -takefocus 1
bind .input.t <Key-Return> {send_message; break}
pack .input.t -side left -fill both -expand 1
scrollbar .input.sb -orient vertical -command [list .input.t yview]
pack .input.sb -side left -fill y

frame .msg
pack .msg -side top -fill both -expand 1
text .msg.t -bd 1 -relief sunken -bg white -fg black -width 60 -height 20 \
   -wrap word -yscrollcommand [list .msg.sb set] -takefocus 0
bindtags .msg.t [list .msg.t . all]
.msg.t tag config error -foreground red
.msg.t tag config meta -foreground forestgreen
.msg.t tag config norm -foreground black
pack .msg.t -side left -fill both -expand 1
scrollbar .msg.sb -orient vertical -command [list .msg.t yview]
pack .msg.sb -side left -fill y

update

# Send periodic messages to keep the TCP/IP link up
#
proc keep_alive {} {
  global TIMER SOCKET
  catch {after cancel $TIMER}
  set TIMER [after 300000 keep_alive]
  catch {puts $SOCKET noop; flush $SOCKET}
}

# Connect to the server
proc connect {} {
  global SOCKET env
  catch {close $SOCKET}
  if {[catch {
    set SOCKET [socket $::SERVERHOST $::SERVERPORT]
    fconfigure $SOCKET -translation binary -blocking 0
    puts $SOCKET [list login $env(USER) fact,fuzz]
    flush $SOCKET
    fileevent $SOCKET readable handle_input
    keep_alive
  } errmsg]} {
    if {[tk_messageBox -icon error -type yesno -parent . -message \
           "Unable to connect to server.  $errmsg.\n\nTry again?"]=="yes"} {
      after 100 connect
    }
  }
}
connect

# Send the message text contained in the .input.t widget to the server.
#
proc send_message {} {
  set txt [.input.t get 1.0 end]
  .input.t delete 1.0 end
  regsub -all "\[ \t\n\f\r\]+" [string trim $txt] { } txt
  if {$txt==""} return
  global SOCKET
  if {$::PRIVATE} {
    puts $SOCKET [list private_message $txt [list dan drh]]
  } else {
    puts $SOCKET [list message $txt]
  }
  flush $SOCKET
}

.mb add cascade -label "Transfer" -underline 0 -menu .mb.files
menu .mb.files -tearoff 0
.mb.files add command -label "Send file..." -command send_file
.mb.files add command -label "Delete files" -command delete_files \
    -state disabled
.mb.files add separator

# Encode a string (possibly containing binary and \000 characters) into
# single line of text.
#
proc encode {txt} {
  return [string map [list % %25 + %2b " " + \n %0a \t %09 \000 %00] $txt]
}

# Undo the work of encode.  Convert an encoded string back into its original
# form.
#
proc decode {txt} {
  return [string map [list %00 \000 %09 \t %0a \n + " " %2b + %25 %] $txt]
}

# Delete all of the downloaded files we are currently holding.
#
proc delete_files {} {
  global FILES
  .mb.files delete 3 end
  array unset FILES
  .mb.files entryconfigure 1 -state disabled 
}

# Prompt the user to select a file from the disk.  Then send that
# file to all chat participants.
#
proc send_file {} {
  global SOCKET
  set openfile [tk_getOpenFile]
  if {$openfile==""} return
  set f [open $openfile]
  fconfigure $f -translation binary
  set data [read $f]
  close $f
  if {$::PRIVATE} {
    puts $SOCKET [list private_file [file tail $openfile] [encode $data] \
        [list dan drh]]
  } else {
    puts $SOCKET [list file [file tail $openfile] [encode $data]]
  }
  flush $SOCKET
  set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
  .msg.t insert end "\[$time\] sent file [file tail $openfile]\
        - [string length $data] bytes\n" meta
  .msg.t see end
}

# Save the named file to the disk.
#
 proc save_file {filename} {
  global FILES
  set savefile [tk_getSaveFile -initialfile $filename]
  if {$savefile==""} return
  set f [open $savefile w]
  fconfigure $f -translation binary
  puts -nonewline $f [decode $FILES($filename)]
  close $f
}

# Handle a "file" message from the chat server.
#
proc handle_file {from filename data} {
  global FILES
  foreach prior [array names FILES] {
    if {$filename==$prior} break
  }
  if {![info exists prior] || $filename!=$prior} {
    .mb.files add command -label "Save \"$filename\"" \
        -command [list save_file $filename]
  }
  set FILES($filename) $data
  .mb.files entryconfigure 1 -state active 
  set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
  .msg.t insert end "\[$time $from\] " meta "File: \"$filename\"\n" norm
  .msg.t see end
}

# Handle input from the server
#
proc handle_input {} {
  global SOCKET
  if {[eof $SOCKET]} {
    disconnect
    return
  }
  set line [gets $SOCKET]
  if {$line==""} return
  set cmd [lindex $line 0]
  if {$cmd=="userlist"} {
    set ulist {}
    foreach u [lrange $line 1 end] {
      append ulist $u\n
    }
    .who.list config -text [string trim $ulist]
  } elseif {$cmd=="message"||$cmd=="private_message"} {
    set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
    set from [lindex $line 1]
    .msg.t insert end "\[$time $from\] " meta [lindex $line 2]\n norm
    .msg.t see end
    bell
    wm deiconify .
    update
    raise .
  } elseif {$cmd=="noop"} {
    # do nothing
  } elseif {$cmd=="meta"} {
    set now [clock seconds]
    set time [clock format $now -format {%H:%M} -gmt 1]
    .msg.t insert end "\[$time\] [lindex $line 1]\n" meta
    .msg.t see end
  } elseif {$cmd=="file"||$cmd=="private_file"} {
    if {[info commands handle_file]=="handle_file"} {
      handle_file [lindex $line 1] [lindex $line 2] [lindex $line 3]
    }
  }
}    

# Handle a broken socket connection
#
proc disconnect {} {
  global SOCKET
  close $SOCKET
  set q [tk_messageBox -icon error -type yesno -parent . -message \
           "TCP/IP link lost.  Try to reconnet?"]
  if {$q=="yes"} {
    connect
  } else {
    exit
  }
}

# Remove all but the most recent 100 message from the message log
#
proc cleanup_record {} {
  .msg.t delete 1.0 {end -100 lines}
}