root/branches/gajim_0.8/src/gajim.py

Revision 3114, 42.6 kB (checked in by nk, 3 years ago)

xs:boolean is '0', '1', 'true', 'false'. so update bookmark to handle the last two

  • Property executable set to 1
  • Property svn:executable set to *
  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
Line 
1#!/bin/sh
2''':'
3exec python -OOt "$0" ${1+"$@"}
4' '''
5##      gajim.py
6##
7## Gajim Team:
8## - Yann Le Boulanger <asterix@lagaule.org>
9## - Vincent Hanquez <tab@snarc.org>
10## - Nikos Kouremenos <kourem@gmail.com>
11## - Dimitur Kirov <dkirov@gmail.com>
12##
13##      Copyright (C) 2003-2005 Gajim Team
14##
15## This program is free software; you can redistribute it and/or modify
16## it under the terms of the GNU General Public License as published
17## by the Free Software Foundation; version 2 only.
18##
19## This program is distributed in the hope that it will be useful,
20## but WITHOUT ANY WARRANTY; without even the implied warranty of
21## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22## GNU General Public License for more details.
23##
24               
25import sys
26import pygtk
27import os
28if not os.name == 'nt': # py2exe only in windows
29                pygtk.require('2.0') # py2exe fails on this
30try:
31        import gtk
32except RuntimeError, msg:
33        if str(msg) == 'could not open display':
34                print 'Gajim needs Xserver to run. Exiting...'
35                sys.exit()
36               
37import gobject
38import pango
39import sre
40import signal
41import getopt
42import time
43
44from common import socks5
45import gtkgui_helpers
46
47from common import i18n
48i18n.init()
49_ = i18n._
50
51import common.sleepy
52import check_for_new_version
53from common import gajim
54from common import connection
55from common import helpers
56
57from common import optparser
58
59profile = ''
60try:
61        opts, args = getopt.getopt(sys.argv[1:], 'hvp:', ['help', 'verbose',
62                'profile='])
63except getopt.error, msg:
64        print msg
65        print 'for help use --help'
66        sys.exit(2)
67for o, a in opts:
68        if o in ('-h', '--help'):
69                print 'gajim [--help] [--verbose] [--profile name]'
70                sys.exit()
71        elif o in ('-v', '--verbose'):
72                gajim.verbose = True
73        elif o in ('-p', '--profile'): # gajim --profile name
74                profile = a
75
76
77config_filename = os.path.expanduser('~/.gajim/config')
78if os.name == 'nt':
79        try:
80                # Documents and Settings\[User Name]\Application Data\Gajim\logs
81                config_filename = os.environ['appdata'] + '/Gajim/config'
82        except KeyError:
83                # win9x so ./config
84                config_filename = 'config'
85
86if profile:
87        config_filename += '.%s' % profile
88
89parser = optparser.OptionsParser(config_filename)
90
91try:
92        import winsound # windows-only built-in module for playing wav
93except ImportError:
94        pass
95
96class Contact:
97        '''Information concerning each contact'''
98        def __init__(self, jid='', name='', groups=[], show='', status='', sub='',\
99                        ask='', resource='', priority=5, keyID='', role='', affiliation='',\
100                        chatstate=None):
101                self.jid = jid
102                self.name = name
103                self.groups = groups
104                self.show = show
105                self.status = status
106                self.sub = sub
107                self.ask = ask
108                self.resource = resource
109                self.priority = priority
110                self.keyID = keyID
111                self.role = role
112                self.affiliation = affiliation
113
114                # please read jep-85 http://www.jabber.org/jeps/jep-0085.html
115                # we keep track of jep85 support by the peer by three extra states:
116                # None, False and 'ask'
117                # None if no info about peer
118                # False if peer does not support jep85
119                # 'ask' if we sent the first 'active' chatstate and are waiting for reply
120                # this holds what WE SEND to contact (the current chatstate)
121                self.chatstate = chatstate
122
123import roster_window
124import systray
125import dialogs
126import config
127
128GTKGUI_GLADE = 'gtkgui.glade'
129
130
131class Interface:
132        def handle_event_roster(self, account, data):
133                #('ROSTER', account, array)
134                self.roster.fill_contacts_and_groups_dicts(data, account)
135                self.roster.draw_roster()
136                if self.remote and self.remote.is_enabled():
137                        self.remote.raise_signal('Roster', (account, data))
138
139        def handle_event_warning(self, unused, data):
140                #('WARNING', account, (title_text, section_text))
141                dialogs.WarningDialog(data[0], data[1]).get_response()
142
143        def handle_event_error(self, unused, data):
144                #('ERROR', account, (title_text, section_text))
145                dialogs.ErrorDialog(data[0], data[1]).get_response()
146
147        def handle_event_information(self, unused, data):
148                #('INFORMATION', account, (title_text, section_text))
149                dialogs.InformationDialog(data[0], data[1])
150
151        def handle_event_http_auth(self, account, data):
152                #('HTTP_AUTH', account, (method, url, iq_obj))
153                dialog = dialogs.ConfirmationDialog(_('HTTP (%s) Authorization for %s') \
154                        % (data[0], data[1]), _('Do you accept this request?'))
155                if dialog.get_response() == gtk.RESPONSE_OK:
156                        answer = 'yes'
157                else:
158                        answer = 'no'
159                gajim.connections[account].build_http_auth_answer(data[2], answer)
160
161        def handle_event_error_answer(self, account, array):
162                id, jid_from, errmsg, errcode = array
163                if str(errcode) in ['403', '406'] and id:
164                        # show the error dialog
165                        ft = self.windows['file_transfers']
166                        sid = id
167                        if len(id) > 3 and id[2] == '_':
168                                sid = id[3:]
169                        if ft.files_props['s'].has_key(sid):
170                                file_props = ft.files_props['s'][sid]
171                                file_props['error'] = -4
172                                self.handle_event_file_request_error(account, 
173                                        (jid_from, file_props))
174                                conn = gajim.connections[account]
175                                conn.disconnect_transfer(file_props)
176                                return
177                elif str(errcode) == '404':
178                        conn = gajim.connections[account]
179                        sid = id
180                        if len(id) > 3 and id[2] == '_':
181                                sid = id[3:]
182                        if conn.files_props.has_key(sid):
183                                file_props = conn.files_props[sid]
184                                self.handle_event_file_send_error(account, 
185                                        (jid_from, file_props))
186                                conn.disconnect_transfer(file_props)
187                                return
188                #('ERROR_ANSWER', account, (id, jid_from. errmsg, errcode))
189                if jid_from in self.windows[account]['gc']:
190                        self.windows[account]['gc'][jid_from].print_conversation(
191                                'Error %s: %s' % (array[2], array[1]), jid_from)
192
193        def handle_event_con_type(self, account, con_type):
194                # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'tcp'
195                gajim.con_types[account] = con_type
196
197        def allow_notif(self, account):
198                gajim.allow_notifications[account] = True
199
200        def handle_event_status(self, account, status): # OUR status
201                #('STATUS', account, status)
202                if status != 'offline':
203                        gobject.timeout_add(30000, self.allow_notif, account)
204                else:
205                        gajim.allow_notifications[account] = False
206                        # we are disconnected from all gc
207                        for room_jid in gajim.gc_connected[account]:
208                                if self.windows[account]['gc'].has_key(room_jid):
209                                        self.windows[account]['gc'][room_jid].got_disconnected(room_jid)
210                self.roster.on_status_changed(account, status)
211                if self.remote and self.remote.is_enabled():
212                        self.remote.raise_signal('AccountPresence', (status, account))
213       
214        def handle_event_notify(self, account, array):
215                #('NOTIFY', account, (jid, status, message, resource, priority, keyID,
216                # role, affiliation, real_jid, reason, actor, statusCode, new_nick))
217                # if we're here it means contact changed show
218                statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
219                        'invisible']
220                old_show = 0
221                new_show = statuss.index(array[1])
222                jid = array[0].split('/')[0]
223                keyID = array[5]
224                attached_keys = gajim.config.get_per('accounts', account,
225                        'attached_gpg_keys').split()
226                if jid in attached_keys:
227                        keyID = attached_keys[attached_keys.index(jid) + 1]
228                resource = array[3]
229                if not resource:
230                        resource = ''
231                priority = array[4]
232                if jid.find('@') <= 0:
233                        #It must be an agent
234                        ji = jid.replace('@', '')
235                else:
236                        ji = jid
237                #Update user
238                if gajim.contacts[account].has_key(ji):
239                        luser = gajim.contacts[account][ji]
240                        user1 = None
241                        resources = []
242                        for u in luser:
243                                resources.append(u.resource)
244                                if u.resource == resource:
245                                        user1 = u
246                                        break
247                        if user1:
248                                if user1.show in statuss:
249                                        old_show = statuss.index(user1.show)
250                                if old_show == new_show and user1.status == array[2]: #no change
251                                        return
252                        else:
253                                user1 = gajim.contacts[account][ji][0]
254                                if user1.show in statuss:
255                                        old_show = statuss.index(user1.show)
256                                if (resources != [''] and (len(luser) != 1 or 
257                                        luser[0].show != 'offline')) and jid.find('@') > 0:
258                                        old_show = 0
259                                        user1 = Contact(jid = user1.jid, name = user1.name,
260                                                groups = user1.groups, show = user1.show,
261                                                status = user1.status, sub = user1.sub, ask = user1.ask,
262                                                resource = user1.resource, priority = user1.priority,
263                                                keyID = user1.keyID)
264                                        luser.append(user1)
265                                user1.resource = resource
266                        if user1.jid.find('@') > 0 and len(luser) == 1: # It's not an agent
267                                if old_show == 0 and new_show > 1:
268                                        if not user1.jid in gajim.newly_added[account]:
269                                                gajim.newly_added[account].append(user1.jid)
270                                        if user1.jid in gajim.to_be_removed[account]:
271                                                gajim.to_be_removed[account].remove(user1.jid)
272                                        gobject.timeout_add(5000, self.roster.remove_newly_added, \
273                                                user1.jid, account)
274                                if old_show > 1 and new_show == 0 and gajim.connections[account].\
275                                        connected > 1:
276                                        if not user1.jid in gajim.to_be_removed[account]:
277                                                gajim.to_be_removed[account].append(user1.jid)
278                                        if user1.jid in gajim.newly_added[account]:
279                                                gajim.newly_added[account].remove(user1.jid)
280                                        self.roster.draw_contact(user1.jid, account)
281                                        if not gajim.awaiting_messages[account].has_key(jid):
282                                                gobject.timeout_add(5000, self.roster.really_remove_contact, \
283                                                        user1, account)
284                        user1.show = array[1]
285                        user1.status = array[2]
286                        user1.priority = priority
287                        user1.keyID = keyID
288                if jid.find('@') <= 0:
289                        #It must be an agent
290                        if gajim.contacts[account].has_key(ji):
291                                #Update existing iter
292                                self.roster.draw_contact(ji, account)
293                elif gajim.contacts[account].has_key(ji):
294                        #It isn't an agent
295                        self.roster.chg_contact_status(user1, array[1], array[2], account)
296                        #play sound
297                        if old_show < 2 and new_show > 1:
298                                if gajim.config.get_per('soundevents', 'contact_connected',
299                                                                                                'enabled'):
300                                        helpers.play_sound('contact_connected')
301                                if not self.windows[account]['chats'].has_key(jid) and \
302                                        not gajim.awaiting_messages[account].has_key(jid) and \
303                                        gajim.config.get('notify_on_signin') and \
304                                        gajim.allow_notifications[account]:
305                                        show_notification = False
306                                        # check OUR status and if we allow notifications for that status
307                                        if gajim.config.get('autopopupaway'): # always notify
308                                                show_notification = True
309                                        elif gajim.connections[account].connected in (2, 3): # we're online or chat
310                                                show_notification = True
311                                        if show_notification:
312                                                instance = dialogs.PopupNotificationWindow(self,
313                                                                                                                _('Contact Signed In'), jid, account)
314                                                self.roster.popup_notification_windows.append(instance)
315                                if self.remote and self.remote.is_enabled():
316                                        self.remote.raise_signal('ContactPresence',
317                                                (account, array))
318                               
319                                # when contact signs out we reset his chatstate
320                                contact = gajim.get_first_contact_instance_from_jid(account, jid)
321                                contact.chatstate = None
322                               
323                        elif old_show > 1 and new_show < 2:
324                                if gajim.config.get_per('soundevents', 'contact_disconnected',
325                                                                                                'enabled'):
326                                        helpers.play_sound('contact_disconnected')
327                                if not self.windows[account]['chats'].has_key(jid) and \
328                                        not gajim.awaiting_messages[account].has_key(jid) and \
329                                        gajim.config.get('notify_on_signout'):
330                                        show_notification = False
331                                        # check OUR status and if we allow notifications for that status
332                                        if gajim.config.get('autopopupaway'): # always notify
333                                                show_notification = True
334                                        elif gajim.connections[account].connected in (2, 3): # we're online or chat
335                                                show_notification = True
336                                        if show_notification:
337                                                instance = dialogs.PopupNotificationWindow(self,
338                                                                                                        _('Contact Signed Out'), jid, account)
339                                                self.roster.popup_notification_windows.append(instance)
340                                if self.remote and self.remote.is_enabled():
341                                        self.remote.raise_signal('ContactAbsence', (account, array))
342                               
343                elif self.windows[account]['gc'].has_key(ji):
344                        #it is a groupchat presence
345                        #TODO: upgrade the chat instances (for pm)
346                        self.windows[account]['gc'][ji].chg_contact_status(ji, resource,
347                                array[1], array[2], array[6], array[7], array[8], array[9],
348                                array[10], array[11], array[12], account)
349                        if self.remote and self.remote.is_enabled():
350                                self.remote.raise_signal('GCPresence', (account, array))
351
352        def handle_event_msg(self, account, array):
353                #('MSG', account, (jid, msg, time, encrypted, msg_type, subject, chatstate))
354                jid = gajim.get_jid_without_resource(array[0])
355                msg_type = array[4]
356                chatstate = array[6]
357                if jid.find('@') <= 0:
358                        jid = jid.replace('@', '')
359
360                if self.windows[account]['gc'].has_key(jid): # it's a Private Message
361                        nick = array[0].split('/', 1)[1]
362                        fjid = jid + '/' + nick
363                        if self.windows[account]['chats'].has_key(fjid):
364                                chat_win = self.windows[account]['chats'][fjid]
365                                chat_win.print_conversation(array[1], fjid, tim = array[2])
366                                return
367                        qs = gajim.awaiting_messages[account]
368                        if not qs.has_key(fjid):
369                                qs[fjid] = []
370                        qs[fjid].append((array[1], array[2], array[3]))
371                        self.roster.nb_unread += 1
372                        show = gajim.gc_contacts[account][jid][nick].show
373                        c = Contact(jid = fjid, name = nick, groups = ['none'], show = show,
374                                ask = 'none')
375                        self.roster.new_chat(c, account)
376                        return
377                               
378                if gajim.config.get('ignore_unknown_contacts') and \
379                        not gajim.contacts[account].has_key(jid):
380                        return
381
382                if self.windows[account]['chats'].has_key(jid):
383                        chat_win = self.windows[account]['chats'][jid]
384                        contact = gajim.get_first_contact_instance_from_jid(account, jid)
385                        if chatstate is not None: # he sent us reply, so he supports jep85
386                                if contact.chatstate == 'ask': # we were jep85 disco?
387                                        contact.chatstate = 'active' # no more
388                               
389                                chat_win.handle_incoming_chatstate(account, jid, chatstate)
390                        else:
391                                # got no valid jep85 answer, peer does not support it
392                                contact.chatstate = False
393
394                if not array[1]: #empty message text
395                        return
396
397                first = False
398                if not self.windows[account]['chats'].has_key(jid) and \
399                                                not gajim.awaiting_messages[account].has_key(jid):
400                        first = True
401                        if gajim.config.get('notify_on_new_message'):
402                                show_notification = False
403                                # check OUR status and if we allow notifications for that status
404                                if gajim.config.get('autopopupaway'): # always show notification
405                                        show_notification = True
406                                elif gajim.connections[account].connected in (2, 3): # we're online or chat
407                                        show_notification = True
408                                if show_notification:
409                                        if msg_type == 'normal': # single message
410                                                instance = dialogs.PopupNotificationWindow(self,
411                                                        _('New Single Message'), jid, account, msg_type)
412                                        else: # chat message
413                                                instance = dialogs.PopupNotificationWindow(self,
414                                                        _('New Message'), jid, account, msg_type)
415
416                                        self.roster.popup_notification_windows.append(instance)
417
418                # array : (contact, msg, time, encrypted, msg_type, subject)
419                self.roster.on_message(jid, array[1], array[2], account, array[3],
420                        array[4], array[5])
421                if gajim.config.get_per('soundevents', 'first_message_received',
422                        'enabled') and first:
423                        helpers.play_sound('first_message_received')
424                if gajim.config.get_per('soundevents', 'next_message_received',
425                        'enabled') and not first:
426                        helpers.play_sound('next_message_received')
427                if self.remote and self.remote.is_enabled():
428                        self.remote.raise_signal('NewMessage', (account, array))
429
430        def handle_event_msgerror(self, account, array):
431                #('MSGERROR', account, (jid, error_code, error_msg, msg, time))
432                fjid = array[0]
433                jids = fjid.split('/', 1)
434                jid = jids[0]
435                gcs = self.windows[account]['gc']
436                if jid in gcs:
437                        if len(jids) > 1: # it's a pm
438                                nick = jids[1]
439                                if not self.windows[account]['chats'].has_key(fjid):
440                                        gc = gcs[jid]
441                                        tv = gc.list_treeview[jid]
442                                        model = tv.get_model()
443                                        i = gc.get_contact_iter(jid, nick)
444                                        if i:
445                                                show = model[i][3]
446                                        else:
447                                                show = 'offline'
448                                        c = Contact(jid = fjid, name = nick, groups = ['none'],
449                                                show = show, ask = 'none')
450                                        self.roster.new_chat(c, account)
451                                self.windows[account]['chats'][fjid].print_conversation(
452                                        'Error %s: %s' % (array[1], array[2]), fjid, 'status')
453                                return
454                        gcs[jid].print_conversation('Error %s: %s' % \
455                                (array[1], array[2]), jid)
456                        if gcs[jid].get_active_jid() == jid:
457                                gcs[jid].set_subject(jid,
458                                        gcs[jid].subjects[jid])
459                        return
460                if jid.find('@') <= 0:
461                        jid = jid.replace('@', '')
462                self.roster.on_message(jid, _('error while sending') + \
463                        ' \"%s\" ( %s )' % (array[3], array[2]), array[4], account)
464               
465        def handle_event_msgsent(self, account, array):
466                #('MSGSENT', account, (jid, msg, keyID))
467                msg = array[1]
468                # do not play sound when standalone chatstate message (eg no msg)
469                if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'):
470                        helpers.play_sound('message_sent')
471               
472        def handle_event_subscribe(self, account, array):
473                #('SUBSCRIBE', account, (jid, text))
474                dialogs.SubscriptionRequestWindow(self, array[0], array[1], account)
475                if self.remote and self.remote.is_enabled():
476                        self.remote.raise_signal('Subscribe', (account, array))
477
478        def handle_event_subscribed(self, account, array):
479                #('SUBSCRIBED', account, (jid, resource))
480                jid = array[0]
481                if gajim.contacts[account].has_key(jid):
482                        c = gajim.get_first_contact_instance_from_jid(account, jid)
483                        c.resource = array[1]
484                        self.roster.remove_contact(c, account)
485                        if _('not in the roster') in c.groups:
486                                c.groups.remove(_('not in the roster'))
487                        if len(c.groups) == 0:
488                                c.groups = [_('General')]
489                        self.roster.add_contact_to_roster(c.jid, account)
490                        gajim.connections[account].update_contact(c.jid, c.name, c.groups)
491                else:
492                        keyID = ''
493                        attached_keys = gajim.config.get_per('accounts', account,
494                                'attached_gpg_keys').split()
495                        if jid in attached_keys:
496                                keyID = attached_keys[attached_keys.index(jid) + 1]
497                        contact1 = Contact(jid = jid, name = jid.split('@')[0],
498                                groups = [_('General')], show = 'online', status = 'online',
499                                ask = 'to', resource = array[1], keyID = keyID)
500                        gajim.contacts[account][jid] = [contact1]
501                        self.roster.add_contact_to_roster(jid, account)
502                dialogs.InformationDialog(_('Authorization accepted'),
503                                _('The contact "%s" has authorized you to see his status.')
504                                % jid)
505                if self.remote and self.remote.is_enabled():
506                        self.remote.raise_signal('Subscribed', (account, array))
507
508        def handle_event_unsubscribed(self, account, jid):
509                dialogs.InformationDialog(_('Contact "%s" removed subscription from you') % jid,
510                                _('You will always see him as offline.'))
511                if self.remote and self.remote.is_enabled():
512                        self.remote.raise_signal('Unsubscribed', (account, jid))
513
514        def handle_event_agent_info(self, account, array):
515                #('AGENT_INFO', account, (agent, identities, features, items))
516                if self.windows[account].has_key('disco'):
517                        self.windows[account]['disco'].agent_info(array[0], array[1], \
518                                array[2], array[3])
519
520        def handle_event_register_agent_info(self, account, array):
521                #('AGENT_INFO', account, (agent, infos))
522                if array[1].has_key('instructions'):
523                        config.ServiceRegistrationWindow(array[0], array[1], self, account)
524                else:
525                        dialogs.ErrorDialog(_('Contact with "%s" cannot be established'\
526% array[0]), _('Check your connection or try again later.')).get_response()
527
528        def handle_event_agent_info_items(self, account, array):
529                #('AGENT_INFO_ITEMS', account, (agent, node, items))
530</