Newer
Older
## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
## Norman Rasmussen <norman AT rasmussen.co.za>
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
## Alex Mauer <hawke AT hawkesnest.net>
## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
## Nikos Kouremenos <kourem AT gmail.com>
## Stefan Bethge <stefan AT lanpartei.de>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
## James Newton <redshodan AT gmail.com>
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
## Julien Pivotto <roidelapluie AT gmail.com>
## Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
if os.name == 'nt':
warnings.filterwarnings(action='ignore')
if os.path.isdir('gtk'):
# Used to create windows installer with GTK included
paths = os.environ['PATH']
list_ = paths.split(';')
new_list = []
for p in list_:
if p.find('gtk') < 0 and p.find('GTK') < 0:
new_list.append(p)
new_list.insert(0, 'gtk/lib')
new_list.insert(0, 'gtk/bin')
os.environ['PATH'] = ';'.join(new_list)
os.environ['GTK_BASEPATH'] = 'gtk'

Yann Leboulanger
committed
if os.name == 'nt':
# needed for docutils
sys.path.append('.')
from common import logging_helpers
logging_helpers.init('TERM' in os.environ)
import logging
# gajim.gui or gajim.gtk more appropriate ?
log = logging.getLogger('gajim.gajim')
import getopt
def parseOpts():
profile = ''
try:
shortargs = 'hqvl:p:c:'
longargs = 'help quiet verbose loglevel= profile= config_path='
opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
except getopt.error, msg:
print msg
print 'for help use --help'
sys.exit(2)
for o, a in opts:
if o in ('-h', '--help'):
print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]'
sys.exit()
elif o in ('-q', '--quiet'):
logging_helpers.set_quiet()
elif o in ('-v', '--verbose'):
logging_helpers.set_verbose()
elif o in ('-p', '--profile'): # gajim --profile name
profile = a
elif o in ('-l', '--loglevel'):
logging_helpers.set_loglevels(a)
elif o in ('-c', '--config-path'):
config_path = a
return profile, config_path
profile, config_path = parseOpts()
del parseOpts
import locale
profile = unicode(profile, locale.getpreferredencoding())
import common.configpaths
common.configpaths.gajimpaths.init(config_path)
del config_path
common.configpaths.gajimpaths.init_profile(profile)
del profile

Yann Leboulanger
committed
if os.name == 'nt':
class MyStderr(object):
_file = None
_error = None
def write(self, text):
fname=os.path.join(common.configpaths.gajimpaths.root,
os.path.split(sys.executable)[1]+'.log')
if self._file is None and self._error is None:
try:
self._file = open(fname, 'a')
except Exception, details:
self._error = details
if self._file is not None:
self._file.write(text)
self._file.flush()
def flush(self):
if self._file is not None:
self._file.flush()
sys.stderr = MyStderr()
# PyGTK2.10+ only throws a warning
warnings.filterwarnings('error', module='gtk')
try:
import gtk
except Warning, msg:
if str(msg) == 'could not open display':
print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
else:
print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg)
sys.exit()
if os.name == 'nt':
warnings.filterwarnings(action='ignore')
pritext = ''
from common import exceptions
from common import gajim
except exceptions.DatabaseMalformed:
pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH
else:
from common import dbus_support
if dbus_support.supported:
from music_track_listener import MusicTrackListener
import dbus
from ctypes import CDLL
from ctypes.util import find_library
import platform
if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'):
libc = CDLL(find_library('c'))
# The constant defined in <linux/prctl.h> which is used to set the name of
# the process.
PR_SET_NAME = 15
if sysname == 'Linux':
libc.prctl(PR_SET_NAME, 'gajim')
elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'):
libc.setproctitle('gajim')
if gtk.pygtk_version < (2, 12, 0):
pritext = _('Gajim needs PyGTK 2.12 or above')
sectext = _('Gajim needs PyGTK 2.12 or above to run. Quiting...')
elif gtk.gtk_version < (2, 12, 0):
pritext = _('Gajim needs GTK 2.12 or above')
sectext = _('Gajim needs GTK 2.12 or above to run. Quiting...')
try:
import gtk.glade # check if user has libglade (in pygtk and in gtk)
except ImportError:
pritext = _('GTK+ runtime is missing libglade support')
if os.name == 'nt':
sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net'
else:
sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
from common import check_paths
except exceptions.PysqliteNotAvailable, e:
pritext = _('Gajim needs PySQLite2 to run')
sectext = str(e)
if os.name == 'nt':
try:
import winsound # windows-only built-in module for playing wav
import win32api # do NOT remove. we req this module
pritext = _('Gajim needs pywin32 to run')
sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018'
if pritext:
gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
dlg.format_secondary_text(sectext)
dlg.run()
dlg.destroy()
sys.exit()

