don't prevent removing a sevret key when none in available. Fixes #1210
[gajim.git] / src / gajim.py
blob41a29ce65de29fe95dda731c5d07df882a5bc678
1 # -*- coding:utf-8 -*-
2 ## src/gajim.py
3 ##
4 ## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
6 ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
7 ## Norman Rasmussen <norman AT rasmussen.co.za>
8 ## Stéphan Kochen <stephan AT kochen.nl>
9 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
10 ## Alex Mauer <hawke AT hawkesnest.net>
11 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
12 ## Nikos Kouremenos <kourem AT gmail.com>
13 ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
14 ## Stefan Bethge <stefan AT lanpartei.de>
15 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
16 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
17 ## James Newton <redshodan AT gmail.com>
18 ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
19 ## Julien Pivotto <roidelapluie AT gmail.com>
20 ## Stephan Erb <steve-e AT h3c.de>
21 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
23 ## This file is part of Gajim.
25 ## Gajim is free software; you can redistribute it and/or modify
26 ## it under the terms of the GNU General Public License as published
27 ## by the Free Software Foundation; version 3 only.
29 ## Gajim is distributed in the hope that it will be useful,
30 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
31 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 ## GNU General Public License for more details.
34 ## You should have received a copy of the GNU General Public License
35 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
38 import os
39 import sys
40 import warnings
42 if os.name == 'nt':
43 warnings.filterwarnings(action='ignore')
45 if os.path.isdir('gtk'):
46 # Used to create windows installer with GTK included
47 paths = os.environ['PATH']
48 list_ = paths.split(';')
49 new_list = []
50 for p in list_:
51 if p.find('gtk') < 0 and p.find('GTK') < 0:
52 new_list.append(p)
53 new_list.insert(0, 'gtk/lib')
54 new_list.insert(0, 'gtk/bin')
55 os.environ['PATH'] = ';'.join(new_list)
56 os.environ['GTK_BASEPATH'] = 'gtk'
58 if os.name == 'nt':
59 # needed for docutils
60 sys.path.append('.')
62 from common import logging_helpers
63 logging_helpers.init('TERM' in os.environ)
65 import logging
66 # gajim.gui or gajim.gtk more appropriate ?
67 log = logging.getLogger('gajim.gajim')
69 import getopt
70 from common import i18n
72 def parseOpts():
73 profile = ''
74 config_path = None
76 try:
77 shortargs = 'hqvl:p:c:'
78 longargs = 'help quiet verbose loglevel= profile= config_path='
79 opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
80 except getopt.error, msg:
81 print msg
82 print 'for help use --help'
83 sys.exit(2)
84 for o, a in opts:
85 if o in ('-h', '--help'):
86 print 'gajim [--help] [--quiet] [--verbose] [--loglevel subsystem=level[,subsystem=level[...]]] [--profile name] [--config-path]'
87 sys.exit()
88 elif o in ('-q', '--quiet'):
89 logging_helpers.set_quiet()
90 elif o in ('-v', '--verbose'):
91 logging_helpers.set_verbose()
92 elif o in ('-p', '--profile'): # gajim --profile name
93 profile = a
94 elif o in ('-l', '--loglevel'):
95 logging_helpers.set_loglevels(a)
96 elif o in ('-c', '--config-path'):
97 config_path = a
98 return profile, config_path
100 profile, config_path = parseOpts()
101 del parseOpts
103 import locale
104 profile = unicode(profile, locale.getpreferredencoding())
106 import common.configpaths
107 common.configpaths.gajimpaths.init(config_path)
108 del config_path
109 common.configpaths.gajimpaths.init_profile(profile)
110 del profile
112 if os.name == 'nt':
113 class MyStderr(object):
114 _file = None
115 _error = None
116 def write(self, text):
117 fname=os.path.join(common.configpaths.gajimpaths.root,
118 os.path.split(sys.executable)[1]+'.log')
119 if self._file is None and self._error is None:
120 try:
121 self._file = open(fname, 'a')
122 except Exception, details:
123 self._error = details
124 if self._file is not None:
125 self._file.write(text)
126 self._file.flush()
127 def flush(self):
128 if self._file is not None:
129 self._file.flush()
131 sys.stderr = MyStderr()
133 # PyGTK2.10+ only throws a warning
134 warnings.filterwarnings('error', module='gtk')
135 try:
136 import gtk
137 except Warning, msg:
138 if str(msg) == 'could not open display':
139 print >> sys.stderr, _('Gajim needs X server to run. Quiting...')
140 else:
141 print >> sys.stderr, _('importing PyGTK failed: %s') % str(msg)
142 sys.exit()
143 warnings.resetwarnings()
145 if os.name == 'nt':
146 warnings.filterwarnings(action='ignore')
148 pritext = ''
150 from common import exceptions
151 try:
152 from common import gajim
153 except exceptions.DatabaseMalformed:
154 pritext = _('Database Error')
155 sectext = _('The database file (%s) cannot be read. Try to repair it (see http://trac.gajim.org/wiki/DatabaseBackup) or remove it (all history will be lost).') % common.logger.LOG_DB_PATH
156 else:
157 from common import dbus_support
158 if dbus_support.supported:
159 import dbus
161 if os.name == 'posix': # dl module is Unix Only
162 try: # rename the process name to gajim
163 import dl
164 libc = dl.open('/lib/libc.so.6')
165 libc.call('prctl', 15, 'gajim\0', 0, 0, 0)
166 except Exception:
167 pass
169 if gtk.pygtk_version < (2, 12, 0):
170 pritext = _('Gajim needs PyGTK 2.12 or above')
171 sectext = _('Gajim needs PyGTK 2.12 or above to run. Quiting...')
172 elif gtk.gtk_version < (2, 12, 0):
173 pritext = _('Gajim needs GTK 2.12 or above')
174 sectext = _('Gajim needs GTK 2.12 or above to run. Quiting...')
176 try:
177 import gtk.glade # check if user has libglade (in pygtk and in gtk)
178 except ImportError:
179 pritext = _('GTK+ runtime is missing libglade support')
180 if os.name == 'nt':
181 sectext = _('Please remove your current GTK+ runtime and install the latest stable version from %s') % 'http://gladewin32.sourceforge.net'
182 else:
183 sectext = _('Please make sure that GTK+ and PyGTK have libglade support in your system.')
185 try:
186 from common import check_paths
187 except exceptions.PysqliteNotAvailable, e:
188 pritext = _('Gajim needs PySQLite2 to run')
189 sectext = str(e)
191 if os.name == 'nt':
192 try:
193 import winsound # windows-only built-in module for playing wav
194 import win32api # do NOT remove. we req this module
195 except Exception:
196 pritext = _('Gajim needs pywin32 to run')
197 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'
199 if pritext:
200 dlg = gtk.MessageDialog(None,
201 gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
202 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
204 dlg.format_secondary_text(sectext)
205 dlg.run()
206 dlg.destroy()
207 sys.exit()
209 del pritext
211 import gtkexcepthook
213 import gobject
214 if not hasattr(gobject, 'timeout_add_seconds'):
215 def timeout_add_seconds_fake(time_sec, *args):
216 return gobject.timeout_add(time_sec * 1000, *args)
217 gobject.timeout_add_seconds = timeout_add_seconds_fake
219 import re
220 import signal
221 import time
222 import math
224 import gtkgui_helpers
225 import notify
226 import message_control
228 from chat_control import ChatControlBase
229 from chat_control import ChatControl
230 from groupchat_control import GroupchatControl
231 from groupchat_control import PrivateChatControl
232 from atom_window import AtomWindow
233 from session import ChatControlSession
235 import common.sleepy
237 from common.xmpp import idlequeue
238 from common.zeroconf import connection_zeroconf
239 from common import resolver
240 from common import proxy65_manager
241 from common import socks5
242 from common import helpers
243 from common import optparser
244 from common import dataforms
245 from common import passwords
247 gajimpaths = common.configpaths.gajimpaths
249 pid_filename = gajimpaths['PID_FILE']
250 config_filename = gajimpaths['CONFIG_FILE']
252 import traceback
253 import errno
255 import dialogs
256 def pid_alive():
257 try:
258 pf = open(pid_filename)
259 except IOError:
260 # probably file not found
261 return False
263 try:
264 pid = int(pf.read().strip())
265 pf.close()
266 except Exception:
267 traceback.print_exc()
268 # PID file exists, but something happened trying to read PID
269 # Could be 0.10 style empty PID file, so assume Gajim is running
270 return True
272 if os.name == 'nt':
273 try:
274 from ctypes import (windll, c_ulong, c_int, Structure, c_char, POINTER, pointer, )
275 except Exception:
276 return True
278 class PROCESSENTRY32(Structure):
279 _fields_ = [
280 ('dwSize', c_ulong, ),
281 ('cntUsage', c_ulong, ),
282 ('th32ProcessID', c_ulong, ),
283 ('th32DefaultHeapID', c_ulong, ),
284 ('th32ModuleID', c_ulong, ),
285 ('cntThreads', c_ulong, ),
286 ('th32ParentProcessID', c_ulong, ),
287 ('pcPriClassBase', c_ulong, ),
288 ('dwFlags', c_ulong, ),
289 ('szExeFile', c_char*512, ),
291 def __init__(self):
292 Structure.__init__(self, 512+9*4)
294 k = windll.kernel32
295 k.CreateToolhelp32Snapshot.argtypes = c_ulong, c_ulong,
296 k.CreateToolhelp32Snapshot.restype = c_int
297 k.Process32First.argtypes = c_int, POINTER(PROCESSENTRY32),
298 k.Process32First.restype = c_int
299 k.Process32Next.argtypes = c_int, POINTER(PROCESSENTRY32),
300 k.Process32Next.restype = c_int
302 def get_p(p):
303 h = k.CreateToolhelp32Snapshot(2, 0) # TH32CS_SNAPPROCESS
304 assert h > 0, 'CreateToolhelp32Snapshot failed'
305 b = pointer(PROCESSENTRY32())
306 f = k.Process32First(h, b)
307 while f:
308 if b.contents.th32ProcessID == p:
309 return b.contents.szExeFile
310 f = k.Process32Next(h, b)
312 if get_p(pid) in ('python.exe', 'gajim.exe'):
313 return True
314 return False
315 try:
316 if not os.path.exists('/proc'):
317 return True # no /proc, assume Gajim is running
319 try:
320 f = open('/proc/%d/cmdline'% pid)
321 except IOError, e:
322 if e.errno == errno.ENOENT:
323 return False # file/pid does not exist
324 raise
326 n = f.read().lower()
327 f.close()
328 if n.find('gajim') < 0:
329 return False
330 return True # Running Gajim found at pid
331 except Exception:
332 traceback.print_exc()
334 # If we are here, pidfile exists, but some unexpected error occured.
335 # Assume Gajim is running.
336 return True
338 if pid_alive():
339 path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
340 pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
341 gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
342 pritext = _('Gajim is already running')
343 sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
344 dialog = dialogs.YesNoDialog(pritext, sectext)
345 dialog.popup()
346 if dialog.run() != gtk.RESPONSE_YES:
347 sys.exit(3)
348 dialog.destroy()
349 # run anyway, delete pid and useless global vars
350 if os.path.exists(pid_filename):
351 os.remove(pid_filename)
352 del path_to_file
353 del pix
354 del pritext
355 del sectext
356 dialog.destroy()
358 # Create .gajim dir
359 pid_dir = os.path.dirname(pid_filename)
360 if not os.path.exists(pid_dir):
361 check_paths.create_path(pid_dir)
362 # Create pid file
363 try:
364 f = open(pid_filename, 'w')
365 f.write(str(os.getpid()))
366 f.close()
367 except IOError, e:
368 dlg = dialogs.ErrorDialog(_('Disk Write Error'), str(e))
369 dlg.run()
370 dlg.destroy()
371 sys.exit()
372 del pid_dir
373 del f
375 def on_exit():
376 # delete pid file on normal exit
377 if os.path.exists(pid_filename):
378 os.remove(pid_filename)
379 # Shutdown GUI and save config
380 gajim.interface.roster.prepare_quit()
382 import atexit
383 atexit.register(on_exit)
385 parser = optparser.OptionsParser(config_filename)
387 import roster_window
388 import profile_window
389 import config
390 from threading import Thread
393 class PassphraseRequest:
394 def __init__(self, keyid):
395 self.keyid = keyid
396 self.callbacks = []
397 self.dialog_created = False
398 self.dialog = None
399 self.completed = False
401 def interrupt(self):
402 self.dialog.window.destroy()
403 self.callbacks = []
405 def run_callback(self, account, callback):
406 gajim.connections[account].gpg_passphrase(self.passphrase)
407 callback()
409 def add_callback(self, account, cb):
410 if self.completed:
411 self.run_callback(account, cb)
412 else:
413 self.callbacks.append((account, cb))
414 if not self.dialog_created:
415 self.create_dialog(account)
417 def complete(self, passphrase):
418 self.passphrase = passphrase
419 self.completed = True
420 if passphrase is not None:
421 gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase,
422 self.keyid)
423 for (account, cb) in self.callbacks:
424 self.run_callback(account, cb)
425 del self.callbacks
427 def create_dialog(self, account):
428 title = _('Passphrase Required')
429 second = _('Enter GPG key passphrase for key %(keyid)s (account '
430 '%(account)s).') % {'keyid': self.keyid, 'account': account}
432 def _cancel():
433 # user cancelled, continue without GPG
434 self.complete(None)
436 def _ok(passphrase, checked, count):
437 result = gajim.connections[account].test_gpg_passphrase(passphrase)
438 if result == 'ok':
439 # passphrase is good
440 self.complete(passphrase)
441 return
442 elif result == 'expired':
443 dialogs.ErrorDialog(_('GPG key expired'),
444 _('Your GPG key has expied, you will be connected to %s without '
445 'OpenPGP.') % account)
446 # Don't try to connect with GPG
447 gajim.connections[account].continue_connect_info[2] = False
448 self.complete(None)
449 return
451 if count < 3:
452 # ask again
453 dialogs.PassphraseDialog(_('Wrong Passphrase'),
454 _('Please retype your GPG passphrase or press Cancel.'),
455 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
456 else:
457 # user failed 3 times, continue without GPG
458 self.complete(None)
460 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1),
461 cancel_handler=_cancel)
462 self.dialog_created = True
465 class ThreadInterface:
466 def __init__(self, func, func_args, callback, callback_args):
467 '''Call a function in a thread
469 :param func: the function to call in the thread
470 :param func_args: list or arguments for this function
471 :param callback: callback to call once function is finished
472 :param callback_args: list of arguments for this callback
474 def thread_function(func, func_args, callback, callback_args):
475 output = func(*func_args)
476 gobject.idle_add(callback, output, *callback_args)
477 Thread(target=thread_function, args=(func, func_args, callback,
478 callback_args)).start()
480 class Interface:
482 ################################################################################
483 ### Methods handling events from connection
484 ################################################################################
486 def handle_event_roster(self, account, data):
487 #('ROSTER', account, array)
488 # FIXME: Those methods depend to highly on each other
489 # and the order in which they are called
490 self.roster.fill_contacts_and_groups_dicts(data, account)
491 self.roster.add_account_contacts(account)
492 self.roster.fire_up_unread_messages_events(account)
493 if self.remote_ctrl:
494 self.remote_ctrl.raise_signal('Roster', (account, data))
496 def handle_event_warning(self, unused, data):
497 #('WARNING', account, (title_text, section_text))
498 dialogs.WarningDialog(data[0], data[1])
500 def handle_event_error(self, unused, data):
501 #('ERROR', account, (title_text, section_text))
502 dialogs.ErrorDialog(data[0], data[1])
504 def handle_event_information(self, unused, data):
505 #('INFORMATION', account, (title_text, section_text))
506 dialogs.InformationDialog(data[0], data[1])
508 def handle_event_ask_new_nick(self, account, data):
509 #('ASK_NEW_NICK', account, (room_jid,))
510 room_jid = data[0]
511 title = _('Unable to join group chat')
512 prompt = _('Your desired nickname in group chat %s is in use or '
513 'registered by another occupant.\nPlease specify another nickname '
514 'below:') % room_jid
515 check_text = _('Always use this nickname when there is a conflict')
516 if 'change_nick_dialog' in self.instances:
517 self.instances['change_nick_dialog'].add_room(account, room_jid,
518 prompt)
519 else:
520 self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
521 account, room_jid, title, prompt)
523 def handle_event_http_auth(self, account, data):
524 #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
525 def response(account, iq_obj, answer):
526 self.dialog.destroy()
527 gajim.connections[account].build_http_auth_answer(iq_obj, answer)
529 def on_yes(is_checked, account, iq_obj):
530 response(account, iq_obj, 'yes')
532 sec_msg = _('Do you accept this request?')
533 if gajim.get_number_of_connected_accounts() > 1:
534 sec_msg = _('Do you accept this request on account %s?') % account
535 if data[4]:
536 sec_msg = data[4] + '\n' + sec_msg
537 self.dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
538 '%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
539 'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
540 on_response_no=(response, account, data[3], 'no'))
542 def handle_event_error_answer(self, account, array):
543 #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
544 id_, jid_from, errmsg, errcode = array
545 if unicode(errcode) in ('400', '403', '406') and id_:
546 # show the error dialog
547 ft = self.instances['file_transfers']
548 sid = id_
549 if len(id_) > 3 and id_[2] == '_':
550 sid = id_[3:]
551 if sid in ft.files_props['s']:
552 file_props = ft.files_props['s'][sid]
553 if unicode(errcode) == '400':
554 file_props['error'] = -3
555 else:
556 file_props['error'] = -4
557 self.handle_event_file_request_error(account,
558 (jid_from, file_props, errmsg))
559 conn = gajim.connections[account]
560 conn.disconnect_transfer(file_props)
561 return
562 elif unicode(errcode) == '404':
563 conn = gajim.connections[account]
564 sid = id_
565 if len(id_) > 3 and id_[2] == '_':
566 sid = id_[3:]
567 if sid in conn.files_props:
568 file_props = conn.files_props[sid]
569 self.handle_event_file_send_error(account,
570 (jid_from, file_props))
571 conn.disconnect_transfer(file_props)
572 return
574 ctrl = self.msg_win_mgr.get_control(jid_from, account)
575 if ctrl and ctrl.type_id == message_control.TYPE_GC:
576 ctrl.print_conversation('Error %s: %s' % (array[2], array[1]))
578 def handle_event_con_type(self, account, con_type):
579 # ('CON_TYPE', account, con_type) which can be 'ssl', 'tls', 'plain'
580 gajim.con_types[account] = con_type
581 self.roster.draw_account(account)
583 def handle_event_connection_lost(self, account, array):
584 # ('CONNECTION_LOST', account, [title, text])
585 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
586 'connection_lost.png')
587 path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
588 notify.popup(_('Connection Failed'), account, account,
589 'connection_failed', path, array[0], array[1])
591 def unblock_signed_in_notifications(self, account):
592 gajim.block_signed_in_notifications[account] = False
594 def handle_event_status(self, account, status): # OUR status
595 #('STATUS', account, status)
596 model = self.roster.status_combobox.get_model()
597 if status in ('offline', 'error'):
598 for name in self.instances[account]['online_dialog'].keys():
599 # .keys() is needed to not have a dictionary length changed during
600 # iteration error
601 self.instances[account]['online_dialog'][name].destroy()
602 del self.instances[account]['online_dialog'][name]
603 for request in self.gpg_passphrase.values():
604 if request:
605 request.interrupt()
606 # .keys() is needed because dict changes during loop
607 for account in self.pass_dialog.keys():
608 self.pass_dialog[account].window.destroy()
609 if status == 'offline':
610 # sensitivity for this menuitem
611 if gajim.get_number_of_connected_accounts() == 0:
612 model[self.roster.status_message_menuitem_iter][3] = False
613 gajim.block_signed_in_notifications[account] = True
614 else:
615 # 30 seconds after we change our status to sth else than offline
616 # we stop blocking notifications of any kind
617 # this prevents from getting the roster items as 'just signed in'
618 # contacts. 30 seconds should be enough time
619 gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account)
620 # sensitivity for this menuitem
621 model[self.roster.status_message_menuitem_iter][3] = True
623 # Inform all controls for this account of the connection state change
624 ctrls = self.msg_win_mgr.get_controls()
625 if account in self.minimized_controls:
626 # Can not be the case when we remove account
627 ctrls += self.minimized_controls[account].values()
628 for ctrl in ctrls:
629 if ctrl.account == account:
630 if status == 'offline' or (status == 'invisible' and \
631 gajim.connections[account].is_zeroconf):
632 ctrl.got_disconnected()
633 else:
634 # Other code rejoins all GCs, so we don't do it here
635 if not ctrl.type_id == message_control.TYPE_GC:
636 ctrl.got_connected()
637 if ctrl.parent_win:
638 ctrl.parent_win.redraw_tab(ctrl)
640 self.roster.on_status_changed(account, status)
641 if account in self.show_vcard_when_connect and status not in ('offline',
642 'error'):
643 self.edit_own_details(account)
644 if self.remote_ctrl:
645 self.remote_ctrl.raise_signal('AccountPresence', (status, account))
647 def edit_own_details(self, account):
648 jid = gajim.get_jid_from_account(account)
649 if 'profile' not in self.instances[account]:
650 self.instances[account]['profile'] = \
651 profile_window.ProfileWindow(account)
652 gajim.connections[account].request_vcard(jid)
654 def handle_event_notify(self, account, array):
655 # 'NOTIFY' (account, (jid, status, status message, resource,
656 # priority, # keyID, timestamp, contact_nickname))
658 # Contact changed show
660 # FIXME: Drop and rewrite...
662 statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
663 'invisible']
664 # Ignore invalid show
665 if array[1] not in statuss:
666 return
667 old_show = 0
668 new_show = statuss.index(array[1])
669 status_message = array[2]
670 jid = array[0].split('/')[0]
671 keyID = array[5]
672 contact_nickname = array[7]
674 # Get the proper keyID
675 keyID = helpers.prepare_and_validate_gpg_keyID(account, jid, keyID)
677 resource = array[3]
678 if not resource:
679 resource = ''
680 priority = array[4]
681 if gajim.jid_is_transport(jid):
682 # It must be an agent
683 ji = jid.replace('@', '')
684 else:
685 ji = jid
687 highest = gajim.contacts. \
688 get_contact_with_highest_priority(account, jid)
689 was_highest = (highest and highest.resource == resource)
691 conn = gajim.connections[account]
693 # Update contact
694 jid_list = gajim.contacts.get_jid_list(account)
695 if ji in jid_list or jid == gajim.get_jid_from_account(account):
696 lcontact = gajim.contacts.get_contacts(account, ji)
697 contact1 = None
698 resources = []
699 for c in lcontact:
700 resources.append(c.resource)
701 if c.resource == resource:
702 contact1 = c
703 break
705 if contact1:
706 if contact1.show in statuss:
707 old_show = statuss.index(contact1.show)
708 # nick changed
709 if contact_nickname is not None and \
710 contact1.contact_name != contact_nickname:
711 contact1.contact_name = contact_nickname
712 self.roster.draw_contact(jid, account)
714 if old_show == new_show and contact1.status == status_message and \
715 contact1.priority == priority: # no change
716 return
717 else:
718 contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
719 if not contact1:
720 # Presence of another resource of our
721 # jid
722 # Create self contact and add to roster
723 if resource == conn.server_resource:
724 return
725 # Ignore offline presence of unknown self resource
726 if new_show < 2:
727 return
728 contact1 = gajim.contacts.create_contact(jid=ji,
729 name=gajim.nicks[account], groups=['self_contact'],
730 show=array[1], status=status_message, sub='both', ask='none',
731 priority=priority, keyID=keyID, resource=resource,
732 mood=conn.mood, tune=conn.tune, activity=conn.activity)
733 old_show = 0
734 gajim.contacts.add_contact(account, contact1)
735 lcontact.append(contact1)
736 elif contact1.show in statuss:
737 old_show = statuss.index(contact1.show)
738 # FIXME: What am I?
739 if (resources != [''] and (len(lcontact) != 1 or \
740 lcontact[0].show != 'offline')) and jid.find('@') > 0:
741 old_show = 0
742 contact1 = gajim.contacts.copy_contact(contact1)
743 lcontact.append(contact1)
744 contact1.resource = resource
746 self.roster.add_contact(contact1.jid, account)
748 if contact1.jid.find('@') > 0 and len(lcontact) == 1:
749 # It's not an agent
750 if old_show == 0 and new_show > 1:
751 if not contact1.jid in gajim.newly_added[account]:
752 gajim.newly_added[account].append(contact1.jid)
753 if contact1.jid in gajim.to_be_removed[account]:
754 gajim.to_be_removed[account].remove(contact1.jid)
755 gobject.timeout_add_seconds(5, self.roster.remove_newly_added,
756 contact1.jid, account)
757 elif old_show > 1 and new_show == 0 and conn.connected > 1:
758 if not contact1.jid in gajim.to_be_removed[account]:
759 gajim.to_be_removed[account].append(contact1.jid)
760 if contact1.jid in gajim.newly_added[account]:
761 gajim.newly_added[account].remove(contact1.jid)
762 self.roster.draw_contact(contact1.jid, account)
763 gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed,
764 contact1.jid, account)
766 # unset custom status
767 if (old_show == 0 and new_show > 1) or (old_show > 1 and new_show == 0\
768 and conn.connected > 1):
769 if account in self.status_sent_to_users and \
770 jid in self.status_sent_to_users[account]:
771 del self.status_sent_to_users[account][jid]
773 contact1.show = array[1]
774 contact1.status = status_message
775 contact1.priority = priority
776 contact1.keyID = keyID
777 timestamp = array[6]
778 if timestamp:
779 contact1.last_status_time = timestamp
780 elif not gajim.block_signed_in_notifications[account]:
781 # We're connected since more that 30 seconds
782 contact1.last_status_time = time.localtime()
783 contact1.contact_nickname = contact_nickname
785 if gajim.jid_is_transport(jid):
786 # It must be an agent
787 if ji in jid_list:
788 # Update existing iter and group counting
789 self.roster.draw_contact(ji, account)
790 self.roster.draw_group(_('Transports'), account)
791 if new_show > 1 and ji in gajim.transport_avatar[account]:
792 # transport just signed in.
793 # request avatars
794 for jid_ in gajim.transport_avatar[account][ji]:
795 conn.request_vcard(jid_)
796 # transport just signed in/out, don't show
797 # popup notifications for 30s
798 account_ji = account + '/' + ji
799 gajim.block_signed_in_notifications[account_ji] = True
800 gobject.timeout_add_seconds(30,
801 self.unblock_signed_in_notifications, account_ji)
802 locations = (self.instances, self.instances[account])
803 for location in locations:
804 if 'add_contact' in location:
805 if old_show == 0 and new_show > 1:
806 location['add_contact'].transport_signed_in(jid)
807 break
808 elif old_show > 1 and new_show == 0:
809 location['add_contact'].transport_signed_out(jid)
810 break
811 elif ji in jid_list:
812 # It isn't an agent
813 # reset chatstate if needed:
814 # (when contact signs out or has errors)
815 if array[1] in ('offline', 'error'):
816 contact1.our_chatstate = contact1.chatstate = \
817 contact1.composing_xep = None
819 # TODO: This causes problems when another
820 # resource signs off!
821 conn.remove_transfers_for_contact(contact1)
823 # disable encryption, since if any messages are
824 # lost they'll be not decryptable (note that
825 # this contradicts XEP-0201 - trying to get that
826 # in the XEP, though)
828 # there won't be any sessions here if the contact terminated
829 # their sessions before going offline (which we do)
830 for sess in conn.get_sessions(ji):
831 if (ji+'/'+resource) != str(sess.jid):
832 continue
833 if sess.control:
834 sess.control.no_autonegotiation = False
835 if sess.enable_encryption:
836 sess.terminate_e2e()
837 conn.delete_session(jid, sess.thread_id)
839 self.roster.chg_contact_status(contact1, array[1], status_message,
840 account)
841 # Notifications
842 if old_show < 2 and new_show > 1:
843 notify.notify('contact_connected', jid, account, status_message)
844 if self.remote_ctrl:
845 self.remote_ctrl.raise_signal('ContactPresence', (account,
846 array))
848 elif old_show > 1 and new_show < 2:
849 notify.notify('contact_disconnected', jid, account, status_message)
850 if self.remote_ctrl:
851 self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
852 # FIXME: stop non active file transfers
853 # Status change (not connected/disconnected or
854 # error (<1))
855 elif new_show > 1:
856 notify.notify('status_change', jid, account, [new_show,
857 status_message])
858 if self.remote_ctrl:
859 self.remote_ctrl.raise_signal('ContactStatus', (account, array))
860 else:
861 # FIXME: MSN transport (CMSN1.2.1 and PyMSN) don't
862 # follow the XEP, still the case in 2008.
863 # It's maybe a GC_NOTIFY (specialy for MSN gc)
864 self.handle_event_gc_notify(account, (jid, array[1], status_message,
865 array[3], None, None, None, None, None, [], None, None))
867 highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
868 is_highest = (highest and highest.resource == resource)
870 # disconnect the session from the ctrl if the highest resource has changed
871 if (was_highest and not is_highest) or (not was_highest and is_highest):
872 ctrl = self.msg_win_mgr.get_control(jid, account)
874 if ctrl:
875 ctrl.set_session(None)
876 ctrl.contact = highest
878 def handle_event_msgerror(self, account, array):
879 #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, session]))
880 full_jid_with_resource = array[0]
881 jids = full_jid_with_resource.split('/', 1)
882 jid = jids[0]
884 session = None
885 if len(array) > 5:
886 session = array[5]
888 gc_control = self.msg_win_mgr.get_gc_control(jid, account)
889 if not gc_control and \
890 jid in self.minimized_controls[account]:
891 gc_control = self.minimized_controls[account][jid]
892 if gc_control and gc_control.type_id != message_control.TYPE_GC:
893 gc_control = None
894 if gc_control:
895 if len(jids) > 1: # it's a pm
896 nick = jids[1]
898 if session:
899 ctrl = session.control
900 else:
901 ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
903 if not ctrl:
904 tv = gc_control.list_treeview
905 model = tv.get_model()
906 iter_ = gc_control.get_contact_iter(nick)
907 if iter_:
908 show = model[iter_][3]
909 else:
910 show = 'offline'
911 gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
912 name = nick, show = show)
913 ctrl = self.new_private_chat(gc_c, account, session)
915 ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
916 'code': array[1], 'msg': array[2]}, 'status')
917 return
919 gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
920 'code': array[1], 'msg': array[2]}, 'status')
921 if gc_control.parent_win and gc_control.parent_win.get_active_jid() == jid:
922 gc_control.set_subject(gc_control.subject)
923 return
925 if gajim.jid_is_transport(jid):
926 jid = jid.replace('@', '')
927 msg = array[2]
928 if array[3]:
929 msg = _('error while sending %(message)s ( %(error)s )') % {
930 'message': array[3], 'error': msg}
931 if session:
932 session.roster_message(jid, msg, array[4], msg_type='error')
934 def handle_event_msgsent(self, account, array):
935 #('MSGSENT', account, (jid, msg, keyID))
936 msg = array[1]
937 # do not play sound when standalone chatstate message (eg no msg)
938 if msg and gajim.config.get_per('soundevents', 'message_sent', 'enabled'):
939 helpers.play_sound('message_sent')
941 def handle_event_msgnotsent(self, account, array):
942 #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
943 msg = _('error while sending %(message)s ( %(error)s )') % {
944 'message': array[2], 'error': array[1]}
945 if not array[4]:
946 # No session. This can happen when sending a message from gajim-remote
947 log.warn(msg)
948 return
949 array[4].roster_message(array[0], msg, array[3], account,
950 msg_type='error')
952 def handle_event_subscribe(self, account, array):
953 #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
954 if self.remote_ctrl:
955 self.remote_ctrl.raise_signal('Subscribe', (account, array))
957 jid = array[0]
958 text = array[1]
959 nick = array[2]
960 if helpers.allow_popup_window(account) or not self.systray_enabled:
961 dialogs.SubscriptionRequestWindow(jid, text, account, nick)
962 return
964 self.add_event(account, jid, 'subscription_request', (text, nick))
966 if helpers.allow_showing_notification(account):
967 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
968 'subscription_request.png')
969 path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
970 event_type = _('Subscription request')
971 notify.popup(event_type, jid, account, 'subscription_request', path,
972 event_type, jid)
974 def handle_event_subscribed(self, account, array):
975 #('SUBSCRIBED', account, (jid, resource))
976 jid = array[0]
977 if jid in gajim.contacts.get_jid_list(account):
978 c = gajim.contacts.get_first_contact_from_jid(account, jid)
979 c.resource = array[1]
980 self.roster.remove_contact_from_groups(c.jid, account,
981 [_('Not in Roster'), _('Observers')], update=False)
982 else:
983 keyID = ''
984 attached_keys = gajim.config.get_per('accounts', account,
985 'attached_gpg_keys').split()
986 if jid in attached_keys:
987 keyID = attached_keys[attached_keys.index(jid) + 1]
988 name = jid.split('@', 1)[0]
989 name = name.split('%', 1)[0]
990 contact1 = gajim.contacts.create_contact(jid=jid, name=name,
991 groups=[], show='online', status='online',
992 ask='to', resource=array[1], keyID=keyID)
993 gajim.contacts.add_contact(account, contact1)
994 self.roster.add_contact(jid, account)
995 dialogs.InformationDialog(_('Authorization accepted'),
996 _('The contact "%s" has authorized you to see his or her status.')
997 % jid)
998 if not gajim.config.get_per('accounts', account, 'dont_ack_subscription'):
999 gajim.connections[account].ack_subscribed(jid)
1000 if self.remote_ctrl:
1001 self.remote_ctrl.raise_signal('Subscribed', (account, array))
1003 def show_unsubscribed_dialog(self, account, contact):
1004 def on_yes(is_checked, list_):
1005 self.roster.on_req_usub(None, list_)
1006 list_ = [(contact, account)]
1007 dialogs.YesNoDialog(
1008 _('Contact "%s" removed subscription from you') % contact.jid,
1009 _('You will always see him or her as offline.\nDo you want to '
1010 'remove him or her from your contact list?'),
1011 on_response_yes=(on_yes, list_))
1012 # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
1013 # not show deny
1015 def handle_event_unsubscribed(self, account, jid):
1016 #('UNSUBSCRIBED', account, jid)
1017 gajim.connections[account].ack_unsubscribed(jid)
1018 if self.remote_ctrl:
1019 self.remote_ctrl.raise_signal('Unsubscribed', (account, jid))
1021 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
1022 if not contact:
1023 return
1025 if helpers.allow_popup_window(account) or not self.systray_enabled:
1026 self.show_unsubscribed_dialog(account, contact)
1028 self.add_event(account, jid, 'unsubscribed', contact)
1030 if helpers.allow_showing_notification(account):
1031 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
1032 'unsubscribed.png')
1033 path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
1034 event_type = _('Unsubscribed')
1035 notify.popup(event_type, jid, account, 'unsubscribed', path,
1036 event_type, jid)
1038 def handle_event_agent_info_error(self, account, agent):
1039 #('AGENT_ERROR_INFO', account, (agent))
1040 try:
1041 gajim.connections[account].services_cache.agent_info_error(agent)
1042 except AttributeError:
1043 return
1045 def handle_event_agent_items_error(self, account, agent):
1046 #('AGENT_ERROR_INFO', account, (agent))
1047 try:
1048 gajim.connections[account].services_cache.agent_items_error(agent)
1049 except AttributeError:
1050 return
1052 def handle_event_agent_removed(self, account, agent):
1053 # remove transport's contacts from treeview
1054 jid_list = gajim.contacts.get_jid_list(account)
1055 for jid in jid_list:
1056 if jid.endswith('@' + agent):
1057 c = gajim.contacts.get_first_contact_from_jid(account, jid)
1058 gajim.log.debug(
1059 'Removing contact %s due to unregistered transport %s'\
1060 % (jid, agent))
1061 gajim.connections[account].unsubscribe(c.jid)
1062 # Transport contacts can't have 2 resources
1063 if c.jid in gajim.to_be_removed[account]:
1064 # This way we'll really remove it
1065 gajim.to_be_removed[account].remove(c.jid)
1066 self.roster.remove_contact(c.jid, account, backend=True)
1068 def handle_event_register_agent_info(self, account, array):
1069 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
1070 # info in a dataform if is_form is True
1071 if array[2] or 'instructions' in array[1]:
1072 config.ServiceRegistrationWindow(array[0], array[1], account,
1073 array[2])
1074 else:
1075 dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
1076 % array[0], _('Check your connection or try again later.'))
1078 def handle_event_agent_info_items(self, account, array):
1079 #('AGENT_INFO_ITEMS', account, (agent, node, items))
1080 our_jid = gajim.get_jid_from_account(account)
1081 if 'pep_services' in gajim.interface.instances[account] and \
1082 array[0] == our_jid:
1083 gajim.interface.instances[account]['pep_services'].items_received(
1084 array[2])
1085 try:
1086 gajim.connections[account].services_cache.agent_items(array[0],
1087 array[1], array[2])
1088 except AttributeError:
1089 return
1091 def handle_event_agent_info_info(self, account, array):
1092 #('AGENT_INFO_INFO', account, (agent, node, identities, features, data))
1093 try:
1094 gajim.connections[account].services_cache.agent_info(array[0],
1095 array[1], array[2], array[3], array[4])
1096 except AttributeError:
1097 return
1099 def handle_event_new_acc_connected(self, account, array):
1100 #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err,
1101 # ssl_cert, ssl_fingerprint))
1102 if 'account_creation_wizard' in self.instances:
1103 self.instances['account_creation_wizard'].new_acc_connected(array[0],
1104 array[1], array[2], array[3], array[4], array[5])
1106 def handle_event_new_acc_not_connected(self, account, array):
1107 #('NEW_ACC_NOT_CONNECTED', account, (reason))
1108 if 'account_creation_wizard' in self.instances:
1109 self.instances['account_creation_wizard'].new_acc_not_connected(array)
1111 def handle_event_acc_ok(self, account, array):
1112 #('ACC_OK', account, (config))
1113 if 'account_creation_wizard' in self.instances:
1114 self.instances['account_creation_wizard'].acc_is_ok(array)
1116 if self.remote_ctrl:
1117 self.remote_ctrl.raise_signal('NewAccount', (account, array))
1119 def handle_event_acc_not_ok(self, account, array):
1120 #('ACC_NOT_OK', account, (reason))
1121 if 'account_creation_wizard' in self.instances:
1122 self.instances['account_creation_wizard'].acc_is_not_ok(array)
1124 def handle_event_quit(self, p1, p2):
1125 self.roster.quit_gtkgui_interface()
1127 def handle_event_myvcard(self, account, array):
1128 nick = ''
1129 if 'NICKNAME' in array and array['NICKNAME']:
1130 gajim.nicks[account] = array['NICKNAME']
1131 elif 'FN' in array and array['FN']:
1132 gajim.nicks[account] = array['FN']
1133 if 'profile' in self.instances[account]:
1134 win = self.instances[account]['profile']
1135 win.set_values(array)
1136 if account in self.show_vcard_when_connect:
1137 self.show_vcard_when_connect.remove(account)
1138 jid = array['jid']
1139 if jid in self.instances[account]['infos']:
1140 self.instances[account]['infos'][jid].set_values(array)
1142 def handle_event_vcard(self, account, vcard):
1143 # ('VCARD', account, data)
1144 '''vcard holds the vcard data'''
1145 jid = vcard['jid']
1146 resource = vcard.get('resource', '')
1147 fjid = jid + '/' + str(resource)
1149 # vcard window
1150 win = None
1151 if jid in self.instances[account]['infos']:
1152 win = self.instances[account]['infos'][jid]
1153 elif resource and fjid in self.instances[account]['infos']:
1154 win = self.instances[account]['infos'][fjid]
1155 if win:
1156 win.set_values(vcard)
1158 # show avatar in chat
1159 ctrl = None
1160 if resource and self.msg_win_mgr.has_window(fjid, account):
1161 win = self.msg_win_mgr.get_window(fjid, account)
1162 ctrl = win.get_control(fjid, account)
1163 elif self.msg_win_mgr.has_window(jid, account):
1164 win = self.msg_win_mgr.get_window(jid, account)
1165 ctrl = win.get_control(jid, account)
1167 if ctrl and ctrl.type_id != message_control.TYPE_GC:
1168 ctrl.show_avatar()
1170 # Show avatar in roster or gc_roster
1171 gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account)
1172 if not gc_ctrl and \
1173 jid in self.minimized_controls[account]:
1174 gc_ctrl = self.minimized_controls[account][jid]
1175 if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC:
1176 gc_ctrl.draw_avatar(resource)
1177 else:
1178 self.roster.draw_avatar(jid, account)
1179 if self.remote_ctrl:
1180 self.remote_ctrl.raise_signal('VcardInfo', (account, vcard))
1182 def handle_event_last_status_time(self, account, array):
1183 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
1184 tim = array[2]
1185 if tim < 0:
1186 # Ann error occured
1187 return
1188 win = None
1189 if array[0] in self.instances[account]['infos']:
1190 win = self.instances[account]['infos'][array[0]]
1191 elif array[0] + '/' + array[1] in self.instances[account]['infos']:
1192 win = self.instances[account]['infos'][array[0] + '/' + array[1]]
1193 c = gajim.contacts.get_contact(account, array[0], array[1])
1194 if c: # c can be none if it's a gc contact
1195 c.last_status_time = time.localtime(time.time() - tim)
1196 if array[3]:
1197 c.status = array[3]
1198 self.roster.draw_contact(c.jid, account) # draw offline status
1199 if win:
1200 win.set_last_status_time()
1201 if self.remote_ctrl:
1202 self.remote_ctrl.raise_signal('LastStatusTime', (account, array))
1204 def handle_event_os_info(self, account, array):
1205 #'OS_INFO' (account, (jid, resource, client_info, os_info))
1206 win = None
1207 if array[0] in self.instances[account]['infos']:
1208 win = self.instances[account]['infos'][array[0]]
1209 elif array[0] + '/' + array[1] in self.instances[account]['infos']:
1210 win = self.instances[account]['infos'][array[0] + '/' + array[1]]
1211 if win:
1212 win.set_os_info(array[1], array[2], array[3])
1213 if self.remote_ctrl:
1214 self.remote_ctrl.raise_signal('OsInfo', (account, array))
1216 def handle_event_entity_time(self, account, array):
1217 #'ENTITY_TIME' (account, (jid, resource, time_info))
1218 win = None
1219 if array[0] in self.instances[account]['infos']:
1220 win = self.instances[account]['infos'][array[0]]
1221 elif array[0] + '/' + array[1] in self.instances[account]['infos']:
1222 win = self.instances[account]['infos'][array[0] + '/' + array[1]]
1223 if win:
1224 win.set_entity_time(array[1], array[2])
1225 if self.remote_ctrl:
1226 self.remote_ctrl.raise_signal('EntityTime', (account, array))
1228 def handle_event_gc_notify(self, account, array):
1229 #'GC_NOTIFY' (account, (room_jid, show, status, nick,
1230 # role, affiliation, jid, reason, actor, statusCode, newNick, avatar_sha))
1231 nick = array[3]
1232 if not nick:
1233 return
1234 room_jid = array[0]
1235 fjid = room_jid + '/' + nick
1236 show = array[1]
1237 status = array[2]
1238 conn = gajim.connections[account]
1240 # Get the window and control for the updated status, this may be a
1241 # PrivateChatControl
1242 control = self.msg_win_mgr.get_gc_control(room_jid, account)
1244 if not control and \
1245 room_jid in self.minimized_controls[account]:
1246 control = self.minimized_controls[account][room_jid]
1248 if not control or (control and control.type_id != message_control.TYPE_GC):
1249 return
1251 control.chg_contact_status(nick, show, status, array[4], array[5],
1252 array[6], array[7], array[8], array[9], array[10], array[11])
1254 contact = gajim.contacts.\
1255 get_contact_with_highest_priority(account, room_jid)
1256 if contact:
1257 self.roster.draw_contact(room_jid, account)
1259 # print status in chat window and update status/GPG image
1260 ctrl = self.msg_win_mgr.get_control(fjid, account)
1261 if ctrl:
1262 statusCode = array[9]
1263 if '303' in statusCode:
1264 new_nick = array[10]
1265 ctrl.print_conversation(_('%(nick)s is now known as %(new_nick)s') \
1266 % {'nick': nick, 'new_nick': new_nick}, 'status')
1267 gc_c = gajim.contacts.get_gc_contact(account, room_jid, new_nick)
1268 c = gajim.contacts.contact_from_gc_contact(gc_c)
1269 ctrl.gc_contact = gc_c
1270 ctrl.contact = c
1271 if ctrl.session:
1272 # stop e2e
1273 if ctrl.session.enable_encryption:
1274 thread_id = ctrl.session.thread_id
1275 ctrl.session.terminate_e2e()
1276 conn.delete_session(fjid, thread_id)
1277 ctrl.no_autonegotiation = False
1278 ctrl.draw_banner()
1279 old_jid = room_jid + '/' + nick
1280 new_jid = room_jid + '/' + new_nick
1281 self.msg_win_mgr.change_key(old_jid, new_jid, account)
1282 else:
1283 contact = ctrl.contact
1284 contact.show = show
1285 contact.status = status
1286 gc_contact = ctrl.gc_contact
1287 gc_contact.show = show
1288 gc_contact.status = status
1289 uf_show = helpers.get_uf_show(show)
1290 ctrl.print_conversation(_('%(nick)s is now %(status)s') % {
1291 'nick': nick, 'status': uf_show}, 'status')
1292 if status:
1293 ctrl.print_conversation(' (', 'status', simple=True)
1294 ctrl.print_conversation('%s' % (status), 'status', simple=True)
1295 ctrl.print_conversation(')', 'status', simple=True)
1296 ctrl.parent_win.redraw_tab(ctrl)
1297 ctrl.update_ui()
1298 if self.remote_ctrl:
1299 self.remote_ctrl.raise_signal('GCPresence', (account, array))
1301 def handle_event_gc_msg(self, account, array):
1302 # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg,
1303 # [status_codes]))
1304 jids = array[0].split('/', 1)
1305 room_jid = jids[0]
1307 msg = array[1]
1309 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
1310 if not gc_control and \
1311 room_jid in self.minimized_controls[account]:
1312 gc_control = self.minimized_controls[account][room_jid]
1314 if not gc_control:
1315 return
1316 xhtml = array[4]
1318 if gajim.config.get('ignore_incoming_xhtml'):
1319 xhtml = None
1320 if len(jids) == 1:
1321 # message from server
1322 nick = ''
1323 else:
1324 # message from someone
1325 nick = jids[1]
1327 gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5])
1329 if self.remote_ctrl:
1330 highlight = gc_control.needs_visual_notification(msg)
1331 array += (highlight,)
1332 self.remote_ctrl.raise_signal('GCMessage', (account, array))
1334 def handle_event_gc_subject(self, account, array):
1335 #('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
1336 jids = array[0].split('/', 1)
1337 jid = jids[0]
1339 gc_control = self.msg_win_mgr.get_gc_control(jid, account)
1341 if not gc_control and \
1342 jid in self.minimized_controls[account]:
1343 gc_control = self.minimized_controls[account][jid]
1345 contact = gajim.contacts.\
1346 get_contact_with_highest_priority(account, jid)
1347 if contact:
1348 contact.status = array[1]
1349 self.roster.draw_contact(jid, account)
1351 if not gc_control:
1352 return
1353 gc_control.set_subject(array[1])
1354 # Standard way, the message comes from the occupant who set the subject
1355 text = None
1356 if len(jids) > 1:
1357 text = _('%(jid)s has set the subject to %(subject)s') % {
1358 'jid': jids[1], 'subject': array[1]}
1359 # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be
1360 # deleted one day. We can receive a subject with a body that contains
1361 # "X has set the subject to Y" ...
1362 elif array[2]:
1363 text = array[2]
1364 if text is not None:
1365 if array[3]:
1366 gc_control.print_old_conversation(text)
1367 else:
1368 gc_control.print_conversation(text)
1370 def handle_event_gc_config(self, account, array):
1371 #('GC_CONFIG', account, (jid, form)) config is a dict
1372 room_jid = array[0].split('/')[0]
1373 if room_jid in gajim.automatic_rooms[account]:
1374 if 'continue_tag' in gajim.automatic_rooms[account][room_jid]:
1375 # We're converting chat to muc. allow participants to invite
1376 form = dataforms.ExtendForm(node = array[1])
1377 for f in form.iter_fields():
1378 if f.var == 'muc#roomconfig_allowinvites':
1379 f.value = True
1380 elif f.var == 'muc#roomconfig_publicroom':
1381 f.value = False
1382 elif f.var == 'muc#roomconfig_membersonly':
1383 f.value = True
1384 elif f.var == 'public_list':
1385 f.value = False
1386 gajim.connections[account].send_gc_config(room_jid, form)
1387 else:
1388 # use default configuration
1389 gajim.connections[account].send_gc_config(room_jid, array[1])
1390 # invite contacts
1391 # check if it is necessary to add <continue />
1392 continue_tag = False
1393 if 'continue_tag' in gajim.automatic_rooms[account][room_jid]:
1394 continue_tag = True
1395 if 'invities' in gajim.automatic_rooms[account][room_jid]:
1396 for jid in gajim.automatic_rooms[account][room_jid]['invities']:
1397 gajim.connections[account].send_invite(room_jid, jid,
1398 continue_tag=continue_tag)
1399 del gajim.automatic_rooms[account][room_jid]
1400 elif room_jid not in self.instances[account]['gc_config']:
1401 self.instances[account]['gc_config'][room_jid] = \
1402 config.GroupchatConfigWindow(account, room_jid, array[1])
1404 def handle_event_gc_config_change(self, account, array):
1405 #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list
1406 # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
1407 # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-init
1408 jid = array[0]
1409 statusCode = array[1]
1411 gc_control = self.msg_win_mgr.get_gc_control(jid, account)
1412 if not gc_control and \
1413 jid in self.minimized_controls[account]:
1414 gc_control = self.minimized_controls[account][jid]
1415 if not gc_control:
1416 return
1418 changes = []
1419 if '100' in statusCode:
1420 # Can be a presence (see chg_contact_status in groupchat_control.py)
1421 changes.append(_('Any occupant is allowed to see your full JID'))
1422 gc_control.is_anonymous = False
1423 if '102' in statusCode:
1424 changes.append(_('Room now shows unavailable member'))
1425 if '103' in statusCode:
1426 changes.append(_('room now does not show unavailable members'))
1427 if '104' in statusCode:
1428 changes.append(
1429 _('A non-privacy-related room configuration change has occurred'))
1430 if '170' in statusCode:
1431 # Can be a presence (see chg_contact_status in groupchat_control.py)
1432 changes.append(_('Room logging is now enabled'))
1433 if '171' in statusCode:
1434 changes.append(_('Room logging is now disabled'))
1435 if '172' in statusCode:
1436 changes.append(_('Room is now non-anonymous'))
1437 gc_control.is_anonymous = False
1438 if '173' in statusCode:
1439 changes.append(_('Room is now semi-anonymous'))
1440 gc_control.is_anonymous = True
1441 if '174' in statusCode:
1442 changes.append(_('Room is now fully-anonymous'))
1443 gc_control.is_anonymous = True
1445 for change in changes:
1446 gc_control.print_conversation(change)
1448 def handle_event_gc_affiliation(self, account, array):
1449 #('GC_AFFILIATION', account, (room_jid, users_dict))
1450 room_jid = array[0]
1451 if room_jid in self.instances[account]['gc_config']:
1452 self.instances[account]['gc_config'][room_jid].\
1453 affiliation_list_received(array[1])
1455 def handle_event_gc_password_required(self, account, array):
1456 #('GC_PASSWORD_REQUIRED', account, (room_jid, nick))
1457 room_jid = array[0]
1458 nick = array[1]
1460 def on_ok(text):
1461 gajim.connections[account].join_gc(nick, room_jid, text)
1462 gajim.gc_passwords[room_jid] = text
1464 def on_cancel():
1465 # get and destroy window
1466 if room_jid in gajim.interface.minimized_controls[account]:
1467 self.roster.on_disconnect(None, room_jid, account)
1468 else:
1469 win = self.msg_win_mgr.get_window(room_jid, account)
1470 ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
1471 win.remove_tab(ctrl, 3)
1473 dlg = dialogs.InputDialog(_('Password Required'),
1474 _('A Password is required to join the room %s. Please type it.') % \
1475 room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel)
1476 dlg.input_entry.set_visibility(False)
1478 def handle_event_gc_invitation(self, account, array):
1479 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
1480 jid = gajim.get_jid_without_resource(array[1])
1481 room_jid = array[0]
1482 if helpers.allow_popup_window(account) or not self.systray_enabled:
1483 dialogs.InvitationReceivedDialog(account, room_jid, jid, array[3],
1484 array[2], is_continued=array[4])
1485 return
1487 self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
1488 array[3], array[4]))
1490 if helpers.allow_showing_notification(account):
1491 path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
1492 'gc_invitation.png')
1493 path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
1494 event_type = _('Groupchat Invitation')
1495 notify.popup(event_type, jid, account, 'gc-invitation', path,
1496 event_type, room_jid)
1498 def forget_gpg_passphrase(self, keyid):
1499 if keyid in self.gpg_passphrase:
1500 del self.gpg_passphrase[keyid]
1501 return False
1503 def handle_event_bad_passphrase(self, account, array):
1504 #('BAD_PASSPHRASE', account, ())
1505 use_gpg_agent = gajim.config.get('use_gpg_agent')
1506 sectext = ''
1507 if use_gpg_agent:
1508 sectext = _('You configured Gajim to use GPG agent, but there is no '
1509 'GPG agent running or it returned a wrong passphrase.\n')
1510 sectext += _('You are currently connected without your OpenPGP key.')
1511 keyID = gajim.config.get_per('accounts', account, 'keyid')
1512 self.forget_gpg_passphrase(keyID)
1513 dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
1515 def handle_event_gpg_password_required(self, account, array):
1516 #('GPG_PASSWORD_REQUIRED', account, (callback,))
1517 callback = array[0]
1518 keyid = gajim.config.get_per('accounts', account, 'keyid')
1519 if keyid in self.gpg_passphrase:
1520 request = self.gpg_passphrase[keyid]
1521 else:
1522 request = PassphraseRequest(keyid)
1523 self.gpg_passphrase[keyid] = request
1524 request.add_callback(account, callback)
1526 def handle_event_gpg_always_trust(self, account, callback):
1527 #('GPG_ALWAYS_TRUST', account, callback)
1528 def on_yes(checked):
1529 if checked:
1530 gajim.connections[account].gpg.always_trust = True
1531 callback(True)
1533 def on_no():
1534 callback(False)
1536 dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
1537 'encrypt this chat is not trusted. Do you really want to encrypt this '
1538 'message?'), checktext=_('Do _not ask me again'),
1539 on_response_yes=on_yes, on_response_no=on_no)
1541 def handle_event_password_required(self, account, array):
1542 #('PASSWORD_REQUIRED', account, None)
1543 if account in self.pass_dialog:
1544 return
1545 text = _('Enter your password for account %s') % account
1546 if passwords.USER_HAS_GNOMEKEYRING and \
1547 not passwords.USER_USES_GNOMEKEYRING:
1548 text += '\n' + _('Gnome Keyring is installed but not \
1549 correctly started (environment variable probably not \
1550 correctly set)')
1552 def on_ok(passphrase, save):
1553 if save:
1554 gajim.config.set_per('accounts', account, 'savepass', True)
1555 passwords.save_password(account, passphrase)
1556 gajim.connections[account].set_password(passphrase)
1557 del self.pass_dialog[account]
1559 def on_cancel():
1560 self.roster.set_state(account, 'offline')
1561 self.roster.update_status_combobox()
1562 del self.pass_dialog[account]
1564 self.pass_dialog[account] = dialogs.PassphraseDialog(
1565 _('Password Required'), text, _('Save password'), ok_handler=on_ok,
1566 cancel_handler=on_cancel)
1568 def handle_event_roster_info(self, account, array):
1569 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
1570 jid = array[0]
1571 name = array[1]
1572 sub = array[2]
1573 ask = array[3]
1574 groups = array[4]
1575 contacts = gajim.contacts.get_contacts(account, jid)
1576 if (not sub or sub == 'none') and (not ask or ask == 'none') and \
1577 not name and not groups:
1578 # contact removed us.
1579 if contacts:
1580 self.roster.remove_contact(jid, account, backend=True)
1581 return
1582 elif not contacts:
1583 if sub == 'remove':
1584 return
1585 # Add new contact to roster
1586 contact = gajim.contacts.create_contact(jid=jid, name=name,
1587 groups=groups, show='offline', sub=sub, ask=ask)
1588 gajim.contacts.add_contact(account, contact)
1589 self.roster.add_contact(jid, account)
1590 else:
1591 # it is an existing contact that might has changed
1592 re_place = False
1593 # If contact has changed (sub, ask or group) update roster
1594 # Mind about observer status changes:
1595 # According to xep 0162, a contact is not an observer anymore when
1596 # we asked for auth, so also remove him if ask changed
1597 old_groups = contacts[0].groups
1598 if contacts[0].sub != sub or contacts[0].ask != ask\
1599 or old_groups != groups:
1600 re_place = True
1601 # c.get_shown_groups() has changed. Reflect that in roster_winodow
1602 self.roster.remove_contact(jid, account, force=True)
1603 for contact in contacts:
1604 contact.name = name or ''
1605 contact.sub = sub
1606 contact.ask = ask
1607 contact.groups = groups or []
1608 if re_place:
1609 self.roster.add_contact(jid, account)
1610 # Refilter and update old groups
1611 for group in old_groups:
1612 self.roster.draw_group(group, account)
1613 else:
1614 self.roster.draw_contact(jid, account)
1616 if self.remote_ctrl:
1617 self.remote_ctrl.raise_signal('RosterInfo', (account, array))
1619 def handle_event_bookmarks(self, account, bms):
1620 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
1621 # We received a bookmark item from the server (JEP48)
1622 # Auto join GC windows if neccessary
1624 self.roster.set_actions_menu_needs_rebuild()
1625 invisible_show = gajim.SHOW_LIST.index('invisible')
1626 # do not autojoin if we are invisible
1627 if gajim.connections[account].connected == invisible_show:
1628 return
1630 self.auto_join_bookmarks(account)
1632 def handle_event_file_send_error(self, account, array):
1633 jid = array[0]
1634 file_props = array[1]
1635 ft = self.instances['file_transfers']
1636 ft.set_status(file_props['type'], file_props['sid'], 'stop')
1638 if helpers.allow_popup_window(account):
1639 ft.show_send_error(file_props)
1640 return
1642 self.add_event(account, jid, 'file-send-error', file_props)
1644 if helpers.allow_showing_notification(account):
1645 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png')
1646 path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
1647 event_type = _('File Transfer Error')
1648 notify.popup(event_type, jid, account, 'file-send-error', path,
1649 event_type, file_props['name'])
1651 def handle_event_gmail_notify(self, account, array):
1652 jid = array[0]
1653 gmail_new_messages = int(array[1])
1654 gmail_messages_list = array[2]
1655 if gajim.config.get('notify_on_new_gmail_email'):
1656 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
1657 'new_email_recv.png')
1658 title = _('New mail on %(gmail_mail_address)s') % \
1659 {'gmail_mail_address': jid}
1660 text = i18n.ngettext('You have %d new mail conversation',
1661 'You have %d new mail conversations', gmail_new_messages,
1662 gmail_new_messages, gmail_new_messages)
1664 if gajim.config.get('notify_on_new_gmail_email_extra'):
1665 cnt = 0
1666 for gmessage in gmail_messages_list:
1667 #FIXME: emulate Gtalk client popups. find out what they parse and
1668 # how they decide what to show each message has a 'From',
1669 # 'Subject' and 'Snippet' field
1670 if cnt >=5:
1671 break
1672 senders = ',\n '.join(reversed(gmessage['From']))
1673 text += _('\n\nFrom: %(from_address)s\nSubject: %(subject)s\n%(snippet)s') % \
1674 {'from_address': senders, 'subject': gmessage['Subject'],
1675 'snippet': gmessage['Snippet']}
1676 cnt += 1
1678 if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
1679 helpers.play_sound('gmail_received')
1680 path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
1681 notify.popup(_('New E-mail'), jid, account, 'gmail',
1682 path_to_image=path, title=title,
1683 text=text)
1685 if self.remote_ctrl:
1686 self.remote_ctrl.raise_signal('NewGmail', (account, array))
1688 def handle_event_file_request_error(self, account, array):
1689 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
1690 jid, file_props, errmsg = array
1691 ft = self.instances['file_transfers']
1692 ft.set_status(file_props['type'], file_props['sid'], 'stop')
1693 errno = file_props['error']
1695 if helpers.allow_popup_window(account):
1696 if errno in (-4, -5):
1697 ft.show_stopped(jid, file_props, errmsg)
1698 else:
1699 ft.show_request_error(file_props)
1700 return
1702 if errno in (-4, -5):
1703 msg_type = 'file-error'
1704 else:
1705 msg_type = 'file-request-error'
1707 self.add_event(account, jid, msg_type, file_props)
1709 if helpers.allow_showing_notification(account):
1710 # check if we should be notified
1711 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png')
1713 path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
1714 event_type = _('File Transfer Error')
1715 notify.popup(event_type, jid, account, msg_type, path,
1716 title = event_type, text = file_props['name'])
1718 def handle_event_file_request(self, account, array):
1719 jid = array[0]
1720 if jid not in gajim.contacts.get_jid_list(account):
1721 keyID = ''
1722 attached_keys = gajim.config.get_per('accounts', account,
1723 'attached_gpg_keys').split()
1724 if jid in attached_keys:
1725 keyID = attached_keys[attached_keys.index(jid) + 1]
1726 contact = gajim.contacts.create_contact(jid=jid, name='',
1727 groups=[_('Not in Roster')], show='not in roster', status='',
1728 sub='none', keyID=keyID)
1729 gajim.contacts.add_contact(account, contact)
1730 self.roster.add_contact(contact.jid, account)
1731 file_props = array[1]
1732 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
1734 if helpers.allow_popup_window(account):
1735 self.instances['file_transfers'].show_file_request(account, contact,
1736 file_props)
1737 return
1739 self.add_event(account, jid, 'file-request', file_props)
1741 if helpers.allow_showing_notification(account):
1742 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
1743 'ft_request.png')
1744 txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
1745 account, jid)
1746 path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
1747 event_type = _('File Transfer Request')
1748 notify.popup(event_type, jid, account, 'file-request',
1749 path_to_image = path, title = event_type, text = txt)
1751 def handle_event_file_error(self, title, message):
1752 dialogs.ErrorDialog(title, message)
1754 def handle_event_file_progress(self, account, file_props):
1755 if time.time() - self.last_ftwindow_update > 0.5:
1756 # update ft window every 500ms
1757 self.last_ftwindow_update = time.time()
1758 self.instances['file_transfers'].set_progress(file_props['type'],
1759 file_props['sid'], file_props['received-len'])
1761 def handle_event_file_rcv_completed(self, account, file_props):
1762 ft = self.instances['file_transfers']
1763 if file_props['error'] == 0:
1764 ft.set_progress(file_props['type'], file_props['sid'],
1765 file_props['received-len'])
1766 else:
1767 ft.set_status(file_props['type'], file_props['sid'], 'stop')
1768 if 'stalled' in file_props and file_props['stalled'] or \
1769 'paused' in file_props and file_props['paused']:
1770 return
1771 if file_props['type'] == 'r': # we receive a file
1772 jid = unicode(file_props['sender'])
1773 else: # we send a file
1774 jid = unicode(file_props['receiver'])
1776 if helpers.allow_popup_window(account):
1777 if file_props['error'] == 0:
1778 if gajim.config.get('notify_on_file_complete'):
1779 ft.show_completed(jid, file_props)
1780 elif file_props['error'] == -1:
1781 ft.show_stopped(jid, file_props,
1782 error_msg=_('Remote contact stopped transfer'))
1783 elif file_props['error'] == -6:
1784 ft.show_stopped(jid, file_props, error_msg=_('Error opening file'))
1785 return
1787 msg_type = ''
1788 event_type = ''
1789 if file_props['error'] == 0 and gajim.config.get(
1790 'notify_on_file_complete'):
1791 msg_type = 'file-completed'
1792 event_type = _('File Transfer Completed')
1793 elif file_props['error'] in (-1, -6):
1794 msg_type = 'file-stopped'
1795 event_type = _('File Transfer Stopped')
1797 if event_type == '':
1798 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
1799 # this should never happen but it does. see process_result() in socks5.py
1800 # who calls this func (sth is really wrong unless this func is also registered
1801 # as progress_cb
1802 return
1804 if msg_type:
1805 self.add_event(account, jid, msg_type, file_props)
1807 if file_props is not None:
1808 if file_props['type'] == 'r':
1809 # get the name of the sender, as it is in the roster
1810 sender = unicode(file_props['sender']).split('/')[0]
1811 name = gajim.contacts.get_first_contact_from_jid(account,
1812 sender).get_shown_name()
1813 filename = os.path.basename(file_props['file-name'])
1814 if event_type == _('File Transfer Completed'):
1815 txt = _('You successfully received %(filename)s from %(name)s.')\
1816 % {'filename': filename, 'name': name}
1817 img = 'ft_done.png'
1818 else: # ft stopped
1819 txt = _('File transfer of %(filename)s from %(name)s stopped.')\
1820 % {'filename': filename, 'name': name}
1821 img = 'ft_stopped.png'
1822 else:
1823 receiver = file_props['receiver']
1824 if hasattr(receiver, 'jid'):
1825 receiver = receiver.jid
1826 receiver = receiver.split('/')[0]
1827 # get the name of the contact, as it is in the roster
1828 name = gajim.contacts.get_first_contact_from_jid(account,
1829 receiver).get_shown_name()
1830 filename = os.path.basename(file_props['file-name'])
1831 if event_type == _('File Transfer Completed'):
1832 txt = _('You successfully sent %(filename)s to %(name)s.')\
1833 % {'filename': filename, 'name': name}
1834 img = 'ft_done.png'
1835 else: # ft stopped
1836 txt = _('File transfer of %(filename)s to %(name)s stopped.')\
1837 % {'filename': filename, 'name': name}
1838 img = 'ft_stopped.png'
1839 img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', img)
1840 path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
1841 else:
1842 txt = ''
1844 if gajim.config.get('notify_on_file_complete') and \
1845 (gajim.config.get('autopopupaway') or \
1846 gajim.connections[account].connected in (2, 3)):
1847 # we want to be notified and we are online/chat or we don't mind
1848 # bugged when away/na/busy
1849 notify.popup(event_type, jid, account, msg_type, path_to_image = path,
1850 title = event_type, text = txt)
1852 def handle_event_stanza_arrived(self, account, stanza):
1853 if account not in self.instances:
1854 return
1855 if 'xml_console' in self.instances[account]:
1856 self.instances[account]['xml_console'].print_stanza(stanza, 'incoming')
1858 def handle_event_stanza_sent(self, account, stanza):
1859 if account not in self.instances:
1860 return
1861 if 'xml_console' in self.instances[account]:
1862 self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing')
1864 def handle_event_vcard_published(self, account, array):
1865 if 'profile' in self.instances[account]:
1866 win = self.instances[account]['profile']
1867 win.vcard_published()
1868 for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) + \
1869 self.minimized_controls[account].values():
1870 if gc_control.account == account:
1871 show = gajim.SHOW_LIST[gajim.connections[account].connected]
1872 status = gajim.connections[account].status
1873 gajim.connections[account].send_gc_status(gc_control.nick,
1874 gc_control.room_jid, show, status)
1876 def handle_event_vcard_not_published(self, account, array):
1877 if 'profile' in self.instances[account]:
1878 win = self.instances[account]['profile']
1879 win.vcard_not_published()
1881 def ask_offline_status(self, account):
1882 for contact in gajim.contacts.iter_contacts(account):
1883 gajim.connections[account].request_last_status_time(contact.jid,
1884 contact.resource)
1886 def handle_event_signed_in(self, account, empty):
1887 '''SIGNED_IN event is emitted when we sign in, so handle it'''
1888 # block signed in notifications for 30 seconds
1889 gajim.block_signed_in_notifications[account] = True
1890 self.roster.set_actions_menu_needs_rebuild()
1891 self.roster.draw_account(account)
1892 state = self.sleeper.getState()
1893 connected = gajim.connections[account].connected
1894 if gajim.config.get('ask_offline_status_on_connection'):
1895 # Ask offline status in 1 minute so w'are sure we got all online
1896 # presences
1897 gobject.timeout_add_seconds(60, self.ask_offline_status, account)
1898 if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
1899 # we go online or free for chat, so we activate auto status
1900 gajim.sleeper_state[account] = 'online'
1901 elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
1902 (state == common.sleepy.STATE_XA and connected == 5)):
1903 # If we are autoaway/xa and come back after a disconnection, do nothing
1904 # Else disable autoaway
1905 gajim.sleeper_state[account] = 'off'
1906 invisible_show = gajim.SHOW_LIST.index('invisible')
1907 # We cannot join rooms if we are invisible
1908 if gajim.connections[account].connected == invisible_show:
1909 return
1910 # join already open groupchats
1911 for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC) \
1912 + self.minimized_controls[account].values():
1913 if account != gc_control.account:
1914 continue
1915 room_jid = gc_control.room_jid
1916 if room_jid in gajim.gc_connected[account] and \
1917 gajim.gc_connected[account][room_jid]:
1918 continue
1919 nick = gc_control.nick
1920 password = gajim.gc_passwords.get(room_jid, '')
1921 gajim.connections[account].join_gc(nick, room_jid, password)
1923 def handle_event_metacontacts(self, account, tags_list):
1924 gajim.contacts.define_metacontacts(account, tags_list)
1925 self.roster.redraw_metacontacts(account)
1927 def handle_atom_entry(self, account, data):
1928 atom_entry, = data
1929 AtomWindow.newAtomEntry(atom_entry)
1931 def handle_event_failed_decrypt(self, account, data):
1932 jid, tim, session = data
1934 details = _('Unable to decrypt message from '
1935 '%s\nIt may have been tampered with.') % jid
1937 ctrl = session.control
1938 if ctrl:
1939 ctrl.print_conversation_line(details, 'status', '', tim)
1940 else:
1941 dialogs.WarningDialog(_('Unable to decrypt message'),
1942 details)
1944 # terminate the session
1945 session.terminate_e2e()
1946 session.conn.delete_session(jid, session.thread_id)
1948 # restart the session
1949 if ctrl:
1950 ctrl.begin_e2e_negotiation()
1952 def handle_event_privacy_lists_received(self, account, data):
1953 # ('PRIVACY_LISTS_RECEIVED', account, list)
1954 if account not in self.instances:
1955 return
1956 if 'privacy_lists' in self.instances[account]:
1957 self.instances[account]['privacy_lists'].privacy_lists_received(data)
1959 def handle_event_privacy_list_received(self, account, data):
1960 # ('PRIVACY_LIST_RECEIVED', account, (name, rules))
1961 if account not in self.instances:
1962 return
1963 name = data[0]
1964 rules = data[1]
1965 if 'privacy_list_%s' % name in self.instances[account]:
1966 self.instances[account]['privacy_list_%s' % name].\
1967 privacy_list_received(rules)
1968 if name == 'block':
1969 gajim.connections[account].blocked_contacts = []
1970 gajim.connections[account].blocked_groups = []
1971 gajim.connections[account].blocked_list = []
1972 gajim.connections[account].blocked_all = False
1973 for rule in rules:
1974 if not 'type' in rule:
1975 gajim.connections[account].blocked_all = True
1976 elif rule['type'] == 'jid' and rule['action'] == 'deny':
1977 gajim.connections[account].blocked_contacts.append(rule['value'])
1978 elif rule['type'] == 'group' and rule['action'] == 'deny':
1979 gajim.connections[account].blocked_groups.append(rule['value'])
1980 gajim.connections[account].blocked_list.append(rule)
1981 #elif rule['type'] == "group" and action == "deny":
1982 # text_item = _('%s group "%s"') % _(rule['action']), rule['value']
1983 # self.store.append([text_item])
1984 # self.global_rules.append(rule)
1985 #else:
1986 # self.global_rules_to_append.append(rule)
1987 if 'blocked_contacts' in self.instances[account]:
1988 self.instances[account]['blocked_contacts'].\
1989 privacy_list_received(rules)
1991 def handle_event_privacy_lists_active_default(self, account, data):
1992 if not data:
1993 return
1994 # Send to all privacy_list_* windows as we can't know which one asked
1995 for win in self.instances[account]:
1996 if win.startswith('privacy_list_'):
1997 self.instances[account][win].check_active_default(data)
1999 def handle_event_privacy_list_removed(self, account, name):
2000 # ('PRIVACY_LISTS_REMOVED', account, name)
2001 if account not in self.instances:
2002 return
2003 if 'privacy_lists' in self.instances[account]:
2004 self.instances[account]['privacy_lists'].privacy_list_removed(name)
2006 def handle_event_zc_name_conflict(self, account, data):
2007 def on_ok(new_name):
2008 gajim.config.set_per('accounts', account, 'name', new_name)
2009 status = gajim.connections[account].status
2010 gajim.connections[account].username = new_name
2011 gajim.connections[account].change_status(status, '')
2012 def on_cancel():
2013 gajim.connections[account].change_status('offline','')
2015 dlg = dialogs.InputDialog(_('Username Conflict'),
2016 _('Please type a new username for your local account'), input_str=data,
2017 is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel)
2019 def handle_event_ping_sent(self, account, contact):
2020 if contact.jid == contact.get_full_jid():
2021 # If contact is a groupchat user
2022 jids = [contact.jid]
2023 else:
2024 jids = [contact.jid, contact.get_full_jid()]
2025 for jid in jids:
2026 ctrl = self.msg_win_mgr.get_control(jid, account)
2027 if ctrl:
2028 ctrl.print_conversation(_('Ping?'), 'status')
2030 def handle_event_ping_reply(self, account, data):
2031 contact = data[0]
2032 seconds = data[1]
2033 if contact.jid == contact.get_full_jid():
2034 # If contact is a groupchat user
2035 jids = [contact.jid]
2036 else:
2037 jids = [contact.jid, contact.get_full_jid()]
2038 for jid in jids:
2039 ctrl = self.msg_win_mgr.get_control(jid, account)
2040 if ctrl:
2041 ctrl.print_conversation(_('Pong! (%s s.)') % seconds, 'status')
2043 def handle_event_ping_error(self, account, contact):
2044 if contact.jid == contact.get_full_jid():
2045 # If contact is a groupchat user
2046 jids = [contact.jid]
2047 else:
2048 jids = [contact.jid, contact.get_full_jid()]
2049 for jid in jids:
2050 ctrl = self.msg_win_mgr.get_control(jid, account)
2051 if ctrl:
2052 ctrl.print_conversation(_('Error.'), 'status')
2054 def handle_event_search_form(self, account, data):
2055 # ('SEARCH_FORM', account, (jid, dataform, is_dataform))
2056 if data[0] not in self.instances[account]['search']:
2057 return
2058 self.instances[account]['search'][data[0]].on_form_arrived(data[1],
2059 data[2])
2061 def handle_event_search_result(self, account, data):
2062 # ('SEARCH_RESULT', account, (jid, dataform, is_dataform))
2063 if data[0] not in self.instances[account]['search']:
2064 return
2065 self.instances[account]['search'][data[0]].on_result_arrived(data[1],
2066 data[2])
2068 def handle_event_resource_conflict(self, account, data):
2069 # ('RESOURCE_CONFLICT', account, ())
2070 # First we go offline, but we don't overwrite status message
2071 self.roster.send_status(account, 'offline',
2072 gajim.connections[account].status)
2073 def on_ok(new_resource):
2074 gajim.config.set_per('accounts', account, 'resource', new_resource)
2075 self.roster.send_status(account, gajim.connections[account].old_show,
2076 gajim.connections[account].status)
2077 dlg = dialogs.InputDialog(_('Resource Conflict'),
2078 _('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource,
2079 is_modal = False, ok_handler = on_ok)
2081 def handle_event_pep_config(self, account, data):
2082 # ('PEP_CONFIG', account, (node, form))
2083 if 'pep_services' in self.instances[account]:
2084 self.instances[account]['pep_services'].config(data[0], data[1])
2086 def handle_event_roster_item_exchange(self, account, data):
2087 # data = (action in [add, delete, modify], exchange_list, jid_from)
2088 dialogs.RosterItemExchangeWindow(account, data[0], data[1], data[2])
2090 def handle_event_unique_room_id_supported(self, account, data):
2091 '''Receive confirmation that unique_room_id are supported'''
2092 # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
2093 instance = data[1]
2094 instance.unique_room_id_supported(data[0], data[2])
2096 def handle_event_unique_room_id_unsupported(self, account, data):
2097 # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance)
2098 instance = data[1]
2099 instance.unique_room_id_error(data[0])
2101 def handle_event_ssl_error(self, account, data):
2102 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
2103 server = gajim.config.get_per('accounts', account, 'hostname')
2105 def on_ok(is_checked):
2106 del self.instances[account]['online_dialog']['ssl_error']
2107 if is_checked[0]:
2108 # Check if cert is already in file
2109 certs = ''
2110 if os.path.isfile(gajim.MY_CACERTS):
2111 f = open(gajim.MY_CACERTS)
2112 certs = f.read()
2113 f.close()
2114 if data[2] in certs:
2115 dialogs.ErrorDialog(_('Certificate Already in File'),
2116 _('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS)
2117 else:
2118 f = open(gajim.MY_CACERTS, 'a')
2119 f.write(server + '\n')
2120 f.write(data[2] + '\n\n')
2121 f.close()
2122 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
2123 data[3])
2124 if is_checked[1]:
2125 ignore_ssl_errors = gajim.config.get_per('accounts', account,
2126 'ignore_ssl_errors').split()
2127 ignore_ssl_errors.append(str(data[1]))
2128 gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
2129 ' '.join(ignore_ssl_errors))
2130 gajim.connections[account].ssl_certificate_accepted()
2132 def on_cancel():
2133 del self.instances[account]['online_dialog']['ssl_error']
2134 gajim.connections[account].disconnect(on_purpose=True)
2135 self.handle_event_status(account, 'offline')
2137 pritext = _('Error verifying SSL certificate')
2138 sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]}
2139 if data[1] in (18, 27):
2140 checktext1 = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3]
2141 else:
2142 checktext1 = ''
2143 checktext2 = _('Ignore this error for this certificate.')
2144 if 'ssl_error' in self.instances[account]['online_dialog']:
2145 self.instances[account]['online_dialog']['ssl_error'].destroy()
2146 self.instances[account]['online_dialog']['ssl_error'] = \
2147 dialogs.ConfirmationDialogDubbleCheck(pritext, sectext, checktext1,
2148 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
2150 def handle_event_fingerprint_error(self, account, data):
2151 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
2152 def on_yes(is_checked):
2153 del self.instances[account]['online_dialog']['fingerprint_error']
2154 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
2155 data[0])
2156 # Reset the ignored ssl errors
2157 gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
2158 gajim.connections[account].ssl_certificate_accepted()
2159 def on_no():
2160 del self.instances[account]['online_dialog']['fingerprint_error']
2161 gajim.connections[account].disconnect(on_purpose=True)
2162 self.handle_event_status(account, 'offline')
2163 pritext = _('SSL certificate error')
2164 sectext = _('It seems the SSL certificate of account %(account)s has '
2165 'changed or your connection is being hacked.\nOld fingerprint: %(old)s'
2166 '\nNew fingerprint: %(new)s\n\nDo you still want to connect and update'
2167 ' the fingerprint of the certificate?') % {'account': account,
2168 'old': gajim.config.get_per('accounts', account,
2169 'ssl_fingerprint_sha1'), 'new': data[0]}
2170 if 'fingerprint_error' in self.instances[account]['online_dialog']:
2171 self.instances[account]['online_dialog']['fingerprint_error'].destroy()
2172 self.instances[account]['online_dialog']['fingerprint_error'] = \
2173 dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
2174 on_response_no=on_no)
2176 def handle_event_plain_connection(self, account, data):
2177 # ('PLAIN_CONNECTION', account, (connection))
2178 server = gajim.config.get_per('accounts', account, 'hostname')
2179 def on_ok(is_checked):
2180 if not is_checked[0]:
2181 on_cancel()
2182 return
2183 # On cancel call del self.instances, so don't call it another time
2184 # before
2185 del self.instances[account]['online_dialog']['plain_connection']
2186 if is_checked[1]:
2187 gajim.config.set_per('accounts', account,
2188 'warn_when_plaintext_connection', False)
2189 gajim.connections[account].connection_accepted(data[0], 'plain')
2190 def on_cancel():
2191 del self.instances[account]['online_dialog']['plain_connection']
2192 gajim.connections[account].disconnect(on_purpose=True)
2193 self.handle_event_status(account, 'offline')
2194 pritext = _('Insecure connection')
2195 sectext = _('You are about to send your password on an unencrypted '
2196 'connection. Are you sure you want to do that?')
2197 checktext1 = _('Yes, I really want to connect insecurely')
2198 checktext2 = _('Do _not ask me again')
2199 if 'plain_connection' in self.instances[account]['online_dialog']:
2200 self.instances[account]['online_dialog']['plain_connection'].destroy()
2201 self.instances[account]['online_dialog']['plain_connection'] = \
2202 dialogs.ConfirmationDialogDubbleCheck(pritext, sectext,
2203 checktext1, checktext2, on_response_ok=on_ok,
2204 on_response_cancel=on_cancel, is_modal=False)
2206 def handle_event_insecure_ssl_connection(self, account, data):
2207 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
2208 server = gajim.config.get_per('accounts', account, 'hostname')
2209 def on_ok(is_checked):
2210 del self.instances[account]['online_dialog']['insecure_ssl']
2211 if not is_checked[0]:
2212 on_cancel()
2213 return
2214 if is_checked[1]:
2215 gajim.config.set_per('accounts', account,
2216 'warn_when_insecure_ssl_connection', False)
2217 if gajim.connections[account].connected == 0:
2218 # We have been disconnecting (too long time since window is opened)
2219 # re-connect with auto-accept
2220 gajim.connections[account].connection_auto_accepted = True
2221 show, msg = gajim.connections[account].continue_connect_info[:2]
2222 self.roster.send_status(account, show, msg)
2223 return
2224 gajim.connections[account].connection_accepted(data[0], data[1])
2225 def on_cancel():
2226 del self.instances[account]['online_dialog']['insecure_ssl']
2227 gajim.connections[account].disconnect(on_purpose=True)
2228 self.handle_event_status(account, 'offline')
2229 pritext = _('Insecure connection')
2230 sectext = _('You are about to send your password on an insecure '
2231 'connection. You should install PyOpenSSL to prevent that. Are you sure you want to do that?')
2232 checktext1 = _('Yes, I really want to connect insecurely')
2233 checktext2 = _('Do _not ask me again')
2234 if 'insecure_ssl' in self.instances[account]['online_dialog']:
2235 self.instances[account]['online_dialog']['insecure_ssl'].destroy()
2236 self.instances[account]['online_dialog']['insecure_ssl'] = \
2237 dialogs.ConfirmationDialogDubbleCheck(pritext, sectext,
2238 checktext1, checktext2, on_response_ok=on_ok,
2239 on_response_cancel=on_cancel, is_modal=False)
2241 def handle_event_pubsub_node_removed(self, account, data):
2242 # ('PUBSUB_NODE_REMOVED', account, (jid, node))
2243 if 'pep_services' in self.instances[account]:
2244 if data[0] == gajim.get_jid_from_account(account):
2245 self.instances[account]['pep_services'].node_removed(data[1])
2247 def handle_event_pubsub_node_not_removed(self, account, data):
2248 # ('PUBSUB_NODE_NOT_REMOVED', account, (jid, node, msg))
2249 if data[0] == gajim.get_jid_from_account(account):
2250 dialogs.WarningDialog(_('PEP node was not removed'),
2251 _('PEP node %(node)s was not removed: %(message)s') % {
2252 'node': data[1], 'message': data[2]})
2254 def register_handlers(self):
2255 self.handlers = {
2256 'ROSTER': self.handle_event_roster,
2257 'WARNING': self.handle_event_warning,
2258 'ERROR': self.handle_event_error,
2259 'INFORMATION': self.handle_event_information,
2260 'ERROR_ANSWER': self.handle_event_error_answer,
2261 'STATUS': self.handle_event_status,
2262 'NOTIFY': self.handle_event_notify,
2263 'MSGERROR': self.handle_event_msgerror,
2264 'MSGSENT': self.handle_event_msgsent,
2265 'MSGNOTSENT': self.handle_event_msgnotsent,
2266 'SUBSCRIBED': self.handle_event_subscribed,
2267 'UNSUBSCRIBED': self.handle_event_unsubscribed,
2268 'SUBSCRIBE': self.handle_event_subscribe,
2269 'AGENT_ERROR_INFO': self.handle_event_agent_info_error,
2270 'AGENT_ERROR_ITEMS': self.handle_event_agent_items_error,
2271 'AGENT_REMOVED': self.handle_event_agent_removed,
2272 'REGISTER_AGENT_INFO': self.handle_event_register_agent_info,
2273 'AGENT_INFO_ITEMS': self.handle_event_agent_info_items,
2274 'AGENT_INFO_INFO': self.handle_event_agent_info_info,
2275 'QUIT': self.handle_event_quit,
2276 'NEW_ACC_CONNECTED': self.handle_event_new_acc_connected,
2277 'NEW_ACC_NOT_CONNECTED': self.handle_event_new_acc_not_connected,
2278 'ACC_OK': self.handle_event_acc_ok,
2279 'ACC_NOT_OK': self.handle_event_acc_not_ok,
2280 'MYVCARD': self.handle_event_myvcard,
2281 'VCARD': self.handle_event_vcard,
2282 'LAST_STATUS_TIME': self.handle_event_last_status_time,
2283 'OS_INFO': self.handle_event_os_info,
2284 'ENTITY_TIME': self.handle_event_entity_time,
2285 'GC_NOTIFY': self.handle_event_gc_notify,
2286 'GC_MSG': self.handle_event_gc_msg,
2287 'GC_SUBJECT': self.handle_event_gc_subject,
2288 'GC_CONFIG': self.handle_event_gc_config,
2289 'GC_CONFIG_CHANGE': self.handle_event_gc_config_change,
2290 'GC_INVITATION': self.handle_event_gc_invitation,
2291 'GC_AFFILIATION': self.handle_event_gc_affiliation,
2292 'GC_PASSWORD_REQUIRED': self.handle_event_gc_password_required,
2293 'BAD_PASSPHRASE': self.handle_event_bad_passphrase,
2294 'ROSTER_INFO': self.handle_event_roster_info,
2295 'BOOKMARKS': self.handle_event_bookmarks,
2296 'CON_TYPE': self.handle_event_con_type,
2297 'CONNECTION_LOST': self.handle_event_connection_lost,
2298 'FILE_REQUEST': self.handle_event_file_request,
2299 'GMAIL_NOTIFY': self.handle_event_gmail_notify,
2300 'FILE_REQUEST_ERROR': self.handle_event_file_request_error,
2301 'FILE_SEND_ERROR': self.handle_event_file_send_error,
2302 'STANZA_ARRIVED': self.handle_event_stanza_arrived,
2303 'STANZA_SENT': self.handle_event_stanza_sent,
2304 'HTTP_AUTH': self.handle_event_http_auth,
2305 'VCARD_PUBLISHED': self.handle_event_vcard_published,
2306 'VCARD_NOT_PUBLISHED': self.handle_event_vcard_not_published,
2307 'ASK_NEW_NICK': self.handle_event_ask_new_nick,
2308 'SIGNED_IN': self.handle_event_signed_in,
2309 'METACONTACTS': self.handle_event_metacontacts,
2310 'ATOM_ENTRY': self.handle_atom_entry,
2311 'FAILED_DECRYPT': self.handle_event_failed_decrypt,
2312 'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received,
2313 'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
2314 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
2315 self.handle_event_privacy_lists_active_default,
2316 'PRIVACY_LIST_REMOVED': self.handle_event_privacy_list_removed,
2317 'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict,
2318 'PING_SENT': self.handle_event_ping_sent,
2319 'PING_REPLY': self.handle_event_ping_reply,
2320 'PING_ERROR': self.handle_event_ping_error,
2321 'SEARCH_FORM': self.handle_event_search_form,
2322 'SEARCH_RESULT': self.handle_event_search_result,
2323 'RESOURCE_CONFLICT': self.handle_event_resource_conflict,
2324 'ROSTERX': self.handle_event_roster_item_exchange,
2325 'PEP_CONFIG': self.handle_event_pep_config,
2326 'UNIQUE_ROOM_ID_UNSUPPORTED': \
2327 self.handle_event_unique_room_id_unsupported,
2328 'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported,
2329 'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required,
2330 'GPG_ALWAYS_TRUST': self.handle_event_gpg_always_trust,
2331 'PASSWORD_REQUIRED': self.handle_event_password_required,
2332 'SSL_ERROR': self.handle_event_ssl_error,
2333 'FINGERPRINT_ERROR': self.handle_event_fingerprint_error,
2334 'PLAIN_CONNECTION': self.handle_event_plain_connection,
2335 'INSECURE_SSL_CONNECTION': self.handle_event_insecure_ssl_connection,
2336 'PUBSUB_NODE_REMOVED': self.handle_event_pubsub_node_removed,
2337 'PUBSUB_NODE_NOT_REMOVED': self.handle_event_pubsub_node_not_removed,
2339 gajim.handlers = self.handlers
2341 ################################################################################
2342 ### Methods dealing with gajim.events
2343 ################################################################################
2345 def add_event(self, account, jid, type_, event_args):
2346 '''add an event to the gajim.events var'''
2347 # We add it to the gajim.events queue
2348 # Do we have a queue?
2349 jid = gajim.get_jid_without_resource(jid)
2350 no_queue = len(gajim.events.get_events(account, jid)) == 0
2351 # type_ can be gc-invitation file-send-error file-error file-request-error
2352 # file-request file-completed file-stopped
2353 # event_type can be in advancedNotificationWindow.events_list
2354 event_types = {'file-request': 'ft_request',
2355 'file-completed': 'ft_finished'}
2356 event_type = event_types.get(type_)
2357 show_in_roster = notify.get_show_in_roster(event_type, account, jid)
2358 show_in_systray = notify.get_show_in_systray(event_type, account, jid)
2359 event = gajim.events.create_event(type_, event_args,
2360 show_in_roster=show_in_roster,
2361 show_in_systray=show_in_systray)
2362 gajim.events.add_event(account, jid, event)
2364 self.roster.show_title()
2365 if no_queue: # We didn't have a queue: we change icons
2366 if not gajim.contacts.get_contact_with_highest_priority(account, jid):
2367 if type_ == 'gc-invitation':
2368 self.roster.add_groupchat(jid, account, status='offline')
2369 else:
2370 # add contact to roster ("Not In The Roster") if he is not
2371 self.roster.add_to_not_in_the_roster(account, jid)
2372 else:
2373 self.roster.draw_contact(jid, account)
2375 # Select the big brother contact in roster, it's visible because it has
2376 # events.
2377 family = gajim.contacts.get_metacontacts_family(account, jid)
2378 if family:
2379 nearby_family, bb_jid, bb_account = \
2380 self.roster._get_nearby_family_and_big_brother(family, account)
2381 else:
2382 bb_jid, bb_account = jid, account
2383 self.roster.select_contact(bb_jid, bb_account)
2385 def handle_event(self, account, fjid, type_):
2386 w = None
2387 ctrl = None
2388 session = None
2390 resource = gajim.get_resource_from_jid(fjid)
2391 jid = gajim.get_jid_without_resource(fjid)
2393 if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
2394 w = self.msg_win_mgr.get_window(jid, account)
2395 if jid in self.minimized_controls[account]:
2396 self.roster.on_groupchat_maximized(None, jid, account)
2397 return
2398 else:
2399 ctrl = self.msg_win_mgr.get_gc_control(jid, account)
2401 elif type_ in ('printed_chat', 'chat', ''):
2402 # '' is for log in/out notifications
2404 if type_ != '':
2405 event = gajim.events.get_first_event(account, fjid, type_)
2406 if not event:
2407 event = gajim.events.get_first_event(account, jid, type_)
2408 if not event:
2409 return
2411 if type_ == 'printed_chat':
2412 ctrl = event.parameters[0]
2413 elif type_ == 'chat':
2414 session = event.parameters[8]
2415 ctrl = session.control
2416 elif type_ == '':
2417 ctrl = self.msg_win_mgr.get_control(fjid, account)
2419 if not ctrl:
2420 highest_contact = gajim.contacts.get_contact_with_highest_priority(
2421 account, jid)
2422 # jid can have a window if this resource was lower when he sent
2423 # message and is now higher because the other one is offline
2424 if resource and highest_contact.resource == resource and \
2425 not self.msg_win_mgr.has_window(jid, account):
2426 # remove resource of events too
2427 gajim.events.change_jid(account, fjid, jid)
2428 resource = None
2429 fjid = jid
2430 contact = None
2431 if resource:
2432 contact = gajim.contacts.get_contact(account, jid, resource)
2433 if not contact:
2434 contact = highest_contact
2436 ctrl = self.new_chat(contact, account, resource = resource, session = session)
2438 gajim.last_message_time[account][jid] = 0 # long time ago
2440 w = ctrl.parent_win
2441 elif type_ in ('printed_pm', 'pm'):
2442 # assume that the most recently updated control we have for this party
2443 # is the one that this event was in
2444 event = gajim.events.get_first_event(account, fjid, type_)
2445 if not event:
2446 event = gajim.events.get_first_event(account, jid, type_)
2448 if type_ == 'printed_pm':
2449 ctrl = event.parameters[0]
2450 elif type_ == 'pm':
2451 session = event.parameters[8]
2453 if session and session.control:
2454 ctrl = session.control
2455 elif not ctrl:
2456 room_jid = jid
2457 nick = resource
2458 gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
2459 nick)
2460 if gc_contact:
2461 show = gc_contact.show
2462 else:
2463 show = 'offline'
2464 gc_contact = gajim.contacts.create_gc_contact(
2465 room_jid = room_jid, name = nick, show = show)
2467 if not session:
2468 session = gajim.connections[account].make_new_session(
2469 fjid, None, type_='pm')
2471 self.new_private_chat(gc_contact, account, session=session)
2472 ctrl = session.control
2474 w = ctrl.parent_win
2475 elif type_ in ('normal', 'file-request', 'file-request-error',
2476 'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
2477 # Get the first single message event
2478 event = gajim.events.get_first_event(account, fjid, type_)
2479 if not event:
2480 # default to jid without resource
2481 event = gajim.events.get_first_event(account, jid, type_)
2482 if not event:
2483 return
2484 # Open the window
2485 self.roster.open_event(account, jid, event)
2486 else:
2487 # Open the window
2488 self.roster.open_event(account, fjid, event)
2489 elif type_ == 'gmail':
2490 url=gajim.connections[account].gmail_url
2491 if url:
2492 helpers.launch_browser_mailer('url', url)
2493 elif type_ == 'gc-invitation':
2494 event = gajim.events.get_first_event(account, jid, type_)
2495 data = event.parameters
2496 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
2497 data[1], data[3])
2498 gajim.events.remove_events(account, jid, event)
2499 self.roster.draw_contact(jid, account)
2500 elif type_ == 'subscription_request':
2501 event = gajim.events.get_first_event(account, jid, type_)
2502 data = event.parameters
2503 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
2504 gajim.events.remove_events(account, jid, event)
2505 self.roster.draw_contact(jid, account)
2506 elif type_ == 'unsubscribed':
2507 event = gajim.events.get_first_event(account, jid, type_)
2508 contact = event.parameters
2509 self.show_unsubscribed_dialog(account, contact)
2510 gajim.events.remove_events(account, jid, event)
2511 self.roster.draw_contact(jid, account)
2512 if w:
2513 w.set_active_tab(ctrl)
2514 w.window.window.focus()
2515 # Using isinstance here because we want to catch all derived types
2516 if isinstance(ctrl, ChatControlBase):
2517 tv = ctrl.conv_textview
2518 tv.scroll_to_end()
2520 ################################################################################
2521 ### Methods dealing with emoticons
2522 ################################################################################
2524 def image_is_ok(self, image):
2525 if not os.path.exists(image):
2526 return False
2527 img = gtk.Image()
2528 try:
2529 img.set_from_file(image)
2530 except Exception:
2531 return False
2532 t = img.get_storage_type()
2533 if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
2534 return False
2535 return True
2537 @property
2538 def basic_pattern_re(self):
2539 try:
2540 return self._basic_pattern_re
2541 except AttributeError:
2542 self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE)
2543 return self._basic_pattern_re
2545 @property
2546 def emot_and_basic_re(self):
2547 try:
2548 return self._emot_and_basic_re
2549 except AttributeError:
2550 self._emot_and_basic_re = re.compile(self.emot_and_basic,
2551 re.IGNORECASE + re.UNICODE)
2552 return self._emot_and_basic_re
2554 @property
2555 def sth_at_sth_dot_sth_re(self):
2556 try:
2557 return self._sth_at_sth_dot_sth_re
2558 except AttributeError:
2559 self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
2560 return self._sth_at_sth_dot_sth_re
2562 @property
2563 def invalid_XML_chars_re(self):
2564 try:
2565 return self._invalid_XML_chars_re
2566 except AttributeError:
2567 self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
2568 return self._invalid_XML_chars_re
2570 def make_regexps(self):
2571 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
2572 # one escapes the metachars with \
2573 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
2574 # \s matches any whitespace character
2575 # \w any alphanumeric character
2576 # \W any non-alphanumeric character
2577 # \b means word boundary. This is a zero-width assertion that
2578 # matches only at the beginning or end of a word.
2579 # ^ matches at the beginning of lines
2581 # * means 0 or more times
2582 # + means 1 or more times
2583 # ? means 0 or 1 time
2584 # | means or
2585 # [^*] anything but '*' (inside [] you don't have to escape metachars)
2586 # [^\s*] anything but whitespaces and '*'
2587 # (?<!\S) is a one char lookbehind assertion and asks for any leading whitespace
2588 # and mathces beginning of lines so we have correct formatting detection
2589 # even if the the text is just '*foo*'
2590 # (?!\S) is the same thing but it's a lookahead assertion
2591 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at the end
2592 # so http://be) will match http://be and http://be)be) will match http://be)be
2594 legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
2595 r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
2596 r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
2597 # NOTE: it's ok to catch www.gr such stuff exist!
2599 #FIXME: recognize xmpp: and treat it specially
2600 links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
2601 r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
2602 r"(?=\)))|([A-Za-z][A-Za-z0-9\+\.\-]*:([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
2604 #2nd one: at_least_one_char@at_least_one_char.at_least_one_char
2605 mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
2607 #detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
2608 #doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
2609 formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
2610 r'(?<!\w|\<)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\w)|'\
2611 r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
2613 latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
2615 basic_pattern = links + '|' + mail + '|' + legacy_prefixes
2617 link_pattern = basic_pattern
2618 self.link_pattern_re = re.compile(link_pattern, re.IGNORECASE)
2620 if gajim.config.get('use_latex'):
2621 basic_pattern += latex
2623 if gajim.config.get('ascii_formatting'):
2624 basic_pattern += formatting
2625 self.basic_pattern = basic_pattern
2627 emoticons_pattern = ''
2628 if gajim.config.get('emoticons_theme'):
2629 # When an emoticon is bordered by an alpha-numeric character it is NOT
2630 # expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
2631 # We still allow multiple emoticons side-by-side like :P:P:P
2632 # sort keys by length so :qwe emot is checked before :q
2633 keys = sorted(self.emoticons, key=len, reverse=True)
2634 emoticons_pattern_prematch = ''
2635 emoticons_pattern_postmatch = ''
2636 emoticon_length = 0
2637 for emoticon in keys: # travel thru emoticons list
2638 emoticon = emoticon.decode('utf-8')
2639 emoticon_escaped = re.escape(emoticon) # espace regexp metachars
2640 emoticons_pattern += emoticon_escaped + '|'# | means or in regexp
2641 if (emoticon_length != len(emoticon)):
2642 # Build up expressions to match emoticons next to other emoticons
2643 emoticons_pattern_prematch = emoticons_pattern_prematch[:-1] + ')|(?<='
2644 emoticons_pattern_postmatch = emoticons_pattern_postmatch[:-1] + ')|(?='
2645 emoticon_length = len(emoticon)
2646 emoticons_pattern_prematch += emoticon_escaped + '|'
2647 emoticons_pattern_postmatch += emoticon_escaped + '|'
2648 # We match from our list of emoticons, but they must either have
2649 # whitespace, or another emoticon next to it to match successfully
2650 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
2651 emoticons_pattern = '|' + \
2652 '(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \
2653 '(?:' + emoticons_pattern[:-1] + ')' + \
2654 '(?:(?![\w]' + emoticons_pattern_postmatch[:-1] + '))'
2656 # because emoticons match later (in the string) they need to be after
2657 # basic matches that may occur earlier
2658 self.emot_and_basic = basic_pattern + emoticons_pattern
2660 # needed for xhtml display
2661 self.emot_only = emoticons_pattern
2663 # at least one character in 3 parts (before @, after @, after .)
2664 self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
2666 # Invalid XML chars
2667 self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|[\ud800-\udfff]|[\ufffe-\uffff]'
2669 def popup_emoticons_under_button(self, button, parent_win):
2670 ''' pops emoticons menu under button, located in parent_win'''
2671 gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
2672 button, parent_win)
2674 def prepare_emoticons_menu(self):
2675 menu = gtk.Menu()
2676 def emoticon_clicked(w, str_):
2677 if self.emoticon_menuitem_clicked:
2678 self.emoticon_menuitem_clicked(str_)
2679 # don't keep reference to CB of object
2680 # this will prevent making it uncollectable
2681 self.emoticon_menuitem_clicked = None
2682 def selection_done(widget):
2683 # remove reference to CB of object, which will
2684 # make it uncollectable
2685 self.emoticon_menuitem_clicked = None
2686 counter = 0
2687 # Calculate the side lenght of the popup to make it a square
2688 size = int(round(math.sqrt(len(self.emoticons_images))))
2689 for image in self.emoticons_images:
2690 item = gtk.MenuItem()
2691 img = gtk.Image()
2692 if isinstance(image[1], gtk.gdk.PixbufAnimation):
2693 img.set_from_animation(image[1])
2694 else:
2695 img.set_from_pixbuf(image[1])
2696 item.add(img)
2697 item.connect('activate', emoticon_clicked, image[0])
2698 #FIXME: add tooltip with ascii
2699 menu.attach(item, counter % size, counter % size + 1,
2700 counter / size, counter / size + 1)
2701 counter += 1
2702 menu.connect('selection-done', selection_done)
2703 menu.show_all()
2704 return menu
2706 def _init_emoticons(self, path, need_reload = False):
2707 #initialize emoticons dictionary and unique images list
2708 self.emoticons_images = list()
2709 self.emoticons = dict()
2710 self.emoticons_animations = dict()
2712 sys.path.append(path)
2713 import emoticons
2714 if need_reload:
2715 # we need to reload else that doesn't work when changing emoticon set
2716 reload(emoticons)
2717 emots = emoticons.emoticons
2718 for emot_filename in emots:
2719 emot_file = os.path.join(path, emot_filename)
2720 if not self.image_is_ok(emot_file):
2721 continue
2722 for emot in emots[emot_filename]:
2723 emot = emot.decode('utf-8')
2724 # This avoids duplicated emoticons with the same image eg. :) and :-)
2725 if not emot_file in self.emoticons.values():
2726 if emot_file.endswith('.gif'):
2727 pix = gtk.gdk.PixbufAnimation(emot_file)
2728 else:
2729 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file, 16, 16)
2730 self.emoticons_images.append((emot, pix))
2731 self.emoticons[emot.upper()] = emot_file
2732 del emoticons
2733 sys.path.remove(path)
2735 def init_emoticons(self, need_reload = False):
2736 emot_theme = gajim.config.get('emoticons_theme')
2737 if not emot_theme:
2738 return
2740 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
2741 if not os.path.exists(path):
2742 # It's maybe a user theme
2743 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
2744 if not os.path.exists(path): # theme doesn't exist, disable emoticons
2745 dialogs.WarningDialog(_('Emoticons disabled'),
2746 _('Your configured emoticons theme has not been found, so emoticons have been disabled.'))
2747 gajim.config.set('emoticons_theme', '')
2748 return
2749 self._init_emoticons(path, need_reload)
2750 if len(self.emoticons) == 0:
2751 # maybe old format of emoticons file, try to convert it
2752 try:
2753 import pprint
2754 import emoticons
2755 emots = emoticons.emoticons
2756 fd = open(os.path.join(path, 'emoticons.py'), 'w')
2757 fd.write('emoticons = ')
2758 pprint.pprint( dict([
2759 (file_, [i for i in emots.keys() if emots[i] == file_])
2760 for file_ in set(emots.values())]), fd)
2761 fd.close()
2762 del emoticons
2763 self._init_emoticons(path, need_reload=True)
2764 except Exception:
2765 pass
2766 if len(self.emoticons) == 0:
2767 dialogs.WarningDialog(_('Emoticons disabled'),
2768 _('Your configured emoticons theme cannot been loaded. You maybe need to update the format of emoticons.py file. See http://trac.gajim.org/wiki/Emoticons for more details.'))
2769 if self.emoticons_menu:
2770 self.emoticons_menu.destroy()
2771 self.emoticons_menu = self.prepare_emoticons_menu()
2773 ################################################################################
2774 ### Methods for opening new messages controls
2775 ################################################################################
2777 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
2778 is_continued=False):
2779 '''joins the room immediately'''
2780 if not nick:
2781 nick = gajim.nicks[account]
2783 if self.msg_win_mgr.has_window(room_jid, account) and \
2784 gajim.gc_connected[account][room_jid]:
2785 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
2786 win = gc_ctrl.parent_win
2787 win.set_active_tab(gc_ctrl)
2788 dialogs.ErrorDialog(_('You are already in group chat %s') % room_jid)
2789 return
2791 invisible_show = gajim.SHOW_LIST.index('invisible')
2792 if gajim.connections[account].connected == invisible_show:
2793 dialogs.ErrorDialog(
2794 _('You cannot join a group chat while you are invisible'))
2795 return
2797 minimized_control = gajim.interface.minimized_controls[account].get(
2798 room_jid, None)
2800 if minimized_control is None and not self.msg_win_mgr.has_window(room_jid,
2801 account):
2802 # Join new groupchat
2803 if minimize:
2804 contact = gajim.contacts.create_contact(jid=room_jid, name=nick)
2805 gc_control = GroupchatControl(None, contact, account)
2806 gajim.interface.minimized_controls[account][room_jid] = gc_control
2807 self.roster.add_groupchat(room_jid, account)
2808 else:
2809 self.new_room(room_jid, nick, account, is_continued=is_continued)
2810 elif minimized_control is None:
2811 # We are already in that groupchat
2812 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
2813 gc_control.nick = nick
2814 gc_control.parent_win.set_active_tab(gc_control)
2815 else:
2816 # We are already in this groupchat and it is minimized
2817 minimized_control.nick = nick
2818 self.roster.add_groupchat(room_jid, account)
2820 # Connect
2821 gajim.connections[account].join_gc(nick, room_jid, password)
2822 if password:
2823 gajim.gc_passwords[room_jid] = password
2825 def new_room(self, room_jid, nick, account, is_continued=False):
2826 # Get target window, create a control, and associate it with the window
2827 contact = gajim.contacts.create_contact(jid=room_jid, name=nick)
2828 mw = self.msg_win_mgr.get_window(contact.jid, account)
2829 if not mw:
2830 mw = self.msg_win_mgr.create_window(contact, account,
2831 GroupchatControl.TYPE_ID)
2832 gc_control = GroupchatControl(mw, contact, account,
2833 is_continued=is_continued)
2834 mw.new_tab(gc_control)
2836 def new_private_chat(self, gc_contact, account, session=None):
2837 contact = gajim.contacts.contact_from_gc_contact(gc_contact)
2838 type_ = message_control.TYPE_PM
2839 fjid = gc_contact.room_jid + '/' + gc_contact.name
2841 conn = gajim.connections[account]
2843 if not session and fjid in conn.sessions:
2844 sessions = [s for s in conn.sessions[fjid].values() if isinstance(s, ChatControlSession)]
2846 # look for an existing session with a chat control
2847 for s in sessions:
2848 if s.control:
2849 session = s
2850 break
2852 if not session and not len(sessions) == 0:
2853 # there are no sessions with chat controls, just take the first one
2854 session = sessions[0]
2856 if not session:
2857 # couldn't find an existing ChatControlSession, just make a new one
2859 session = conn.make_new_session(fjid, None, 'pm')
2861 if not session.control:
2862 mw = self.msg_win_mgr.get_window(fjid, account)
2863 if not mw:
2864 mw = self.msg_win_mgr.create_window(contact, account, type_)
2866 session.control = PrivateChatControl(mw, gc_contact, contact, account,
2867 session)
2868 mw.new_tab(session.control)
2870 if len(gajim.events.get_events(account, fjid)):
2871 # We call this here to avoid race conditions with widget validation
2872 session.control.read_queue()
2874 return session.control
2876 def new_chat(self, contact, account, resource=None, session=None):
2877 # Get target window, create a control, and associate it with the window
2878 type_ = message_control.TYPE_CHAT
2880 fjid = contact.jid
2881 if resource:
2882 fjid += '/' + resource
2884 mw = self.msg_win_mgr.get_window(fjid, account)
2885 if not mw:
2886 mw = self.msg_win_mgr.create_window(contact, account, type_, resource)
2888 chat_control = ChatControl(mw, contact, account, session, resource)
2890 mw.new_tab(chat_control)
2892 if len(gajim.events.get_events(account, fjid)):
2893 # We call this here to avoid race conditions with widget validation
2894 chat_control.read_queue()
2896 return chat_control
2898 def new_chat_from_jid(self, account, fjid, message=None):
2899 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2900 contact = gajim.contacts.get_contact(account, jid, resource)
2901 added_to_roster = False
2902 if not contact:
2903 added_to_roster = True
2904 contact = self.roster.add_to_not_in_the_roster(account, jid,
2905 resource=resource)
2907 ctrl = self.msg_win_mgr.get_control(fjid, account)
2909 if not ctrl:
2910 ctrl = self.new_chat(contact, account,
2911 resource=resource)
2912 if len(gajim.events.get_events(account, fjid)):
2913 ctrl.read_queue()
2915 if message:
2916 buffer = ctrl.msg_textview.get_buffer()
2917 buffer.set_text(message)
2918 mw = ctrl.parent_win
2919 mw.set_active_tab(ctrl)
2920 # For JEP-0172
2921 if added_to_roster:
2922 ctrl.user_nick = gajim.nicks[account]
2923 gobject.idle_add(lambda: mw.window.grab_focus())
2925 def on_open_chat_window(self, widget, contact, account, resource=None,
2926 session=None):
2928 # Get the window containing the chat
2929 fjid = contact.jid
2931 if resource:
2932 fjid += '/' + resource
2934 ctrl = None
2936 if session:
2937 ctrl = session.control
2938 if not ctrl:
2939 win = self.msg_win_mgr.get_window(fjid, account)
2941 if win:
2942 ctrl = win.get_control(fjid, account)
2944 if not ctrl:
2945 ctrl = self.new_chat(contact, account, resource=resource,
2946 session=session)
2947 # last message is long time ago
2948 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2950 win = ctrl.parent_win
2952 win.set_active_tab(ctrl)
2954 if gajim.connections[account].is_zeroconf and \
2955 gajim.connections[account].status in ('offline', 'invisible'):
2956 ctrl = win.get_control(fjid, account)
2957 if ctrl:
2958 ctrl.got_disconnected()
2960 ################################################################################
2961 ### Other Methods
2962 ################################################################################
2964 def read_sleepy(self):
2965 '''Check idle status and change that status if needed'''
2966 if not self.sleeper.poll():
2967 # idle detection is not supported in that OS
2968 return False # stop looping in vain
2969 state = self.sleeper.getState()
2970 for account in gajim.connections:
2971 if account not in gajim.sleeper_state or \
2972 not gajim.sleeper_state[account]:
2973 continue
2974 if state == common.sleepy.STATE_AWAKE and \
2975 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2976 # we go online
2977 self.roster.send_status(account, 'online',
2978 gajim.status_before_autoaway[account])
2979 gajim.status_before_autoaway[account] = ''
2980 gajim.sleeper_state[account] = 'online'
2981 elif state == common.sleepy.STATE_AWAY and \
2982 gajim.sleeper_state[account] == 'online' and \
2983 gajim.config.get('autoaway'):
2984 # we save out online status
2985 gajim.status_before_autoaway[account] = \
2986 gajim.connections[account].status
2987 # we go away (no auto status) [we pass True to auto param]
2988 auto_message = gajim.config.get('autoaway_message')
2989 if not auto_message:
2990 auto_message = gajim.connections[account].status
2991 else:
2992 auto_message = auto_message.replace('$S','%(status)s')
2993 auto_message = auto_message.replace('$T','%(time)s')
2994 auto_message = auto_message % {
2995 'status': gajim.status_before_autoaway[account],
2996 'time': gajim.config.get('autoawaytime')
2998 self.roster.send_status(account, 'away', auto_message, auto=True)
2999 gajim.sleeper_state[account] = 'autoaway'
3000 elif state == common.sleepy.STATE_XA and \
3001 gajim.sleeper_state[account] in ('online', 'autoaway',
3002 'autoaway-forced') and gajim.config.get('autoxa'):
3003 # we go extended away [we pass True to auto param]
3004 auto_message = gajim.config.get('autoxa_message')
3005 if not auto_message:
3006 auto_message = gajim.connections[account].status
3007 else:
3008 auto_message = auto_message.replace('$S','%(status)s')
3009 auto_message = auto_message.replace('$T','%(time)s')
3010 auto_message = auto_message % {
3011 'status': gajim.status_before_autoaway[account],
3012 'time': gajim.config.get('autoxatime')
3014 self.roster.send_status(account, 'xa', auto_message, auto=True)
3015 gajim.sleeper_state[account] = 'autoxa'
3016 return True # renew timeout (loop for ever)
3018 def autoconnect(self):
3019 '''auto connect at startup'''
3020 # dict of account that want to connect sorted by status
3021 shows = {}
3022 for a in gajim.connections:
3023 if gajim.config.get_per('accounts', a, 'autoconnect'):
3024 if gajim.config.get_per('accounts', a, 'restore_last_status'):
3025 self.roster.send_status(a, gajim.config.get_per('accounts', a,
3026 'last_status'), helpers.from_one_line(gajim.config.get_per(
3027 'accounts', a, 'last_status_msg')))
3028 continue
3029 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
3030 if not show in gajim.SHOW_LIST:
3031 continue
3032 if not show in shows:
3033 shows[show] = [a]
3034 else:
3035 shows[show].append(a)
3036 def on_message(message, pep_dict):
3037 if message is None:
3038 return
3039 for a in shows[show]:
3040 self.roster.send_status(a, show, message)
3041 self.roster.send_pep(a, pep_dict)
3042 for show in shows:
3043 message = self.roster.get_status_message(show, on_message)
3044 return False
3046 def show_systray(self):
3047 self.systray_enabled = True
3048 self.systray.show_icon()
3050 def hide_systray(self):
3051 self.systray_enabled = False
3052 self.systray.hide_icon()
3054 def on_launch_browser_mailer(self, widget, url, kind):
3055 helpers.launch_browser_mailer(kind, url)
3057 def process_connections(self):
3058 ''' Called each foo (200) miliseconds. Check for idlequeue timeouts. '''
3059 try:
3060 gajim.idlequeue.process()
3061 except Exception:
3062 # Otherwise, an exception will stop our loop
3063 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
3064 if in_seconds:
3065 gobject.timeout_add_seconds(timeout, self.process_connections)
3066 else:
3067 gobject.timeout_add(timeout, self.process_connections)
3068 raise
3069 return True # renew timeout (loop for ever)
3071 def save_config(self):
3072 err_str = parser.write()
3073 if err_str is not None:
3074 print >> sys.stderr, err_str
3075 # it is good to notify the user
3076 # in case he or she cannot see the output of the console
3077 dialogs.ErrorDialog(_('Could not save your settings and preferences'),
3078 err_str)
3079 sys.exit()
3081 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
3082 '''Saves an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image.'''
3083 puny_jid = helpers.sanitize_filename(jid)
3084 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
3085 if puny_nick:
3086 path_to_file = os.path.join(path_to_file, puny_nick)
3087 # remove old avatars
3088 for typ in ('jpeg', 'png'):
3089 if local:
3090 path_to_original_file = path_to_file + '_local'+ '.' + typ
3091 else:
3092 path_to_original_file = path_to_file + '.' + typ
3093 if os.path.isfile(path_to_original_file):
3094 os.remove(path_to_original_file)
3095 if local and photo:
3096 pixbuf = photo
3097 typ = 'png'
3098 extension = '_local.png' # save local avatars as png file
3099 else:
3100 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type = True)
3101 if pixbuf is None:
3102 return
3103 extension = '.' + typ
3104 if typ not in ('jpeg', 'png'):
3105 gajim.log.debug('gtkpixbuf cannot save other than jpeg and png formats. saving %s\'avatar as png file (originaly %s)' % (jid, typ))
3106 typ = 'png'
3107 extension = '.png'
3108 path_to_original_file = path_to_file + extension
3109 try:
3110 pixbuf.save(path_to_original_file, typ)
3111 except Exception, e:
3112 log.error('Error writing avatar file %s: %s' % (path_to_original_file,
3113 str(e)))
3114 # Generate and save the resized, color avatar
3115 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
3116 if pixbuf:
3117 path_to_normal_file = path_to_file + '_notif_size_colored' + extension
3118 try:
3119 pixbuf.save(path_to_normal_file, 'png')
3120 except Exception, e:
3121 log.error('Error writing avatar file %s: %s' % \
3122 (path_to_original_file, str(e)))
3123 # Generate and save the resized, black and white avatar
3124 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
3125 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
3126 if bwbuf:
3127 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
3128 try:
3129 bwbuf.save(path_to_bw_file, 'png')
3130 except Exception, e:
3131 log.error('Error writing avatar file %s: %s' % \
3132 (path_to_original_file, str(e)))
3134 def remove_avatar_files(self, jid, puny_nick = None, local = False):
3135 '''remove avatar files of a jid'''
3136 puny_jid = helpers.sanitize_filename(jid)
3137 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
3138 if puny_nick:
3139 path_to_file = os.path.join(path_to_file, puny_nick)
3140 for ext in ('.jpeg', '.png'):
3141 if local:
3142 ext = '_local' + ext
3143 path_to_original_file = path_to_file + ext
3144 if os.path.isfile(path_to_file + ext):
3145 os.remove(path_to_file + ext)
3146 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
3147 os.remove(path_to_file + '_notif_size_colored' + ext)
3148 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
3149 os.remove(path_to_file + '_notif_size_bw' + ext)
3151 def auto_join_bookmarks(self, account):
3152 '''autojoin bookmarked GCs that have 'auto join' on for this account'''
3153 for bm in gajim.connections[account].bookmarks:
3154 if bm['autojoin'] in ('1', 'true'):
3155 jid = bm['jid']
3156 # Only join non-opened groupchats. Opened one are already
3157 # auto-joined on re-connection
3158 if not jid in gajim.gc_connected[account]:
3159 # we are not already connected
3160 minimize = bm['minimize'] in ('1', 'true')
3161 gajim.interface.join_gc_room(account, jid, bm['nick'],
3162 bm['password'], minimize = minimize)
3163 elif jid in self.minimized_controls[account]:
3164 # more or less a hack:
3165 # On disconnect the minimized gc contact instances
3166 # were set to offline. Reconnect them to show up in the roster.
3167 self.roster.add_groupchat(jid, account)
3169 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
3170 nick):
3171 '''add a bookmark for this account, sorted in bookmark list'''
3172 bm = {
3173 'name': name,
3174 'jid': jid,
3175 'autojoin': autojoin,
3176 'minimize': minimize,
3177 'password': password,
3178 'nick': nick
3180 place_found = False
3181 index = 0
3182 # check for duplicate entry and respect alpha order
3183 for bookmark in gajim.connections[account].bookmarks:
3184 if bookmark['jid'] == bm['jid']:
3185 dialogs.ErrorDialog(
3186 _('Bookmark already set'),
3187 _('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
3188 return
3189 if bookmark['name'] > bm['name']:
3190 place_found = True
3191 break
3192 index += 1
3193 if place_found:
3194 gajim.connections[account].bookmarks.insert(index, bm)
3195 else:
3196 gajim.connections[account].bookmarks.append(bm)
3197 gajim.connections[account].store_bookmarks()
3198 self.roster.set_actions_menu_needs_rebuild()
3199 dialogs.InformationDialog(
3200 _('Bookmark has been added successfully'),
3201 _('You can manage your bookmarks via Actions menu in your roster.'))
3204 # does JID exist only within a groupchat?
3205 def is_pm_contact(self, fjid, account):
3206 bare_jid = gajim.get_jid_without_resource(fjid)
3208 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
3210 if not gc_ctrl and \
3211 bare_jid in self.minimized_controls[account]:
3212 gc_ctrl = self.minimized_controls[account][bare_jid]
3214 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
3216 def create_ipython_window(self):
3217 try:
3218 from ipython_view import IPythonView
3219 except ImportError:
3220 print 'ipython_view not found'
3221 return
3222 import pango
3224 if os.name == 'nt':
3225 font = 'Lucida Console 9'
3226 else:
3227 font = 'Luxi Mono 10'
3229 window = gtk.Window()
3230 window.set_size_request(750,550)
3231 window.set_resizable(True)
3232 sw = gtk.ScrolledWindow()
3233 sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
3234 view = IPythonView()
3235 view.modify_font(pango.FontDescription(font))
3236 view.set_wrap_mode(gtk.WRAP_CHAR)
3237 sw.add(view)
3238 window.add(sw)
3239 window.show_all()
3240 def on_delete(win, event):
3241 win.hide()
3242 return True
3243 window.connect('delete_event',on_delete)
3244 view.updateNamespace({'gajim': gajim})
3245 gajim.ipython_window = window
3247 def __init__(self):
3248 gajim.interface = self
3249 gajim.thread_interface = ThreadInterface
3250 # This is the manager and factory of message windows set by the module
3251 self.msg_win_mgr = None
3252 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
3253 'closed': {}}
3254 self.emoticons_menu = None
3255 # handler when an emoticon is clicked in emoticons_menu
3256 self.emoticon_menuitem_clicked = None
3257 self.minimized_controls = {}
3258 self.status_sent_to_users = {}
3259 self.status_sent_to_groups = {}
3260 self.gpg_passphrase = {}
3261 self.pass_dialog = {}
3262 self.default_colors = {
3263 'inmsgcolor': gajim.config.get('inmsgcolor'),
3264 'outmsgcolor': gajim.config.get('outmsgcolor'),
3265 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
3266 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
3269 cfg_was_read = parser.read()
3270 # override logging settings from config (don't take care of '-q' option)
3271 if gajim.config.get('verbose'):
3272 logging_helpers.set_verbose()
3274 # Is Gajim default app?
3275 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
3276 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
3278 for account in gajim.config.get_per('accounts'):
3279 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
3280 gajim.ZEROCONF_ACC_NAME = account
3281 break
3282 # Is gnome configured to activate row on single click ?
3283 try:
3284 import gconf
3285 client = gconf.client_get_default()
3286 click_policy = client.get_string(
3287 '/apps/nautilus/preferences/click_policy')
3288 if click_policy == 'single':
3289 gajim.single_click = True
3290 except Exception:
3291 pass
3292 # add default status messages if there is not in the config file
3293 if len(gajim.config.get_per('statusmsg')) == 0:
3294 default = gajim.config.statusmsg_default
3295 for msg in default:
3296 gajim.config.add_per('statusmsg', msg)
3297 gajim.config.set_per('statusmsg', msg, 'message', default[msg][0])
3298 gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1])
3299 gajim.config.set_per('statusmsg', msg, 'subactivity',
3300 default[msg][2])
3301 gajim.config.set_per('statusmsg', msg, 'activity_text',
3302 default[msg][3])
3303 gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4])
3304 gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5])
3305 #add default themes if there is not in the config file
3306 theme = gajim.config.get('roster_theme')
3307 if not theme in gajim.config.get_per('themes'):
3308 gajim.config.set('roster_theme', _('default'))
3309 if len(gajim.config.get_per('themes')) == 0:
3310 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
3311 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
3312 'groupfontattrs', 'contacttextcolor', 'contactbgcolor',
3313 'contactfont', 'contactfontattrs', 'bannertextcolor',
3314 'bannerbgcolor']
3316 default = gajim.config.themes_default
3317 for theme_name in default:
3318 gajim.config.add_per('themes', theme_name)
3319 theme = default[theme_name]
3320 for o in d:
3321 gajim.config.set_per('themes', theme_name, o,
3322 theme[d.index(o)])
3324 if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
3325 gtkgui_helpers.autodetect_browser_mailer()
3327 gajim.idlequeue = idlequeue.get_idlequeue()
3328 # resolve and keep current record of resolved hosts
3329 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
3330 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
3331 self.handle_event_file_rcv_completed,
3332 self.handle_event_file_progress,
3333 self.handle_event_file_error)
3334 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
3335 gajim.default_session_type = ChatControlSession
3336 self.register_handlers()
3337 if gajim.config.get('enable_zeroconf') and gajim.HAVE_ZEROCONF:
3338 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
3339 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
3340 for account in gajim.config.get_per('accounts'):
3341 if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
3342 gajim.connections[account] = common.connection.Connection(account)
3344 # gtk hooks
3345 gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
3346 gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
3347 gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
3349 self.instances = {}
3351 for a in gajim.connections:
3352 self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
3353 'search': {}, 'online_dialog': {}}
3354 # online_dialog contains all dialogs that have a meaning only when we
3355 # are not disconnected
3356 self.minimized_controls[a] = {}
3357 gajim.contacts.add_account(a)
3358 gajim.groups[a] = {}
3359 gajim.gc_connected[a] = {}
3360 gajim.automatic_rooms[a] = {}
3361 gajim.newly_added[a] = []
3362 gajim.to_be_removed[a] = []
3363 gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
3364 gajim.block_signed_in_notifications[a] = True
3365 gajim.sleeper_state[a] = 0
3366 gajim.encrypted_chats[a] = []
3367 gajim.last_message_time[a] = {}
3368 gajim.status_before_autoaway[a] = ''
3369 gajim.transport_avatar[a] = {}
3370 gajim.gajim_optional_features[a] = []
3371 gajim.caps_hash[a] = ''
3373 helpers.update_optional_features()
3375 self.remote_ctrl = None
3377 if gajim.config.get('networkmanager_support') and dbus_support.supported:
3378 import network_manager_listener
3380 # Handle gnome screensaver
3381 if dbus_support.supported:
3382 def gnome_screensaver_ActiveChanged_cb(active):
3383 if not active:
3384 for account in gajim.connections:
3385 if gajim.sleeper_state[account] == 'autoaway-forced':
3386 # We came back online ofter gnome-screensaver autoaway
3387 self.roster.send_status(account, 'online',
3388 gajim.status_before_autoaway[account])
3389 gajim.status_before_autoaway[account] = ''
3390 gajim.sleeper_state[account] = 'online'
3391 return
3392 if not gajim.config.get('autoaway'):
3393 # Don't go auto away if user disabled the option
3394 return
3395 for account in gajim.connections:
3396 if account not in gajim.sleeper_state or \
3397 not gajim.sleeper_state[account]:
3398 continue
3399 if gajim.sleeper_state[account] == 'online':
3400 # we save out online status
3401 gajim.status_before_autoaway[account] = \
3402 gajim.connections[account].status
3403 # we go away (no auto status) [we pass True to auto param]
3404 auto_message = gajim.config.get('autoaway_message')
3405 if not auto_message:
3406 auto_message = gajim.connections[account].status
3407 else:
3408 auto_message = auto_message.replace('$S','%(status)s')
3409 auto_message = auto_message.replace('$T','%(time)s')
3410 auto_message = auto_message % {
3411 'status': gajim.status_before_autoaway[account],
3412 'time': gajim.config.get('autoxatime')
3414 self.roster.send_status(account, 'away', auto_message,
3415 auto=True)
3416 gajim.sleeper_state[account] = 'autoaway-forced'
3418 try:
3419 bus = dbus.SessionBus()
3420 bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
3421 'ActiveChanged', 'org.gnome.ScreenSaver')
3422 except Exception:
3423 pass
3425 self.show_vcard_when_connect = []
3427 self.sleeper = common.sleepy.Sleepy(
3428 gajim.config.get('autoawaytime') * 60, # make minutes to seconds
3429 gajim.config.get('autoxatime') * 60)
3431 gtkgui_helpers.make_jabber_state_images()
3433 self.systray_enabled = False
3434 self.systray_capabilities = False
3436 if (os.name == 'nt'):
3437 import statusicon
3438 self.systray = statusicon.StatusIcon()
3439 self.systray_capabilities = True
3440 else: # use ours, not GTK+ one
3441 # [FIXME: remove this when we migrate to 2.10 and we can do
3442 # cool tooltips somehow and (not dying to keep) animation]
3443 import systray
3444 self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES
3445 if self.systray_capabilities:
3446 self.systray = systray.Systray()
3448 if self.systray_capabilities and gajim.config.get('trayicon') != 'never':
3449 self.show_systray()
3451 path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png')
3452 pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
3453 # set the icon to all windows
3454 gtk.window_set_default_icon(pix)
3456 self.init_emoticons()
3457 self.make_regexps()
3459 self.roster = roster_window.RosterWindow()
3460 for account in gajim.connections:
3461 gajim.connections[account].load_roster_from_db()
3463 # get instances for windows/dialogs that will show_all()/hide()
3464 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
3466 # get transports type from DB
3467 gajim.transport_type = gajim.logger.get_transports_type()
3469 # test is dictionnary is present for speller
3470 if gajim.config.get('use_speller'):
3471 lang = gajim.config.get('speller_language')
3472 if not lang:
3473 lang = gajim.LANG
3474 tv = gtk.TextView()
3475 try:
3476 import gtkspell
3477 spell = gtkspell.Spell(tv, lang)
3478 except (ImportError, TypeError, RuntimeError, OSError):
3479 dialogs.AspellDictError(lang)
3481 if gajim.config.get('soundplayer') == '':
3482 # only on first time Gajim starts
3483 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
3484 for command in commands:
3485 if helpers.is_in_path(command):
3486 if command == 'aplay':
3487 command += ' -q'
3488 gajim.config.set('soundplayer', command)
3489 break
3491 self.last_ftwindow_update = 0
3493 gobject.timeout_add(100, self.autoconnect)
3494 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
3495 if in_seconds:
3496 gobject.timeout_add_seconds(timeout, self.process_connections)
3497 else:
3498 gobject.timeout_add(timeout, self.process_connections)
3499 gobject.timeout_add_seconds(gajim.config.get(
3500 'check_idle_every_foo_seconds'), self.read_sleepy)
3502 # when using libasyncns we need to process resolver in regular intervals
3503 if resolver.USE_LIBASYNCNS:
3504 gobject.timeout_add(200, gajim.resolver.process)
3506 # setup the indicator
3507 if gajim.HAVE_INDICATOR:
3508 notify.setup_indicator_server()
3510 def remote_init():
3511 if gajim.config.get('remote_control'):
3512 try:
3513 import remote_control
3514 self.remote_ctrl = remote_control.Remote()
3515 except Exception:
3516 pass
3517 gobject.timeout_add_seconds(5, remote_init)
3519 if __name__ == '__main__':
3520 def sigint_cb(num, stack):
3521 sys.exit(5)
3522 # ^C exits the application normally to delete pid file
3523 signal.signal(signal.SIGINT, sigint_cb)
3525 log.info("Encodings: d:%s, fs:%s, p:%s", sys.getdefaultencoding(), \
3526 sys.getfilesystemencoding(), locale.getpreferredencoding())
3528 if os.name != 'nt':
3529 # Session Management support
3530 try:
3531 import gnome.ui
3532 except ImportError:
3533 pass
3534 else:
3535 def die_cb(cli):
3536 gajim.interface.roster.quit_gtkgui_interface()
3537 gnome.program_init('gajim', gajim.version)
3538 cli = gnome.ui.master_client()
3539 cli.connect('die', die_cb)
3541 path_to_gajim_script = gtkgui_helpers.get_abspath_for_script(
3542 'gajim')
3544 if path_to_gajim_script:
3545 argv = [path_to_gajim_script]
3546 # FIXME: remove this typeerror catch when gnome python is old and
3547 # not bad patched by distro men [2.12.0 + should not need all that
3548 # NORMALLY]
3549 try:
3550 cli.set_restart_command(argv)
3551 except AttributeError:
3552 cli.set_restart_command(len(argv), argv)
3554 check_paths.check_and_possibly_create_paths()
3556 Interface()
3558 try:
3559 if os.name != 'nt':
3560 # This makes Gajim unusable under windows, and threads are used only
3561 # for GPG, so not under windows
3562 gtk.gdk.threads_init()
3563 gtk.main()
3564 except KeyboardInterrupt:
3565 print >> sys.stderr, 'KeyboardInterrupt'
3567 # vim: se ts=3: