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

Revision 4854, 55.0 kB (checked in by nk, 3 years ago)

[greblus] preferences window now can control the color of URLs

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