Yann Leboulanger
committed
if not hasattr(gobject, 'timeout_add_seconds'):
def timeout_add_seconds_fake(time_sec, *args):
return gobject.timeout_add(time_sec * 1000, *args)
gobject.timeout_add_seconds = timeout_add_seconds_fake
import signal
import gtkgui_helpers
import notify
import message_control
from chat_control import ChatControlBase
from chat_control import ChatControl
from groupchat_control import GroupchatControl
from groupchat_control import PrivateChatControl
from atom_window import AtomWindow
from session import ChatControlSession
import common.sleepy
from common.xmpp import idlequeue
from common.zeroconf import connection_zeroconf
from common import resolver
from common import proxy65_manager
from common import socks5
from common import helpers
from common import optparser
from common import dataforms
from common import passwords
from common import pep
gajimpaths = common.configpaths.gajimpaths
pid_filename = gajimpaths['PID_FILE']
config_filename = gajimpaths['CONFIG_FILE']
import traceback
import errno

Yann Leboulanger
committed
def pid_alive():

Yann Leboulanger
committed
pf = open(pid_filename)
# probably file not found
return False
try:
pid = int(pf.read().strip())
pf.close()
traceback.print_exc()
# PID file exists, but something happened trying to read PID
# Could be 0.10 style empty PID file, so assume Gajim is running
return True
if os.name == 'nt':
try:
from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
return True
class PROCESSENTRY32(Structure):
_fields_ = [
('dwSize', c_ulong, ),
('cntUsage', c_ulong, ),
('th32ProcessID', c_ulong, ),
('th32DefaultHeapID', c_ulong, ),
('th32ModuleID', c_ulong, ),
('cntThreads', c_ulong, ),
('th32ParentProcessID', c_ulong, ),
('pcPriClassBase', c_ulong, ),
('dwFlags', c_ulong, ),
('szExeFile', c_char*512, ),
]
def __init__(self):
Structure.__init__(self, 512+9*4)
k = windll.kernel32
k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
k.CreateToolhelp32Snapshot.restype = c_int
k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
k.Process32First.restype = c_int
k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
k.Process32Next.restype = c_int
def get_p(p):
h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
assert h > 0, 'CreateToolhelp32Snapshot failed'
b = pointer(PROCESSENTRY32())
f = k.Process32First(h, b)
while f:
if b.contents.th32ProcessID == p:
return b.contents.szExeFile
f = k.Process32Next(h, b)

Yann Leboulanger
committed
if get_p(pid) in ('python.exe', 'gajim.exe'):
return True
return False
try:
if not os.path.exists('/proc'):
return True # no /proc, assume Gajim is running
except IOError, e:
if e.errno == errno.ENOENT:
return False # file/pid does not exist

Yann Leboulanger
committed
f.close()
if n.find('gajim') < 0:
return False
return True # Running Gajim found at pid
traceback.print_exc()
# If we are here, pidfile exists, but some unexpected error occured.
# Assume Gajim is running.
return True

