root/tags/gajim-0.11.4/src/gajim.py

Revision 9103, 81.9 kB (checked in by asterix, 9 months ago)

add windows specific stuff in sources

  • Property executable set to 1
  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
Line 
1#!/usr/bin/env python
2##      gajim.py
3##
4##
5## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
6## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
7## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
8## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
9##
10## This program is free software; you can redistribute it and/or modify
11## it under the terms of the GNU General Public License as published
12## by the Free Software Foundation; version 2 only.
13##
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17## GNU General Public License for more details.
18##
19
20import os
21
22if os.name == 'nt':
23        import warnings
24        warnings.filterwarnings(action='ignore')
25
26# Used to create windows installer with GTK included
27#       paths = os.environ['PATH']
28#       list_ = paths.split(';')
29#       new_list = []
30#       for p in list_:
31#               if p.find('gtk') < 0 and p.find('GTK') < 0:
32#                       new_list.append(p)
33#       new_list.insert(0, 'gtk/lib')
34#       new_list.insert(0, 'gtk/bin')
35#       os.environ['PATH'] = ';'.join(new_list)
36#       os.environ['GTK_BASEPATH'] = 'gtk'
37
38import sys
39import urllib
40
41from common import i18n
42
43# PyGTK2.10+ only throws a warning
44import warnings
45warnings.filterwarnings('error', module='gtk')
46try:
47        import gtk
48except Warning, msg:
49        if str(msg) == 'could not open display':
50                print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
51                sys.exit()
52warnings.resetwarnings()
53
54import message_control
55
56from chat_control import ChatControlBase
57from atom_window import AtomWindow
58
59from common import exceptions
60from common.zeroconf import connection_zeroconf
61from common import dbus_support
62
63if os.name == 'posix': # dl module is Unix Only
64        try: # rename the process name to gajim
65                import dl
66                libc = dl.open('/lib/libc.so.6')
67                libc.call('prctl', 15, 'gajim\0', 0, 0, 0)
68        except:
69                pass
70
71pritext = ''
72if gtk.pygtk_version < (2, 6, 0):
73        pritext = _('Gajim needs PyGTK 2.6 or above')
74        sectext = _('Gajim needs PyGTK 2.6 or above to run. Quiting...')
75elif gtk.gtk_version < (2, 6, 0):
76        pritext = _('Gajim needs GTK 2.6 or above')
77        sectext = _('Gajim needs GTK 2.6 or above to run. Quiting...')
78
79try:
80        import gtk.glade # check if user has libglade (in pygtk and in gtk)
81except ImportError:
82        pritext = _('GTK+ runtime is missing libglade support')
83        if os.name == 'nt':
84                sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net'
85        else:
86                sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
87
88try:
89        from common import check_paths
90except exceptions.PysqliteNotAvailable, e:
91        pritext = _('Gajim needs PySQLite2 to run')
92        sectext = str(e)
93
94if os.name == 'nt':
95        try:
96                import winsound # windows-only built-in module for playing wav
97                import win32api # do NOT remove. we req this module
98        except:
99                pritext = _('Gajim needs pywin32 to run')
100                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'
101
102if pritext:
103        dlg = gtk.MessageDialog(None,
104                gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
105                gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
106
107        dlg.format_secondary_text(sectext)
108        dlg.run()
109        dlg.destroy()
110        sys.exit()
111
112del pritext
113
114path = os.getcwd()
115if '.svn' in os.listdir(path) or '_svn' in os.listdir(path):
116        # import gtkexcepthook only for those that run svn
117        # those than run with --verbose run from terminal so no need to care
118        # about those
119        import gtkexcepthook
120del path
121
122import gobject
123
124import re
125import signal
126import getopt
127import time
128import math
129
130import gtkgui_helpers
131import notify
132
133import common.sleepy
134
135from common.xmpp import idlequeue
136from common import nslookup
137from common import proxy65_manager
138from common import socks5
139from common import gajim
140from common import helpers
141from common import optparser
142
143profile = ''
144try:
145        opts, args = getopt.getopt(sys.argv[1:], 'hvp:', ['help', 'verbose',
146                'profile=', 'sm-config-prefix=', 'sm-client-id='])
147except getopt.error, msg:
148        print msg
149        print 'for help use --help'
150        sys.exit(2)
151for o, a in opts:
152        if o in ('-h', '--help'):
153                print 'gajim [--help] [--verbose] [--profile name]'
154                sys.exit()
155        elif o in ('-v', '--verbose'):
156                gajim.verbose = True
157        elif o in ('-p', '--profile'): # gajim --profile name
158                profile = a
159del opts
160del args
161
162import locale
163profile = unicode(profile, locale.getpreferredencoding())
164
165import common.configpaths
166common.configpaths.init_profile(profile)
167del profile
168gajimpaths = common.configpaths.gajimpaths
169
170pid_filename = gajimpaths['PID_FILE']
171config_filename = gajimpaths['CONFIG_FILE']
172
173import traceback
174import errno
175
176import dialogs
177def pid_alive():
178        try:
179                pf = open(pid_filename)
180        except:
181                # probably file not found
182                return False
183
184        try:
185                pid = int(pf.read().strip())
186                pf.close()
187        except:
188                traceback.print_exc()
189                # PID file exists, but something happened trying to read PID
190                # Could be 0.10 style empty PID file, so assume Gajim is running
191                return True
192
193        if os.name == 'nt':
194                try:
195                        from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
196                except:
197                        return True
198
199                class PROCESSENTRY32(Structure):
200                        _fields_ = [
201                                ('dwSize', c_ulong, ),
202                                ('cntUsage', c_ulong, ),
203                                ('th32ProcessID', c_ulong, ),
204                                ('th32DefaultHeapID', c_ulong, ),
205                                ('th32ModuleID', c_ulong, ),
206                                ('cntThreads', c_ulong, ),
207                                ('th32ParentProcessID', c_ulong, ),
208                                ('pcPriClassBase', c_ulong, ),
209                                ('dwFlags', c_ulong, ),
210                                ('szExeFile', c_char*512, ),
211                                ]
212                        def __init__(self):
213                                Structure.__init__(self, 512+9*4)
214
215                k = windll.kernel32
216                k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
217                k.CreateToolhelp32Snapshot.restype = c_int
218                k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
219                k.Process32First.restype = c_int
220                k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
221                k.Process32Next.restype = c_int
222
223                def get_p(p):
224                        h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
225                        assert h > 0, 'CreateToolhelp32Snapshot failed'
226                        b = pointer(PROCESSENTRY32())
227                        f = k.Process32First(h, b)
228                        while f:
229                                if b.contents.th32ProcessID == p:
230                                        return b.contents.szExeFile
231                                f = k.Process32Next(h, b)
232
233                if get_p(pid) in ('python.exe', 'gajim.exe'):
234                        return True
235                return False
236        try:
237                if not os.path.exists('/proc'):
238                        return True # no /proc, assume Gajim is running
239
240                try:
241                        f = open('/proc/%d/cmdline'% pid)
242                except IOError, e:
243                        if e.errno == errno.ENOENT:
244                                return False # file/pid does not exist
245                        raise 
246
247                n = f.read().lower()
248                f.close()
249                if n.find('gajim') < 0:
250                        return False
251                return True # Running Gajim found at pid
252        except:
253                traceback.print_exc()
254
255        # If we are here, pidfile exists, but some unexpected error occured.
256        # Assume Gajim is running.
257        return True
258
259if pid_alive():
260        path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
261        pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
262        gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
263        pritext = _('Gajim is already running')
264        sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
265        dialog = dialogs.YesNoDialog(pritext, sectext)
266        if dialog.get_response() != gtk.RESPONSE_YES:
267                sys.exit(3)
268        # run anyway, delete pid and useless global vars
269        if os.path.exists(pid_filename):
270                os.remove(pid_filename)
271        del path_to_file
272        del pix
273        del pritext
274        del sectext
275        dialog.destroy()
276
277# Create .gajim dir
278pid_dir =  os.path.dirname(pid_filename)
279if not os.path.exists(pid_dir):
280        check_paths.create_path(pid_dir)
281# Create pid file
282try:
283        f = open(pid_filename, 'w')
284        f.write(str(os.getpid()))
285        f.close()
286except IOError, e:
287        dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e))
288        dlg.run()
289        dlg.destroy()
290        sys.exit()
291del pid_dir
292del f
293
294def on_exit():
295        # delete pid file on normal exit
296        if os.path.exists(pid_filename):
297                os.remove(pid_filename)
298
299import atexit
300atexit.register(on_exit)
301
302parser = optparser.OptionsParser(config_filename)
303
304import roster_window
305import profile_window
306import config
307
308class GlibIdleQueue(idlequeue.IdleQueue):
309        '''
310        Extends IdleQueue to use glib io_add_wath, instead of select/poll
311        In another, `non gui' implementation of Gajim IdleQueue can be used safetly.
312        '''
313        def init_idle(self):
314                ''' this method is called at the end of class constructor.
315                Creates a dict, which maps file/pipe/sock descriptor to glib event id'''
316                self.events = {}
317                if gtk.pygtk_version >= (2, 8, 0):
318                        # time() is already called in glib, we just get the last value
319                        # overrides IdleQueue.current_time()
320                        self.current_time = lambda: gobject.get_current_time()
321                       
322        def add_idle(self, fd, flags):
323                ''' this method is called when we plug a new idle object.
324                Start listening for events from fd
325                '''
326                res = gobject.io_add_watch(fd, flags, self.process_events,
327                        priority=gobject.PRIORITY_LOW)
328                # store the id of the watch, so that we can remove it on unplug
329                self.events[fd] = res
330       
331        def remove_idle(self, fd):
332                ''' this method is called when we unplug a new idle object.
333                Stop listening for events from fd
334                '''
335                gobject.source_remove(self.events[fd])
336                del(self.events[fd])
337       
338        def process(self):
339                self.check_time_events()
340       
341class Interface:
342        def handle_event_roster(self, account, data):
343                #('ROSTER', account, array)
344                self.roster.fill_contacts_and_groups_dicts(data, account)
345                self.roster.add_account_contacts(account)
346                self.roster.fire_up_unread_messages_events(account)
347                if self.remote_ctrl:
348                        self.remote_ctrl.raise_signal('Roster', (account, data))
349
350        def handle_event_warning(self, unused, data):
351                #('WARNING', account, (title_text, section_text))
352                dialogs.WarningDialog(data[0], data[1])
353
354        def handle_event_error(self, unused, data):
355                #('ERROR', account, (title_text, section_text))
356                dialogs.ErrorDialog(data[0], data[1])
357
358        def handle_event_information(self, unused, data):
359                #('INFORMATION', account, (title_text, section_text))
360                dialogs.InformationDialog(data[0], data[1])
361               
362        def handle_event_ask_new_nick(self, account, data):
363                #('ASK_NEW_NICK', account, (room_jid, title_text, prompt_text, proposed_nick))
364                room_jid = data[0]
365                title = data[1]
366                prompt = data[2]
367                proposed_nick = data[3]
368                gc_control = self.msg_win_mgr.get_control(room_jid, account)
369                if gc_control: # user may close the window before we are here
370                        gc_control.show_change_nick_input_dialog(title, prompt, proposed_nick)
371
372        def handle_event_http_auth(self, account, data):
373                #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
374                def response(widget, account, iq_obj, answer):
375                        self.dialog.destroy()
376                        gajim.connections[account].build_http_auth_answer(iq_obj, answer)
377
378                sec_msg = _('Do you accept this request?')
379                if data[4]:
380                        sec_msg = data[4] + '\n' + sec_msg
381                self.dialog = dialogs.YesNoDialog(_('HTTP (%s) Authorization for %s (id: %s)') \
382                        % (data[0], data[1], data[2]), sec_msg,
383                        on_response_yes = (response, account, data[3], 'yes'),
384                        on_response_no = (response, account, data[3], 'no'))
385
386        def handle_event_error_answer(self, account, array):
387                #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
388                id, jid_from, errmsg, errcode = array
389                if unicode(errcode) in ('403', '406') and id:
390                        # show the error dialog
391                        ft = self.instances['file_transfers']
392                        sid = id
393                        if len(id) > 3 and id[2] == '_':
394                                sid = id[3:]
395                        if ft.files_props['s'].has_key(sid):
396                                file_props = ft.files_props['s'][sid]
397                                file_props['error'] = -4
398                                self.handle_event_file_request_error(account,
399                                        (jid_from, file_props, errmsg))
400                                conn = gajim.connections[account]
401                                conn.disconnect_transfer(file_props)
402                                return
403                elif unicode(errcode) == '404':
404                        conn = gajim.connections[account]
405                        sid = id
406                        if len(id) > 3 and id[2] == '_':
407                                sid = id[3:]
408                        if conn.files_props.has_key(sid):
409                                file_props = conn.files_props[sid]
410                                self.handle_event_file_send_error(account,
411                                        (jid_from, file_props))
412                                conn.disconnect_transfer(file_props)
413                                return
414                ctrl = self.msg_win_mgr.get_control(jid_from, account)
415                if ctrl and ctrl.type_id == message_control.TYPE_GC:
416                        ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
417
418        def handle_event_con_type(self, account, con_type):
419                # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp'
420                gajim.con_types[account] = con_type
421                self.roster.draw_account(account)
422
423        def handle_event_connection_lost(self, account, array):
424                # ('CONNECTION_LOST', account, [title, text])
425                path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
426                        'connection_lost.png')
427                path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
428                notify.popup(_('Connection Failed'), account, account,
429                        'connection_failed', path, array[0], array[1])
430
431        def unblock_signed_in_notifications(self, account):
432                gajim.block_signed_in_notifications[account] = False
433
434        def handle_event_status(self, account, status): # OUR status
435                #('STATUS', account, status)
436                model = self.roster.status_combobox.get_model()
437                if status == 'offline':
438                        # sensitivity for this menuitem
439                        if gajim.get_number_of_connected_accounts() == 0:
440                                model[self.roster.status_message_menuitem_iter][3] = False
441                        gajim.block_signed_in_notifications[account] = True
442                else:
443                        # 30 seconds after we change our status to sth else than offline
444                        # we stop blocking notifications of any kind
445                        # this prevents from getting the roster items as 'just signed in'
446                        # contacts. 30 seconds should be enough time
447                        gobject.timeout_add(30000, self.unblock_signed_in_notifications, account)
448                        # sensitivity for this menuitem
449                        model[self.roster.status_message_menuitem_iter][3] = True
450
451                # Inform all controls for this account of the connection state change
452                for ctrl in self.msg_win_mgr.get_controls():
453                        if ctrl.account == account:
454                                if status == 'offline' or (status == 'invisible' and \
455                                                gajim.connections[account].is_zeroconf):
456                                        ctrl.got_disconnected()
457                                else:
458                                        # Other code rejoins all GCs, so we don't do it here
459                                        if not ctrl.type_id == message_control.TYPE_GC:
460                                                ctrl.got_connected()
461                                ctrl.parent_win.redraw_tab(ctrl)
462
463                self.roster.on_status_changed(account, status)
464                if account in self.show_vcard_when_connect:
465                        self.edit_own_details(account)
466                if self.remote_ctrl:
467                        self.remote_ctrl.raise_signal('AccountPresence', (status, account))
468       
469        def edit_own_details(self, account):
470                jid = gajim.get_jid_from_account(account)
471                if not self.instances[account].has_key('profile'):
472                        self.instances[account]['profile'] = \
473                                profile_window.ProfileWindow(account)
474                        gajim.connections[account].request_vcard(jid)
475
476        def handle_event_notify(self, account, array):
477                # 'NOTIFY' (account, (jid, status, status message, resource, priority,
478                # keyID, timestamp, contact_nickname))
479                # if we're here it means contact changed show
480                statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
481                        'invisible']
482                # Ignore invalid show
483                if array[1] not in statuss:
484                        return
485                old_show = 0
486                new_show = statuss.index(array[1])
487                status_message = array[2]
488                jid = array[0].split('/')[0]
489                keyID = array[5]
490                contact_nickname = array[7]
491                attached_keys = gajim.config.get_per('accounts', account,
492                        'attached_gpg_keys').split()
493                if jid in attached_keys:
494                        keyID = attached_keys[attached_keys.index(jid) + 1]
495                resource = array[3]
496                if not resource:
497                        resource = ''
498                priority = array[4]
499                if gajim.jid_is_transport(jid):
500                        # It must be an agent
501                        ji = jid.replace('@', '')
502                else:
503                        ji = jid
504
505                # Update contact
506                jid_list = gajim.contacts.get_jid_list(account)
507                if ji in jid_list or jid == gajim.get_jid_from_account(account):
508                        lcontact = gajim.contacts.get_contacts_from_jid(account, ji)
509                        contact1 = None
510                        resources = []
511                        for c in lcontact:
512                                resources.append(c.resource)
513                                if c.resource == resource:
514                                        contact1 = c
515                                        break
516                        if contact1:
517                                if contact1.show in statuss:
518                                        old_show = statuss.index(contact1.show)
519                                if contact_nickname is not None and \
520                                contact1.contact_name != contact_nickname:
521                                        contact1.contact_name = contact_nickname
522                                        self.roster.draw_contact(jid, account)
523                                if old_show == new_show and contact1.status == status_message and \
524                                        contact1.priority == priority: # no change
525                                        return
526                        else:
527                                contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
528                                if not contact1:
529                                        # presence of another resource of our jid
530                                        if resource == gajim.connections[account].server_resource:
531                                                return
532                                        contact1 = gajim.contacts.create_contact(jid = ji,
533                                                name = gajim.nicks[account], groups = [],
534                                                show = array[1], status = status_message, sub = 'both',
535                                                ask = 'none', priority = priority, keyID = keyID,
536                                                resource = resource)
537                                        old_show = 0
538                                        gajim.contacts.add_contact(account, contact1)
539                                        lcontact.append(contact1)
540                                        self.roster.add_self_contact(account)
541                                elif contact1.show in statuss:
542                                        old_show = statuss.index(contact1.show)
543                                if (resources != [''] and (len(lcontact) != 1 or 
544                                lcontact[0].show != 'offline')) and jid.find('@') > 0:
545                                        old_show = 0
546                                        contact1 = gajim.contacts.copy_contact(contact1)
547                                        lcontact.append(contact1)
548                                contact1.resource = resource
549                        if contact1.jid.find('@') > 0 and len(lcontact) == 1:
550                                # It's not an agent
551                                if old_show == 0 and new_show > 1:
552                                        if not contact1.jid in gajim.newly_added[account]:
553                                                gajim.newly_added[account].append(contact1.jid)
554                                        if contact1.jid in gajim.to_be_removed[account]:
555                                                gajim.to_be_removed[account].remove(contact1.jid)
556                                        gobject.timeout_add(5000, self.roster.remove_newly_added,
557                                                contact1.jid, account)
558                                elif old_show > 1 and new_show == 0 and gajim.connections[account].\
559                                        connected > 1:
560                                        if not contact1.jid in gajim.to_be_removed[account]:
561                                                gajim.to_be_removed[account].append(contact1.jid)
562                                        if contact1.jid in gajim.newly_added[account]:
563                                                gajim.newly_added[account].remove(contact1.jid)
564                                        self.roster.draw_contact(contact1.jid, account)
565                                        gobject.timeout_add(5000, self.roster.really_remove_contact,
566                                                contact1, account)
567                        contact1.show = array[1]
568                        contact1.status = status_message
569                        contact1.priority = priority
570                        contact1.keyID = keyID
571                        timestamp = array[6]
572                        if timestamp:
573                                contact1.last_status_time = timestamp
574                        elif not gajim.block_signed_in_notifications[account]:
575                                # We're connected since more that 30 seconds
576                                contact1.last_status_time = time.localtime()
577                        contact1.contact_nickname = contact_nickname
578                if gajim.jid_is_transport(jid):
579                        # It must be an agent
580                        if ji in jid_list:
581                                # Update existing iter
582                                self.roster.draw_contact(ji, account)
583                                self.roster.draw_group(_('Transports'), account)
584                                if new_show > 1 and ji in gajim.transport_avatar[account]:
585                                        # transport just signed in. request avatars
586                                        for jid_ in gajim.transport_avatar[account][ji]:
587                                                gajim.connections[account].request_vcard(jid_)
588                                # transport just signed in/out, don't show popup notifications
589                                # for 30s
590                                account_ji = account + '/' + ji
591                                gajim.block_signed_in_notifications[account_ji] = True
592                                gobject.timeout_add(30000, self.unblock_signed_in_notifications,
593                                        account_ji)
594                        locations = (self.instances, self.instances[account])
595                        for location in locations:
596                                if location.has_key('add_contact'):
597                                        if old_show == 0 and new_show > 1:
598                                                location['add_contact'].transport_signed_in(jid)
599                                                break
600                                        elif old_show > 1 and new_show == 0:
601                                                location['add_contact'].transport_signed_out(jid)
602                                                break
603                elif ji in jid_list:
604                        # It isn't an agent
605                        # reset chatstate if needed:
606                        # (when contact signs out or has errors)
607                        if array[1] in ('offline', 'error'):
608                                contact1.our_chatstate = contact1.chatstate = \
609                                        contact1.composing_xep = None
610                                gajim.connections[account].remove_transfers_for_contact(contact1)
611                        self.roster.chg_contact_status(contact1, array[1], status_message,
612                                account)
613                        # Notifications
614                        if old_show < 2 and new_show > 1:
615                                notify.notify('contact_connected', jid, account, status_message)
616                                if self.remote_ctrl:
617                                        self.remote_ctrl.raise_signal('ContactPresence',
618                                                (account, array))
619
620                        elif old_show > 1 and new_show < 2:
621                                notify.notify('contact_disconnected', jid, account, status_message)
622                                if self.remote_ctrl:
623                                        self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
624                                # FIXME: stop non active file transfers
625                        elif new_show > 1: # Status change (not connected/disconnected or error (<1))
626                                notify.notify('status_change', jid, account, [new_show,
627                                        status_message])
628                else:
629                        # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) doesn't follow the JEP
630                        # remove in 2007
631                        # It's maybe a GC_NOTIFY (specialy for MSN gc)
632                        self.handle_event_gc_notify(account, (jid, array[1], status_message,
633                                array[3], None, None, None, None, None, None, None, None))
634                       
635
636        def handle_event_msg(self, account, array):
637                # 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
638                # chatstate, msg_id, composing_xep, user_nick, xhtml))
639                # user_nick is JEP-0172
640
641                full_jid_with_resource = array[0]
642                jid = gajim.get_jid_without_resource(full_jid_with_resource)
643                resource = gajim.get_resource_from_jid(full_jid_with_resource)
644
645                message = array[1]
646                encrypted = array[3]
647                msg_type = array[4]
648                subject = array[5]
649                chatstate = array[6]
650                msg_id = array[7]
651                composing_xep = array[8]
652                xhtml = array[10]
653                if gajim.config.get('ignore_incoming_xhtml'):
654                        xhtml = None
655                if gajim.jid_is_transport(jid):
656                        jid = jid.replace('@', '')
657
658                groupchat_control = self.msg_win_mgr.get_control(jid, account)
659                pm = False
660                if groupchat_control and groupchat_control.type_id == \
661                message_control.TYPE_GC:
662                        # It's a Private message
663                        pm = True
664                        msg_type = 'pm'
665
666                chat_control = None
667                jid_of_control = full_jid_with_resource
668                highest_contact = gajim.contacts.get_contact_with_highest_priority(
669                        account, jid)
670                # Look for a chat control that has the given resource, or default to one
671                # without resource
672                ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
673                if ctrl:
674                        chat_control = ctrl
675                elif not pm and (not highest_contact or not highest_contact.resource):
676                        # unknow contact or offline message
677                        jid_of_control = jid
678                        chat_control = self.msg_win_mgr.get_control(jid, account)
679                elif highest_contact and resource != highest_contact.resource and \
680                highest_contact.show != 'offline':
681                        jid_of_control = full_jid_with_resource
682                        chat_control = None
683                elif not pm:
684                        jid_of_control = jid
685                        chat_control = self.msg_win_mgr.get_control(jid, account)
686
687                # Handle chat states 
688                contact = gajim.contacts.get_contact(account, jid, resource)
689                if contact and isinstance(contact, list):
690                        contact = contact[0]
691                if contact:
692                        if contact.composing_xep != 'XEP-0085': # We cache xep85 support
693                                contact.composing_xep = composing_xep
694                        if chat_control and chat_control.type_id == message_control.TYPE_CHAT:
695                                if chatstate is not None:
696                                        # other peer sent us reply, so he supports jep85 or jep22
697                                        contact.chatstate = chatstate
698                                        if contact.our_chatstate == 'ask': # we were jep85 disco?
699                                                contact.our_chatstate = 'active' # no more
700                                        chat_control.handle_incoming_chatstate()
701                                elif contact.chatstate != 'active':
702                                        # got no valid jep85 answer, peer does not support it
703                                        contact.chatstate = False
704                        elif chatstate == 'active':
705                                # Brand new message, incoming. 
706                                contact.our_chatstate = chatstate
707                                contact.chatstate = chatstate
708                                if msg_id: # Do not overwrite an existing msg_id with None
709                                        contact.msg_id = msg_id
710
711                # THIS MUST BE AFTER chatstates handling
712                # AND BEFORE playsound (else we ear sounding on chatstates!)
713                if not message: # empty message text
714                        return
715
716                if gajim.config.get('ignore_unknown_contacts') and \
717                        not gajim.contacts.get_contact(account, jid) and not pm:
718                        return
719                if not contact:
720                        # contact is not in the roster, create a fake one to display
721                        # notification
722                        contact = common.contacts.Contact(jid = jid, resource = resource)
723                advanced_notif_num = notify.get_advanced_notification('message_received',
724                        account, contact)
725
726                # Is it a first or next message received ?
727                first = False
728                if msg_type == 'normal':
729                        if not gajim.events.get_events(account, jid, ['normal']):
730                                first = True
731                elif not chat_control and not gajim.events.get_events(account,
732                jid_of_control, [msg_type]): # msg_type can be chat or pm
733                        first = True
734
735                if pm:
736                        nickname = resource
737                        groupchat_control.on_private_message(nickname, message, array[2],
738                                xhtml, msg_id)
739                else:
740                        # array: (jid, msg, time, encrypted, msg_type, subject)
741                        if encrypted:
742                                self.roster.on_message(jid, message, array[2], account, array[3],
743                                        msg_type, subject, resource, msg_id, array[9],
744                                        advanced_notif_num)
745                        else:
746                                # xhtml in last element
747                                self.roster.on_message(jid, message, array[2], account, array[3],
748                                        msg_type, subject, resource, msg_id, array[9],
749                                        advanced_notif_num, xhtml = xhtml)
750                        nickname = gajim.get_name_from_jid(account, jid)
751                # Check and do wanted notifications     
752                msg = message
753                if subject:
754                        msg = _('Subject: %s') % subject + '\n' + msg
755                notify.notify('new_message', jid_of_control, account, [msg_type,
756                        first, nickname, msg], advanced_notif_num)
757
758                if self.remote_ctrl:
759                        self.remote_ctrl.raise_signal('NewMessage', (account, array))
760
761        def handle_event_msgerror(self, account, array):
762                #'MSGERROR' (account, (jid, error_code, error_msg, msg, time))
763                full_jid_with_resource = array[0]
764                jids = full_jid_with_resource.split('/', 1)
765                jid = jids[0]
766                gc_control = self.msg_win_mgr.get_control(jid, account)
767                if gc_control and gc_control.type_id != message_control.TYPE_GC:
768                        gc_control = None
769                if gc_control:
770                        if len(jids) > 1: # it's a pm
771                                nick = jids[1]
772                                if not self.msg_win_mgr.get_control(full_jid_with_resource,
773                                account):
774                                        tv = gc_control.list_treeview
775                                        model = tv.get_model()
776                                        iter = gc_control.get_contact_iter(nick)
777                                        if iter:
778                                                show = mod