don't try to reconnect in a MUC when we get an error. Fixes #6097
[gajim.git] / src / gui_interface.py
blob672eea327246301883aa65cb4725143eeb9ea426
1 # -*- coding:utf-8 -*-
2 ## src/gajim.py
3 ##
4 ## Copyright (C) 2003-2010 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 re
41 import time
42 import math
43 from subprocess import Popen
45 import gtk
46 import gobject
48 from common import i18n
49 from common import gajim
51 from common import dbus_support
52 if dbus_support.supported:
53 from music_track_listener import MusicTrackListener
54 from common import location_listener
55 import dbus
57 import gtkgui_helpers
59 import dialogs
60 import notify
61 import message_control
63 from chat_control import ChatControlBase
64 from chat_control import ChatControl
65 from groupchat_control import GroupchatControl
66 from groupchat_control import PrivateChatControl
68 from atom_window import AtomWindow
69 from session import ChatControlSession
71 import common.sleepy
73 from common.xmpp import idlequeue
74 from common.zeroconf import connection_zeroconf
75 from common import resolver
76 from common import caps_cache
77 from common import proxy65_manager
78 from common import socks5
79 from common import helpers
80 from common import dataforms
81 from common import passwords
82 from common import logging_helpers
83 from common.connection_handlers_events import OurShowEvent, \
84 FileRequestErrorEvent
85 from common.connection import Connection
87 import roster_window
88 import profile_window
89 import config
90 from threading import Thread
91 from common import ged
93 gajimpaths = common.configpaths.gajimpaths
94 config_filename = gajimpaths['CONFIG_FILE']
96 from common import optparser
97 parser = optparser.OptionsParser(config_filename)
99 import logging
100 log = logging.getLogger('gajim.interface')
102 class Interface:
104 ################################################################################
105 ### Methods handling events from connection
106 ################################################################################
108 def handle_event_error(self, unused, data):
109 #('ERROR', account, (title_text, section_text))
110 dialogs.ErrorDialog(data[0], data[1])
112 def handle_event_db_error(self, unused, data):
113 #('DB_ERROR', account, (title_text, section_text))
114 if self.db_error_dialog:
115 return
116 self.db_error_dialog = dialogs.ErrorDialog(data[0], data[1])
117 def destroyed(win):
118 self.db_error_dialog = None
119 self.db_error_dialog.connect('destroy', destroyed)
121 def handle_event_information(self, unused, data):
122 #('INFORMATION', account, (title_text, section_text))
123 dialogs.InformationDialog(data[0], data[1])
125 def handle_ask_new_nick(self, account, room_jid):
126 title = _('Unable to join group chat')
127 prompt = _('Your desired nickname in group chat %s is in use or '
128 'registered by another occupant.\nPlease specify another nickname '
129 'below:') % room_jid
130 check_text = _('Always use this nickname when there is a conflict')
131 if 'change_nick_dialog' in self.instances:
132 self.instances['change_nick_dialog'].add_room(account, room_jid,
133 prompt)
134 else:
135 self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
136 account, room_jid, title, prompt)
138 def handle_event_http_auth(self, obj):
139 #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
140 def response(account, answer):
141 obj.conn.build_http_auth_answer(obj.stanza, answer)
143 def on_yes(is_checked, obj):
144 response(obj, 'yes')
146 account = obj.conn.name
147 sec_msg = _('Do you accept this request?')
148 if gajim.get_number_of_connected_accounts() > 1:
149 sec_msg = _('Do you accept this request on account %s?') % account
150 if obj.msg:
151 sec_msg = obj.msg + '\n' + sec_msg
152 dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
153 '%(url)s (id: %(id)s)') % {'method': obj.method, 'url': obj.url,
154 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
155 on_response_no=(response, obj, 'no'))
157 def handle_event_iq_error(self, obj):
158 #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode))
159 if unicode(obj.errcode) in ('400', '403', '406') and obj.id_:
160 # show the error dialog
161 ft = self.instances['file_transfers']
162 sid = obj.id_
163 if len(obj.id_) > 3 and obj.id_[2] == '_':
164 sid = obj.id_[3:]
165 if sid in ft.files_props['s']:
166 file_props = ft.files_props['s'][sid]
167 if unicode(obj.errcode) == '400':
168 file_props['error'] = -3
169 else:
170 file_props['error'] = -4
171 gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
172 conn=obj.conn, jid=obj.jid, file_props=file_props,
173 error_msg=obj.errmsg))
174 obj.conn.disconnect_transfer(file_props)
175 return
176 elif unicode(obj.errcode) == '404':
177 sid = obj.id_
178 if len(obj.id_) > 3 and obj.id_[2] == '_':
179 sid = obj.id_[3:]
180 if sid in obj.conn.files_props:
181 file_props = obj.conn.files_props[sid]
182 self.handle_event_file_send_error(obj.conn.name, (obj.fjid,
183 file_props))
184 obj.conn.disconnect_transfer(file_props)
185 return
187 ctrl = self.msg_win_mgr.get_control(obj.fjid, obj.conn.name)
188 if ctrl and ctrl.type_id == message_control.TYPE_GC:
189 ctrl.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg))
191 def handle_event_connection_lost(self, obj):
192 # ('CONNECTION_LOST', account, [title, text])
193 path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48)
194 account = obj.conn.name
195 notify.popup(_('Connection Failed'), account, account,
196 'connection_failed', path, obj.title, obj.msg)
198 def unblock_signed_in_notifications(self, account):
199 gajim.block_signed_in_notifications[account] = False
201 def handle_event_status(self, obj): # OUR status
202 #('STATUS', account, show)
203 account = obj.conn.name
204 if obj.show in ('offline', 'error'):
205 for name in self.instances[account]['online_dialog'].keys():
206 # .keys() is needed to not have a dictionary length changed
207 # during iteration error
208 self.instances[account]['online_dialog'][name].destroy()
209 if name in self.instances[account]['online_dialog']:
210 # destroy handler may have already removed it
211 del self.instances[account]['online_dialog'][name]
212 for request in self.gpg_passphrase.values():
213 if request:
214 request.interrupt(account=account)
215 if account in self.pass_dialog:
216 self.pass_dialog[account].window.destroy()
217 if obj.show == 'offline':
218 gajim.block_signed_in_notifications[account] = True
219 else:
220 # 30 seconds after we change our status to sth else than offline
221 # we stop blocking notifications of any kind
222 # this prevents from getting the roster items as 'just signed in'
223 # contacts. 30 seconds should be enough time
224 gobject.timeout_add_seconds(30,
225 self.unblock_signed_in_notifications, account)
227 if account in self.show_vcard_when_connect and obj.show not in (
228 'offline', 'error'):
229 self.edit_own_details(account)
231 def edit_own_details(self, account):
232 jid = gajim.get_jid_from_account(account)
233 if 'profile' not in self.instances[account]:
234 self.instances[account]['profile'] = \
235 profile_window.ProfileWindow(account)
236 gajim.connections[account].request_vcard(jid)
238 def handle_gc_error(self, gc_control, pritext, sectext):
239 if gc_control and gc_control.autorejoin is not None:
240 if gc_control.error_dialog:
241 gc_control.error_dialog.destroy()
242 def on_close(dummy):
243 gc_control.error_dialog.destroy()
244 gc_control.error_dialog = None
245 gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext,
246 on_response_ok=on_close, on_response_cancel=on_close)
247 else:
248 dialogs.ErrorDialog(pritext, sectext)
250 def handle_gc_password_required(self, account, room_jid, nick):
251 def on_ok(text):
252 gajim.connections[account].join_gc(nick, room_jid, text)
253 gajim.gc_passwords[room_jid] = text
255 def on_cancel():
256 # get and destroy window
257 if room_jid in gajim.interface.minimized_controls[account]:
258 self.roster.on_disconnect(None, room_jid, account)
259 else:
260 win = self.msg_win_mgr.get_window(room_jid, account)
261 ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
262 win.remove_tab(ctrl, 3)
264 dlg = dialogs.InputDialog(_('Password Required'),
265 _('A Password is required to join the room %s. Please type it.') % \
266 room_jid, is_modal=False, ok_handler=on_ok,
267 cancel_handler=on_cancel)
268 dlg.input_entry.set_visibility(False)
270 def handle_event_gc_presence(self, obj):
271 gc_control = obj.gc_control
272 if obj.ptype == 'error':
273 if obj.errcode == '503':
274 # maximum user number reached
275 self.handle_gc_error(gc_control,
276 _('Unable to join group chat'),
277 _('Maximum number of users for %s has been reached') % \
278 obj.room_jid)
279 elif (obj.errcode == '401') or (obj.errcon == 'not-authorized'):
280 # password required to join
281 self.handle_gc_password_required(obj.conn.name, obj.room_jid,
282 obj.nick)
283 elif (obj.errcode == '403') or (obj.errcon == 'forbidden'):
284 # we are banned
285 self.handle_gc_error(gc_control, _('Unable to join group chat'),
286 _('You are banned from group chat %s.') % obj.room_jid)
287 elif (obj.errcode == '404') or (obj.errcon in ('item-not-found',
288 'remote-server-not-found')):
289 # group chat does not exist
290 self.handle_gc_error(gc_control, _('Unable to join group chat'),
291 _('Group chat %s does not exist.') % obj.room_jid)
292 elif (obj.errcode == '405') or (obj.errcon == 'not-allowed'):
293 self.handle_gc_error(gc_control, _('Unable to join group chat'),
294 _('Group chat creation is restricted.'))
295 elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'):
296 self.handle_gc_error(gc_control, _('Unable to join group chat'),
297 _('Your registered nickname must be used in group chat '
298 '%s.') % obj.room_jid)
299 elif (obj.errcode == '407') or (obj.errcon == \
300 'registration-required'):
301 self.handle_gc_error(gc_control, _('Unable to join group chat'),
302 _('You are not in the members list in groupchat %s.') % \
303 obj.room_jid)
304 elif (obj.errcode == '409') or (obj.errcon == 'conflict'):
305 self.handle_ask_new_nick(obj.conn.name, obj.room_jid)
306 elif gc_control:
307 gc_control.print_conversation('Error %s: %s' % (obj.errcode,
308 obj.errmsg))
309 if gc_control and gc_control.autorejoin:
310 gc_control.autorejoin = False
312 def handle_event_presence(self, obj):
313 # 'NOTIFY' (account, (jid, status, status message, resource,
314 # priority, # keyID, timestamp, contact_nickname))
316 # Contact changed show
318 account = obj.conn.name
319 jid = obj.jid
320 show = obj.show
321 status = obj.status
322 resource = obj.resource or ''
324 jid_list = gajim.contacts.get_jid_list(account)
326 # unset custom status
327 if (obj.old_show == 0 and obj.new_show > 1) or \
328 (obj.old_show > 1 and obj.new_show == 0 and obj.conn.connected > 1):
329 if account in self.status_sent_to_users and \
330 jid in self.status_sent_to_users[account]:
331 del self.status_sent_to_users[account][jid]
333 if gajim.jid_is_transport(jid):
334 # It must be an agent
336 # transport just signed in/out, don't show
337 # popup notifications for 30s
338 account_jid = account + '/' + jid
339 gajim.block_signed_in_notifications[account_jid] = True
340 gobject.timeout_add_seconds(30,
341 self.unblock_signed_in_notifications, account_jid)
343 else:
344 # It isn't an agent
345 # Notifications
346 obj.show_notif = True
347 for c in obj.contact_list:
348 if c.resource == resource:
349 # we look for other connected resources
350 continue
351 if c.show not in ('offline', 'error'):
352 obj.show_notif = False
353 break
354 if obj.show_notif:
355 # no other resource is connected, let's look in metacontacts
356 family = gajim.contacts.get_metacontacts_family(account,
357 jid)
358 for info in family:
359 acct_ = info['account']
360 jid_ = info['jid']
361 c_ = gajim.contacts.get_contact_with_highest_priority(
362 acct_, jid_)
363 if not c_:
364 continue
365 if c_.show not in ('offline', 'error'):
366 obj.show_notif = False
367 break
368 if obj.show_notif:
369 if obj.old_show < 2 and obj.new_show > 1:
370 notify.notify('contact_connected', jid, account, status)
372 elif obj.old_show > 1 and obj.new_show < 2:
373 notify.notify('contact_disconnected', jid, account, status)
374 # Status change (not connected/disconnected or
375 # error (<1))
376 elif obj.new_show > 1:
377 notify.notify('status_change', jid, account, [obj.new_show,
378 status])
380 highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
381 is_highest = (highest and highest.resource == resource)
383 # disconnect the session from the ctrl if the highest resource has
384 # changed
385 if (obj.was_highest and not is_highest) or \
386 (not obj.was_highest and is_highest):
387 ctrl = self.msg_win_mgr.get_control(jid, account)
388 if ctrl:
389 ctrl.no_autonegotiation = False
390 ctrl.set_session(None)
391 ctrl.contact = highest
393 def handle_event_msgerror(self, obj):
394 #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session]))
395 account = obj.conn.name
396 jids = obj.fjid.split('/', 1)
397 jid = jids[0]
399 if obj.error_code == '503':
400 # If we get server-not-found error, stop sending chatstates
401 for contact in gajim.contacts.get_contacts(account, jid):
402 contact.composing_xep = False
404 session = obj.session
406 gc_control = self.msg_win_mgr.get_gc_control(jid, account)
407 if not gc_control and \
408 jid in self.minimized_controls[account]:
409 gc_control = self.minimized_controls[account][jid]
410 if gc_control and gc_control.type_id != message_control.TYPE_GC:
411 gc_control = None
412 if gc_control:
413 if len(jids) > 1: # it's a pm
414 nick = jids[1]
416 if session:
417 ctrl = session.control
418 else:
419 ctrl = self.msg_win_mgr.get_control(obj.fjid, account)
421 if not ctrl:
422 tv = gc_control.list_treeview
423 model = tv.get_model()
424 iter_ = gc_control.get_contact_iter(nick)
425 if iter_:
426 show = model[iter_][3]
427 else:
428 show = 'offline'
429 gc_c = gajim.contacts.create_gc_contact(room_jid=jid,
430 account=account, name=nick, show=show)
431 ctrl = self.new_private_chat(gc_c, account, session)
433 ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
434 'code': obj.error_code, 'msg': obj.error_msg}, 'status')
435 return
437 gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
438 'code': obj.error_code, 'msg': obj.error_msg}, 'status')
439 if gc_control.parent_win and \
440 gc_control.parent_win.get_active_jid() == jid:
441 gc_control.set_subject(gc_control.subject)
442 return
444 if gajim.jid_is_transport(jid):
445 jid = jid.replace('@', '')
446 msg = obj.error_msg
447 if obj.msg:
448 msg = _('error while sending %(message)s ( %(error)s )') % {
449 'message': obj.msg, 'error': msg}
450 if session:
451 session.roster_message(jid, msg, obj.time_, msg_type='error')
453 def handle_event_msgsent(self, obj):
454 #('MSGSENT', account, (jid, msg, keyID))
455 # do not play sound when standalone chatstate message (eg no msg)
456 if obj.message and gajim.config.get_per('soundevents', 'message_sent',
457 'enabled'):
458 helpers.play_sound('message_sent')
460 def handle_event_msgnotsent(self, obj):
461 #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
462 msg = _('error while sending %(message)s ( %(error)s )') % {
463 'message': obj.message, 'error': obj.error}
464 if not obj.session:
465 # No session. This can happen when sending a message from
466 # gajim-remote
467 log.warn(msg)
468 return
469 obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name,
470 msg_type='error')
472 def handle_event_subscribe_presence(self, obj):
473 #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
474 account = obj.conn.name
475 if helpers.allow_popup_window(account) or not self.systray_enabled:
476 dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account,
477 obj.user_nick)
478 return
480 self.add_event(account, obj.jid, 'subscription_request', (obj.status,
481 obj.user_nick))
483 if helpers.allow_showing_notification(account):
484 path = gtkgui_helpers.get_icon_path('gajim-subscription_request',
486 event_type = _('Subscription request')
487 notify.popup(event_type, obj.jid, account, 'subscription_request',
488 path, event_type, obj.jid)
490 def handle_event_subscribed_presence(self, obj):
491 #('SUBSCRIBED', account, (jid, resource))
492 account = obj.conn.name
493 if obj.jid in gajim.contacts.get_jid_list(account):
494 c = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
495 c.resource = obj.resource
496 self.roster.remove_contact_from_groups(c.jid, account,
497 [_('Not in Roster'), _('Observers')], update=False)
498 else:
499 keyID = ''
500 attached_keys = gajim.config.get_per('accounts', account,
501 'attached_gpg_keys').split()
502 if obj.jid in attached_keys:
503 keyID = attached_keys[attached_keys.index(obj.jid) + 1]
504 name = obj.jid.split('@', 1)[0]
505 name = name.split('%', 1)[0]
506 contact1 = gajim.contacts.create_contact(jid=obj.jid,
507 account=account, name=name, groups=[], show='online',
508 status='online', ask='to', resource=obj.resource, keyID=keyID)
509 gajim.contacts.add_contact(account, contact1)
510 self.roster.add_contact(obj.jid, account)
511 dialogs.InformationDialog(_('Authorization accepted'),
512 _('The contact "%s" has authorized you to see his or her status.')
513 % obj.jid)
515 def show_unsubscribed_dialog(self, account, contact):
516 def on_yes(is_checked, list_):
517 self.roster.on_req_usub(None, list_)
518 list_ = [(contact, account)]
519 dialogs.YesNoDialog(
520 _('Contact "%s" removed subscription from you') % contact.jid,
521 _('You will always see him or her as offline.\nDo you want to '
522 'remove him or her from your contact list?'),
523 on_response_yes=(on_yes, list_))
524 # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
525 # not show deny
527 def handle_event_unsubscribed_presence(self, obj):
528 #('UNSUBSCRIBED', account, jid)
529 account = obj.conn.name
530 contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
531 if not contact:
532 return
534 if helpers.allow_popup_window(account) or not self.systray_enabled:
535 self.show_unsubscribed_dialog(account, contact)
536 return
538 self.add_event(account, obj.jid, 'unsubscribed', contact)
540 if helpers.allow_showing_notification(account):
541 path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
542 event_type = _('Unsubscribed')
543 notify.popup(event_type, obj.jid, account, 'unsubscribed', path,
544 event_type, obj.jid)
546 def handle_event_register_agent_info(self, obj):
547 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
548 # info in a dataform if is_form is True
549 if obj.is_form or 'instructions' in obj.config:
550 config.ServiceRegistrationWindow(obj.agent, obj.config,
551 obj.conn.name, obj.is_form)
552 else:
553 dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \
554 obj.agent, _('Check your connection or try again later.'))
556 def handle_event_vcard(self, obj):
557 # ('VCARD', account, data)
558 '''vcard holds the vcard data'''
559 our_jid = gajim.get_jid_from_account(obj.conn.name)
560 if obj.jid == our_jid:
561 if obj.nickname:
562 gajim.nicks[obj.conn.name] = obj.nickname
563 if obj.conn.name in self.show_vcard_when_connect:
564 self.show_vcard_when_connect.remove(obj.conn.name)
566 def handle_event_last_status_time(self, obj):
567 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
568 if obj.seconds < 0:
569 # Ann error occured
570 return
571 account = obj.conn.name
572 c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
573 if c: # c can be none if it's a gc contact
574 if obj.status:
575 c.status = obj.status
576 self.roster.draw_contact(c.jid, account) # draw offline status
577 last_time = time.localtime(time.time() - obj.seconds)
578 if c.show == 'offline':
579 c.last_status_time = last_time
580 else:
581 c.last_activity_time = last_time
582 if self.roster.tooltip.id and self.roster.tooltip.win:
583 self.roster.tooltip.update_last_time(last_time)
585 def handle_event_gc_config(self, obj):
586 #('GC_CONFIG', account, (jid, form_node)) config is a dict
587 account = obj.conn.name
588 if obj.jid in gajim.automatic_rooms[account]:
589 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
590 # We're converting chat to muc. allow participants to invite
591 for f in obj.dataform.iter_fields():
592 if f.var == 'muc#roomconfig_allowinvites':
593 f.value = True
594 elif f.var == 'muc#roomconfig_publicroom':
595 f.value = False
596 elif f.var == 'muc#roomconfig_membersonly':
597 f.value = True
598 elif f.var == 'public_list':
599 f.value = False
600 obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged())
601 else:
602 # use default configuration
603 obj.conn.send_gc_config(obj.jid, obj.form_node)
604 # invite contacts
605 # check if it is necessary to add <continue />
606 continue_tag = False
607 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
608 continue_tag = True
609 if 'invities' in gajim.automatic_rooms[account][obj.jid]:
610 for jid in gajim.automatic_rooms[account][obj.jid]['invities']:
611 obj.conn.send_invite(obj.jid, jid,
612 continue_tag=continue_tag)
613 del gajim.automatic_rooms[account][obj.jid]
614 elif obj.jid not in self.instances[account]['gc_config']:
615 self.instances[account]['gc_config'][obj.jid] = \
616 config.GroupchatConfigWindow(account, obj.jid, obj.dataform)
618 def handle_event_gc_affiliation(self, obj):
619 #('GC_AFFILIATION', account, (room_jid, users_dict))
620 account = obj.conn.name
621 if obj.jid in self.instances[account]['gc_config']:
622 self.instances[account]['gc_config'][obj.jid].\
623 affiliation_list_received(obj.users_dict)
625 def handle_event_gc_invitation(self, obj):
626 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
627 jid = gajim.get_jid_without_resource(obj.jid_from)
628 account = obj.conn.name
629 if helpers.allow_popup_window(account) or not self.systray_enabled:
630 dialogs.InvitationReceivedDialog(account, obj.room_jid, jid,
631 obj.password, obj.reason, is_continued=obj.is_continued)
632 return
634 self.add_event(account, jid, 'gc-invitation', (obj.room_jid,
635 obj.reason, obj.password, obj.is_continued))
637 if helpers.allow_showing_notification(account):
638 path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
639 event_type = _('Groupchat Invitation')
640 notify.popup(event_type, jid, account, 'gc-invitation', path,
641 event_type, obj.room_jid)
643 def forget_gpg_passphrase(self, keyid):
644 if keyid in self.gpg_passphrase:
645 del self.gpg_passphrase[keyid]
646 return False
648 def handle_event_bad_gpg_passphrase(self, obj):
649 #('BAD_PASSPHRASE', account, ())
650 if obj.use_gpg_agent:
651 sectext = _('You configured Gajim to use GPG agent, but there is no'
652 ' GPG agent running or it returned a wrong passphrase.\n')
653 sectext += _('You are currently connected without your OpenPGP '
654 'key.')
655 dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
656 else:
657 path = gtkgui_helpers.get_icon_path('gajim-warning', 48)
658 account = obj.conn.name
659 notify.popup('warning', account, account, 'warning', path,
660 _('OpenGPG Passphrase Incorrect'),
661 _('You are currently connected without your OpenPGP key.'))
662 self.forget_gpg_passphrase(obj.keyID)
664 def handle_event_gpg_password_required(self, obj):
665 #('GPG_PASSWORD_REQUIRED', account, (callback,))
666 if obj.keyid in self.gpg_passphrase:
667 request = self.gpg_passphrase[obj.keyid]
668 else:
669 request = PassphraseRequest(obj.keyid)
670 self.gpg_passphrase[obj.keyid] = request
671 request.add_callback(obj.conn.name, obj.callback)
673 def handle_event_gpg_trust_key(self, obj):
674 #('GPG_ALWAYS_TRUST', account, callback)
675 def on_yes(checked):
676 if checked:
677 obj.conn.gpg.always_trust = True
678 obj.callback(True)
680 def on_no():
681 obj.callback(False)
683 dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
684 'encrypt this chat is not trusted. Do you really want to encrypt '
685 'this message?'), checktext=_('_Do not ask me again'),
686 on_response_yes=on_yes, on_response_no=on_no)
688 def handle_event_password_required(self, obj):
689 #('PASSWORD_REQUIRED', account, None)
690 account = obj.conn.name
691 if account in self.pass_dialog:
692 return
693 text = _('Enter your password for account %s') % account
694 if passwords.USER_HAS_GNOMEKEYRING and \
695 not passwords.USER_USES_GNOMEKEYRING:
696 text += '\n' + _('Gnome Keyring is installed but not \
697 correctly started (environment variable probably not \
698 correctly set)')
700 def on_ok(passphrase, save):
701 if save:
702 gajim.config.set_per('accounts', account, 'savepass', True)
703 passwords.save_password(account, passphrase)
704 obj.conn.set_password(passphrase)
705 del self.pass_dialog[account]
707 def on_cancel():
708 self.roster.set_state(account, 'offline')
709 self.roster.update_status_combobox()
710 del self.pass_dialog[account]
712 self.pass_dialog[account] = dialogs.PassphraseDialog(
713 _('Password Required'), text, _('Save password'), ok_handler=on_ok,
714 cancel_handler=on_cancel)
716 def handle_event_roster_info(self, obj):
717 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
718 account = obj.conn.name
719 contacts = gajim.contacts.get_contacts(account, obj.jid)
720 if (not obj.sub or obj.sub == 'none') and \
721 (not obj.ask or obj.ask == 'none') and not obj.nickname and \
722 not obj.groups:
723 # contact removed us.
724 if contacts:
725 self.roster.remove_contact(obj.jid, account, backend=True)
726 return
727 elif not contacts:
728 if obj.sub == 'remove':
729 return
730 # Add new contact to roster
731 contact = gajim.contacts.create_contact(jid=obj.jid,
732 account=account, name=obj.nickname, groups=obj.groups,
733 show='offline', sub=obj.sub, ask=obj.ask)
734 gajim.contacts.add_contact(account, contact)
735 self.roster.add_contact(obj.jid, account)
736 else:
737 # If contact has changed (sub, ask or group) update roster
738 # Mind about observer status changes:
739 # According to xep 0162, a contact is not an observer anymore when
740 # we asked for auth, so also remove him if ask changed
741 old_groups = contacts[0].groups
742 if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\
743 or old_groups != obj.groups:
744 # c.get_shown_groups() has changed. Reflect that in
745 # roster_winodow
746 self.roster.remove_contact(obj.jid, account, force=True)
747 for contact in contacts:
748 contact.name = obj.nickname or ''
749 contact.sub = obj.sub
750 contact.ask = obj.ask
751 contact.groups = obj.groups or []
752 self.roster.add_contact(obj.jid, account)
753 # Refilter and update old groups
754 for group in old_groups:
755 self.roster.draw_group(group, account)
756 self.roster.draw_contact(obj.jid, account)
758 def handle_event_bookmarks(self, obj):
759 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
760 # We received a bookmark item from the server (JEP48)
761 # Auto join GC windows if neccessary
763 self.roster.set_actions_menu_needs_rebuild()
764 invisible_show = gajim.SHOW_LIST.index('invisible')
765 # do not autojoin if we are invisible
766 if obj.conn.connected == invisible_show:
767 return
769 self.auto_join_bookmarks(obj.conn.name)
771 def handle_event_file_send_error(self, account, array):
772 jid = array[0]
773 file_props = array[1]
774 ft = self.instances['file_transfers']
775 ft.set_status(file_props['type'], file_props['sid'], 'stop')
777 if helpers.allow_popup_window(account):
778 ft.show_send_error(file_props)
779 return
781 self.add_event(account, jid, 'file-send-error', file_props)
783 if helpers.allow_showing_notification(account):
784 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
785 event_type = _('File Transfer Error')
786 notify.popup(event_type, jid, account, 'file-send-error', path,
787 event_type, file_props['name'])
789 def handle_event_gmail_notify(self, obj):
790 jid = obj.jid
791 gmail_new_messages = int(obj.newmsgs)
792 gmail_messages_list = obj.gmail_messages_list
793 if not gajim.config.get('notify_on_new_gmail_email'):
794 return
795 path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
796 title = _('New mail on %(gmail_mail_address)s') % \
797 {'gmail_mail_address': jid}
798 text = i18n.ngettext('You have %d new mail conversation',
799 'You have %d new mail conversations', gmail_new_messages,
800 gmail_new_messages, gmail_new_messages)
802 if gajim.config.get('notify_on_new_gmail_email_extra'):
803 cnt = 0
804 for gmessage in gmail_messages_list:
805 # FIXME: emulate Gtalk client popups. find out what they
806 # parse and how they decide what to show each message has a
807 # 'From', 'Subject' and 'Snippet' field
808 if cnt >= 5:
809 break
810 senders = ',\n '.join(reversed(gmessage['From']))
811 text += _('\n\nFrom: %(from_address)s\nSubject: '
812 '%(subject)s\n%(snippet)s') % {'from_address': senders,
813 'subject': gmessage['Subject'],
814 'snippet': gmessage['Snippet']}
815 cnt += 1
817 command = gajim.config.get('notify_on_new_gmail_email_command')
818 if command:
819 Popen(command, shell=True)
821 if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
822 helpers.play_sound('gmail_received')
823 notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail',
824 path_to_image=path, title=title, text=text)
826 def handle_event_file_request_error(self, obj):
827 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
828 ft = self.instances['file_transfers']
829 ft.set_status(obj.file_props['type'], obj.file_props['sid'], 'stop')
830 errno = obj.file_props['error']
832 if helpers.allow_popup_window(obj.conn.name):
833 if errno in (-4, -5):
834 ft.show_stopped(obj.jid, obj.file_props, obj.error_msg)
835 else:
836 ft.show_request_error(obj.file_props)
837 return
839 if errno in (-4, -5):
840 msg_type = 'file-error'
841 else:
842 msg_type = 'file-request-error'
844 self.add_event(obj.conn.name, obj.jid, msg_type, obj.file_props)
846 if helpers.allow_showing_notification(obj.conn.name):
847 # check if we should be notified
848 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
849 event_type = _('File Transfer Error')
850 notify.popup(event_type, obj.jid, obj.conn.name, msg_type, path,
851 title=event_type, text=obj.file_props['name'])
853 def handle_event_file_request(self, obj):
854 account = obj.conn.name
855 if obj.jid not in gajim.contacts.get_jid_list(account):
856 keyID = ''
857 attached_keys = gajim.config.get_per('accounts', account,
858 'attached_gpg_keys').split()
859 if obj.jid in attached_keys:
860 keyID = attached_keys[attached_keys.index(obj.jid) + 1]
861 contact = gajim.contacts.create_not_in_roster_contact(jid=obj.jid,
862 account=account, keyID=keyID)
863 gajim.contacts.add_contact(account, contact)
864 self.roster.add_contact(obj.jid, account)
865 contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
867 if helpers.allow_popup_window(account):
868 self.instances['file_transfers'].show_file_request(account, contact,
869 obj.file_props)
870 return
872 self.add_event(account, obj.jid, 'file-request', obj.file_props)
874 if helpers.allow_showing_notification(account):
875 path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
876 txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
877 account, obj.jid)
878 event_type = _('File Transfer Request')
879 notify.popup(event_type, obj.jid, account, 'file-request',
880 path_to_image=path, title=event_type, text=txt)
882 def handle_event_file_error(self, title, message):
883 dialogs.ErrorDialog(title, message)
885 def handle_event_file_progress(self, account, file_props):
886 if time.time() - self.last_ftwindow_update > 0.5:
887 # update ft window every 500ms
888 self.last_ftwindow_update = time.time()
889 self.instances['file_transfers'].set_progress(file_props['type'],
890 file_props['sid'], file_props['received-len'])
892 def handle_event_file_rcv_completed(self, account, file_props):
893 ft = self.instances['file_transfers']
894 if file_props['error'] == 0:
895 ft.set_progress(file_props['type'], file_props['sid'],
896 file_props['received-len'])
897 else:
898 ft.set_status(file_props['type'], file_props['sid'], 'stop')
899 if 'stalled' in file_props and file_props['stalled'] or \
900 'paused' in file_props and file_props['paused']:
901 return
902 if file_props['type'] == 'r': # we receive a file
903 jid = unicode(file_props['sender'])
904 else: # we send a file
905 jid = unicode(file_props['receiver'])
907 if helpers.allow_popup_window(account):
908 if file_props['error'] == 0:
909 if gajim.config.get('notify_on_file_complete'):
910 ft.show_completed(jid, file_props)
911 elif file_props['error'] == -1:
912 ft.show_stopped(jid, file_props,
913 error_msg=_('Remote contact stopped transfer'))
914 elif file_props['error'] == -6:
915 ft.show_stopped(jid, file_props,
916 error_msg=_('Error opening file'))
917 return
919 msg_type = ''
920 event_type = ''
921 if file_props['error'] == 0 and gajim.config.get(
922 'notify_on_file_complete'):
923 msg_type = 'file-completed'
924 event_type = _('File Transfer Completed')
925 elif file_props['error'] in (-1, -6):
926 msg_type = 'file-stopped'
927 event_type = _('File Transfer Stopped')
929 if event_type == '':
930 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
931 # this should never happen but it does. see process_result() in
932 # socks5.py
933 # who calls this func (sth is really wrong unless this func is also
934 # registered as progress_cb
935 return
937 if msg_type:
938 self.add_event(account, jid, msg_type, file_props)
940 if file_props is not None:
941 if file_props['type'] == 'r':
942 # get the name of the sender, as it is in the roster
943 sender = unicode(file_props['sender']).split('/')[0]
944 name = gajim.contacts.get_first_contact_from_jid(account,
945 sender).get_shown_name()
946 filename = os.path.basename(file_props['file-name'])
947 if event_type == _('File Transfer Completed'):
948 txt = _('You successfully received %(filename)s from '
949 '%(name)s.') % {'filename': filename, 'name': name}
950 img_name = 'gajim-ft_done'
951 else: # ft stopped
952 txt = _('File transfer of %(filename)s from %(name)s '
953 'stopped.') % {'filename': filename, 'name': name}
954 img_name = 'gajim-ft_stopped'
955 else:
956 receiver = file_props['receiver']
957 if hasattr(receiver, 'jid'):
958 receiver = receiver.jid
959 receiver = receiver.split('/')[0]
960 # get the name of the contact, as it is in the roster
961 name = gajim.contacts.get_first_contact_from_jid(account,
962 receiver).get_shown_name()
963 filename = os.path.basename(file_props['file-name'])
964 if event_type == _('File Transfer Completed'):
965 txt = _('You successfully sent %(filename)s to %(name)s.')\
966 % {'filename': filename, 'name': name}
967 img_name = 'gajim-ft_done'
968 else: # ft stopped
969 txt = _('File transfer of %(filename)s to %(name)s '
970 'stopped.') % {'filename': filename, 'name': name}
971 img_name = 'gajim-ft_stopped'
972 path = gtkgui_helpers.get_icon_path(img_name, 48)
973 else:
974 txt = ''
975 path = ''
977 if gajim.config.get('notify_on_file_complete') and \
978 (gajim.config.get('autopopupaway') or \
979 gajim.connections[account].connected in (2, 3)):
980 # we want to be notified and we are online/chat or we don't mind
981 # bugged when away/na/busy
982 notify.popup(event_type, jid, account, msg_type, path_to_image=path,
983 title=event_type, text=txt)
985 def ask_offline_status(self, account):
986 for contact in gajim.contacts.iter_contacts(account):
987 gajim.connections[account].request_last_status_time(contact.jid,
988 contact.resource)
990 def handle_event_signed_in(self, obj):
992 SIGNED_IN event is emitted when we sign in, so handle it
994 # ('SIGNED_IN', account, ())
995 # block signed in notifications for 30 seconds
996 account = obj.conn.name
997 gajim.block_signed_in_notifications[account] = True
998 state = self.sleeper.getState()
999 connected = obj.conn.connected
1000 if gajim.config.get('ask_offline_status_on_connection'):
1001 # Ask offline status in 1 minute so w'are sure we got all online
1002 # presences
1003 gobject.timeout_add_seconds(60, self.ask_offline_status, account)
1004 if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
1005 # we go online or free for chat, so we activate auto status
1006 gajim.sleeper_state[account] = 'online'
1007 elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
1008 (state == common.sleepy.STATE_XA and connected == 5)):
1009 # If we are autoaway/xa and come back after a disconnection, do
1010 # nothing
1011 # Else disable autoaway
1012 gajim.sleeper_state[account] = 'off'
1014 if obj.conn.archiving_supported:
1015 # Start merging logs from server
1016 obj.conn.request_modifications_page(gajim.config.get_per('accounts',
1017 account, 'last_archiving_time'))
1018 gajim.config.set_per('accounts', account, 'last_archiving_time',
1019 time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
1021 invisible_show = gajim.SHOW_LIST.index('invisible')
1022 # We cannot join rooms if we are invisible
1023 if connected == invisible_show:
1024 return
1025 # send currently played music
1026 if obj.conn.pep_supported and dbus_support.supported and \
1027 gajim.config.get_per('accounts', account, 'publish_tune'):
1028 self.enable_music_listener()
1029 # enable location listener
1030 if obj.conn.pep_supported and dbus_support.supported and \
1031 gajim.config.get_per('accounts', account, 'publish_location'):
1032 location_listener.enable()
1035 def handle_event_metacontacts(self, obj):
1036 gajim.contacts.define_metacontacts(obj.conn.name, obj.meta_list)
1038 def handle_atom_entry(self, obj):
1039 AtomWindow.newAtomEntry(obj.atom_entry)
1041 def handle_event_failed_decrypt(self, obj):
1042 details = _('Unable to decrypt message from %s\nIt may have been '
1043 'tampered with.') % obj.fjid
1044 dialogs.WarningDialog(_('Unable to decrypt message'), details)
1046 def handle_event_zc_name_conflict(self, obj):
1047 def on_ok(new_name):
1048 gajim.config.set_per('accounts', obj.conn.name, 'name', new_name)
1049 show = obj.conn.old_show
1050 status = obj.conn.status
1051 obj.conn.username = new_name
1052 obj.conn.change_status(show, status)
1053 def on_cancel():
1054 obj.conn.change_status('offline', '')
1056 dlg = dialogs.InputDialog(_('Username Conflict'),
1057 _('Please type a new username for your local account'),
1058 input_str=obj.alt_name, is_modal=True, ok_handler=on_ok,
1059 cancel_handler=on_cancel)
1061 def handle_event_resource_conflict(self, obj):
1062 # ('RESOURCE_CONFLICT', account, ())
1063 # First we go offline, but we don't overwrite status message
1064 account = obj.conn.name
1065 conn = obj.conn
1066 self.roster.send_status(account, 'offline', conn.status)
1067 def on_ok(new_resource):
1068 gajim.config.set_per('accounts', account, 'resource', new_resource)
1069 self.roster.send_status(account, conn.old_show, conn.status)
1070 proposed_resource = conn.server_resource
1071 proposed_resource += gajim.config.get('gc_proposed_nick_char')
1072 dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
1073 _('You are already connected to this account with the same '
1074 'resource. Please type a new one'), resource=proposed_resource,
1075 ok_handler=on_ok)
1077 def handle_event_jingle_incoming(self, obj):
1078 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
1079 # data...))
1080 # TODO: conditional blocking if peer is not in roster
1082 account = obj.conn.name
1083 content_types = set(c[0] for c in obj.contents)
1085 # check type of jingle session
1086 if 'audio' in content_types or 'video' in content_types:
1087 # a voip session...
1088 # we now handle only voip, so the only thing we will do here is
1089 # not to return from function
1090 pass
1091 else:
1092 # unknown session type... it should be declined in common/jingle.py
1093 return
1095 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1096 or self.msg_win_mgr.get_control(obj.jid, account))
1097 if ctrl:
1098 if 'audio' in content_types:
1099 ctrl.set_audio_state('connection_received', obj.sid)
1100 if 'video' in content_types:
1101 ctrl.set_video_state('connection_received', obj.sid)
1103 dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1104 if dlg:
1105 dlg.add_contents(content_types)
1106 return
1108 if helpers.allow_popup_window(account):
1109 dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid,
1110 content_types)
1111 return
1113 self.add_event(account, obj.jid, 'jingle-incoming', (obj.fjid, obj.sid,
1114 content_types))
1116 if helpers.allow_showing_notification(account):
1117 # TODO: we should use another pixmap ;-)
1118 txt = _('%s wants to start a voice chat.') % \
1119 gajim.get_name_from_jid(account, obj.fjid)
1120 path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
1121 event_type = _('Voice Chat Request')
1122 notify.popup(event_type, obj.fjid, account, 'jingle-incoming',
1123 path_to_image=path, title=event_type, text=txt)
1125 def handle_event_jingle_connected(self, obj):
1126 # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
1127 if obj.media in ('audio', 'video'):
1128 account = obj.conn.name
1129 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1130 or self.msg_win_mgr.get_control(obj.jid, account))
1131 if ctrl:
1132 if obj.media == 'audio':
1133 ctrl.set_audio_state('connected', obj.sid)
1134 else:
1135 ctrl.set_video_state('connected', obj.sid)
1137 def handle_event_jingle_disconnected(self, obj):
1138 # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
1139 account = obj.conn.name
1140 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1141 or self.msg_win_mgr.get_control(obj.jid, account))
1142 if ctrl:
1143 if obj.media is None:
1144 ctrl.stop_jingle(sid=obj.sid, reason=obj.reason)
1145 elif obj.media == 'audio':
1146 ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason)
1147 elif obj.media == 'video':
1148 ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason)
1149 dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1150 if dialog:
1151 if obj.media is None:
1152 dialog.dialog.destroy()
1153 else:
1154 dialog.remove_contents((obj.media, ))
1156 def handle_event_jingle_error(self, obj):
1157 # ('JINGLE_ERROR', account, (peerjid, sid, reason))
1158 account = obj.conn.name
1159 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1160 or self.msg_win_mgr.get_control(obj.jid, account))
1161 if ctrl:
1162 ctrl.set_audio_state('error', reason=obj.reason)
1164 def handle_event_roster_item_exchange(self, obj):
1165 # data = (action in [add, delete, modify], exchange_list, jid_from)
1166 dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action,
1167 obj.exchange_items_list, obj.fjid)
1169 def handle_event_ssl_error(self, obj):
1170 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
1171 account = obj.conn.name
1172 server = gajim.config.get_per('accounts', account, 'hostname')
1174 def on_ok(is_checked):
1175 del self.instances[account]['online_dialog']['ssl_error']
1176 if is_checked[0]:
1177 # Check if cert is already in file
1178 certs = ''
1179 if os.path.isfile(gajim.MY_CACERTS):
1180 f = open(gajim.MY_CACERTS)
1181 certs = f.read()
1182 f.close()
1183 if obj.cert in certs:
1184 dialogs.ErrorDialog(_('Certificate Already in File'),
1185 _('This certificate is already in file %s, so it\'s '
1186 'not added again.') % gajim.MY_CACERTS)
1187 else:
1188 f = open(gajim.MY_CACERTS, 'a')
1189 f.write(server + '\n')
1190 f.write(obj.cert + '\n\n')
1191 f.close()
1192 gajim.config.set_per('accounts', account,
1193 'ssl_fingerprint_sha1', obj.fingerprint)
1194 if is_checked[1]:
1195 ignore_ssl_errors = gajim.config.get_per('accounts', account,
1196 'ignore_ssl_errors').split()
1197 ignore_ssl_errors.append(str(obj.error_num))
1198 gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
1199 ' '.join(ignore_ssl_errors))
1200 obj.conn.ssl_certificate_accepted()
1202 def on_cancel():
1203 del self.instances[account]['online_dialog']['ssl_error']
1204 obj.conn.disconnect(on_purpose=True)
1205 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1206 show='offline'))
1208 pritext = _('Error verifying SSL certificate')
1209 sectext = _('There was an error verifying the SSL certificate of your '
1210 'jabber server: %(error)s\nDo you still want to connect to this '
1211 'server?') % {'error': obj.error_text}
1212 if obj.error_num in (18, 27):
1213 checktext1 = _('Add this certificate to the list of trusted '
1214 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % \
1215 obj.fingerprint
1216 else:
1217 checktext1 = ''
1218 checktext2 = _('Ignore this error for this certificate.')
1219 if 'ssl_error' in self.instances[account]['online_dialog']:
1220 self.instances[account]['online_dialog']['ssl_error'].destroy()
1221 self.instances[account]['online_dialog']['ssl_error'] = \
1222 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1223 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
1225 def handle_event_fingerprint_error(self, obj):
1226 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
1227 account = obj.conn.name
1228 def on_yes(is_checked):
1229 del self.instances[account]['online_dialog']['fingerprint_error']
1230 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
1231 obj.new_fingerprint)
1232 # Reset the ignored ssl errors
1233 gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
1234 obj.conn.ssl_certificate_accepted()
1236 def on_no():
1237 del self.instances[account]['online_dialog']['fingerprint_error']
1238 obj.conn.disconnect(on_purpose=True)
1239 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1240 show='offline'))
1242 pritext = _('SSL certificate error')
1243 sectext = _('It seems the SSL certificate of account %(account)s has '
1244 'changed or your connection is being hacked.\nOld fingerprint: '
1245 '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect '
1246 'and update the fingerprint of the certificate?') % \
1247 {'account': account, 'old': gajim.config.get_per('accounts',
1248 account, 'ssl_fingerprint_sha1'), 'new': obj.new_fingerprint}
1249 if 'fingerprint_error' in self.instances[account]['online_dialog']:
1250 self.instances[account]['online_dialog']['fingerprint_error'].\
1251 destroy()
1252 self.instances[account]['online_dialog']['fingerprint_error'] = \
1253 dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
1254 on_response_no=on_no)
1256 def handle_event_plain_connection(self, obj):
1257 # ('PLAIN_CONNECTION', account, (connection))
1258 def on_ok(is_checked):
1259 if not is_checked[0]:
1260 on_cancel()
1261 return
1262 # On cancel call del self.instances, so don't call it another time
1263 # before
1264 del self.instances[obj.conn.name]['online_dialog']\
1265 ['plain_connection']
1266 if is_checked[1]:
1267 gajim.config.set_per('accounts', obj.conn.name,
1268 'warn_when_plaintext_connection', False)
1269 obj.conn.connection_accepted(obj.xmpp_client, 'plain')
1271 def on_cancel():
1272 del self.instances[obj.conn.name]['online_dialog']\
1273 ['plain_connection']
1274 obj.conn.disconnect(on_purpose=True)
1275 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1276 show='offline'))
1278 pritext = _('Insecure connection')
1279 sectext = _('You are about to connect to the server with an insecure '
1280 'connection. This means all your conversations will be '
1281 'exchanged unencrypted. Are you sure you want to do that?')
1282 checktext1 = _('Yes, I really want to connect insecurely')
1283 checktext2 = _('_Do not ask me again')
1284 if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']:
1285 self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
1286 destroy()
1287 self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
1288 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1289 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1290 is_modal=False)
1292 def handle_event_insecure_ssl_connection(self, obj):
1293 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1294 def on_ok(is_checked):
1295 if not is_checked[0]:
1296 on_cancel()
1297 return
1298 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1299 if is_checked[1]:
1300 gajim.config.set_per('accounts', obj.conn.name,
1301 'warn_when_insecure_ssl_connection', False)
1302 if obj.conn.connected == 0:
1303 # We have been disconnecting (too long time since window is
1304 # opened)
1305 # re-connect with auto-accept
1306 obj.conn.connection_auto_accepted = True
1307 show, msg = obj.conn.continue_connect_info[:2]
1308 self.roster.send_status(obj.conn.name, show, msg)
1309 return
1310 obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type)
1312 def on_cancel():
1313 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1314 obj.conn.disconnect(on_purpose=True)
1315 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1316 show='offline'))
1318 pritext = _('Insecure connection')
1319 sectext = _('You are about to send your password on an insecure '
1320 'connection. You should install PyOpenSSL to prevent that. Are you '
1321 'sure you want to do that?')
1322 checktext1 = _('Yes, I really want to connect insecurely')
1323 checktext2 = _('_Do not ask me again')
1324 if 'insecure_ssl' in self.instances[obj.conn.name]['online_dialog']:
1325 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\
1326 destroy()
1327 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \
1328 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1329 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1330 is_modal=False)
1332 def handle_event_insecure_password(self, obj):
1333 # ('INSECURE_PASSWORD', account, ())
1334 def on_ok(is_checked):
1335 if not is_checked[0]:
1336 on_cancel()
1337 return
1338 del self.instances[obj.conn.name]['online_dialog']\
1339 ['insecure_password']
1340 if is_checked[1]:
1341 gajim.config.set_per('accounts', obj.conn.name,
1342 'warn_when_insecure_password', False)
1343 if obj.conn.connected == 0:
1344 # We have been disconnecting (too long time since window is
1345 # opened)
1346 # re-connect with auto-accept
1347 obj.conn.connection_auto_accepted = True
1348 show, msg = obj.conn.continue_connect_info[:2]
1349 self.roster.send_status(obj.conn.name, show, msg)
1350 return
1351 obj.conn.accept_insecure_password()
1353 def on_cancel():
1354 del self.instances[obj.conn.name]['online_dialog']\
1355 ['insecure_password']
1356 obj.conn.disconnect(on_purpose=True)
1357 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1358 show='offline'))
1360 pritext = _('Insecure connection')
1361 sectext = _('You are about to send your password unencrypted on an '
1362 'insecure connection. Are you sure you want to do that?')
1363 checktext1 = _('Yes, I really want to connect insecurely')
1364 checktext2 = _('_Do not ask me again')
1365 if 'insecure_password' in self.instances[obj.conn.name]\
1366 ['online_dialog']:
1367 self.instances[obj.conn.name]['online_dialog']\
1368 ['insecure_password'].destroy()
1369 self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
1370 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1371 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1372 is_modal=False)
1374 def create_core_handlers_list(self):
1375 self.handlers = {
1376 'ERROR': [self.handle_event_error],
1377 'DB_ERROR': [self.handle_event_db_error],
1378 'INFORMATION': [self.handle_event_information],
1379 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
1380 'atom-entry-received': [self.handle_atom_entry],
1381 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
1382 'bookmarks-received': [self.handle_event_bookmarks],
1383 'connection-lost': [self.handle_event_connection_lost],
1384 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
1385 'file-request-error': [self.handle_event_file_request_error],
1386 'file-request-received': [self.handle_event_file_request],
1387 'fingerprint-error': [self.handle_event_fingerprint_error],
1388 'gc-invitation-received': [self.handle_event_gc_invitation],
1389 'gc-presence-received': [self.handle_event_gc_presence],
1390 'gmail-notify': [self.handle_event_gmail_notify],
1391 'gpg-password-required': [self.handle_event_gpg_password_required],
1392 'gpg-trust-key': [self.handle_event_gpg_trust_key],
1393 'http-auth-received': [self.handle_event_http_auth],
1394 'insecure-password': [self.handle_event_insecure_password],
1395 'insecure-ssl-connection': \
1396 [self.handle_event_insecure_ssl_connection],
1397 'iq-error-received': [self.handle_event_iq_error],
1398 'jingle-connected-received': [self.handle_event_jingle_connected],
1399 'jingle-disconnected-received': [
1400 self.handle_event_jingle_disconnected],
1401 'jingle-error-received': [self.handle_event_jingle_error],
1402 'jingle-request-received': [self.handle_event_jingle_incoming],
1403 'last-result-received': [self.handle_event_last_status_time],
1404 'message-error': [self.handle_event_msgerror],
1405 'message-not-sent': [self.handle_event_msgnotsent],
1406 'message-sent': [self.handle_event_msgsent],
1407 'metacontacts-received': [self.handle_event_metacontacts],
1408 'muc-admin-received': [self.handle_event_gc_affiliation],
1409 'muc-owner-received': [self.handle_event_gc_config],
1410 'our-show': [self.handle_event_status],
1411 'password-required': [self.handle_event_password_required],
1412 'plain-connection': [self.handle_event_plain_connection],
1413 'presence-received': [self.handle_event_presence],
1414 'register-agent-info-received': [self.handle_event_register_agent_info],
1415 'roster-info': [self.handle_event_roster_info],
1416 'roster-item-exchange-received': \
1417 [self.handle_event_roster_item_exchange],
1418 'signed-in': [self.handle_event_signed_in],
1419 'ssl-error': [self.handle_event_ssl_error],
1420 'stream-conflict-received': [self.handle_event_resource_conflict],
1421 'subscribe-presence-received': [
1422 self.handle_event_subscribe_presence],
1423 'subscribed-presence-received': [
1424 self.handle_event_subscribed_presence],
1425 'unsubscribed-presence-received': [
1426 self.handle_event_unsubscribed_presence],
1427 'vcard-received': [self.handle_event_vcard],
1428 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
1431 def register_core_handlers(self):
1433 Register core handlers in Global Events Dispatcher (GED).
1435 This is part of rewriting whole events handling system to use GED.
1437 for event_name, event_handlers in self.handlers.iteritems():
1438 for event_handler in event_handlers:
1439 prio = ged.GUI1
1440 if type(event_handler) == tuple:
1441 prio = event_handler[1]
1442 event_handler = event_handler[0]
1443 gajim.ged.register_event_handler(event_name, prio,
1444 event_handler)
1446 ################################################################################
1447 ### Methods dealing with gajim.events
1448 ################################################################################
1450 def add_event(self, account, jid, type_, event_args):
1452 Add an event to the gajim.events var
1454 # We add it to the gajim.events queue
1455 # Do we have a queue?
1456 jid = gajim.get_jid_without_resource(jid)
1457 no_queue = len(gajim.events.get_events(account, jid)) == 0
1458 # type_ can be gc-invitation file-send-error file-error
1459 # file-request-error file-request file-completed file-stopped
1460 # jingle-incoming
1461 # event_type can be in advancedNotificationWindow.events_list
1462 event_types = {'file-request': 'ft_request',
1463 'file-completed': 'ft_finished'}
1464 event_type = event_types.get(type_)
1465 show_in_roster = notify.get_show_in_roster(event_type, account, jid)
1466 show_in_systray = notify.get_show_in_systray(event_type, account, jid)
1467 event = gajim.events.create_event(type_, event_args,
1468 show_in_roster=show_in_roster,
1469 show_in_systray=show_in_systray)
1470 gajim.events.add_event(account, jid, event)
1472 self.roster.show_title()
1473 if no_queue: # We didn't have a queue: we change icons
1474 if not gajim.contacts.get_contact_with_highest_priority(account,
1475 jid):
1476 if type_ == 'gc-invitation':
1477 self.roster.add_groupchat(jid, account, status='offline')
1478 else:
1479 # add contact to roster ("Not In The Roster") if he is not
1480 self.roster.add_to_not_in_the_roster(account, jid)
1481 else:
1482 self.roster.draw_contact(jid, account)
1484 # Select the big brother contact in roster, it's visible because it has
1485 # events.
1486 family = gajim.contacts.get_metacontacts_family(account, jid)
1487 if family:
1488 nearby_family, bb_jid, bb_account = \
1489 gajim.contacts.get_nearby_family_and_big_brother(family,
1490 account)
1491 else:
1492 bb_jid, bb_account = jid, account
1493 self.roster.select_contact(bb_jid, bb_account)
1495 def handle_event(self, account, fjid, type_):
1496 w = None
1497 ctrl = None
1498 session = None
1500 resource = gajim.get_resource_from_jid(fjid)
1501 jid = gajim.get_jid_without_resource(fjid)
1503 if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
1504 w = self.msg_win_mgr.get_window(jid, account)
1505 if jid in self.minimized_controls[account]:
1506 self.roster.on_groupchat_maximized(None, jid, account)
1507 return
1508 else:
1509 ctrl = self.msg_win_mgr.get_gc_control(jid, account)
1511 elif type_ in ('printed_chat', 'chat', ''):
1512 # '' is for log in/out notifications
1514 if type_ != '':
1515 event = gajim.events.get_first_event(account, fjid, type_)
1516 if not event:
1517 event = gajim.events.get_first_event(account, jid, type_)
1518 if not event:
1519 return
1521 if type_ == 'printed_chat':
1522 ctrl = event.parameters[0]
1523 elif type_ == 'chat':
1524 session = event.parameters[8]
1525 ctrl = session.control
1526 elif type_ == '':
1527 ctrl = self.msg_win_mgr.get_control(fjid, account)
1529 if not ctrl:
1530 highest_contact = gajim.contacts.\
1531 get_contact_with_highest_priority(account, jid)
1532 # jid can have a window if this resource was lower when he sent
1533 # message and is now higher because the other one is offline
1534 if resource and highest_contact.resource == resource and \
1535 not self.msg_win_mgr.has_window(jid, account):
1536 # remove resource of events too
1537 gajim.events.change_jid(account, fjid, jid)
1538 resource = None
1539 fjid = jid
1540 contact = None
1541 if resource:
1542 contact = gajim.contacts.get_contact(account, jid, resource)
1543 if not contact:
1544 contact = highest_contact
1546 ctrl = self.new_chat(contact, account, resource=resource,
1547 session=session)
1549 gajim.last_message_time[account][jid] = 0 # long time ago
1551 w = ctrl.parent_win
1552 elif type_ in ('printed_pm', 'pm'):
1553 # assume that the most recently updated control we have for this
1554 # party is the one that this event was in
1555 event = gajim.events.get_first_event(account, fjid, type_)
1556 if not event:
1557 event = gajim.events.get_first_event(account, jid, type_)
1559 if type_ == 'printed_pm':
1560 ctrl = event.parameters[0]
1561 elif type_ == 'pm':
1562 session = event.parameters[8]
1564 if session and session.control:
1565 ctrl = session.control
1566 elif not ctrl:
1567 room_jid = jid
1568 nick = resource
1569 gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
1570 nick)
1571 if gc_contact:
1572 show = gc_contact.show
1573 else:
1574 show = 'offline'
1575 gc_contact = gajim.contacts.create_gc_contact(
1576 room_jid=room_jid, account=account, name=nick,
1577 show=show)
1579 if not session:
1580 session = gajim.connections[account].make_new_session(
1581 fjid, None, type_='pm')
1583 self.new_private_chat(gc_contact, account, session=session)
1584 ctrl = session.control
1586 w = ctrl.parent_win
1587 elif type_ in ('normal', 'file-request', 'file-request-error',
1588 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
1589 'jingle-incoming'):
1590 # Get the first single message event
1591 event = gajim.events.get_first_event(account, fjid, type_)
1592 if not event:
1593 # default to jid without resource
1594 event = gajim.events.get_first_event(account, jid, type_)
1595 if not event:
1596 return
1597 # Open the window
1598 self.roster.open_event(account, jid, event)
1599 else:
1600 # Open the window
1601 self.roster.open_event(account, fjid, event)
1602 elif type_ == 'gmail':
1603 url = gajim.connections[account].gmail_url
1604 if url:
1605 helpers.launch_browser_mailer('url', url)
1606 elif type_ == 'gc-invitation':
1607 event = gajim.events.get_first_event(account, jid, type_)
1608 data = event.parameters
1609 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1610 data[1], data[3])
1611 gajim.events.remove_events(account, jid, event)
1612 self.roster.draw_contact(jid, account)
1613 elif type_ == 'subscription_request':
1614 event = gajim.events.get_first_event(account, jid, type_)
1615 data = event.parameters
1616 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1617 gajim.events.remove_events(account, jid, event)
1618 self.roster.draw_contact(jid, account)
1619 elif type_ == 'unsubscribed':
1620 event = gajim.events.get_first_event(account, jid, type_)
1621 contact = event.parameters
1622 self.show_unsubscribed_dialog(account, contact)
1623 gajim.events.remove_events(account, jid, event)
1624 self.roster.draw_contact(jid, account)
1625 if w:
1626 w.set_active_tab(ctrl)
1627 w.window.window.focus(gtk.get_current_event_time())
1628 # Using isinstance here because we want to catch all derived types
1629 if isinstance(ctrl, ChatControlBase):
1630 tv = ctrl.conv_textview
1631 tv.scroll_to_end()
1633 ################################################################################
1634 ### Methods dealing with emoticons
1635 ################################################################################
1637 def image_is_ok(self, image):
1638 if not os.path.exists(image):
1639 return False
1640 img = gtk.Image()
1641 try:
1642 img.set_from_file(image)
1643 except Exception:
1644 return False
1645 t = img.get_storage_type()
1646 if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
1647 return False
1648 return True
1650 @property
1651 def basic_pattern_re(self):
1652 if not self._basic_pattern_re:
1653 self._basic_pattern_re = re.compile(self.basic_pattern,
1654 re.IGNORECASE)
1655 return self._basic_pattern_re
1657 @property
1658 def emot_and_basic_re(self):
1659 if not self._emot_and_basic_re:
1660 self._emot_and_basic_re = re.compile(self.emot_and_basic,
1661 re.IGNORECASE + re.UNICODE)
1662 return self._emot_and_basic_re
1664 @property
1665 def sth_at_sth_dot_sth_re(self):
1666 if not self._sth_at_sth_dot_sth_re:
1667 self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
1668 return self._sth_at_sth_dot_sth_re
1670 @property
1671 def invalid_XML_chars_re(self):
1672 if not self._invalid_XML_chars_re:
1673 self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
1674 return self._invalid_XML_chars_re
1676 def make_regexps(self):
1677 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
1678 # one escapes the metachars with \
1679 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
1680 # \s matches any whitespace character
1681 # \w any alphanumeric character
1682 # \W any non-alphanumeric character
1683 # \b means word boundary. This is a zero-width assertion that
1684 # matches only at the beginning or end of a word.
1685 # ^ matches at the beginning of lines
1687 # * means 0 or more times
1688 # + means 1 or more times
1689 # ? means 0 or 1 time
1690 # | means or
1691 # [^*] anything but '*' (inside [] you don't have to escape metachars)
1692 # [^\s*] anything but whitespaces and '*'
1693 # (?<!\S) is a one char lookbehind assertion and asks for any leading
1694 # whitespace
1695 # and mathces beginning of lines so we have correct formatting detection
1696 # even if the the text is just '*foo*'
1697 # (?!\S) is the same thing but it's a lookahead assertion
1698 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
1699 # the end
1700 # so http://be) will match http://be and http://be)be) will match
1701 # http://be)be
1703 legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
1704 r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
1705 r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
1706 r"|%[A-Fa-f0-9]{2})+"\
1707 r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
1708 # NOTE: it's ok to catch www.gr such stuff exist!
1710 # FIXME: recognize xmpp: and treat it specially
1711 links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
1712 r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
1713 r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
1715 # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
1716 mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
1718 # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
1719 # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
1720 formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
1721 r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
1722 r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
1724 latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\
1725 r'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
1727 basic_pattern = links + '|' + mail + '|' + legacy_prefixes
1729 link_pattern = basic_pattern
1730 self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
1732 if gajim.config.get('use_latex'):
1733 basic_pattern += latex
1735 if gajim.config.get('ascii_formatting'):
1736 basic_pattern += formatting
1737 self.basic_pattern = basic_pattern
1739 emoticons_pattern = ''
1740 if gajim.config.get('emoticons_theme'):
1741 # When an emoticon is bordered by an alpha-numeric character it is
1742 # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
1743 # We still allow multiple emoticons side-by-side like :P:P:P
1744 # sort keys by length so :qwe emot is checked before :q
1745 keys = sorted(self.emoticons, key=len, reverse=True)
1746 emoticons_pattern_prematch = ''
1747 emoticons_pattern_postmatch = ''
1748 emoticon_length = 0
1749 for emoticon in keys: # travel thru emoticons list
1750 emoticon = emoticon.decode('utf-8')
1751 emoticon_escaped = re.escape(emoticon) # espace regexp metachars
1752 # | means or in regexp
1753 emoticons_pattern += emoticon_escaped + '|'
1754 if (emoticon_length != len(emoticon)):
1755 # Build up expressions to match emoticons next to others
1756 emoticons_pattern_prematch = \
1757 emoticons_pattern_prematch[:-1] + ')|(?<='
1758 emoticons_pattern_postmatch = \
1759 emoticons_pattern_postmatch[:-1] + ')|(?='
1760 emoticon_length = len(emoticon)
1761 emoticons_pattern_prematch += emoticon_escaped + '|'
1762 emoticons_pattern_postmatch += emoticon_escaped + '|'
1763 # We match from our list of emoticons, but they must either have
1764 # whitespace, or another emoticon next to it to match successfully
1765 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
1766 emoticons_pattern = '|' + '(?:(?<![\w.]' + \
1767 emoticons_pattern_prematch[:-1] + '))' + '(?:' + \
1768 emoticons_pattern[:-1] + ')' + '(?:(?![\w]' + \
1769 emoticons_pattern_postmatch[:-1] + '))'
1771 # because emoticons match later (in the string) they need to be after
1772 # basic matches that may occur earlier
1773 self.emot_and_basic = basic_pattern + emoticons_pattern
1775 # needed for xhtml display
1776 self.emot_only = emoticons_pattern
1778 # at least one character in 3 parts (before @, after @, after .)
1779 self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
1781 # Invalid XML chars
1782 self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|'\
1783 u'[\ud800-\udfff]|[\ufffe-\uffff]'
1785 def popup_emoticons_under_button(self, button, parent_win):
1787 Popup the emoticons menu under button, located in parent_win
1789 gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
1790 button, parent_win)
1792 def prepare_emoticons_menu(self):
1793 menu = gtk.Menu()
1794 def emoticon_clicked(w, str_):
1795 if self.emoticon_menuitem_clicked:
1796 self.emoticon_menuitem_clicked(str_)
1797 # don't keep reference to CB of object
1798 # this will prevent making it uncollectable
1799 self.emoticon_menuitem_clicked = None
1800 def selection_done(widget):
1801 # remove reference to CB of object, which will
1802 # make it uncollectable
1803 self.emoticon_menuitem_clicked = None
1804 counter = 0
1805 # Calculate the side lenght of the popup to make it a square
1806 size = int(round(math.sqrt(len(self.emoticons_images))))
1807 for image in self.emoticons_images:
1808 item = gtk.MenuItem()
1809 img = gtk.Image()
1810 if isinstance(image[1], gtk.gdk.PixbufAnimation):
1811 img.set_from_animation(image[1])
1812 else:
1813 img.set_from_pixbuf(image[1])
1814 item.add(img)
1815 item.connect('activate', emoticon_clicked, image[0])
1816 #FIXME: add tooltip with ascii
1817 menu.attach(item, counter % size, counter % size + 1,
1818 counter / size, counter / size + 1)
1819 counter += 1
1820 menu.connect('selection-done', selection_done)
1821 menu.show_all()
1822 return menu
1824 def _init_emoticons(self, path, need_reload = False):
1825 #initialize emoticons dictionary and unique images list
1826 self.emoticons_images = list()
1827 self.emoticons = dict()
1828 self.emoticons_animations = dict()
1830 sys.path.append(path)
1831 import emoticons
1832 if need_reload:
1833 # we need to reload else that doesn't work when changing emoticon
1834 # set
1835 reload(emoticons)
1836 emots = emoticons.emoticons
1837 for emot_filename in emots:
1838 emot_file = os.path.join(path, emot_filename)
1839 if not self.image_is_ok(emot_file):
1840 continue
1841 for emot in emots[emot_filename]:
1842 emot = emot.decode('utf-8')
1843 # This avoids duplicated emoticons with the same image eg. :)
1844 # and :-)
1845 if not emot_file in self.emoticons.values():
1846 if emot_file.endswith('.gif'):
1847 pix = gtk.gdk.PixbufAnimation(emot_file)
1848 else:
1849 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file,
1850 16, 16)
1851 self.emoticons_images.append((emot, pix))
1852 self.emoticons[emot.upper()] = emot_file
1853 del emoticons
1854 sys.path.remove(path)
1856 def init_emoticons(self, need_reload = False):
1857 emot_theme = gajim.config.get('emoticons_theme')
1858 if not emot_theme:
1859 return
1861 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
1862 if not os.path.exists(path):
1863 # It's maybe a user theme
1864 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
1865 if not os.path.exists(path):
1866 # theme doesn't exist, disable emoticons
1867 dialogs.WarningDialog(_('Emoticons disabled'),
1868 _('Your configured emoticons theme has not been found, so '
1869 'emoticons have been disabled.'))
1870 gajim.config.set('emoticons_theme', '')
1871 return
1872 self._init_emoticons(path, need_reload)
1873 if len(self.emoticons) == 0:
1874 # maybe old format of emoticons file, try to convert it
1875 try:
1876 import pprint
1877 import emoticons
1878 emots = emoticons.emoticons
1879 fd = open(os.path.join(path, 'emoticons.py'), 'w')
1880 fd.write('emoticons = ')
1881 pprint.pprint( dict([
1882 (file_, [i for i in emots.keys() if emots[i] == file_])
1883 for file_ in set(emots.values())]), fd)
1884 fd.close()
1885 del emoticons
1886 self._init_emoticons(path, need_reload=True)
1887 except Exception:
1888 pass
1889 if len(self.emoticons) == 0:
1890 dialogs.WarningDialog(_('Emoticons disabled'),
1891 _('Your configured emoticons theme cannot been loaded. You '
1892 'maybe need to update the format of emoticons.py file. See '
1893 'http://trac.gajim.org/wiki/Emoticons for more details.'))
1894 if self.emoticons_menu:
1895 self.emoticons_menu.destroy()
1896 self.emoticons_menu = self.prepare_emoticons_menu()
1898 ################################################################################
1899 ### Methods for opening new messages controls
1900 ################################################################################
1902 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
1903 is_continued=False):
1905 Join the room immediately
1907 if not nick:
1908 nick = gajim.nicks[account]
1910 if self.msg_win_mgr.has_window(room_jid, account) and \
1911 gajim.gc_connected[account][room_jid]:
1912 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
1913 win = gc_ctrl.parent_win
1914 win.set_active_tab(gc_ctrl)
1915 dialogs.ErrorDialog(_('You are already in group chat %s') % \
1916 room_jid)
1917 return
1919 invisible_show = gajim.SHOW_LIST.index('invisible')
1920 if gajim.connections[account].connected == invisible_show:
1921 dialogs.ErrorDialog(
1922 _('You cannot join a group chat while you are invisible'))
1923 return
1925 minimized_control = gajim.interface.minimized_controls[account].get(
1926 room_jid, None)
1928 if minimized_control is None and not self.msg_win_mgr.has_window(
1929 room_jid, account):
1930 # Join new groupchat
1931 if minimize:
1932 # GCMIN
1933 contact = gajim.contacts.create_contact(jid=room_jid,
1934 account=account, name=nick)
1935 gc_control = GroupchatControl(None, contact, account)
1936 gajim.interface.minimized_controls[account][room_jid] = \
1937 gc_control
1938 self.roster.add_groupchat(room_jid, account)
1939 else:
1940 self.new_room(room_jid, nick, account,
1941 is_continued=is_continued)
1942 elif minimized_control is None:
1943 # We are already in that groupchat
1944 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
1945 gc_control.nick = nick
1946 gc_control.parent_win.set_active_tab(gc_control)
1947 else:
1948 # We are already in this groupchat and it is minimized
1949 minimized_control.nick = nick
1950 self.roster.add_groupchat(room_jid, account)
1952 # Connect
1953 gajim.connections[account].join_gc(nick, room_jid, password)
1954 if password:
1955 gajim.gc_passwords[room_jid] = password
1957 def new_room(self, room_jid, nick, account, is_continued=False):
1958 # Get target window, create a control, and associate it with the window
1959 # GCMIN
1960 contact = gajim.contacts.create_contact(jid=room_jid, account=account,
1961 name=nick)
1962 mw = self.msg_win_mgr.get_window(contact.jid, account)
1963 if not mw:
1964 mw = self.msg_win_mgr.create_window(contact, account,
1965 GroupchatControl.TYPE_ID)
1966 gc_control = GroupchatControl(mw, contact, account,
1967 is_continued=is_continued)
1968 mw.new_tab(gc_control)
1970 def new_private_chat(self, gc_contact, account, session=None):
1971 conn = gajim.connections[account]
1972 if not session and gc_contact.get_full_jid() in conn.sessions:
1973 sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
1974 values() if isinstance(s, ChatControlSession)]
1976 # look for an existing session with a chat control
1977 for s in sessions:
1978 if s.control:
1979 session = s
1980 break
1981 if not session and not len(sessions) == 0:
1982 # there are no sessions with chat controls, just take the first
1983 # one
1984 session = sessions[0]
1985 if not session:
1986 # couldn't find an existing ChatControlSession, just make a new one
1987 session = conn.make_new_session(gc_contact.get_full_jid(), None,
1988 'pm')
1990 contact = gc_contact.as_contact()
1991 if not session.control:
1992 message_window = self.msg_win_mgr.get_window(
1993 gc_contact.get_full_jid(), account)
1994 if not message_window:
1995 message_window = self.msg_win_mgr.create_window(contact,
1996 account, message_control.TYPE_PM)
1998 session.control = PrivateChatControl(message_window, gc_contact,
1999 contact, account, session)
2000 message_window.new_tab(session.control)
2002 if gajim.events.get_events(account, gc_contact.get_full_jid()):
2003 # We call this here to avoid race conditions with widget validation
2004 session.control.read_queue()
2006 return session.control
2008 def new_chat(self, contact, account, resource=None, session=None):
2009 # Get target window, create a control, and associate it with the window
2010 type_ = message_control.TYPE_CHAT
2012 fjid = contact.jid
2013 if resource:
2014 fjid += '/' + resource
2016 mw = self.msg_win_mgr.get_window(fjid, account)
2017 if not mw:
2018 mw = self.msg_win_mgr.create_window(contact, account, type_,
2019 resource)
2021 chat_control = ChatControl(mw, contact, account, session, resource)
2023 mw.new_tab(chat_control)
2025 if len(gajim.events.get_events(account, fjid)):
2026 # We call this here to avoid race conditions with widget validation
2027 chat_control.read_queue()
2029 return chat_control
2031 def new_chat_from_jid(self, account, fjid, message=None):
2032 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2033 contact = gajim.contacts.get_contact(account, jid, resource)
2034 added_to_roster = False
2035 if not contact:
2036 added_to_roster = True
2037 contact = self.roster.add_to_not_in_the_roster(account, jid,
2038 resource=resource)
2040 ctrl = self.msg_win_mgr.get_control(fjid, account)
2042 if not ctrl:
2043 ctrl = self.new_chat(contact, account,
2044 resource=resource)
2045 if len(gajim.events.get_events(account, fjid)):
2046 ctrl.read_queue()
2048 if message:
2049 buffer_ = ctrl.msg_textview.get_buffer()
2050 buffer_.set_text(message)
2051 mw = ctrl.parent_win
2052 mw.set_active_tab(ctrl)
2053 # For JEP-0172
2054 if added_to_roster:
2055 ctrl.user_nick = gajim.nicks[account]
2056 gobject.idle_add(mw.window.grab_focus)
2058 return ctrl
2060 def on_open_chat_window(self, widget, contact, account, resource=None,
2061 session=None):
2062 # Get the window containing the chat
2063 fjid = contact.jid
2065 if resource:
2066 fjid += '/' + resource
2068 ctrl = None
2070 if session:
2071 ctrl = session.control
2072 if not ctrl:
2073 win = self.msg_win_mgr.get_window(fjid, account)
2075 if win:
2076 ctrl = win.get_control(fjid, account)
2078 if not ctrl:
2079 ctrl = self.new_chat(contact, account, resource=resource,
2080 session=session)
2081 # last message is long time ago
2082 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2084 win = ctrl.parent_win
2086 win.set_active_tab(ctrl)
2088 if gajim.connections[account].is_zeroconf and \
2089 gajim.connections[account].status in ('offline', 'invisible'):
2090 ctrl = win.get_control(fjid, account)
2091 if ctrl:
2092 ctrl.got_disconnected()
2094 ################################################################################
2095 ### Other Methods
2096 ################################################################################
2098 def change_awn_icon_status(self, status):
2099 if not dbus_support.supported:
2100 # do nothing if user doesn't have D-Bus bindings
2101 return
2102 try:
2103 bus = dbus.SessionBus()
2104 if not 'com.google.code.Awn' in bus.list_names():
2105 # Awn is not installed
2106 return
2107 except Exception:
2108 return
2109 iconset = gajim.config.get('iconset')
2110 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
2111 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2112 status = status + '.png'
2113 elif status == 'online':
2114 prefix = ''
2115 status = gtkgui_helpers.get_icon_path('gajim', 32)
2116 path = os.path.join(prefix, status)
2117 try:
2118 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
2119 awn = dbus.Interface(obj, 'com.google.code.Awn')
2120 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
2121 except Exception:
2122 pass
2124 def enable_music_listener(self):
2125 listener = MusicTrackListener.get()
2126 if not self.music_track_changed_signal:
2127 self.music_track_changed_signal = listener.connect(
2128 'music-track-changed', self.music_track_changed)
2129 track = listener.get_playing_track()
2130 self.music_track_changed(listener, track)
2132 def disable_music_listener(self):
2133 listener = MusicTrackListener.get()
2134 listener.disconnect(self.music_track_changed_signal)
2135 self.music_track_changed_signal = None
2137 def music_track_changed(self, unused_listener, music_track_info,
2138 account=None):
2139 if not account:
2140 accounts = gajim.connections.keys()
2141 else:
2142 accounts = [account]
2144 is_paused = hasattr(music_track_info, 'paused') and \
2145 music_track_info.paused == 0
2146 if not music_track_info or is_paused:
2147 artist = title = source = ''
2148 else:
2149 artist = music_track_info.artist
2150 title = music_track_info.title
2151 source = music_track_info.album
2152 for acct in accounts:
2153 if not gajim.account_is_connected(acct):
2154 continue
2155 if not gajim.connections[acct].pep_supported:
2156 continue
2157 if not gajim.config.get_per('accounts', acct, 'publish_tune'):
2158 continue
2159 if gajim.connections[acct].music_track_info == music_track_info:
2160 continue
2161 gajim.connections[acct].send_tune(artist, title, source)
2162 gajim.connections[acct].music_track_info = music_track_info
2164 def get_bg_fg_colors(self):
2165 def gdkcolor_to_rgb (gdkcolor):
2166 return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
2167 gdkcolor.blue)]
2169 def format_rgb (r, g, b):
2170 return ' '.join([str(c) for c in ('rgb', r, g, b)])
2172 def format_gdkcolor (gdkcolor):
2173 return format_rgb (*gdkcolor_to_rgb (gdkcolor))
2175 # get style colors and create string for dvipng
2176 dummy = gtk.Invisible()
2177 dummy.ensure_style()
2178 style = dummy.get_style()
2179 bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
2180 fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
2181 return (bg_str, fg_str)
2183 def get_fg_color(self, fmt='hex'):
2184 def format_gdkcolor (c):
2185 if fmt == 'tex':
2186 return ' '.join([str(s) for s in
2187 ('rgb', c.red_float, c.green_float, c.blue_float)])
2188 elif fmt == 'hex':
2189 return str(c)
2191 # get foreground style color and create string
2192 dummy = gtk.Invisible()
2193 dummy.ensure_style()
2194 return format_gdkcolor(dummy.get_style().text[gtk.STATE_NORMAL])
2196 def read_sleepy(self):
2198 Check idle status and change that status if needed
2200 if not self.sleeper.poll():
2201 # idle detection is not supported in that OS
2202 return False # stop looping in vain
2203 state = self.sleeper.getState()
2204 for account in gajim.connections:
2205 if account not in gajim.sleeper_state or \
2206 not gajim.sleeper_state[account]:
2207 continue
2208 if state == common.sleepy.STATE_AWAKE and \
2209 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2210 # we go online
2211 self.roster.send_status(account, 'online',
2212 gajim.status_before_autoaway[account])
2213 gajim.status_before_autoaway[account] = ''
2214 gajim.sleeper_state[account] = 'online'
2215 elif state == common.sleepy.STATE_AWAY and \
2216 gajim.sleeper_state[account] == 'online' and \
2217 gajim.config.get('autoaway'):
2218 # we save out online status
2219 gajim.status_before_autoaway[account] = \
2220 gajim.connections[account].status
2221 # we go away (no auto status) [we pass True to auto param]
2222 auto_message = gajim.config.get('autoaway_message')
2223 if not auto_message:
2224 auto_message = gajim.connections[account].status
2225 else:
2226 auto_message = auto_message.replace('$S', '%(status)s')
2227 auto_message = auto_message.replace('$T', '%(time)s')
2228 auto_message = auto_message % {
2229 'status': gajim.status_before_autoaway[account],
2230 'time': gajim.config.get('autoawaytime')
2232 self.roster.send_status(account, 'away', auto_message,
2233 auto=True)
2234 gajim.sleeper_state[account] = 'autoaway'
2235 elif state == common.sleepy.STATE_XA and \
2236 gajim.sleeper_state[account] in ('online', 'autoaway',
2237 'autoaway-forced') and gajim.config.get('autoxa'):
2238 # we go extended away [we pass True to auto param]
2239 auto_message = gajim.config.get('autoxa_message')
2240 if not auto_message:
2241 auto_message = gajim.connections[account].status
2242 else:
2243 auto_message = auto_message.replace('$S', '%(status)s')
2244 auto_message = auto_message.replace('$T', '%(time)s')
2245 auto_message = auto_message % {
2246 'status': gajim.status_before_autoaway[account],
2247 'time': gajim.config.get('autoxatime')
2249 self.roster.send_status(account, 'xa', auto_message, auto=True)
2250 gajim.sleeper_state[account] = 'autoxa'
2251 return True # renew timeout (loop for ever)
2253 def autoconnect(self):
2255 Auto connect at startup
2257 # dict of account that want to connect sorted by status
2258 shows = {}
2259 for a in gajim.connections:
2260 if gajim.config.get_per('accounts', a, 'autoconnect'):
2261 if gajim.config.get_per('accounts', a, 'restore_last_status'):
2262 self.roster.send_status(a, gajim.config.get_per('accounts',
2263 a, 'last_status'), helpers.from_one_line(
2264 gajim.config.get_per('accounts', a, 'last_status_msg')))
2265 continue
2266 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
2267 if not show in gajim.SHOW_LIST:
2268 continue
2269 if not show in shows:
2270 shows[show] = [a]
2271 else:
2272 shows[show].append(a)
2273 def on_message(message, pep_dict):
2274 if message is None:
2275 return
2276 for a in shows[show]:
2277 self.roster.send_status(a, show, message)
2278 self.roster.send_pep(a, pep_dict)
2279 for show in shows:
2280 message = self.roster.get_status_message(show, on_message)
2281 return False
2283 def show_systray(self):
2284 self.systray_enabled = True
2285 self.systray.show_icon()
2287 def hide_systray(self):
2288 self.systray_enabled = False
2289 self.systray.hide_icon()
2291 def on_launch_browser_mailer(self, widget, url, kind):
2292 helpers.launch_browser_mailer(kind, url)
2294 def process_connections(self):
2296 Called each foo (200) miliseconds. Check for idlequeue timeouts
2298 try:
2299 gajim.idlequeue.process()
2300 except Exception:
2301 # Otherwise, an exception will stop our loop
2302 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2303 if in_seconds:
2304 gobject.timeout_add_seconds(timeout, self.process_connections)
2305 else:
2306 gobject.timeout_add(timeout, self.process_connections)
2307 raise
2308 return True # renew timeout (loop for ever)
2310 def save_config(self):
2311 err_str = parser.write()
2312 if err_str is not None:
2313 print >> sys.stderr, err_str
2314 # it is good to notify the user
2315 # in case he or she cannot see the output of the console
2316 dialogs.ErrorDialog(_('Could not save your settings and '
2317 'preferences'), err_str)
2318 sys.exit()
2320 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
2322 Save an avatar to a separate file, and generate files for dbus
2323 notifications. An avatar can be given as a pixmap directly or as an
2324 decoded image
2326 puny_jid = helpers.sanitize_filename(jid)
2327 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2328 if puny_nick:
2329 path_to_file = os.path.join(path_to_file, puny_nick)
2330 # remove old avatars
2331 for typ in ('jpeg', 'png'):
2332 if local:
2333 path_to_original_file = path_to_file + '_local'+ '.' + typ
2334 else:
2335 path_to_original_file = path_to_file + '.' + typ
2336 if os.path.isfile(path_to_original_file):
2337 os.remove(path_to_original_file)
2338 if local and photo:
2339 pixbuf = photo
2340 typ = 'png'
2341 extension = '_local.png' # save local avatars as png file
2342 else:
2343 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
2344 want_type=True)
2345 if pixbuf is None:
2346 return
2347 extension = '.' + typ
2348 if typ not in ('jpeg', 'png'):
2349 gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\
2350 'png formats. saving %s\'avatar as png file (originaly %s)'\
2351 % (jid, typ))
2352 typ = 'png'
2353 extension = '.png'
2354 path_to_original_file = path_to_file + extension
2355 try:
2356 pixbuf.save(path_to_original_file, typ)
2357 except Exception, e:
2358 log.error('Error writing avatar file %s: %s' % (
2359 path_to_original_file, str(e)))
2360 # Generate and save the resized, color avatar
2361 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
2362 if pixbuf:
2363 path_to_normal_file = path_to_file + '_notif_size_colored' + \
2364 extension
2365 try:
2366 pixbuf.save(path_to_normal_file, 'png')
2367 except Exception, e:
2368 log.error('Error writing avatar file %s: %s' % \
2369 (path_to_original_file, str(e)))
2370 # Generate and save the resized, black and white avatar
2371 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
2372 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
2373 if bwbuf:
2374 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
2375 try:
2376 bwbuf.save(path_to_bw_file, 'png')
2377 except Exception, e:
2378 log.error('Error writing avatar file %s: %s' % \
2379 (path_to_original_file, str(e)))
2381 def remove_avatar_files(self, jid, puny_nick = None, local = False):
2383 Remove avatar files of a jid
2385 puny_jid = helpers.sanitize_filename(jid)
2386 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2387 if puny_nick:
2388 path_to_file = os.path.join(path_to_file, puny_nick)
2389 for ext in ('.jpeg', '.png'):
2390 if local:
2391 ext = '_local' + ext
2392 path_to_original_file = path_to_file + ext
2393 if os.path.isfile(path_to_file + ext):
2394 os.remove(path_to_file + ext)
2395 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
2396 os.remove(path_to_file + '_notif_size_colored' + ext)
2397 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
2398 os.remove(path_to_file + '_notif_size_bw' + ext)
2400 def auto_join_bookmarks(self, account):
2402 Autojoin bookmarked GCs that have 'auto join' on for this account
2404 for bm in gajim.connections[account].bookmarks:
2405 if bm['autojoin'] in ('1', 'true'):
2406 jid = bm['jid']
2407 # Only join non-opened groupchats. Opened one are already
2408 # auto-joined on re-connection
2409 if not jid in gajim.gc_connected[account]:
2410 # we are not already connected
2411 minimize = bm['minimize'] in ('1', 'true')
2412 gajim.interface.join_gc_room(account, jid, bm['nick'],
2413 bm['password'], minimize = minimize)
2414 elif jid in self.minimized_controls[account]:
2415 # more or less a hack:
2416 # On disconnect the minimized gc contact instances
2417 # were set to offline. Reconnect them to show up in the
2418 # roster.
2419 self.roster.add_groupchat(jid, account)
2421 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
2422 nick):
2424 Add a bookmark for this account, sorted in bookmark list
2426 bm = {
2427 'name': name,
2428 'jid': jid,
2429 'autojoin': autojoin,
2430 'minimize': minimize,
2431 'password': password,
2432 'nick': nick
2434 place_found = False
2435 index = 0
2436 # check for duplicate entry and respect alpha order
2437 for bookmark in gajim.connections[account].bookmarks:
2438 if bookmark['jid'] == bm['jid']:
2439 dialogs.ErrorDialog(
2440 _('Bookmark already set'),
2441 _('Group Chat "%s" is already in your bookmarks.') % \
2442 bm['jid'])
2443 return
2444 if bookmark['name'] > bm['name']:
2445 place_found = True
2446 break
2447 index += 1
2448 if place_found:
2449 gajim.connections[account].bookmarks.insert(index, bm)
2450 else:
2451 gajim.connections[account].bookmarks.append(bm)
2452 gajim.connections[account].store_bookmarks()
2453 self.roster.set_actions_menu_needs_rebuild()
2454 dialogs.InformationDialog(
2455 _('Bookmark has been added successfully'),
2456 _('You can manage your bookmarks via Actions menu in your roster.'))
2459 # does JID exist only within a groupchat?
2460 def is_pm_contact(self, fjid, account):
2461 bare_jid = gajim.get_jid_without_resource(fjid)
2463 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
2465 if not gc_ctrl and \
2466 bare_jid in self.minimized_controls[account]:
2467 gc_ctrl = self.minimized_controls[account][bare_jid]
2469 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
2471 def create_ipython_window(self):
2472 try:
2473 from ipython_view import IPythonView
2474 except ImportError:
2475 print 'ipython_view not found'
2476 return
2477 import pango
2479 if os.name == 'nt':
2480 font = 'Lucida Console 9'
2481 else:
2482 font = 'Luxi Mono 10'
2484 window = gtk.Window()
2485 window.set_size_request(750, 550)
2486 window.set_resizable(True)
2487 sw = gtk.ScrolledWindow()
2488 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2489 view = IPythonView()
2490 view.modify_font(pango.FontDescription(font))
2491 view.set_wrap_mode(gtk.WRAP_CHAR)
2492 sw.add(view)
2493 window.add(sw)
2494 window.show_all()
2495 def on_delete(win, event):
2496 win.hide()
2497 return True
2498 window.connect('delete_event', on_delete)
2499 view.updateNamespace({'gajim': gajim})
2500 gajim.ipython_window = window
2502 def run(self):
2503 if gajim.config.get('trayicon') != 'never':
2504 self.show_systray()
2506 self.roster = roster_window.RosterWindow()
2507 # Creating plugin manager
2508 import plugins
2509 gajim.plugin_manager = plugins.PluginManager()
2511 self.roster._before_fill()
2512 for account in gajim.connections:
2513 gajim.connections[account].load_roster_from_db()
2514 self.roster._after_fill()
2516 # get instances for windows/dialogs that will show_all()/hide()
2517 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
2519 gobject.timeout_add(100, self.autoconnect)
2520 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2521 if in_seconds:
2522 gobject.timeout_add_seconds(timeout, self.process_connections)
2523 else:
2524 gobject.timeout_add(timeout, self.process_connections)
2525 gobject.timeout_add_seconds(gajim.config.get(
2526 'check_idle_every_foo_seconds'), self.read_sleepy)
2528 # when using libasyncns we need to process resolver in regular intervals
2529 if resolver.USE_LIBASYNCNS:
2530 gobject.timeout_add(200, gajim.resolver.process)
2532 def remote_init():
2533 if gajim.config.get('remote_control'):
2534 try:
2535 import remote_control
2536 self.remote_ctrl = remote_control.Remote()
2537 except Exception:
2538 pass
2539 gobject.timeout_add_seconds(5, remote_init)
2541 def __init__(self):
2542 gajim.interface = self
2543 gajim.thread_interface = ThreadInterface
2544 # This is the manager and factory of message windows set by the module
2545 self.msg_win_mgr = None
2546 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
2547 'closed': {}}
2548 self.emoticons_menu = None
2549 # handler when an emoticon is clicked in emoticons_menu
2550 self.emoticon_menuitem_clicked = None
2551 self.minimized_controls = {}
2552 self.status_sent_to_users = {}
2553 self.status_sent_to_groups = {}
2554 self.gpg_passphrase = {}
2555 self.pass_dialog = {}
2556 self.db_error_dialog = None
2557 self.default_colors = {
2558 'inmsgcolor': gajim.config.get('inmsgcolor'),
2559 'outmsgcolor': gajim.config.get('outmsgcolor'),
2560 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
2561 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
2562 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
2563 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
2566 self.handlers = {}
2567 self.roster = None
2568 self._invalid_XML_chars_re = None
2569 self._basic_pattern_re = None
2570 self._emot_and_basic_re = None
2571 self._sth_at_sth_dot_sth_re = None
2572 self.link_pattern_re = None
2573 self.invalid_XML_chars = None
2574 self.basic_pattern = None
2575 self.emot_and_basic = None
2576 self.sth_at_sth_dot_sth = None
2577 self.emot_only = None
2578 self.emoticons = []
2579 self.emoticons_animations = {}
2580 self.emoticons_images = {}
2582 cfg_was_read = parser.read()
2584 from common import latex
2585 gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
2586 latex.check_for_latex_support()
2588 gajim.logger.reset_shown_unread_messages()
2589 # override logging settings from config (don't take care of '-q' option)
2590 if gajim.config.get('verbose'):
2591 logging_helpers.set_verbose()
2593 # Is Gajim default app?
2594 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
2595 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
2597 for account in gajim.config.get_per('accounts'):
2598 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
2599 gajim.ZEROCONF_ACC_NAME = account
2600 break
2601 # Is gnome configured to activate row on single click ?
2602 try:
2603 import gconf
2604 client = gconf.client_get_default()
2605 click_policy = client.get_string(
2606 '/apps/nautilus/preferences/click_policy')
2607 if click_policy == 'single':
2608 gajim.single_click = True
2609 except Exception:
2610 pass
2611 # add default status messages if there is not in the config file
2612 if len(gajim.config.get_per('statusmsg')) == 0:
2613 default = gajim.config.statusmsg_default
2614 for msg in default:
2615 gajim.config.add_per('statusmsg', msg)
2616 gajim.config.set_per('statusmsg', msg, 'message',
2617 default[msg][0])
2618 gajim.config.set_per('statusmsg', msg, 'activity',
2619 default[msg][1])
2620 gajim.config.set_per('statusmsg', msg, 'subactivity',
2621 default[msg][2])
2622 gajim.config.set_per('statusmsg', msg, 'activity_text',
2623 default[msg][3])
2624 gajim.config.set_per('statusmsg', msg, 'mood',
2625 default[msg][4])
2626 gajim.config.set_per('statusmsg', msg, 'mood_text',
2627 default[msg][5])
2628 #add default themes if there is not in the config file
2629 theme = gajim.config.get('roster_theme')
2630 if not theme in gajim.config.get_per('themes'):
2631 gajim.config.set('roster_theme', _('default'))
2632 if len(gajim.config.get_per('themes')) == 0:
2633 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
2634 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2635 'groupfont', 'groupfontattrs', 'contacttextcolor',
2636 'contactbgcolor', 'contactfont', 'contactfontattrs',
2637 'bannertextcolor', 'bannerbgcolor']
2639 default = gajim.config.themes_default
2640 for theme_name in default:
2641 gajim.config.add_per('themes', theme_name)
2642 theme = default[theme_name]
2643 for o in d:
2644 gajim.config.set_per('themes', theme_name, o,
2645 theme[d.index(o)])
2647 if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
2648 gtkgui_helpers.autodetect_browser_mailer()
2650 gajim.idlequeue = idlequeue.get_idlequeue()
2651 # resolve and keep current record of resolved hosts
2652 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
2653 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
2654 self.handle_event_file_rcv_completed,
2655 self.handle_event_file_progress,
2656 self.handle_event_file_error)
2657 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
2658 gajim.default_session_type = ChatControlSession
2660 # Creating Network Events Controller
2661 from common import nec
2662 gajim.nec = nec.NetworkEventsController()
2664 self.create_core_handlers_list()
2665 self.register_core_handlers()
2667 if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
2668 and gajim.HAVE_ZEROCONF:
2669 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
2670 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
2671 for account in gajim.config.get_per('accounts'):
2672 if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
2673 gajim.config.get_per('accounts', account, 'active'):
2674 gajim.connections[account] = Connection(account)
2676 # gtk hooks
2677 gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
2678 gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
2679 gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
2681 self.instances = {}
2683 for a in gajim.connections:
2684 self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
2685 'search': {}, 'online_dialog': {}}
2686 # online_dialog contains all dialogs that have a meaning only when
2687 # we are not disconnected
2688 self.minimized_controls[a] = {}
2689 gajim.contacts.add_account(a)
2690 gajim.groups[a] = {}
2691 gajim.gc_connected[a] = {}
2692 gajim.automatic_rooms[a] = {}
2693 gajim.newly_added[a] = []
2694 gajim.to_be_removed[a] = []
2695 gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
2696 gajim.block_signed_in_notifications[a] = True
2697 gajim.sleeper_state[a] = 0
2698 gajim.encrypted_chats[a] = []
2699 gajim.last_message_time[a] = {}
2700 gajim.status_before_autoaway[a] = ''
2701 gajim.transport_avatar[a] = {}
2702 gajim.gajim_optional_features[a] = []
2703 gajim.caps_hash[a] = ''
2705 helpers.update_optional_features()
2706 # prepopulate data which we are sure of; note: we do not log these info
2707 for account in gajim.connections:
2708 gajimcaps = caps_cache.capscache[('sha-1',
2709 gajim.caps_hash[account])]
2710 gajimcaps.identities = [gajim.gajim_identity]
2711 gajimcaps.features = gajim.gajim_common_features + \
2712 gajim.gajim_optional_features[account]
2714 self.remote_ctrl = None
2716 if gajim.config.get('networkmanager_support') and \
2717 dbus_support.supported:
2718 import network_manager_listener
2720 # Handle gnome screensaver
2721 if dbus_support.supported:
2722 def gnome_screensaver_ActiveChanged_cb(active):
2723 if not active:
2724 for account in gajim.connections:
2725 if gajim.sleeper_state[account] == 'autoaway-forced':
2726 # We came back online ofter gnome-screensaver
2727 # autoaway
2728 self.roster.send_status(account, 'online',
2729 gajim.status_before_autoaway[account])
2730 gajim.status_before_autoaway[account] = ''
2731 gajim.sleeper_state[account] = 'online'
2732 return
2733 if not gajim.config.get('autoaway'):
2734 # Don't go auto away if user disabled the option
2735 return
2736 for account in gajim.connections:
2737 if account not in gajim.sleeper_state or \
2738 not gajim.sleeper_state[account]:
2739 continue
2740 if gajim.sleeper_state[account] == 'online':
2741 # we save out online status
2742 gajim.status_before_autoaway[account] = \
2743 gajim.connections[account].status
2744 # we go away (no auto status) [we pass True to auto
2745 # param]
2746 auto_message = gajim.config.get('autoaway_message')
2747 if not auto_message:
2748 auto_message = gajim.connections[account].status
2749 else:
2750 auto_message = auto_message.replace('$S',
2751 '%(status)s')
2752 auto_message = auto_message.replace('$T',
2753 '%(time)s')
2754 auto_message = auto_message % {
2755 'status': gajim.status_before_autoaway[account],
2756 'time': gajim.config.get('autoxatime')}
2757 self.roster.send_status(account, 'away', auto_message,
2758 auto=True)
2759 gajim.sleeper_state[account] = 'autoaway-forced'
2761 try:
2762 bus = dbus.SessionBus()
2763 bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
2764 'ActiveChanged', 'org.gnome.ScreenSaver')
2765 except Exception:
2766 pass
2768 self.show_vcard_when_connect = []
2770 self.sleeper = common.sleepy.Sleepy(
2771 gajim.config.get('autoawaytime') * 60, # make minutes to seconds
2772 gajim.config.get('autoxatime') * 60)
2774 gtkgui_helpers.make_jabber_state_images()
2776 self.systray_enabled = False
2778 import statusicon
2779 self.systray = statusicon.StatusIcon()
2781 pix = gtkgui_helpers.get_icon_pixmap('gajim', 32)
2782 # set the icon to all windows
2783 gtk.window_set_default_icon(pix)
2785 self.init_emoticons()
2786 self.make_regexps()
2788 # get transports type from DB
2789 gajim.transport_type = gajim.logger.get_transports_type()
2791 # test is dictionnary is present for speller
2792 if gajim.config.get('use_speller'):
2793 lang = gajim.config.get('speller_language')
2794 if not lang:
2795 lang = gajim.LANG
2796 tv = gtk.TextView()
2797 try:
2798 import gtkspell
2799 spell = gtkspell.Spell(tv, lang)
2800 except (ImportError, TypeError, RuntimeError, OSError):
2801 dialogs.AspellDictError(lang)
2803 if gajim.config.get('soundplayer') == '':
2804 # only on first time Gajim starts
2805 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
2806 for command in commands:
2807 if helpers.is_in_path(command):
2808 if command == 'aplay':
2809 command += ' -q'
2810 gajim.config.set('soundplayer', command)
2811 break
2813 self.last_ftwindow_update = 0
2815 self.music_track_changed_signal = None
2818 class PassphraseRequest:
2819 def __init__(self, keyid):
2820 self.keyid = keyid
2821 self.callbacks = []
2822 self.dialog_created = False
2823 self.dialog = None
2824 self.passphrase = None
2825 self.completed = False
2827 def interrupt(self, account=None):
2828 if account:
2829 for (acct, cb) in self.callbacks:
2830 if acct == account:
2831 self.callbacks.remove((acct, cb))
2832 else:
2833 self.callbacks = []
2834 if not len(self.callbacks):
2835 self.dialog.window.destroy()
2837 def run_callback(self, account, callback):
2838 gajim.connections[account].gpg_passphrase(self.passphrase)
2839 callback()
2841 def add_callback(self, account, cb):
2842 if self.completed:
2843 self.run_callback(account, cb)
2844 else:
2845 self.callbacks.append((account, cb))
2846 if not self.dialog_created:
2847 self.create_dialog(account)
2849 def complete(self, passphrase):
2850 self.passphrase = passphrase
2851 self.completed = True
2852 if passphrase is not None:
2853 gobject.timeout_add_seconds(30,
2854 gajim.interface.forget_gpg_passphrase, self.keyid)
2855 for (account, cb) in self.callbacks:
2856 self.run_callback(account, cb)
2857 self.callbacks = []
2859 def create_dialog(self, account):
2860 title = _('Passphrase Required')
2861 second = _('Enter GPG key passphrase for key %(keyid)s (account '
2862 '%(account)s).') % {'keyid': self.keyid, 'account': account}
2864 def _cancel():
2865 # user cancelled, continue without GPG
2866 self.complete(None)
2868 def _ok(passphrase, checked, count):
2869 result = gajim.connections[account].test_gpg_passphrase(passphrase)
2870 if result == 'ok':
2871 # passphrase is good
2872 self.complete(passphrase)
2873 return
2874 elif result == 'expired':
2875 dialogs.ErrorDialog(_('GPG key expired'),
2876 _('Your GPG key has expired, you will be connected to %s '
2877 'without OpenPGP.') % account)
2878 # Don't try to connect with GPG
2879 gajim.connections[account].continue_connect_info[2] = False
2880 self.complete(None)
2881 return
2883 if count < 3:
2884 # ask again
2885 dialogs.PassphraseDialog(_('Wrong Passphrase'),
2886 _('Please retype your GPG passphrase or press Cancel.'),
2887 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
2888 else:
2889 # user failed 3 times, continue without GPG
2890 self.complete(None)
2892 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
2893 1), cancel_handler=_cancel)
2894 self.dialog_created = True
2897 class ThreadInterface:
2898 def __init__(self, func, func_args, callback, callback_args):
2900 Call a function in a thread
2902 def thread_function(func, func_args, callback, callback_args):
2903 output = func(*func_args)
2904 gobject.idle_add(callback, output, *callback_args)
2906 Thread(target=thread_function, args=(func, func_args, callback,
2907 callback_args)).start()