1# $Id$
2
3###############################################################################
4
5array set long_statusdesc [list \
6    available   [::msgcat::mc "is available"] \
7    chat        [::msgcat::mc "is free to chat"] \
8    away        [::msgcat::mc "is away"] \
9    xa          [::msgcat::mc "is extended away"] \
10    dnd         [::msgcat::mc "doesn't want to be disturbed"] \
11    invisible   [::msgcat::mc "is invisible"] \
12    unavailable [::msgcat::mc "is unavailable"]]
13
14proc get_long_status_desc {status} {
15    set ::long_statusdesc($status)
16}
17
18###############################################################################
19
20proc client:presence {xlib from type x args} {
21    global presence
22    global processed_presence
23
24    debugmsg presence "PRESENCE: $from; $type; $x; $args"
25
26    set from [::xmpp::jid::normalize $from]
27
28    switch -- $type {
29	error -
30	unavailable {
31	    catch { unset presence(type,$xlib,$from) }
32	    catch { unset presence(status,$xlib,$from) }
33	    catch { unset presence(priority,$xlib,$from) }
34	    catch { unset presence(show,$xlib,$from) }
35	    catch { unset presence(x,$xlib,$from) }
36	    catch { unset presence(error,$xlib,$from) }
37
38	    set user [::xmpp::jid::stripResource $from]
39	    if {[info exists presence(user_jids,$xlib,$user)]} {
40		set idx [lsearch -exact $presence(user_jids,$xlib,$user) $from]
41		set presence(user_jids,$xlib,$user) \
42		    [lreplace $presence(user_jids,$xlib,$user) $idx $idx]
43	    }
44	    cache_preferred_jid_on_unavailable $xlib $from $user
45	    cache_user_status $xlib $user
46
47	    foreach {attr val} $args {
48		switch -- $attr {
49		    -status {
50			set presence(status,$xlib,$from) $val
51			if {[get_user_status $xlib $user] == "unavailable"} {
52			    set presence(status,$xlib,$user) $val
53			}
54		    }
55		    -error {
56			set presence(error,$xlib,$from) $val
57		    }
58		    -lang {
59			set presence(lang,$xlib,$from) $val
60		    }
61		}
62	    }
63
64	    debugmsg presence "$xlib $from unavailable"
65	}
66	subscribe {}
67	subscribed {}
68	unsubscribe {}
69	unsubscribed {}
70	probe {}
71	default {
72	    set type available
73	    set presence(type,$xlib,$from)     available
74	    set presence(status,$xlib,$from)   ""
75	    set presence(priority,$xlib,$from) 0
76	    set presence(show,$xlib,$from)     available
77	    set presence(x,$xlib,$from)        $x
78	    catch { unset presence(error,$xlib,$from) }
79
80	    foreach {attr val} $args {
81		switch -- $attr {
82		    -status   {set presence(status,$xlib,$from)   $val}
83		    -priority {set presence(priority,$xlib,$from) $val}
84		    -show     {set presence(show,$xlib,$from)     $val}
85		    -lang     {set presence(lang,$xlib,$from)     $val}
86		}
87	    }
88
89	    set presence(show,$xlib,$from) \
90		[normalize_show $presence(show,$xlib,$from)]
91
92	    set user [::xmpp::jid::stripResource $from]
93	    if {![info exists presence(user_jids,$xlib,$user)] || \
94		    ![lcontain $presence(user_jids,$xlib,$user) $from]} {
95		lappend presence(user_jids,$xlib,$user) $from
96	    }
97
98	    cache_preferred_jid_on_available $xlib $from $user
99	    cache_user_status $xlib $user
100	}
101    }
102
103    eval {hook::run client_presence_hook $xlib $from $type $x} $args
104}
105
106###############################################################################
107
108proc get_jids_of_user {xlib user} {
109    global presence
110
111    if {[info exists presence(user_jids,$xlib,$user)]} {
112	return $presence(user_jids,$xlib,$user)
113    } elseif {![cequal [::xmpp::jid::resource $user] ""]} {
114	if {[info exists presence(type,$xlib,$user)]} {
115	    return [list $user]
116	}
117    }
118    return {}
119}
120
121proc get_jid_of_user {xlib user} {
122    global presence
123
124    if {[info exists presence(preferred_jid,$xlib,$user)]} {
125	return $presence(preferred_jid,$xlib,$user)
126    } else {
127	return $user
128    }
129}
130
131proc cache_preferred_jid_on_available {xlib jid user} {
132    global presence
133
134    set pri $presence(priority,$xlib,$jid)
135
136    if {![info exists presence(maxpriority,$xlib,$user)]} {
137	cache_preferred_jid $xlib $user
138	return
139    } elseif {$presence(preferred_jid,$xlib,$user) == $jid && \
140	      $pri < $presence(maxpriority,$xlib,$user)} {
141	unset presence(preferred_jid,$xlib,$user)
142	unset presence(maxpriority,$xlib,$user)
143	cache_preferred_jid $xlib $user
144	return
145    } else {
146	set maxpri $presence(maxpriority,$xlib,$user)
147	if {$pri > $maxpri} {
148	    set presence(maxpriority,$xlib,$user) $pri
149	    set presence(preferred_jid,$xlib,$user) $jid
150	}
151    }
152}
153
154proc cache_preferred_jid_on_unavailable {xlib jid user} {
155    global presence
156
157    if {![info exists presence(maxpriority,$xlib,$user)]} {
158	cache_preferred_jid $xlib $user
159	return
160    }
161
162    if {$presence(preferred_jid,$xlib,$user) == $jid} {
163	unset presence(preferred_jid,$xlib,$user)
164	unset presence(maxpriority,$xlib,$user)
165	cache_preferred_jid $xlib $user
166    }
167}
168
169proc cache_preferred_jid {xlib user} {
170    global presence
171
172    set jids [get_jids_of_user $xlib $user]
173
174    if {$jids != {}} {
175	set rjid [lindex $jids 0]
176	set pri $presence(priority,$xlib,$rjid)
177
178	foreach jid $jids {
179	    if {$presence(priority,$xlib,$jid) > $pri} {
180		set pri $presence(priority,$xlib,$jid)
181		set rjid $jid
182	    }
183	}
184
185	set presence(maxpriority,$xlib,$user) $pri
186	set presence(preferred_jid,$xlib,$user) $rjid
187    }
188}
189
190
191proc get_jid_status {xlib jid} {
192    global presence
193
194    set j $jid
195    if {[info exists presence(show,$xlib,$j)]} {
196	return $presence(show,$xlib,$j)
197    } else {
198	return unavailable
199    }
200}
201
202proc get_jid_presence_info {param xlib jid} {
203    global presence
204
205    if {[info exists presence($param,$xlib,$jid)]} {
206	return $presence($param,$xlib,$jid)
207    } else {
208	return ""
209    }
210}
211
212proc get_user_status {xlib user} {
213    global presence
214
215    if {[info exists presence(cachedstatus,$xlib,$user)]} {
216	return $presence(cachedstatus,$xlib,$user)
217    } elseif {[info exists presence(show,$xlib,$user)]} {
218	return $presence(show,$xlib,$user)
219    } else {
220	return unavailable
221    }
222}
223
224proc cache_user_status {xlib user} {
225    global presence
226
227    set jid [get_jid_of_user $xlib $user]
228    if {[info exists presence(show,$xlib,$jid)]} {
229	set presence(cachedstatus,$xlib,$user) $presence(show,$xlib,$jid)
230    } else {
231	set presence(cachedstatus,$xlib,$user) unavailable
232    }
233}
234
235proc get_user_status_desc {xlib user} {
236    global presence
237
238    set jid [get_jid_of_user $xlib $user]
239    if {[info exists presence(error,$xlib,$jid)]} {
240	return [::xmpp::stanzaerror::message $presence(error,$xlib,$jid)]
241    } elseif {[info exists presence(status,$xlib,$jid)]} {
242	return $presence(status,$xlib,$jid)
243    } else {
244	return ""
245    }
246}
247
248array set status_priority {
249    unavailable 1
250    xa          2
251    away        3
252    dnd         4
253    available   5
254    chat        6
255}
256
257proc compare_status {s1 s2} {
258    global status_priority
259    set p1 $status_priority($s1)
260    set p2 $status_priority($s2)
261    if {$p1 > $p2} {
262	return 1
263    } elseif {$p1 == $p2} {
264	return 0
265    } else {
266	return -1
267    }
268}
269
270proc max_status {s1 s2} {
271    global status_priority
272    set p1 $status_priority($s1)
273    set p2 $status_priority($s2)
274    if {$p1 >= $p2} {
275	return $s1
276    } else {
277	return $s2
278    }
279}
280
281###############################################################################
282
283set curpriority   0
284set curuserstatus unavailable
285set curtextstatus ""
286
287custom::defvar userpriority 0 [::msgcat::mc "Stored user priority."] \
288    -type integer -group Hidden
289custom::defvar userstatus available [::msgcat::mc "Stored user status."] \
290    -type string -group Hidden
291custom::defvar textstatus "" [::msgcat::mc "Stored user text status."] \
292    -type string -group Hidden
293
294set userstatusdesc [::msgcat::mc "Not logged in"]
295
296set statusdesc(available)   [::msgcat::mc "Available"]
297set statusdesc(chat)        [::msgcat::mc "Free to chat"]
298set statusdesc(away)        [::msgcat::mc "Away"]
299set statusdesc(xa)          [::msgcat::mc "Extended away"]
300set statusdesc(dnd)         [::msgcat::mc "Do not disturb"]
301set statusdesc(invisible)   [::msgcat::mc "Invisible"]
302set statusdesc(unavailable) [::msgcat::mc "Unavailable"]
303
304###############################################################################
305
306proc change_priority_dialog {} {
307    global tmppriority
308    global userpriority
309
310    set tmppriority $userpriority
311
312    set w .change_priority
313    if {[winfo exists $w]} {
314	focus -force $w
315	return
316    }
317
318    Dialog $w -title [::msgcat::mc "Change Presence Priority"] \
319	-modal none -separator 1 -anchor e -default 0 -cancel 1
320
321    $w add -text [::msgcat::mc "OK"] \
322	-command [list do_change_priority $w]
323    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
324
325    set f [$w getframe]
326    label $f.lpriority -text [::msgcat::mc "Priority:"]
327    Spinbox $f.priority -1000 1000 1 tmppriority
328
329    grid $f.lpriority -row 0 -column 0 -sticky e
330    grid $f.priority  -row 0 -column 1 -sticky ew
331
332    grid columnconfigure $f 0 -weight 1
333    grid columnconfigure $f 1 -weight 1
334
335    $w draw
336}
337
338###############################################################################
339
340proc do_change_priority {w} {
341    global userstatus
342    global tmppriority
343    global userpriority
344
345    destroy $w
346    if {![cequal $userpriority $tmppriority]} {
347        set userpriority $tmppriority
348	set userstatus $userstatus
349    }
350}
351
352###############################################################################
353
354trace variable userstatus w change_our_presence
355trace variable logoutuserstatus w change_our_presence
356
357###############################################################################
358
359proc change_our_presence {name1 name2 op} {
360    global userstatus logoutuserstatus curuserstatus
361    global textstatus logouttextstatus curtextstatus
362    global userpriority logoutpriority curpriority
363    global statusdesc userstatusdesc
364
365    switch -- $name1 {
366	logoutuserstatus {
367	    set newstatus $logoutuserstatus
368	    set newtextstatus $logouttextstatus
369	    set newpriority $logoutpriority
370	}
371	default {
372	    if {[lempty [connections]]} return
373	    set newstatus $userstatus
374	    set newtextstatus $textstatus
375	    set newpriority $userpriority
376	}
377    }
378
379    if {[cequal $newstatus $curuserstatus] \
380	    && [cequal $newtextstatus $curtextstatus] \
381	    && [cequal $newpriority $curpriority]} {
382	return
383    }
384
385    if {[lsearch -exact [array names statusdesc] $newstatus] < 0} {
386	error [::msgcat::mc "Invalid userstatus value %s" $newstatus]
387    }
388
389    set userstatusdesc $statusdesc($newstatus)
390    set status $newtextstatus
391
392    foreach xlib [connections] {
393	send_presence $xlib $newstatus \
394		      -status $status \
395		      -priority $newpriority
396    }
397
398    foreach chatid [lfilter chat::is_groupchat [chat::opened]] {
399	set xlib [chat::get_xlib $chatid]
400	set group [chat::get_jid $chatid]
401	set nick [get_our_groupchat_nick $chatid]
402
403	if {$newstatus == "invisible"} {
404	    set newst available
405	} else {
406	    set newst $newstatus
407	}
408
409	send_presence $xlib $newst \
410		      -to $group/$nick \
411		      -status $status \
412		      -priority $userpriority
413    }
414
415    set curuserstatus $newstatus
416    set curtextstatus $newtextstatus
417    set curpriority $newpriority
418
419    hook::run change_our_presence_post_hook $newstatus
420}
421
422###############################################################################
423
424proc send_first_presence {xlib} {
425    global userstatus curuserstatus statusdesc userstatusdesc
426    global textstatus curtextstatus
427    global userpriority curpriority
428    global loginconf
429
430    if {[lsearch -exact [array names statusdesc] $userstatus] < 0} {
431	error [::msgcat::mc "Invalid userstatus value %s" $userstatus]
432    }
433
434    set userstatusdesc $statusdesc($userstatus)
435    set status $textstatus
436
437    set curuserstatus $userstatus
438    set curtextstatus $textstatus
439    set curpriority [set userpriority $loginconf(priority)]
440
441    send_presence $xlib $userstatus \
442		  -status $status \
443		  -priority $userpriority
444
445    hook::run change_our_presence_post_hook $userstatus
446}
447
448hook::add connected_hook [namespace current]::send_first_presence 15
449
450###############################################################################
451
452proc send_custom_presence {xlib jid status args} {
453    global userpriority
454    global statusdesc
455
456    set type jid
457    set stat ""
458    foreach {key val} $args {
459	switch -- $key {
460	    -type   { set type $val }
461	    -status { set stat $val }
462	}
463    }
464
465    switch -- $type {
466	group   {
467	    set to $jid/[get_our_groupchat_nick [chat::chatid $xlib $jid]]
468	}
469	default {
470	    set to $jid
471	}
472    }
473
474    eval {send_presence $xlib $status} $args \
475	 {-to $to -status $stat -priority $userpriority}
476}
477
478###############################################################################
479
480proc send_presence {xlib status args} {
481    set newargs [eval [list presence_args $xlib $status] $args]
482    eval [list ::xmpp::sendPresence $xlib] $newargs
483}
484
485###############################################################################
486
487proc presence_args {xlib status args} {
488    switch -- $status {
489	available   {
490	    set newargs {}
491	}
492	unavailable {
493	    set newargs [list -type $status]
494	}
495	default     {
496	    set newargs [list -show $status]
497	}
498    }
499
500    set xlist {}
501    set to ""
502    set stat ""
503    foreach {opt val} $args {
504	switch -- $opt {
505	    -id       { lappend newargs -id $val }
506	    -to       {
507		set to $val
508		lappend newargs -to $val
509	    }
510	    -priority { lappend newargs -priority $val }
511	    -xlist    { set xlist $val }
512	    -status   { set stat $val }
513	}
514    }
515
516    hook::run rewrite_presence_status_hook stat $xlib
517
518    if {$stat != ""} {
519	lappend newargs -status $stat
520    }
521
522    hook::run presence_xlist_hook xlist $xlib $stat
523    lappend newargs -xlist $xlist
524
525    debugmsg presence "$newargs"
526    return $newargs
527}
528
529###############################################################################
530
531proc normalize_show {show} {
532    set res $show
533
534    switch -- $show {
535	away        {}
536	chat        {}
537    	dnd         {}
538	xa          {}
539	unavailable {}
540	default     {set res available}
541    }
542    return $res
543}
544
545###############################################################################
546
547proc add_presence_to_popup_info {infovar xlib jid} {
548    upvar 0 $infovar info
549
550    set bjid [::xmpp::jid::stripResource $jid]
551    if {[chat::is_groupchat [chat::chatid $xlib $bjid]]} return
552
553    set priority [get_jid_presence_info priority $xlib $jid]
554    if {$priority != ""} {
555	append info [format "\n\t[::msgcat::mc {Priority:}] %s" $priority]
556    }
557}
558
559hook::add roster_user_popup_info_hook add_presence_to_popup_info 20
560
561###############################################################################
562
563proc clear_presence_info {xlib} {
564    global curuserstatus
565    global userstatusdesc
566    global presence
567
568    array unset presence type,$xlib,*
569    array unset presence status,$xlib,*
570    array unset presence priority,$xlib,*
571    array unset presence show,$xlib,*
572    array unset presence error,$xlib,*
573    array unset presence x,$xlib,*
574    array unset presence user_jids,$xlib,*
575    array unset presence preferred_jid,$xlib,*
576    array unset presence cachedstatus,$xlib,*
577    array unset presence maxpriority,$xlib,*
578
579    if {[connections] == {}} {
580	set_status "Disconnected"
581
582	set curuserstatus unavailable
583	set userstatusdesc [::msgcat::mc "Not logged in"]
584	hook::run change_our_presence_post_hook unavailable
585    }
586}
587
588hook::add disconnected_hook clear_presence_info
589
590###############################################################################
591
592proc custom_presence_menu {m xlib jid} {
593    set chatid [chat::chatid $xlib $jid]
594    set chatid1 [chat::chatid $xlib [::xmpp::jid::removeResource $jid]]
595
596    if {![chat::is_groupchat $chatid] && [chat::is_groupchat $chatid1]} {
597	return
598    }
599
600    if {[chat::is_groupchat $chatid]} {
601	set jid [::xmpp::jid::replaceResource $jid [get_our_groupchat_nick $chatid]]
602    }
603
604    set mm [menu $m.custom_presence -tearoff 0]
605
606    $mm add command -label [::msgcat::mc "Available"] \
607		    -command [list send_custom_presence $xlib $jid available]
608    $mm add command -label [::msgcat::mc "Free to chat"] \
609		    -command [list send_custom_presence $xlib $jid chat]
610    $mm add command -label [::msgcat::mc "Away"] \
611		    -command [list send_custom_presence $xlib $jid away]
612    $mm add command -label [::msgcat::mc "Extended away"] \
613		    -command [list send_custom_presence $xlib $jid xa]
614    $mm add command -label [::msgcat::mc "Do not disturb"] \
615		    -command [list send_custom_presence $xlib $jid dnd]
616    $mm add command -label [::msgcat::mc "Unavailable"] \
617		    -command [list send_custom_presence $xlib $jid unavailable]
618
619    $m add cascade -label [::msgcat::mc "Send custom presence"] \
620		   -menu $mm
621}
622
623hook::add chat_create_user_menu_hook custom_presence_menu 43
624hook::add roster_jid_popup_menu_hook custom_presence_menu 43
625hook::add roster_service_popup_menu_hook custom_presence_menu 43
626hook::add chat_create_conference_menu_hook custom_presence_menu 43
627
628###############################################################################
629
630proc service_login {xlib jid} {
631    global userstatus curtextstatus
632
633    set newargs {}
634
635    if {$curtextstatus != ""} {
636	lappend newargs -status $curtextstatus
637    }
638
639    switch -- $userstatus {
640	available {
641	    set command [list ::xmpp::sendPresence $xlib -to $jid]
642	}
643	invisible {
644	    set command [list ::xmpp::sendPresence $xlib -to $jid -type $userstatus]
645	}
646	default {
647	    set command [list ::xmpp::sendPresence $xlib -to $jid -show $userstatus]
648	}
649    }
650
651    eval $command $newargs
652}
653
654proc service_logout {xlib jid} {
655    global curtextstatus
656
657    set newargs {}
658
659    if {$curtextstatus != ""} {
660	lappend newargs -status $curtextstatus
661    }
662
663    set command [list ::xmpp::sendPresence $xlib -to $jid -type unavailable]
664
665    eval $command $newargs
666}
667
668proc service_login_logout_menu_item {m xlib jid} {
669    # TODO
670    $m add command -label [::msgcat::mc "Log in"] \
671		   -command [list service_login $xlib $jid]
672    $m add command -label [::msgcat::mc "Log out"] \
673		   -command [list service_logout $xlib $jid]
674}
675
676hook::add roster_service_popup_menu_hook service_login_logout_menu_item 20
677
678###############################################################################
679
680proc systray_presence_menu_item {m} {
681    set mp [menu $m.presence -title [::msgcat::mc "Presence"] \
682			     -tearoff $ifacetk::options(show_tearoffs)]
683
684    $mp add command -label [::msgcat::mc "Available"] \
685		    -command {set userstatus available}
686    $mp add command -label [::msgcat::mc "Free to chat"] \
687		    -command {set userstatus chat}
688    $mp add command -label [::msgcat::mc "Away"] \
689		    -command {set userstatus away}
690    $mp add command -label [::msgcat::mc "Extended away"] \
691		    -command {set userstatus xa}
692    $mp add command -label [::msgcat::mc "Do not disturb"] \
693		    -command {set userstatus dnd}
694    $mp add separator
695    $mp add command -label [::msgcat::mc "Change priority..."] \
696		    -command change_priority_dialog
697
698    $m add cascade -label [::msgcat::mc "Presence"] -menu $mp
699
700}
701
702hook::add systray_menu_hook systray_presence_menu_item 40
703
704# vim:ts=8:sw=4:sts=4:noet
705