Yann Leboulanger
committed
if pid_alive():
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
gtk.window_set_default_icon(pix) # set the icon to all newly opened wind

Yann Leboulanger
committed
pritext = _('Gajim is already running')
sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
dialog = dialogs.YesNoDialog(pritext, sectext)
if os.path.exists(pid_filename):
os.remove(pid_filename)
del path_to_file
del pix
del pritext
del sectext

Yann Leboulanger
committed
# Create .gajim dir
pid_dir = os.path.dirname(pid_filename)
if not os.path.exists(pid_dir):
check_paths.create_path(pid_dir)
# Create pid file
try:
f = open(pid_filename, 'w')
f.write(str(os.getpid()))
f.close()
except IOError, e:
dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e))
dlg.run()
dlg.destroy()
sys.exit()

Yann Leboulanger
committed
def on_exit():
# delete pid file on normal exit
if os.path.exists(pid_filename):
os.remove(pid_filename)

Yann Leboulanger
committed
# Shutdown GUI and save config
gajim.interface.roster.prepare_quit()

Yann Leboulanger
committed
import atexit
atexit.register(on_exit)
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
parser = optparser.OptionsParser(config_filename)
import roster_window
import profile_window
import config
from threading import Thread
class PassphraseRequest:
def __init__(self, keyid):
self.keyid = keyid
self.callbacks = []
self.dialog_created = False
self.dialog = None
self.completed = False
def interrupt(self):
self.dialog.window.destroy()
self.callbacks = []
def run_callback(self, account, callback):
gajim.connections[account].gpg_passphrase(self.passphrase)
callback()
def add_callback(self, account, cb):
if self.completed:
self.run_callback(account, cb)
else:
self.callbacks.append((account, cb))
if not self.dialog_created:
self.create_dialog(account)
def complete(self, passphrase):
self.passphrase = passphrase
self.completed = True
if passphrase is not None:
gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase,
self.keyid)
for (account, cb) in self.callbacks:
self.run_callback(account, cb)
del self.callbacks
def create_dialog(self, account):
title = _('Passphrase Required')
second = _('Enter GPG key passphrase for key %(keyid)s (account '
'%(account)s).') % {'keyid': self.keyid, 'account': account}
def _cancel():
# user cancelled, continue without GPG
self.complete(None)
def _ok(passphrase, checked, count):
result = gajim.connections[account].test_gpg_passphrase(passphrase)
if result == 'ok':
# passphrase is good
self.complete(passphrase)
return
elif result == 'expired':
dialogs.ErrorDialog(_('GPG key expired'),
_('Your GPG key has expied, you will be connected to %s without '
'OpenPGP.') % account)
# Don't try to connect with GPG
gajim.connections[account].continue_connect_info[2] = False
self.complete(None)
return
if count < 3:
# ask again
dialogs.PassphraseDialog(_('Wrong Passphrase'),
_('Please retype your GPG passphrase or press Cancel.'),
ok_handler=(_ok, count + 1), cancel_handler=_cancel)
else:
# user failed 3 times, continue without GPG
self.complete(None)
self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1),
cancel_handler=_cancel)
self.dialog_created = True
class ThreadInterface:
def __init__(self, func, func_args, callback, callback_args):
'''Call a function in a thread
:param func: the function to call in the thread
:param func_args: list or arguments for this function
:param callback: callback to call once function is finished
:param callback_args: list of arguments for this callback
'''
def thread_function(func, func_args, callback, callback_args):
output = func(*func_args)
gobject.idle_add(callback, output, *callback_args)
Thread(target=thread_function, args=(func, func_args, callback,
callback_args)).start()
class Interface:
################################################################################
### Methods handling events from connection
################################################################################
def handle_event_roster(self, account, data):
#('ROSTER', account, array)
# FIXME: Those methods depend to highly on each other
# and the order in which they are called
self.roster.fill_contacts_and_groups_dicts(data, account)
self.roster.add_account_contacts(account)
self.roster.fire_up_unread_messages_events(account)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Roster', (account, data))
def handle_event_warning(self, unused, data):
#('WARNING', account, (title_text, section_text))
dialogs.WarningDialog(data[0], data[1])
def handle_event_error(self, unused, data):
#('ERROR', account, (title_text, section_text))
dialogs.ErrorDialog(data[0], data[1])
def handle_event_information(self, unused, data):
#('INFORMATION', account, (title_text, section_text))
dialogs.InformationDialog(data[0], data[1])
def handle_event_ask_new_nick(self, account, data):
#('ASK_NEW_NICK', account, (room_jid,))
room_jid = data[0]
title = _('Unable to join group chat')
prompt = _('Your desired nickname in group chat %s is in use or '
'registered by another occupant.\nPlease specify another nickname '
'below:') % room_jid
check_text = _('Always use this nickname when there is a conflict')
if 'change_nick_dialog' in self.instances:
self.instances['change_nick_dialog'].add_room(account, room_jid,
prompt)
else:
self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
account, room_jid, title, prompt)
def handle_event_http_auth(self, account, data):
#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
def response(account, iq_obj, answer):
self.dialog.destroy()
gajim.connections[account].build_http_auth_answer(iq_obj, answer)
def on_yes(is_checked, account, iq_obj):
response(account, iq_obj, 'yes')
sec_msg = _('Do you accept this request?')
if gajim.get_number_of_connected_accounts() > 1:
sec_msg = _('Do you accept this request on account %s?') % account
if data[4]:
sec_msg = data[4] + '\n' + sec_msg
self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
'%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
on_response_no=(response, account, data[3], 'no'))
def handle_event_error_answer(self, account, array):
#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
id_, jid_from, errmsg, errcode = array
if unicode(errcode) in ('400', '403', '406') and id_:
# show the error dialog
ft = self.instances['file_transfers']
sid = id_
if len(id_) > 3 and id_[2] == '_':
sid = id_[3:]
if sid in ft.files_props['s']:
file_props = ft.files_props['s'][sid]
if unicode(errcode) == '400':
file_props['error'] = -3
else:
file_props['error'] = -4
self.handle_event_file_request_error(account,
(jid_from, file_props, errmsg))
conn = gajim.connections[account]
conn.disconnect_transfer(file_props)
return
elif unicode(errcode) == '404':
conn = gajim.connections[account]
sid = id_
if len(id_) > 3 and id_[2] == '_':
sid = id_[3:]
if sid in conn.files_props:
file_props = conn.files_props[sid]
self.handle_event_file_send_error(account,
(jid_from, file_props))
conn.disconnect_transfer(file_props)
return
ctrl = self.msg_win_mgr.get_control(jid_from, account)
if ctrl and ctrl.type_id == message_control.TYPE_GC:
ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
def handle_event_con_type(self, account, con_type):
# ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
gajim.con_types[account] = con_type
self.roster.draw_account(account)
def handle_event_connection_lost(self, account, array):
# ('CONNECTION_LOST', account, [title, text])
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'connection_lost.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
notify.popup(_('Connection Failed'), account, account,
'connection_failed', path, array[0], array[1])
def unblock_signed_in_notifications(self, account):
gajim.block_signed_in_notifications[account] = False
def handle_event_status(self, account, show): # OUR status
#('STATUS', account, show)
model = self.roster.status_combobox.get_model()
if show in ('offline', 'error'):
for name in self.instances[account]['online_dialog'].keys():
# .keys() is needed to not have a dictionary length changed during
# iteration error
self.instances[account]['online_dialog'][name].destroy()
del self.instances[account]['online_dialog'][name]
for request in self.gpg_passphrase.values():
if request:
request.interrupt()
# .keys() is needed because dict changes during loop
for account in self.pass_dialog.keys():
self.pass_dialog[account].window.destroy()
if show == 'offline':
# sensitivity for this menuitem
if gajim.get_number_of_connected_accounts() == 0:
model[self.roster.status_message_menuitem_iter][3] = False
gajim.block_signed_in_notifications[account] = True
else:
# 30 seconds after we change our status to sth else than offline
# we stop blocking notifications of any kind
# this prevents from getting the roster items as 'just signed in'
# contacts. 30 seconds should be enough time
gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account)
# sensitivity for this menuitem
model[self.roster.status_message_menuitem_iter][3] = True
# Inform all controls for this account of the connection state change
ctrls = self.msg_win_mgr.get_controls()
if account in self.minimized_controls:
# Can not be the case when we remove account
ctrls += self.minimized_controls[account].values()
for ctrl in ctrls:
if ctrl.account == account:
if show == 'offline' or (show == 'invisible' and \
gajim.connections[account].is_zeroconf):
ctrl.got_disconnected()
else:
# Other code rejoins all GCs, so we don't do it here
if not ctrl.type_id == message_control.TYPE_GC:
ctrl.got_connected()
if ctrl.parent_win:
ctrl.parent_win.redraw_tab(ctrl)
self.roster.on_status_changed(account, show)
if account in self.show_vcard_when_connect and show not in ('offline',
'error'):
self.edit_own_details(account)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('AccountPresence', (show, account))
def edit_own_details(self, account):
jid = gajim.get_jid_from_account(account)
if 'profile' not in self.instances[account]:
self.instances[account]['profile'] = \
profile_window.ProfileWindow(account)
gajim.connections[account].request_vcard(jid)
def handle_event_notify(self, account, array):
# 'NOTIFY' (account, (jid, status, status message, resource,
# priority, # keyID, timestamp, contact_nickname))
#
# Contact changed show
# FIXME: Drop and rewrite...
statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
# Ignore invalid show
if array[1] not in statuss:
return
old_show = 0
new_show = statuss.index(array[1])
status_message = array[2]
jid = array[0].split('/')[0]
keyID = array[5]
contact_nickname = array[7]
# Get the proper keyID
keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID)
resource = array[3]
if not resource:
resource = ''
priority = array[4]
if gajim.jid_is_transport(jid):
# It must be an agent
ji = jid.replace('@', '')
else:
ji = jid
highest = gajim.contacts. \
get_contact_with_highest_priority(account, jid)
was_highest = (highest and highest.resource == resource)
conn = gajim.connections[account]
# Update contact
jid_list = gajim.contacts.get_jid_list(account)
if ji in jid_list or jid == gajim.get_jid_from_account(account):
lcontact = gajim.contacts.get_contacts(account, ji)
contact1 = None
resources = []
for c in lcontact:
resources.append(c.resource)
if c.resource == resource:
contact1 = c
break
if contact1:
if contact1.show in statuss:
old_show = statuss.index(contact1.show)
# nick changed
if contact_nickname is not None and \
contact1.contact_name != contact_nickname:
contact1.contact_name = contact_nickname
self.roster.draw_contact(jid, account)
if old_show == new_show and contact1.status == status_message and \
contact1.priority == priority: # no change
return
else:
contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
if not contact1:
# Presence of another resource of our
# jid
# Create self contact and add to roster
if resource == conn.server_resource:
return
# Ignore offline presence of unknown self resource
if new_show < 2:
return
contact1 = gajim.contacts.create_contact(jid=ji,
name=gajim.nicks[account], groups=['self_contact'],
show=array[1], status=status_message, sub='both', ask='none',
priority=priority, keyID=keyID, resource=resource,
mood=conn.mood, tune=conn.tune, activity=conn.activity)
old_show = 0
gajim.contacts.add_contact(account, contact1)
lcontact.append(contact1)
elif contact1.show in statuss:
old_show = statuss.index(contact1.show)
if (resources != [''] and (len(lcontact) != 1 or \
lcontact[0].show != 'offline')) and jid.find('@') > 0:
# Another resource of an existing contact connected
old_show = 0
contact1 = gajim.contacts.copy_contact(contact1)
lcontact.append(contact1)
contact1.resource = resource
self.roster.add_contact(contact1.jid, account)
if contact1.jid.find('@') > 0 and len(lcontact) == 1:
# It's not an agent
if old_show == 0 and new_show > 1:
if not contact1.jid in gajim.newly_added[account]:
gajim.newly_added[account].append(contact1.jid)
if contact1.jid in gajim.to_be_removed[account]:
gajim.to_be_removed[account].remove(contact1.jid)
gobject.timeout_add_seconds(5, self.roster.remove_newly_added,
contact1.jid, account)
elif old_show > 1 and new_show == 0 and conn.connected > 1:
if not contact1.jid in gajim.to_be_removed[account]:
gajim.to_be_removed[account].append(contact1.jid)
if contact1.jid in gajim.newly_added[account]:
gajim.newly_added[account].remove(contact1.jid)
self.roster.draw_contact(contact1.jid, account)
gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed,
contact1.jid, account)
# unset custom status
if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\
and conn.connected > 1):
if account in self.status_sent_to_users and \
jid in self.status_sent_to_users[account]:
del self.status_sent_to_users[account][jid]
contact1.show = array[1]
contact1.status = status_message
contact1.priority = priority
contact1.keyID = keyID
timestamp = array[6]
if timestamp:
contact1.last_status_time = timestamp
elif not gajim.block_signed_in_notifications[account]:
# We're connected since more that 30 seconds
contact1.last_status_time = time.localtime()
contact1.contact_nickname = contact_nickname
if gajim.jid_is_transport(jid):
# It must be an agent
if ji in jid_list:
# Update existing iter and group counting
self.roster.draw_contact(ji, account)
self.roster.draw_group(_('Transports'), account)
if new_show > 1 and ji in gajim.transport_avatar[account]:
# transport just signed in.
# request avatars
for jid_ in gajim.transport_avatar[account][ji]:
conn.request_vcard(jid_)
# transport just signed in/out, don't show
# popup notifications for 30s
account_ji = account + '/' + ji
gajim.block_signed_in_notifications[account_ji] = True
gobject.timeout_add_seconds(30,
self.unblock_signed_in_notifications, account_ji)
locations = (self.instances, self.instances[account])
for location in locations:
if 'add_contact' in location:
if old_show == 0 and new_show > 1:
location['add_contact'].transport_signed_in(jid)
break
elif old_show > 1 and new_show == 0:
location['add_contact'].transport_signed_out(jid)
break
elif ji in jid_list:
# It isn't an agent
# reset chatstate if needed:
# (when contact signs out or has errors)
if array[1] in ('offline', 'error'):
contact1.our_chatstate = contact1.chatstate = \
contact1.composing_xep = None
# TODO: This causes problems when another
# resource signs off!
conn.remove_transfers_for_contact(contact1)
# disable encryption, since if any messages are
# lost they'll be not decryptable (note that
# this contradicts XEP-0201 - trying to get that
# in the XEP, though)
# there won't be any sessions here if the contact terminated
# their sessions before going offline (which we do)
for sess in conn.get_sessions(ji):
if (ji+'/'+resource) != str(sess.jid):
continue
if sess.control:
sess.control.no_autonegotiation = False
if sess.enable_encryption:
sess.terminate_e2e()
conn.delete_session(jid, sess.thread_id)
self.roster.chg_contact_status(contact1, array[1], status_message,
account)
# Notifications
if old_show < 2 and new_show > 1:
notify.notify('contact_connected', jid, account, status_message)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactPresence', (account,
array))
elif old_show > 1 and new_show < 2:
notify.notify('contact_disconnected', jid, account, status_message)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
# FIXME: stop non active file transfers
# Status change (not connected/disconnected or
# error (<1))
elif new_show > 1:
notify.notify('status_change', jid, account, [new_show,
status_message])
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactStatus', (account, array))
else:
# FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't
# follow the XEP, still the case in 2008.
# It's maybe a GC_NOTIFY (specialy for MSN gc)
self.handle_event_gc_notify(account, (jid, array[1], status_message,
array[3], None, None, None, None, None, [], None, None))
highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
is_highest = (highest and highest.resource == resource)
# disconnect the session from the ctrl if the highest resource has changed
if (was_highest and not is_highest) or (not was_highest and is_highest):
ctrl = self.msg_win_mgr.get_control(jid, account)
if ctrl:
ctrl.no_autonegotiation = False
ctrl.set_session(None)
ctrl.contact = highest
def handle_event_msgerror(self, account, array):
#'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session]))
full_jid_with_resource = array[0]
jids = full_jid_with_resource.split('/', 1)
jid = jids[0]
if array[1] == '503':
# If we get server-not-found error, stop sending chatstates
for contact in gajim.contacts.get_contacts(account, jid):
contact.composing_xep = False
session = None
if len(array) > 5:
session = array[5]
gc_control = self.msg_win_mgr.get_gc_control(jid, account)
if not gc_control and \
jid in self.minimized_controls[account]:
gc_control = self.minimized_controls[account][jid]
if gc_control and gc_control.type_id != message_control.TYPE_GC:
gc_control = None
if gc_control:
if len(jids) > 1: # it's a pm
nick = jids[1]
if session:
ctrl = session.control
else:
ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
if not ctrl:
tv = gc_control.list_treeview
model = tv.get_model()
iter_ = gc_control.get_contact_iter(nick)
if iter_:
show = model[iter_][3]
else:
show = 'offline'
gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
name = nick, show = show)
ctrl = self.new_private_chat(gc_c, account, session)
ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
'code': array[1], 'msg': array[2]}, 'status')
return
gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
'code': array[1], 'msg': array[2]}, 'status')
if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid:
gc_control.set_subject(gc_control.subject)
return
if gajim.jid_is_transport(jid):
jid = jid.replace('@', '')
msg = array[2]
if array[3]:
msg = _('error while sending %(message)s ( %(error)s )') % {
'message': array[3], 'error': msg}
if session:
session.roster_message(jid, msg, array[4], msg_type='error')
def handle_event_msgsent(self, account, array):
#('MSGSENT', account, (jid, msg, keyID))
msg = array[1]
# do not play sound when standalone chatstate message (eg no msg)
if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'):
helpers.play_sound('message_sent')
def handle_event_msgnotsent(self, account, array):
#('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
msg = _('error while sending %(message)s ( %(error)s )') % {
'message': array[2], 'error': array[1]}
if not array[4]:
# No session. This can happen when sending a message from gajim-remote
log.warn(msg)
return
array[4].roster_message(array[0], msg, array[3], account,
msg_type='error')
def handle_event_subscribe(self, account, array):
#('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Subscribe', (account, array))
jid = array[0]
text = array[1]
nick = array[2]
if helpers.allow_popup_window(account) or not self.systray_enabled:
dialogs.SubscriptionRequestWindow(jid, text, account, nick)
return
self.add_event(account, jid, 'subscription_request', (text, nick))
if helpers.allow_showing_notification(account):
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'subscription_request.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
event_type = _('Subscription request')
notify.popup(event_type, jid, account, 'subscription_request', path,
event_type, jid)
def handle_event_subscribed(self, account, array):
#('SUBSCRIBED', account, (jid, resource))
jid = array[0]
if jid in gajim.contacts.get_jid_list(account):
c = gajim.contacts.get_first_contact_from_jid(account, jid)
c.resource = array[1]
self.roster.remove_contact_from_groups(c.jid, account,
[_('Not in Roster'), _('Observers')], update=False)
else: