ability to see certificate information when fingerprint changes. see #3998
[gajim.git] / src / gui_interface.py
bloba5c1a5366d4da457fba24662b8c52592a3b168a7
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.CheckFingerprintDialog(pritext, sectext, on_response_yes=on_yes,
1254 on_response_no=on_no, account=obj.conn.name,
1255 certificate=obj.certificate)
1257 def handle_event_plain_connection(self, obj):
1258 # ('PLAIN_CONNECTION', account, (connection))
1259 def on_ok(is_checked):
1260 if not is_checked[0]:
1261 on_cancel()
1262 return
1263 # On cancel call del self.instances, so don't call it another time
1264 # before
1265 del self.instances[obj.conn.name]['online_dialog']\
1266 ['plain_connection']
1267 if is_checked[1]:
1268 gajim.config.set_per('accounts', obj.conn.name,
1269 'warn_when_plaintext_connection', False)
1270 obj.conn.connection_accepted(obj.xmpp_client, 'plain')
1272 def on_cancel():
1273 del self.instances[obj.conn.name]['online_dialog']\
1274 ['plain_connection']
1275 obj.conn.disconnect(on_purpose=True)
1276 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1277 show='offline'))
1279 pritext = _('Insecure connection')
1280 sectext = _('You are about to connect to the account %(account)s '
1281 '(%(server)s) with an insecure connection. This means all your '
1282 'conversations will be exchanged unencrypted. Are you sure you '
1283 'want to do that?') % {'account': obj.conn.name,
1284 'server': gajim.get_hostname_from_account(obj.conn.name)}
1285 checktext1 = _('Yes, I really want to connect insecurely')
1286 checktext2 = _('_Do not ask me again')
1287 if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']:
1288 self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
1289 destroy()
1290 self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
1291 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1292 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1293 is_modal=False)
1295 def handle_event_insecure_ssl_connection(self, obj):
1296 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1297 def on_ok(is_checked):
1298 if not is_checked[0]:
1299 on_cancel()
1300 return
1301 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1302 if is_checked[1]:
1303 gajim.config.set_per('accounts', obj.conn.name,
1304 'warn_when_insecure_ssl_connection', False)
1305 if obj.conn.connected == 0:
1306 # We have been disconnecting (too long time since window is
1307 # opened)
1308 # re-connect with auto-accept
1309 obj.conn.connection_auto_accepted = True
1310 show, msg = obj.conn.continue_connect_info[:2]
1311 self.roster.send_status(obj.conn.name, show, msg)
1312 return
1313 obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type)
1315 def on_cancel():
1316 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1317 obj.conn.disconnect(on_purpose=True)
1318 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1319 show='offline'))
1321 pritext = _('Insecure connection')
1322 sectext = _('You are about to send your password on an insecure '
1323 'connection. You should install PyOpenSSL to prevent that. Are you '
1324 'sure you want to do that?')
1325 checktext1 = _('Yes, I really want to connect insecurely')
1326 checktext2 = _('_Do not ask me again')
1327 if 'insecure_ssl' in self.instances[obj.conn.name]['online_dialog']:
1328 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\
1329 destroy()
1330 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \
1331 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1332 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1333 is_modal=False)
1335 def handle_event_insecure_password(self, obj):
1336 # ('INSECURE_PASSWORD', account, ())
1337 def on_ok(is_checked):
1338 if not is_checked[0]:
1339 on_cancel()
1340 return
1341 del self.instances[obj.conn.name]['online_dialog']\
1342 ['insecure_password']
1343 if is_checked[1]:
1344 gajim.config.set_per('accounts', obj.conn.name,
1345 'warn_when_insecure_password', False)
1346 if obj.conn.connected == 0:
1347 # We have been disconnecting (too long time since window is
1348 # opened)
1349 # re-connect with auto-accept
1350 obj.conn.connection_auto_accepted = True
1351 show, msg = obj.conn.continue_connect_info[:2]
1352 self.roster.send_status(obj.conn.name, show, msg)
1353 return
1354 obj.conn.accept_insecure_password()
1356 def on_cancel():
1357 del self.instances[obj.conn.name]['online_dialog']\
1358 ['insecure_password']
1359 obj.conn.disconnect(on_purpose=True)
1360 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1361 show='offline'))
1363 pritext = _('Insecure connection')
1364 sectext = _('You are about to send your password unencrypted on an '
1365 'insecure connection. Are you sure you want to do that?')
1366 checktext1 = _('Yes, I really want to connect insecurely')
1367 checktext2 = _('_Do not ask me again')
1368 if 'insecure_password' in self.instances[obj.conn.name]\
1369 ['online_dialog']:
1370 self.instances[obj.conn.name]['online_dialog']\
1371 ['insecure_password'].destroy()
1372 self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
1373 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1374 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1375 is_modal=False)
1377 def create_core_handlers_list(self):
1378 self.handlers = {
1379 'ERROR': [self.handle_event_error],
1380 'DB_ERROR': [self.handle_event_db_error],
1381 'INFORMATION': [self.handle_event_information],
1382 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
1383 'atom-entry-received': [self.handle_atom_entry],
1384 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
1385 'bookmarks-received': [self.handle_event_bookmarks],
1386 'connection-lost': [self.handle_event_connection_lost],
1387 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
1388 'file-request-error': [self.handle_event_file_request_error],
1389 'file-request-received': [self.handle_event_file_request],
1390 'fingerprint-error': [self.handle_event_fingerprint_error],
1391 'gc-invitation-received': [self.handle_event_gc_invitation],
1392 'gc-presence-received': [self.handle_event_gc_presence],
1393 'gmail-notify': [self.handle_event_gmail_notify],
1394 'gpg-password-required': [self.handle_event_gpg_password_required],
1395 'gpg-trust-key': [self.handle_event_gpg_trust_key],
1396 'http-auth-received': [self.handle_event_http_auth],
1397 'insecure-password': [self.handle_event_insecure_password],
1398 'insecure-ssl-connection': \
1399 [self.handle_event_insecure_ssl_connection],
1400 'iq-error-received': [self.handle_event_iq_error],
1401 'jingle-connected-received': [self.handle_event_jingle_connected],
1402 'jingle-disconnected-received': [
1403 self.handle_event_jingle_disconnected],
1404 'jingle-error-received': [self.handle_event_jingle_error],
1405 'jingle-request-received': [self.handle_event_jingle_incoming],
1406 'last-result-received': [self.handle_event_last_status_time],
1407 'message-error': [self.handle_event_msgerror],
1408 'message-not-sent': [self.handle_event_msgnotsent],
1409 'message-sent': [self.handle_event_msgsent],
1410 'metacontacts-received': [self.handle_event_metacontacts],
1411 'muc-admin-received': [self.handle_event_gc_affiliation],
1412 'muc-owner-received': [self.handle_event_gc_config],
1413 'our-show': [self.handle_event_status],
1414 'password-required': [self.handle_event_password_required],
1415 'plain-connection': [self.handle_event_plain_connection],
1416 'presence-received': [self.handle_event_presence],
1417 'register-agent-info-received': [self.handle_event_register_agent_info],
1418 'roster-info': [self.handle_event_roster_info],
1419 'roster-item-exchange-received': \
1420 [self.handle_event_roster_item_exchange],
1421 'signed-in': [self.handle_event_signed_in],
1422 'ssl-error': [self.handle_event_ssl_error],
1423 'stream-conflict-received': [self.handle_event_resource_conflict],
1424 'subscribe-presence-received': [
1425 self.handle_event_subscribe_presence],
1426 'subscribed-presence-received': [
1427 self.handle_event_subscribed_presence],
1428 'unsubscribed-presence-received': [
1429 self.handle_event_unsubscribed_presence],
1430 'vcard-received': [self.handle_event_vcard],
1431 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
1434 def register_core_handlers(self):
1436 Register core handlers in Global Events Dispatcher (GED).
1438 This is part of rewriting whole events handling system to use GED.
1440 for event_name, event_handlers in self.handlers.iteritems():
1441 for event_handler in event_handlers:
1442 prio = ged.GUI1
1443 if type(event_handler) == tuple:
1444 prio = event_handler[1]
1445 event_handler = event_handler[0]
1446 gajim.ged.register_event_handler(event_name, prio,
1447 event_handler)
1449 ################################################################################
1450 ### Methods dealing with gajim.events
1451 ################################################################################
1453 def add_event(self, account, jid, type_, event_args):
1455 Add an event to the gajim.events var
1457 # We add it to the gajim.events queue
1458 # Do we have a queue?
1459 jid = gajim.get_jid_without_resource(jid)
1460 no_queue = len(gajim.events.get_events(account, jid)) == 0
1461 # type_ can be gc-invitation file-send-error file-error
1462 # file-request-error file-request file-completed file-stopped
1463 # jingle-incoming
1464 # event_type can be in advancedNotificationWindow.events_list
1465 event_types = {'file-request': 'ft_request',
1466 'file-completed': 'ft_finished'}
1467 event_type = event_types.get(type_)
1468 show_in_roster = notify.get_show_in_roster(event_type, account, jid)
1469 show_in_systray = notify.get_show_in_systray(event_type, account, jid)
1470 event = gajim.events.create_event(type_, event_args,
1471 show_in_roster=show_in_roster,
1472 show_in_systray=show_in_systray)
1473 gajim.events.add_event(account, jid, event)
1475 self.roster.show_title()
1476 if no_queue: # We didn't have a queue: we change icons
1477 if not gajim.contacts.get_contact_with_highest_priority(account,
1478 jid):
1479 if type_ == 'gc-invitation':
1480 self.roster.add_groupchat(jid, account, status='offline')
1481 else:
1482 # add contact to roster ("Not In The Roster") if he is not
1483 self.roster.add_to_not_in_the_roster(account, jid)
1484 else:
1485 self.roster.draw_contact(jid, account)
1487 # Select the big brother contact in roster, it's visible because it has
1488 # events.
1489 family = gajim.contacts.get_metacontacts_family(account, jid)
1490 if family:
1491 nearby_family, bb_jid, bb_account = \
1492 gajim.contacts.get_nearby_family_and_big_brother(family,
1493 account)
1494 else:
1495 bb_jid, bb_account = jid, account
1496 self.roster.select_contact(bb_jid, bb_account)
1498 def handle_event(self, account, fjid, type_):
1499 w = None
1500 ctrl = None
1501 session = None
1503 resource = gajim.get_resource_from_jid(fjid)
1504 jid = gajim.get_jid_without_resource(fjid)
1506 if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
1507 w = self.msg_win_mgr.get_window(jid, account)
1508 if jid in self.minimized_controls[account]:
1509 self.roster.on_groupchat_maximized(None, jid, account)
1510 return
1511 else:
1512 ctrl = self.msg_win_mgr.get_gc_control(jid, account)
1514 elif type_ in ('printed_chat', 'chat', ''):
1515 # '' is for log in/out notifications
1517 if type_ != '':
1518 event = gajim.events.get_first_event(account, fjid, type_)
1519 if not event:
1520 event = gajim.events.get_first_event(account, jid, type_)
1521 if not event:
1522 return
1524 if type_ == 'printed_chat':
1525 ctrl = event.parameters[0]
1526 elif type_ == 'chat':
1527 session = event.parameters[8]
1528 ctrl = session.control
1529 elif type_ == '':
1530 ctrl = self.msg_win_mgr.get_control(fjid, account)
1532 if not ctrl:
1533 highest_contact = gajim.contacts.\
1534 get_contact_with_highest_priority(account, jid)
1535 # jid can have a window if this resource was lower when he sent
1536 # message and is now higher because the other one is offline
1537 if resource and highest_contact.resource == resource and \
1538 not self.msg_win_mgr.has_window(jid, account):
1539 # remove resource of events too
1540 gajim.events.change_jid(account, fjid, jid)
1541 resource = None
1542 fjid = jid
1543 contact = None
1544 if resource:
1545 contact = gajim.contacts.get_contact(account, jid, resource)
1546 if not contact:
1547 contact = highest_contact
1549 ctrl = self.new_chat(contact, account, resource=resource,
1550 session=session)
1552 gajim.last_message_time[account][jid] = 0 # long time ago
1554 w = ctrl.parent_win
1555 elif type_ in ('printed_pm', 'pm'):
1556 # assume that the most recently updated control we have for this
1557 # party is the one that this event was in
1558 event = gajim.events.get_first_event(account, fjid, type_)
1559 if not event:
1560 event = gajim.events.get_first_event(account, jid, type_)
1562 if type_ == 'printed_pm':
1563 ctrl = event.parameters[0]
1564 elif type_ == 'pm':
1565 session = event.parameters[8]
1567 if session and session.control:
1568 ctrl = session.control
1569 elif not ctrl:
1570 room_jid = jid
1571 nick = resource
1572 gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
1573 nick)
1574 if gc_contact:
1575 show = gc_contact.show
1576 else:
1577 show = 'offline'
1578 gc_contact = gajim.contacts.create_gc_contact(
1579 room_jid=room_jid, account=account, name=nick,
1580 show=show)
1582 if not session:
1583 session = gajim.connections[account].make_new_session(
1584 fjid, None, type_='pm')
1586 self.new_private_chat(gc_contact, account, session=session)
1587 ctrl = session.control
1589 w = ctrl.parent_win
1590 elif type_ in ('normal', 'file-request', 'file-request-error',
1591 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
1592 'jingle-incoming'):
1593 # Get the first single message event
1594 event = gajim.events.get_first_event(account, fjid, type_)
1595 if not event:
1596 # default to jid without resource
1597 event = gajim.events.get_first_event(account, jid, type_)
1598 if not event:
1599 return
1600 # Open the window
1601 self.roster.open_event(account, jid, event)
1602 else:
1603 # Open the window
1604 self.roster.open_event(account, fjid, event)
1605 elif type_ == 'gmail':
1606 url = gajim.connections[account].gmail_url
1607 if url:
1608 helpers.launch_browser_mailer('url', url)
1609 elif type_ == 'gc-invitation':
1610 event = gajim.events.get_first_event(account, jid, type_)
1611 data = event.parameters
1612 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1613 data[1], data[3])
1614 gajim.events.remove_events(account, jid, event)
1615 self.roster.draw_contact(jid, account)
1616 elif type_ == 'subscription_request':
1617 event = gajim.events.get_first_event(account, jid, type_)
1618 data = event.parameters
1619 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1620 gajim.events.remove_events(account, jid, event)
1621 self.roster.draw_contact(jid, account)
1622 elif type_ == 'unsubscribed':
1623 event = gajim.events.get_first_event(account, jid, type_)
1624 contact = event.parameters
1625 self.show_unsubscribed_dialog(account, contact)
1626 gajim.events.remove_events(account, jid, event)
1627 self.roster.draw_contact(jid, account)
1628 if w:
1629 w.set_active_tab(ctrl)
1630 w.window.window.focus(gtk.get_current_event_time())
1631 # Using isinstance here because we want to catch all derived types
1632 if isinstance(ctrl, ChatControlBase):
1633 tv = ctrl.conv_textview
1634 tv.scroll_to_end()
1636 ################################################################################
1637 ### Methods dealing with emoticons
1638 ################################################################################
1640 def image_is_ok(self, image):
1641 if not os.path.exists(image):
1642 return False
1643 img = gtk.Image()
1644 try:
1645 img.set_from_file(image)
1646 except Exception:
1647 return False
1648 t = img.get_storage_type()
1649 if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
1650 return False
1651 return True
1653 @property
1654 def basic_pattern_re(self):
1655 if not self._basic_pattern_re:
1656 self._basic_pattern_re = re.compile(self.basic_pattern,
1657 re.IGNORECASE)
1658 return self._basic_pattern_re
1660 @property
1661 def emot_and_basic_re(self):
1662 if not self._emot_and_basic_re:
1663 self._emot_and_basic_re = re.compile(self.emot_and_basic,
1664 re.IGNORECASE + re.UNICODE)
1665 return self._emot_and_basic_re
1667 @property
1668 def sth_at_sth_dot_sth_re(self):
1669 if not self._sth_at_sth_dot_sth_re:
1670 self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
1671 return self._sth_at_sth_dot_sth_re
1673 @property
1674 def invalid_XML_chars_re(self):
1675 if not self._invalid_XML_chars_re:
1676 self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
1677 return self._invalid_XML_chars_re
1679 def make_regexps(self):
1680 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
1681 # one escapes the metachars with \
1682 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
1683 # \s matches any whitespace character
1684 # \w any alphanumeric character
1685 # \W any non-alphanumeric character
1686 # \b means word boundary. This is a zero-width assertion that
1687 # matches only at the beginning or end of a word.
1688 # ^ matches at the beginning of lines
1690 # * means 0 or more times
1691 # + means 1 or more times
1692 # ? means 0 or 1 time
1693 # | means or
1694 # [^*] anything but '*' (inside [] you don't have to escape metachars)
1695 # [^\s*] anything but whitespaces and '*'
1696 # (?<!\S) is a one char lookbehind assertion and asks for any leading
1697 # whitespace
1698 # and mathces beginning of lines so we have correct formatting detection
1699 # even if the the text is just '*foo*'
1700 # (?!\S) is the same thing but it's a lookahead assertion
1701 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
1702 # the end
1703 # so http://be) will match http://be and http://be)be) will match
1704 # http://be)be
1706 legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
1707 r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
1708 r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
1709 r"|%[A-Fa-f0-9]{2})+"\
1710 r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
1711 # NOTE: it's ok to catch www.gr such stuff exist!
1713 # FIXME: recognize xmpp: and treat it specially
1714 links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
1715 r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
1716 r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
1718 # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
1719 mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
1721 # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
1722 # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
1723 formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
1724 r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
1725 r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
1727 latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\
1728 r'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
1730 basic_pattern = links + '|' + mail + '|' + legacy_prefixes
1732 link_pattern = basic_pattern
1733 self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
1735 if gajim.config.get('use_latex'):
1736 basic_pattern += latex
1738 if gajim.config.get('ascii_formatting'):
1739 basic_pattern += formatting
1740 self.basic_pattern = basic_pattern
1742 emoticons_pattern = ''
1743 if gajim.config.get('emoticons_theme'):
1744 # When an emoticon is bordered by an alpha-numeric character it is
1745 # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
1746 # We still allow multiple emoticons side-by-side like :P:P:P
1747 # sort keys by length so :qwe emot is checked before :q
1748 keys = sorted(self.emoticons, key=len, reverse=True)
1749 emoticons_pattern_prematch = ''
1750 emoticons_pattern_postmatch = ''
1751 emoticon_length = 0
1752 for emoticon in keys: # travel thru emoticons list
1753 emoticon = emoticon.decode('utf-8')
1754 emoticon_escaped = re.escape(emoticon) # espace regexp metachars
1755 # | means or in regexp
1756 emoticons_pattern += emoticon_escaped + '|'
1757 if (emoticon_length != len(emoticon)):
1758 # Build up expressions to match emoticons next to others
1759 emoticons_pattern_prematch = \
1760 emoticons_pattern_prematch[:-1] + ')|(?<='
1761 emoticons_pattern_postmatch = \
1762 emoticons_pattern_postmatch[:-1] + ')|(?='
1763 emoticon_length = len(emoticon)
1764 emoticons_pattern_prematch += emoticon_escaped + '|'
1765 emoticons_pattern_postmatch += emoticon_escaped + '|'
1766 # We match from our list of emoticons, but they must either have
1767 # whitespace, or another emoticon next to it to match successfully
1768 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
1769 emoticons_pattern = '|' + '(?:(?<![\w.]' + \
1770 emoticons_pattern_prematch[:-1] + '))' + '(?:' + \
1771 emoticons_pattern[:-1] + ')' + '(?:(?![\w]' + \
1772 emoticons_pattern_postmatch[:-1] + '))'
1774 # because emoticons match later (in the string) they need to be after
1775 # basic matches that may occur earlier
1776 self.emot_and_basic = basic_pattern + emoticons_pattern
1778 # needed for xhtml display
1779 self.emot_only = emoticons_pattern
1781 # at least one character in 3 parts (before @, after @, after .)
1782 self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
1784 # Invalid XML chars
1785 self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\
1786 u'[\ud800-\udfff]|[\ufffe-\uffff]'
1788 def popup_emoticons_under_button(self, button, parent_win):
1790 Popup the emoticons menu under button, located in parent_win
1792 gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
1793 button, parent_win)
1795 def prepare_emoticons_menu(self):
1796 menu = gtk.Menu()
1797 def emoticon_clicked(w, str_):
1798 if self.emoticon_menuitem_clicked:
1799 self.emoticon_menuitem_clicked(str_)
1800 # don't keep reference to CB of object
1801 # this will prevent making it uncollectable
1802 self.emoticon_menuitem_clicked = None
1803 def selection_done(widget):
1804 # remove reference to CB of object, which will
1805 # make it uncollectable
1806 self.emoticon_menuitem_clicked = None
1807 counter = 0
1808 # Calculate the side lenght of the popup to make it a square
1809 size = int(round(math.sqrt(len(self.emoticons_images))))
1810 for image in self.emoticons_images:
1811 item = gtk.MenuItem()
1812 img = gtk.Image()
1813 if isinstance(image[1], gtk.gdk.PixbufAnimation):
1814 img.set_from_animation(image[1])
1815 else:
1816 img.set_from_pixbuf(image[1])
1817 item.add(img)
1818 item.connect('activate', emoticon_clicked, image[0])
1819 # add tooltip with ascii
1820 item.set_tooltip_text(image[0])
1821 menu.attach(item, counter % size, counter % size + 1,
1822 counter / size, counter / size + 1)
1823 counter += 1
1824 menu.connect('selection-done', selection_done)
1825 menu.show_all()
1826 return menu
1828 def _init_emoticons(self, path, need_reload = False):
1829 #initialize emoticons dictionary and unique images list
1830 self.emoticons_images = list()
1831 self.emoticons = dict()
1832 self.emoticons_animations = dict()
1834 sys.path.append(path)
1835 import emoticons
1836 if need_reload:
1837 # we need to reload else that doesn't work when changing emoticon
1838 # set
1839 reload(emoticons)
1840 emots = emoticons.emoticons
1841 for emot_filename in emots:
1842 emot_file = os.path.join(path, emot_filename)
1843 if not self.image_is_ok(emot_file):
1844 continue
1845 for emot in emots[emot_filename]:
1846 emot = emot.decode('utf-8')
1847 # This avoids duplicated emoticons with the same image eg. :)
1848 # and :-)
1849 if not emot_file in self.emoticons.values():
1850 if emot_file.endswith('.gif'):
1851 pix = gtk.gdk.PixbufAnimation(emot_file)
1852 else:
1853 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file,
1854 16, 16)
1855 self.emoticons_images.append((emot, pix))
1856 self.emoticons[emot.upper()] = emot_file
1857 del emoticons
1858 sys.path.remove(path)
1860 def init_emoticons(self, need_reload = False):
1861 emot_theme = gajim.config.get('emoticons_theme')
1862 if not emot_theme:
1863 return
1865 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
1866 if not os.path.exists(path):
1867 # It's maybe a user theme
1868 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
1869 if not os.path.exists(path):
1870 # theme doesn't exist, disable emoticons
1871 dialogs.WarningDialog(_('Emoticons disabled'),
1872 _('Your configured emoticons theme has not been found, so '
1873 'emoticons have been disabled.'))
1874 gajim.config.set('emoticons_theme', '')
1875 return
1876 self._init_emoticons(path, need_reload)
1877 if len(self.emoticons) == 0:
1878 # maybe old format of emoticons file, try to convert it
1879 try:
1880 import pprint
1881 import emoticons
1882 emots = emoticons.emoticons
1883 fd = open(os.path.join(path, 'emoticons.py'), 'w')
1884 fd.write('emoticons = ')
1885 pprint.pprint( dict([
1886 (file_, [i for i in emots.keys() if emots[i] == file_])
1887 for file_ in set(emots.values())]), fd)
1888 fd.close()
1889 del emoticons
1890 self._init_emoticons(path, need_reload=True)
1891 except Exception:
1892 pass
1893 if len(self.emoticons) == 0:
1894 dialogs.WarningDialog(_('Emoticons disabled'),
1895 _('Your configured emoticons theme cannot been loaded. You '
1896 'maybe need to update the format of emoticons.py file. See '
1897 'http://trac.gajim.org/wiki/Emoticons for more details.'))
1898 if self.emoticons_menu:
1899 self.emoticons_menu.destroy()
1900 self.emoticons_menu = self.prepare_emoticons_menu()
1902 ################################################################################
1903 ### Methods for opening new messages controls
1904 ################################################################################
1906 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
1907 is_continued=False):
1909 Join the room immediately
1911 if not nick:
1912 nick = gajim.nicks[account]
1914 if self.msg_win_mgr.has_window(room_jid, account) and \
1915 gajim.gc_connected[account][room_jid]:
1916 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
1917 win = gc_ctrl.parent_win
1918 win.set_active_tab(gc_ctrl)
1919 dialogs.ErrorDialog(_('You are already in group chat %s') % \
1920 room_jid)
1921 return
1923 invisible_show = gajim.SHOW_LIST.index('invisible')
1924 if gajim.connections[account].connected == invisible_show:
1925 dialogs.ErrorDialog(
1926 _('You cannot join a group chat while you are invisible'))
1927 return
1929 minimized_control = gajim.interface.minimized_controls[account].get(
1930 room_jid, None)
1932 if minimized_control is None and not self.msg_win_mgr.has_window(
1933 room_jid, account):
1934 # Join new groupchat
1935 if minimize:
1936 # GCMIN
1937 contact = gajim.contacts.create_contact(jid=room_jid,
1938 account=account, name=nick)
1939 gc_control = GroupchatControl(None, contact, account)
1940 gajim.interface.minimized_controls[account][room_jid] = \
1941 gc_control
1942 self.roster.add_groupchat(room_jid, account)
1943 else:
1944 self.new_room(room_jid, nick, account,
1945 is_continued=is_continued)
1946 elif minimized_control is None:
1947 # We are already in that groupchat
1948 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
1949 gc_control.nick = nick
1950 gc_control.parent_win.set_active_tab(gc_control)
1951 else:
1952 # We are already in this groupchat and it is minimized
1953 minimized_control.nick = nick
1954 self.roster.add_groupchat(room_jid, account)
1956 # Connect
1957 gajim.connections[account].join_gc(nick, room_jid, password)
1958 if password:
1959 gajim.gc_passwords[room_jid] = password
1961 def new_room(self, room_jid, nick, account, is_continued=False):
1962 # Get target window, create a control, and associate it with the window
1963 # GCMIN
1964 contact = gajim.contacts.create_contact(jid=room_jid, account=account,
1965 name=nick)
1966 mw = self.msg_win_mgr.get_window(contact.jid, account)
1967 if not mw:
1968 mw = self.msg_win_mgr.create_window(contact, account,
1969 GroupchatControl.TYPE_ID)
1970 gc_control = GroupchatControl(mw, contact, account,
1971 is_continued=is_continued)
1972 mw.new_tab(gc_control)
1974 def new_private_chat(self, gc_contact, account, session=None):
1975 conn = gajim.connections[account]
1976 if not session and gc_contact.get_full_jid() in conn.sessions:
1977 sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
1978 values() if isinstance(s, ChatControlSession)]
1980 # look for an existing session with a chat control
1981 for s in sessions:
1982 if s.control:
1983 session = s
1984 break
1985 if not session and not len(sessions) == 0:
1986 # there are no sessions with chat controls, just take the first
1987 # one
1988 session = sessions[0]
1989 if not session:
1990 # couldn't find an existing ChatControlSession, just make a new one
1991 session = conn.make_new_session(gc_contact.get_full_jid(), None,
1992 'pm')
1994 contact = gc_contact.as_contact()
1995 if not session.control:
1996 message_window = self.msg_win_mgr.get_window(
1997 gc_contact.get_full_jid(), account)
1998 if not message_window:
1999 message_window = self.msg_win_mgr.create_window(contact,
2000 account, message_control.TYPE_PM)
2002 session.control = PrivateChatControl(message_window, gc_contact,
2003 contact, account, session)
2004 message_window.new_tab(session.control)
2006 if gajim.events.get_events(account, gc_contact.get_full_jid()):
2007 # We call this here to avoid race conditions with widget validation
2008 session.control.read_queue()
2010 return session.control
2012 def new_chat(self, contact, account, resource=None, session=None):
2013 # Get target window, create a control, and associate it with the window
2014 type_ = message_control.TYPE_CHAT
2016 fjid = contact.jid
2017 if resource:
2018 fjid += '/' + resource
2020 mw = self.msg_win_mgr.get_window(fjid, account)
2021 if not mw:
2022 mw = self.msg_win_mgr.create_window(contact, account, type_,
2023 resource)
2025 chat_control = ChatControl(mw, contact, account, session, resource)
2027 mw.new_tab(chat_control)
2029 if len(gajim.events.get_events(account, fjid)):
2030 # We call this here to avoid race conditions with widget validation
2031 chat_control.read_queue()
2033 return chat_control
2035 def new_chat_from_jid(self, account, fjid, message=None):
2036 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2037 contact = gajim.contacts.get_contact(account, jid, resource)
2038 added_to_roster = False
2039 if not contact:
2040 added_to_roster = True
2041 contact = self.roster.add_to_not_in_the_roster(account, jid,
2042 resource=resource)
2044 ctrl = self.msg_win_mgr.get_control(fjid, account)
2046 if not ctrl:
2047 ctrl = self.new_chat(contact, account,
2048 resource=resource)
2049 if len(gajim.events.get_events(account, fjid)):
2050 ctrl.read_queue()
2052 if message:
2053 buffer_ = ctrl.msg_textview.get_buffer()
2054 buffer_.set_text(message)
2055 mw = ctrl.parent_win
2056 mw.set_active_tab(ctrl)
2057 # For JEP-0172
2058 if added_to_roster:
2059 ctrl.user_nick = gajim.nicks[account]
2060 gobject.idle_add(mw.window.grab_focus)
2062 return ctrl
2064 def on_open_chat_window(self, widget, contact, account, resource=None,
2065 session=None):
2066 # Get the window containing the chat
2067 fjid = contact.jid
2069 if resource:
2070 fjid += '/' + resource
2072 ctrl = None
2074 if session:
2075 ctrl = session.control
2076 if not ctrl:
2077 win = self.msg_win_mgr.get_window(fjid, account)
2079 if win:
2080 ctrl = win.get_control(fjid, account)
2082 if not ctrl:
2083 ctrl = self.new_chat(contact, account, resource=resource,
2084 session=session)
2085 # last message is long time ago
2086 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2088 win = ctrl.parent_win
2090 win.set_active_tab(ctrl)
2092 if gajim.connections[account].is_zeroconf and \
2093 gajim.connections[account].status in ('offline', 'invisible'):
2094 ctrl = win.get_control(fjid, account)
2095 if ctrl:
2096 ctrl.got_disconnected()
2098 ################################################################################
2099 ### Other Methods
2100 ################################################################################
2102 def change_awn_icon_status(self, status):
2103 if not dbus_support.supported:
2104 # do nothing if user doesn't have D-Bus bindings
2105 return
2106 try:
2107 bus = dbus.SessionBus()
2108 if not 'com.google.code.Awn' in bus.list_names():
2109 # Awn is not installed
2110 return
2111 except Exception:
2112 return
2113 iconset = gajim.config.get('iconset')
2114 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
2115 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2116 status = status + '.png'
2117 elif status == 'online':
2118 prefix = ''
2119 status = gtkgui_helpers.get_icon_path('gajim', 32)
2120 path = os.path.join(prefix, status)
2121 try:
2122 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
2123 awn = dbus.Interface(obj, 'com.google.code.Awn')
2124 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
2125 except Exception:
2126 pass
2128 def enable_music_listener(self):
2129 listener = MusicTrackListener.get()
2130 if not self.music_track_changed_signal:
2131 self.music_track_changed_signal = listener.connect(
2132 'music-track-changed', self.music_track_changed)
2133 track = listener.get_playing_track()
2134 self.music_track_changed(listener, track)
2136 def disable_music_listener(self):
2137 listener = MusicTrackListener.get()
2138 listener.disconnect(self.music_track_changed_signal)
2139 self.music_track_changed_signal = None
2141 def music_track_changed(self, unused_listener, music_track_info,
2142 account=None):
2143 if not account:
2144 accounts = gajim.connections.keys()
2145 else:
2146 accounts = [account]
2148 is_paused = hasattr(music_track_info, 'paused') and \
2149 music_track_info.paused == 0
2150 if not music_track_info or is_paused:
2151 artist = title = source = ''
2152 else:
2153 artist = music_track_info.artist
2154 title = music_track_info.title
2155 source = music_track_info.album
2156 for acct in accounts:
2157 if not gajim.account_is_connected(acct):
2158 continue
2159 if not gajim.connections[acct].pep_supported:
2160 continue
2161 if not gajim.config.get_per('accounts', acct, 'publish_tune'):
2162 continue
2163 if gajim.connections[acct].music_track_info == music_track_info:
2164 continue
2165 gajim.connections[acct].send_tune(artist, title, source)
2166 gajim.connections[acct].music_track_info = music_track_info
2168 def get_bg_fg_colors(self):
2169 def gdkcolor_to_rgb (gdkcolor):
2170 return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
2171 gdkcolor.blue)]
2173 def format_rgb (r, g, b):
2174 return ' '.join([str(c) for c in ('rgb', r, g, b)])
2176 def format_gdkcolor (gdkcolor):
2177 return format_rgb (*gdkcolor_to_rgb (gdkcolor))
2179 # get style colors and create string for dvipng
2180 dummy = gtk.Invisible()
2181 dummy.ensure_style()
2182 style = dummy.get_style()
2183 bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
2184 fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
2185 return (bg_str, fg_str)
2187 def get_fg_color(self, fmt='hex'):
2188 def format_gdkcolor (c):
2189 if fmt == 'tex':
2190 return ' '.join([str(s) for s in
2191 ('rgb', c.red_float, c.green_float, c.blue_float)])
2192 elif fmt == 'hex':
2193 return str(c)
2195 # get foreground style color and create string
2196 dummy = gtk.Invisible()
2197 dummy.ensure_style()
2198 return format_gdkcolor(dummy.get_style().text[gtk.STATE_NORMAL])
2200 def read_sleepy(self):
2202 Check idle status and change that status if needed
2204 if not self.sleeper.poll():
2205 # idle detection is not supported in that OS
2206 return False # stop looping in vain
2207 state = self.sleeper.getState()
2208 for account in gajim.connections:
2209 if account not in gajim.sleeper_state or \
2210 not gajim.sleeper_state[account]:
2211 continue
2212 if state == common.sleepy.STATE_AWAKE and \
2213 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2214 # we go online
2215 self.roster.send_status(account, 'online',
2216 gajim.status_before_autoaway[account])
2217 gajim.status_before_autoaway[account] = ''
2218 gajim.sleeper_state[account] = 'online'
2219 elif state == common.sleepy.STATE_AWAY and \
2220 gajim.sleeper_state[account] == 'online' and \
2221 gajim.config.get('autoaway'):
2222 # we save out online status
2223 gajim.status_before_autoaway[account] = \
2224 gajim.connections[account].status
2225 # we go away (no auto status) [we pass True to auto param]
2226 auto_message = gajim.config.get('autoaway_message')
2227 if not auto_message:
2228 auto_message = gajim.connections[account].status
2229 else:
2230 auto_message = auto_message.replace('$S', '%(status)s')
2231 auto_message = auto_message.replace('$T', '%(time)s')
2232 auto_message = auto_message % {
2233 'status': gajim.status_before_autoaway[account],
2234 'time': gajim.config.get('autoawaytime')
2236 self.roster.send_status(account, 'away', auto_message,
2237 auto=True)
2238 gajim.sleeper_state[account] = 'autoaway'
2239 elif state == common.sleepy.STATE_XA and \
2240 gajim.sleeper_state[account] in ('online', 'autoaway',
2241 'autoaway-forced') and gajim.config.get('autoxa'):
2242 # we go extended away [we pass True to auto param]
2243 auto_message = gajim.config.get('autoxa_message')
2244 if not auto_message:
2245 auto_message = gajim.connections[account].status
2246 else:
2247 auto_message = auto_message.replace('$S', '%(status)s')
2248 auto_message = auto_message.replace('$T', '%(time)s')
2249 auto_message = auto_message % {
2250 'status': gajim.status_before_autoaway[account],
2251 'time': gajim.config.get('autoxatime')
2253 self.roster.send_status(account, 'xa', auto_message, auto=True)
2254 gajim.sleeper_state[account] = 'autoxa'
2255 return True # renew timeout (loop for ever)
2257 def autoconnect(self):
2259 Auto connect at startup
2261 # dict of account that want to connect sorted by status
2262 shows = {}
2263 for a in gajim.connections:
2264 if gajim.config.get_per('accounts', a, 'autoconnect'):
2265 if gajim.config.get_per('accounts', a, 'restore_last_status'):
2266 self.roster.send_status(a, gajim.config.get_per('accounts',
2267 a, 'last_status'), helpers.from_one_line(
2268 gajim.config.get_per('accounts', a, 'last_status_msg')))
2269 continue
2270 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
2271 if not show in gajim.SHOW_LIST:
2272 continue
2273 if not show in shows:
2274 shows[show] = [a]
2275 else:
2276 shows[show].append(a)
2277 def on_message(message, pep_dict):
2278 if message is None:
2279 return
2280 for a in shows[show]:
2281 self.roster.send_status(a, show, message)
2282 self.roster.send_pep(a, pep_dict)
2283 for show in shows:
2284 message = self.roster.get_status_message(show, on_message)
2285 return False
2287 def show_systray(self):
2288 self.systray_enabled = True
2289 self.systray.show_icon()
2291 def hide_systray(self):
2292 self.systray_enabled = False
2293 self.systray.hide_icon()
2295 def on_launch_browser_mailer(self, widget, url, kind):
2296 helpers.launch_browser_mailer(kind, url)
2298 def process_connections(self):
2300 Called each foo (200) miliseconds. Check for idlequeue timeouts
2302 try:
2303 gajim.idlequeue.process()
2304 except Exception:
2305 # Otherwise, an exception will stop our loop
2306 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2307 if in_seconds:
2308 gobject.timeout_add_seconds(timeout, self.process_connections)
2309 else:
2310 gobject.timeout_add(timeout, self.process_connections)
2311 raise
2312 return True # renew timeout (loop for ever)
2314 def save_config(self):
2315 err_str = parser.write()
2316 if err_str is not None:
2317 print >> sys.stderr, err_str
2318 # it is good to notify the user
2319 # in case he or she cannot see the output of the console
2320 dialogs.ErrorDialog(_('Could not save your settings and '
2321 'preferences'), err_str)
2322 sys.exit()
2324 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
2326 Save an avatar to a separate file, and generate files for dbus
2327 notifications. An avatar can be given as a pixmap directly or as an
2328 decoded image
2330 puny_jid = helpers.sanitize_filename(jid)
2331 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2332 if puny_nick:
2333 path_to_file = os.path.join(path_to_file, puny_nick)
2334 # remove old avatars
2335 for typ in ('jpeg', 'png'):
2336 if local:
2337 path_to_original_file = path_to_file + '_local'+ '.' + typ
2338 else:
2339 path_to_original_file = path_to_file + '.' + typ
2340 if os.path.isfile(path_to_original_file):
2341 os.remove(path_to_original_file)
2342 if local and photo:
2343 pixbuf = photo
2344 typ = 'png'
2345 extension = '_local.png' # save local avatars as png file
2346 else:
2347 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
2348 want_type=True)
2349 if pixbuf is None:
2350 return
2351 extension = '.' + typ
2352 if typ not in ('jpeg', 'png'):
2353 gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\
2354 'png formats. saving %s\'avatar as png file (originaly %s)'\
2355 % (jid, typ))
2356 typ = 'png'
2357 extension = '.png'
2358 path_to_original_file = path_to_file + extension
2359 try:
2360 pixbuf.save(path_to_original_file, typ)
2361 except Exception, e:
2362 log.error('Error writing avatar file %s: %s' % (
2363 path_to_original_file, str(e)))
2364 # Generate and save the resized, color avatar
2365 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
2366 if pixbuf:
2367 path_to_normal_file = path_to_file + '_notif_size_colored' + \
2368 extension
2369 try:
2370 pixbuf.save(path_to_normal_file, 'png')
2371 except Exception, e:
2372 log.error('Error writing avatar file %s: %s' % \
2373 (path_to_original_file, str(e)))
2374 # Generate and save the resized, black and white avatar
2375 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
2376 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
2377 if bwbuf:
2378 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
2379 try:
2380 bwbuf.save(path_to_bw_file, 'png')
2381 except Exception, e:
2382 log.error('Error writing avatar file %s: %s' % \
2383 (path_to_original_file, str(e)))
2385 def remove_avatar_files(self, jid, puny_nick = None, local = False):
2387 Remove avatar files of a jid
2389 puny_jid = helpers.sanitize_filename(jid)
2390 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2391 if puny_nick:
2392 path_to_file = os.path.join(path_to_file, puny_nick)
2393 for ext in ('.jpeg', '.png'):
2394 if local:
2395 ext = '_local' + ext
2396 path_to_original_file = path_to_file + ext
2397 if os.path.isfile(path_to_file + ext):
2398 os.remove(path_to_file + ext)
2399 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
2400 os.remove(path_to_file + '_notif_size_colored' + ext)
2401 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
2402 os.remove(path_to_file + '_notif_size_bw' + ext)
2404 def auto_join_bookmarks(self, account):
2406 Autojoin bookmarked GCs that have 'auto join' on for this account
2408 for bm in gajim.connections[account].bookmarks:
2409 if bm['autojoin'] in ('1', 'true'):
2410 jid = bm['jid']
2411 # Only join non-opened groupchats. Opened one are already
2412 # auto-joined on re-connection
2413 if not jid in gajim.gc_connected[account]:
2414 # we are not already connected
2415 minimize = bm['minimize'] in ('1', 'true')
2416 gajim.interface.join_gc_room(account, jid, bm['nick'],
2417 bm['password'], minimize = minimize)
2418 elif jid in self.minimized_controls[account]:
2419 # more or less a hack:
2420 # On disconnect the minimized gc contact instances
2421 # were set to offline. Reconnect them to show up in the
2422 # roster.
2423 self.roster.add_groupchat(jid, account)
2425 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
2426 nick):
2428 Add a bookmark for this account, sorted in bookmark list
2430 bm = {
2431 'name': name,
2432 'jid': jid,
2433 'autojoin': autojoin,
2434 'minimize': minimize,
2435 'password': password,
2436 'nick': nick
2438 place_found = False
2439 index = 0
2440 # check for duplicate entry and respect alpha order
2441 for bookmark in gajim.connections[account].bookmarks:
2442 if bookmark['jid'] == bm['jid']:
2443 dialogs.ErrorDialog(
2444 _('Bookmark already set'),
2445 _('Group Chat "%s" is already in your bookmarks.') % \
2446 bm['jid'])
2447 return
2448 if bookmark['name'] > bm['name']:
2449 place_found = True
2450 break
2451 index += 1
2452 if place_found:
2453 gajim.connections[account].bookmarks.insert(index, bm)
2454 else:
2455 gajim.connections[account].bookmarks.append(bm)
2456 gajim.connections[account].store_bookmarks()
2457 self.roster.set_actions_menu_needs_rebuild()
2458 dialogs.InformationDialog(
2459 _('Bookmark has been added successfully'),
2460 _('You can manage your bookmarks via Actions menu in your roster.'))
2463 # does JID exist only within a groupchat?
2464 def is_pm_contact(self, fjid, account):
2465 bare_jid = gajim.get_jid_without_resource(fjid)
2467 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
2469 if not gc_ctrl and \
2470 bare_jid in self.minimized_controls[account]:
2471 gc_ctrl = self.minimized_controls[account][bare_jid]
2473 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
2475 def create_ipython_window(self):
2476 try:
2477 from ipython_view import IPythonView
2478 except ImportError:
2479 print 'ipython_view not found'
2480 return
2481 import pango
2483 if os.name == 'nt':
2484 font = 'Lucida Console 9'
2485 else:
2486 font = 'Luxi Mono 10'
2488 window = gtk.Window()
2489 window.set_size_request(750, 550)
2490 window.set_resizable(True)
2491 sw = gtk.ScrolledWindow()
2492 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2493 view = IPythonView()
2494 view.modify_font(pango.FontDescription(font))
2495 view.set_wrap_mode(gtk.WRAP_CHAR)
2496 sw.add(view)
2497 window.add(sw)
2498 window.show_all()
2499 def on_delete(win, event):
2500 win.hide()
2501 return True
2502 window.connect('delete_event', on_delete)
2503 view.updateNamespace({'gajim': gajim})
2504 gajim.ipython_window = window
2506 def run(self):
2507 if gajim.config.get('trayicon') != 'never':
2508 self.show_systray()
2510 self.roster = roster_window.RosterWindow()
2511 # Creating plugin manager
2512 import plugins
2513 gajim.plugin_manager = plugins.PluginManager()
2515 self.roster._before_fill()
2516 for account in gajim.connections:
2517 gajim.connections[account].load_roster_from_db()
2518 self.roster._after_fill()
2520 # get instances for windows/dialogs that will show_all()/hide()
2521 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
2523 gobject.timeout_add(100, self.autoconnect)
2524 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2525 if in_seconds:
2526 gobject.timeout_add_seconds(timeout, self.process_connections)
2527 else:
2528 gobject.timeout_add(timeout, self.process_connections)
2529 gobject.timeout_add_seconds(gajim.config.get(
2530 'check_idle_every_foo_seconds'), self.read_sleepy)
2532 # when using libasyncns we need to process resolver in regular intervals
2533 if resolver.USE_LIBASYNCNS:
2534 gobject.timeout_add(200, gajim.resolver.process)
2536 def remote_init():
2537 if gajim.config.get('remote_control'):
2538 try:
2539 import remote_control
2540 self.remote_ctrl = remote_control.Remote()
2541 except Exception:
2542 pass
2543 gobject.timeout_add_seconds(5, remote_init)
2545 def __init__(self):
2546 gajim.interface = self
2547 gajim.thread_interface = ThreadInterface
2548 # This is the manager and factory of message windows set by the module
2549 self.msg_win_mgr = None
2550 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
2551 'closed': {}}
2552 self.emoticons_menu = None
2553 # handler when an emoticon is clicked in emoticons_menu
2554 self.emoticon_menuitem_clicked = None
2555 self.minimized_controls = {}
2556 self.status_sent_to_users = {}
2557 self.status_sent_to_groups = {}
2558 self.gpg_passphrase = {}
2559 self.pass_dialog = {}
2560 self.db_error_dialog = None
2561 self.default_colors = {
2562 'inmsgcolor': gajim.config.get('inmsgcolor'),
2563 'outmsgcolor': gajim.config.get('outmsgcolor'),
2564 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
2565 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
2566 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
2567 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
2570 self.handlers = {}
2571 self.roster = None
2572 self._invalid_XML_chars_re = None
2573 self._basic_pattern_re = None
2574 self._emot_and_basic_re = None
2575 self._sth_at_sth_dot_sth_re = None
2576 self.link_pattern_re = None
2577 self.invalid_XML_chars = None
2578 self.basic_pattern = None
2579 self.emot_and_basic = None
2580 self.sth_at_sth_dot_sth = None
2581 self.emot_only = None
2582 self.emoticons = []
2583 self.emoticons_animations = {}
2584 self.emoticons_images = {}
2586 cfg_was_read = parser.read()
2588 from common import latex
2589 gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
2590 latex.check_for_latex_support()
2592 gajim.logger.reset_shown_unread_messages()
2593 # override logging settings from config (don't take care of '-q' option)
2594 if gajim.config.get('verbose'):
2595 logging_helpers.set_verbose()
2597 # Is Gajim default app?
2598 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
2599 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
2601 for account in gajim.config.get_per('accounts'):
2602 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
2603 gajim.ZEROCONF_ACC_NAME = account
2604 break
2605 # Is gnome configured to activate row on single click ?
2606 try:
2607 import gconf
2608 client = gconf.client_get_default()
2609 click_policy = client.get_string(
2610 '/apps/nautilus/preferences/click_policy')
2611 if click_policy == 'single':
2612 gajim.single_click = True
2613 except Exception:
2614 pass
2615 # add default status messages if there is not in the config file
2616 if len(gajim.config.get_per('statusmsg')) == 0:
2617 default = gajim.config.statusmsg_default
2618 for msg in default:
2619 gajim.config.add_per('statusmsg', msg)
2620 gajim.config.set_per('statusmsg', msg, 'message',
2621 default[msg][0])
2622 gajim.config.set_per('statusmsg', msg, 'activity',
2623 default[msg][1])
2624 gajim.config.set_per('statusmsg', msg, 'subactivity',
2625 default[msg][2])
2626 gajim.config.set_per('statusmsg', msg, 'activity_text',
2627 default[msg][3])
2628 gajim.config.set_per('statusmsg', msg, 'mood',
2629 default[msg][4])
2630 gajim.config.set_per('statusmsg', msg, 'mood_text',
2631 default[msg][5])
2632 #add default themes if there is not in the config file
2633 theme = gajim.config.get('roster_theme')
2634 if not theme in gajim.config.get_per('themes'):
2635 gajim.config.set('roster_theme', _('default'))
2636 if len(gajim.config.get_per('themes')) == 0:
2637 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
2638 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2639 'groupfont', 'groupfontattrs', 'contacttextcolor',
2640 'contactbgcolor', 'contactfont', 'contactfontattrs',
2641 'bannertextcolor', 'bannerbgcolor']
2643 default = gajim.config.themes_default
2644 for theme_name in default:
2645 gajim.config.add_per('themes', theme_name)
2646 theme = default[theme_name]
2647 for o in d:
2648 gajim.config.set_per('themes', theme_name, o,
2649 theme[d.index(o)])
2651 if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
2652 gtkgui_helpers.autodetect_browser_mailer()
2654 gajim.idlequeue = idlequeue.get_idlequeue()
2655 # resolve and keep current record of resolved hosts
2656 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
2657 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
2658 self.handle_event_file_rcv_completed,
2659 self.handle_event_file_progress,
2660 self.handle_event_file_error)
2661 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
2662 gajim.default_session_type = ChatControlSession
2664 # Creating Network Events Controller
2665 from common import nec
2666 gajim.nec = nec.NetworkEventsController()
2668 self.create_core_handlers_list()
2669 self.register_core_handlers()
2671 if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
2672 and gajim.HAVE_ZEROCONF:
2673 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
2674 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
2675 for account in gajim.config.get_per('accounts'):
2676 if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
2677 gajim.config.get_per('accounts', account, 'active'):
2678 gajim.connections[account] = Connection(account)
2680 # gtk hooks
2681 gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
2682 gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
2683 gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
2685 self.instances = {}
2687 for a in gajim.connections:
2688 self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
2689 'search': {}, 'online_dialog': {}}
2690 # online_dialog contains all dialogs that have a meaning only when
2691 # we are not disconnected
2692 self.minimized_controls[a] = {}
2693 gajim.contacts.add_account(a)
2694 gajim.groups[a] = {}
2695 gajim.gc_connected[a] = {}
2696 gajim.automatic_rooms[a] = {}
2697 gajim.newly_added[a] = []
2698 gajim.to_be_removed[a] = []
2699 gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
2700 gajim.block_signed_in_notifications[a] = True
2701 gajim.sleeper_state[a] = 0
2702 gajim.encrypted_chats[a] = []
2703 gajim.last_message_time[a] = {}
2704 gajim.status_before_autoaway[a] = ''
2705 gajim.transport_avatar[a] = {}
2706 gajim.gajim_optional_features[a] = []
2707 gajim.caps_hash[a] = ''
2709 helpers.update_optional_features()
2710 # prepopulate data which we are sure of; note: we do not log these info
2711 for account in gajim.connections:
2712 gajimcaps = caps_cache.capscache[('sha-1',
2713 gajim.caps_hash[account])]
2714 gajimcaps.identities = [gajim.gajim_identity]
2715 gajimcaps.features = gajim.gajim_common_features + \
2716 gajim.gajim_optional_features[account]
2718 self.remote_ctrl = None
2720 if gajim.config.get('networkmanager_support') and \
2721 dbus_support.supported:
2722 import network_manager_listener
2724 # Handle gnome screensaver
2725 if dbus_support.supported:
2726 def gnome_screensaver_ActiveChanged_cb(active):
2727 if not active:
2728 for account in gajim.connections:
2729 if gajim.sleeper_state[account] == 'autoaway-forced':
2730 # We came back online ofter gnome-screensaver
2731 # autoaway
2732 self.roster.send_status(account, 'online',
2733 gajim.status_before_autoaway[account])
2734 gajim.status_before_autoaway[account] = ''
2735 gajim.sleeper_state[account] = 'online'
2736 return
2737 if not gajim.config.get('autoaway'):
2738 # Don't go auto away if user disabled the option
2739 return
2740 for account in gajim.connections:
2741 if account not in gajim.sleeper_state or \
2742 not gajim.sleeper_state[account]:
2743 continue
2744 if gajim.sleeper_state[account] == 'online':
2745 # we save out online status
2746 gajim.status_before_autoaway[account] = \
2747 gajim.connections[account].status
2748 # we go away (no auto status) [we pass True to auto
2749 # param]
2750 auto_message = gajim.config.get('autoaway_message')
2751 if not auto_message:
2752 auto_message = gajim.connections[account].status
2753 else:
2754 auto_message = auto_message.replace('$S',
2755 '%(status)s')
2756 auto_message = auto_message.replace('$T',
2757 '%(time)s')
2758 auto_message = auto_message % {
2759 'status': gajim.status_before_autoaway[account],
2760 'time': gajim.config.get('autoxatime')}
2761 self.roster.send_status(account, 'away', auto_message,
2762 auto=True)
2763 gajim.sleeper_state[account] = 'autoaway-forced'
2765 try:
2766 bus = dbus.SessionBus()
2767 bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
2768 'ActiveChanged', 'org.gnome.ScreenSaver')
2769 except Exception:
2770 pass
2772 self.show_vcard_when_connect = []
2774 self.sleeper = common.sleepy.Sleepy(
2775 gajim.config.get('autoawaytime') * 60, # make minutes to seconds
2776 gajim.config.get('autoxatime') * 60)
2778 gtkgui_helpers.make_jabber_state_images()
2780 self.systray_enabled = False
2782 import statusicon
2783 self.systray = statusicon.StatusIcon()
2785 pix = gtkgui_helpers.get_icon_pixmap('gajim', 32)
2786 if pix is not None:
2787 # set the icon to all windows
2788 gtk.window_set_default_icon(pix)
2790 self.init_emoticons()
2791 self.make_regexps()
2793 # get transports type from DB
2794 gajim.transport_type = gajim.logger.get_transports_type()
2796 # test is dictionnary is present for speller
2797 if gajim.config.get('use_speller'):
2798 lang = gajim.config.get('speller_language')
2799 if not lang:
2800 lang = gajim.LANG
2801 tv = gtk.TextView()
2802 try:
2803 import gtkspell
2804 spell = gtkspell.Spell(tv, lang)
2805 except (ImportError, TypeError, RuntimeError, OSError):
2806 dialogs.AspellDictError(lang)
2808 if gajim.config.get('soundplayer') == '':
2809 # only on first time Gajim starts
2810 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
2811 for command in commands:
2812 if helpers.is_in_path(command):
2813 if command == 'aplay':
2814 command += ' -q'
2815 gajim.config.set('soundplayer', command)
2816 break
2818 self.last_ftwindow_update = 0
2820 self.music_track_changed_signal = None
2823 class PassphraseRequest:
2824 def __init__(self, keyid):
2825 self.keyid = keyid
2826 self.callbacks = []
2827 self.dialog_created = False
2828 self.dialog = None
2829 self.passphrase = None
2830 self.completed = False
2832 def interrupt(self, account=None):
2833 if account:
2834 for (acct, cb) in self.callbacks:
2835 if acct == account:
2836 self.callbacks.remove((acct, cb))
2837 else:
2838 self.callbacks = []
2839 if not len(self.callbacks):
2840 self.dialog.window.destroy()
2842 def run_callback(self, account, callback):
2843 gajim.connections[account].gpg_passphrase(self.passphrase)
2844 callback()
2846 def add_callback(self, account, cb):
2847 if self.completed:
2848 self.run_callback(account, cb)
2849 else:
2850 self.callbacks.append((account, cb))
2851 if not self.dialog_created:
2852 self.create_dialog(account)
2854 def complete(self, passphrase):
2855 self.passphrase = passphrase
2856 self.completed = True
2857 if passphrase is not None:
2858 gobject.timeout_add_seconds(30,
2859 gajim.interface.forget_gpg_passphrase, self.keyid)
2860 for (account, cb) in self.callbacks:
2861 self.run_callback(account, cb)
2862 self.callbacks = []
2864 def create_dialog(self, account):
2865 title = _('Passphrase Required')
2866 second = _('Enter GPG key passphrase for key %(keyid)s (account '
2867 '%(account)s).') % {'keyid': self.keyid, 'account': account}
2869 def _cancel():
2870 # user cancelled, continue without GPG
2871 self.complete(None)
2873 def _ok(passphrase, checked, count):
2874 result = gajim.connections[account].test_gpg_passphrase(passphrase)
2875 if result == 'ok':
2876 # passphrase is good
2877 self.complete(passphrase)
2878 return
2879 elif result == 'expired':
2880 dialogs.ErrorDialog(_('GPG key expired'),
2881 _('Your GPG key has expired, you will be connected to %s '
2882 'without OpenPGP.') % account)
2883 # Don't try to connect with GPG
2884 gajim.connections[account].continue_connect_info[2] = False
2885 self.complete(None)
2886 return
2888 if count < 3:
2889 # ask again
2890 dialogs.PassphraseDialog(_('Wrong Passphrase'),
2891 _('Please retype your GPG passphrase or press Cancel.'),
2892 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
2893 else:
2894 # user failed 3 times, continue without GPG
2895 self.complete(None)
2897 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
2898 1), cancel_handler=_cancel)
2899 self.dialog_created = True
2902 class ThreadInterface:
2903 def __init__(self, func, func_args=(), callback=None, callback_args=()):
2905 Call a function in a thread
2907 def thread_function(func, func_args, callback, callback_args):
2908 output = func(*func_args)
2909 if callback:
2910 gobject.idle_add(callback, output, *callback_args)
2912 Thread(target=thread_function, args=(func, func_args, callback,
2913 callback_args)).start()