ignore groupchat invitations from contacts that are not in roster if ignore_unknown_c...
[gajim.git] / src / gui_interface.py
blob716224fcb5d115de95fe06bcdc5183520e1bf206
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
84 import roster_window
85 import profile_window
86 import config
87 from threading import Thread
88 from common import ged
90 gajimpaths = common.configpaths.gajimpaths
91 config_filename = gajimpaths['CONFIG_FILE']
93 from common import optparser
94 parser = optparser.OptionsParser(config_filename)
96 import logging
97 log = logging.getLogger('gajim.interface')
99 class Interface:
101 ################################################################################
102 ### Methods handling events from connection
103 ################################################################################
105 def handle_event_warning(self, unused, data):
106 #('WARNING', account, (title_text, section_text))
107 dialogs.WarningDialog(data[0], data[1])
109 def handle_event_error(self, unused, data):
110 #('ERROR', account, (title_text, section_text))
111 dialogs.ErrorDialog(data[0], data[1])
113 def handle_event_db_error(self, unused, data):
114 #('DB_ERROR', account, (title_text, section_text))
115 if self.db_error_dialog:
116 return
117 self.db_error_dialog = dialogs.ErrorDialog(data[0], data[1])
118 def destroyed(win):
119 self.db_error_dialog = None
120 self.db_error_dialog.connect('destroy', destroyed)
122 def handle_event_information(self, unused, data):
123 #('INFORMATION', account, (title_text, section_text))
124 dialogs.InformationDialog(data[0], data[1])
126 def handle_ask_new_nick(self, account, room_jid):
127 title = _('Unable to join group chat')
128 prompt = _('Your desired nickname in group chat %s is in use or '
129 'registered by another occupant.\nPlease specify another nickname '
130 'below:') % room_jid
131 check_text = _('Always use this nickname when there is a conflict')
132 if 'change_nick_dialog' in self.instances:
133 self.instances['change_nick_dialog'].add_room(account, room_jid,
134 prompt)
135 else:
136 self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
137 account, room_jid, title, prompt)
139 def handle_event_http_auth(self, obj):
140 #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
141 def response(account, answer):
142 obj.conn.build_http_auth_answer(obj.stanza, answer)
144 def on_yes(is_checked, obj):
145 response(obj, 'yes')
147 account = obj.conn.name
148 sec_msg = _('Do you accept this request?')
149 if gajim.get_number_of_connected_accounts() > 1:
150 sec_msg = _('Do you accept this request on account %s?') % account
151 if obj.msg:
152 sec_msg = obj.msg + '\n' + sec_msg
153 dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
154 '%(url)s (id: %(id)s)') % {'method': obj.method, 'url': obj.url,
155 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
156 on_response_no=(response, obj, 'no'))
158 def handle_event_iq_error(self, obj):
159 #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode))
160 if unicode(obj.errcode) in ('400', '403', '406') and obj.id_:
161 # show the error dialog
162 ft = self.instances['file_transfers']
163 sid = obj.id_
164 if len(obj.id_) > 3 and obj.id_[2] == '_':
165 sid = obj.id_[3:]
166 if sid in ft.files_props['s']:
167 file_props = ft.files_props['s'][sid]
168 if unicode(obj.errcode) == '400':
169 file_props['error'] = -3
170 else:
171 file_props['error'] = -4
172 self.handle_event_file_request_error(obj.conn.name, (obj.fjid,
173 file_props, 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 return
311 def handle_event_presence(self, obj):
312 # 'NOTIFY' (account, (jid, status, status message, resource,
313 # priority, # keyID, timestamp, contact_nickname))
315 # Contact changed show
317 account = obj.conn.name
318 jid = obj.jid
319 show = obj.show
320 status = obj.status
321 resource = obj.resource or ''
323 jid_list = gajim.contacts.get_jid_list(account)
325 # unset custom status
326 if (obj.old_show == 0 and obj.new_show > 1) or \
327 (obj.old_show > 1 and obj.new_show == 0 and obj.conn.connected > 1):
328 if account in self.status_sent_to_users and \
329 jid in self.status_sent_to_users[account]:
330 del self.status_sent_to_users[account][jid]
332 if gajim.jid_is_transport(jid):
333 # It must be an agent
335 # transport just signed in/out, don't show
336 # popup notifications for 30s
337 account_jid = account + '/' + jid
338 gajim.block_signed_in_notifications[account_jid] = True
339 gobject.timeout_add_seconds(30,
340 self.unblock_signed_in_notifications, account_jid)
342 else:
343 # It isn't an agent
344 # Notifications
345 obj.show_notif = True
346 for c in obj.contact_list:
347 if c.resource == resource:
348 # we look for other connected resources
349 continue
350 if c.show not in ('offline', 'error'):
351 obj.show_notif = False
352 break
353 if obj.show_notif:
354 # no other resource is connected, let's look in metacontacts
355 family = gajim.contacts.get_metacontacts_family(account,
356 jid)
357 for info in family:
358 acct_ = info['account']
359 jid_ = info['jid']
360 c_ = gajim.contacts.get_contact_with_highest_priority(
361 acct_, jid_)
362 if not c_:
363 continue
364 if c_.show not in ('offline', 'error'):
365 obj.show_notif = False
366 break
367 if obj.show_notif:
368 if obj.old_show < 2 and obj.new_show > 1:
369 notify.notify('contact_connected', jid, account, status)
371 elif obj.old_show > 1 and obj.new_show < 2:
372 notify.notify('contact_disconnected', jid, account, status)
373 # Status change (not connected/disconnected or
374 # error (<1))
375 elif obj.new_show > 1:
376 notify.notify('status_change', jid, account, [obj.new_show,
377 status])
379 highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
380 is_highest = (highest and highest.resource == resource)
382 # disconnect the session from the ctrl if the highest resource has
383 # changed
384 if (obj.was_highest and not is_highest) or \
385 (not obj.was_highest and is_highest):
386 ctrl = self.msg_win_mgr.get_control(jid, account)
387 if ctrl:
388 ctrl.no_autonegotiation = False
389 ctrl.set_session(None)
390 ctrl.contact = highest
392 def handle_event_msgerror(self, account, array):
393 #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[,
394 # session]))
395 full_jid_with_resource = array[0]
396 jids = full_jid_with_resource.split('/', 1)
397 jid = jids[0]
399 if array[1] == '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 = None
405 if len(array) > 5:
406 session = array[5]
408 gc_control = self.msg_win_mgr.get_gc_control(jid, account)
409 if not gc_control and \
410 jid in self.minimized_controls[account]:
411 gc_control = self.minimized_controls[account][jid]
412 if gc_control and gc_control.type_id != message_control.TYPE_GC:
413 gc_control = None
414 if gc_control:
415 if len(jids) > 1: # it's a pm
416 nick = jids[1]
418 if session:
419 ctrl = session.control
420 else:
421 ctrl = self.msg_win_mgr.get_control(full_jid_with_resource,
422 account)
424 if not ctrl:
425 tv = gc_control.list_treeview
426 model = tv.get_model()
427 iter_ = gc_control.get_contact_iter(nick)
428 if iter_:
429 show = model[iter_][3]
430 else:
431 show = 'offline'
432 gc_c = gajim.contacts.create_gc_contact(room_jid=jid,
433 account=account, name=nick, show=show)
434 ctrl = self.new_private_chat(gc_c, account, session)
436 ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
437 'code': array[1], 'msg': array[2]}, 'status')
438 return
440 gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
441 'code': array[1], 'msg': array[2]}, 'status')
442 if gc_control.parent_win and \
443 gc_control.parent_win.get_active_jid() == jid:
444 gc_control.set_subject(gc_control.subject)
445 return
447 if gajim.jid_is_transport(jid):
448 jid = jid.replace('@', '')
449 msg = array[2]
450 if array[3]:
451 msg = _('error while sending %(message)s ( %(error)s )') % {
452 'message': array[3], 'error': msg}
453 if session:
454 session.roster_message(jid, msg, array[4], msg_type='error')
456 def handle_event_msgsent(self, obj):
457 #('MSGSENT', account, (jid, msg, keyID))
458 # do not play sound when standalone chatstate message (eg no msg)
459 if obj.message and gajim.config.get_per('soundevents', 'message_sent',
460 'enabled'):
461 helpers.play_sound('message_sent')
463 def handle_event_msgnotsent(self, obj):
464 #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
465 msg = _('error while sending %(message)s ( %(error)s )') % {
466 'message': obj.message, 'error': obj.error}
467 if not obj.session:
468 # No session. This can happen when sending a message from
469 # gajim-remote
470 log.warn(msg)
471 return
472 obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name,
473 msg_type='error')
475 def handle_event_subscribe_presence(self, obj):
476 #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
477 account = obj.conn.name
478 if helpers.allow_popup_window(account) or not self.systray_enabled:
479 dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account,
480 obj.user_nick)
481 return
483 self.add_event(account, obj.jid, 'subscription_request', (obj.status,
484 obj.user_nick))
486 if helpers.allow_showing_notification(account):
487 path = gtkgui_helpers.get_icon_path('gajim-subscription_request',
489 event_type = _('Subscription request')
490 notify.popup(event_type, obj.jid, account, 'subscription_request',
491 path, event_type, obj.jid)
493 def handle_event_subscribed_presence(self, obj):
494 #('SUBSCRIBED', account, (jid, resource))
495 account = obj.conn.name
496 if obj.jid in gajim.contacts.get_jid_list(account):
497 c = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
498 c.resource = obj.resource
499 self.roster.remove_contact_from_groups(c.jid, account,
500 [_('Not in Roster'), _('Observers')], update=False)
501 else:
502 keyID = ''
503 attached_keys = gajim.config.get_per('accounts', account,
504 'attached_gpg_keys').split()
505 if obj.jid in attached_keys:
506 keyID = attached_keys[attached_keys.index(obj.jid) + 1]
507 name = obj.jid.split('@', 1)[0]
508 name = name.split('%', 1)[0]
509 contact1 = gajim.contacts.create_contact(jid=obj.jid,
510 account=account, name=name, groups=[], show='online',
511 status='online', ask='to', resource=obj.resource, keyID=keyID)
512 gajim.contacts.add_contact(account, contact1)
513 self.roster.add_contact(obj.jid, account)
514 dialogs.InformationDialog(_('Authorization accepted'),
515 _('The contact "%s" has authorized you to see his or her status.')
516 % obj.jid)
518 def show_unsubscribed_dialog(self, account, contact):
519 def on_yes(is_checked, list_):
520 self.roster.on_req_usub(None, list_)
521 list_ = [(contact, account)]
522 dialogs.YesNoDialog(
523 _('Contact "%s" removed subscription from you') % contact.jid,
524 _('You will always see him or her as offline.\nDo you want to '
525 'remove him or her from your contact list?'),
526 on_response_yes=(on_yes, list_))
527 # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
528 # not show deny
530 def handle_event_unsubscribed_presence(self, obj):
531 #('UNSUBSCRIBED', account, jid)
532 account = obj.conn.name
533 contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
534 if not contact:
535 return
537 if helpers.allow_popup_window(account) or not self.systray_enabled:
538 self.show_unsubscribed_dialog(account, contact)
539 return
541 self.add_event(account, obj.jid, 'unsubscribed', contact)
543 if helpers.allow_showing_notification(account):
544 path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
545 event_type = _('Unsubscribed')
546 notify.popup(event_type, obj.jid, account, 'unsubscribed', path,
547 event_type, obj.jid)
549 def handle_event_register_agent_info(self, obj):
550 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
551 # info in a dataform if is_form is True
552 if obj.is_form or 'instructions' in obj.config:
553 config.ServiceRegistrationWindow(obj.agent, obj.config,
554 obj.conn.name, obj.is_form)
555 else:
556 dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \
557 obj.agent, _('Check your connection or try again later.'))
559 def handle_event_vcard(self, obj):
560 # ('VCARD', account, data)
561 '''vcard holds the vcard data'''
562 our_jid = gajim.get_jid_from_account(obj.conn.name)
563 if obj.jid == our_jid:
564 if obj.nickname:
565 gajim.nicks[obj.conn.name] = obj.nickname
566 if obj.conn.name in self.show_vcard_when_connect:
567 self.show_vcard_when_connect.remove(obj.conn.name)
569 def handle_event_last_status_time(self, obj):
570 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
571 if obj.seconds < 0:
572 # Ann error occured
573 return
574 account = obj.conn.name
575 c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
576 if c: # c can be none if it's a gc contact
577 if obj.status:
578 c.status = obj.status
579 self.roster.draw_contact(c.jid, account) # draw offline status
580 last_time = time.localtime(time.time() - obj.seconds)
581 if c.show == 'offline':
582 c.last_status_time = last_time
583 else:
584 c.last_activity_time = last_time
585 if self.roster.tooltip.id and self.roster.tooltip.win:
586 self.roster.tooltip.update_last_time(last_time)
588 def handle_event_gc_config(self, obj):
589 #('GC_CONFIG', account, (jid, form_node)) config is a dict
590 account = obj.conn.name
591 if obj.jid in gajim.automatic_rooms[account]:
592 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
593 # We're converting chat to muc. allow participants to invite
594 for f in obj.dataform.iter_fields():
595 if f.var == 'muc#roomconfig_allowinvites':
596 f.value = True
597 elif f.var == 'muc#roomconfig_publicroom':
598 f.value = False
599 elif f.var == 'muc#roomconfig_membersonly':
600 f.value = True
601 elif f.var == 'public_list':
602 f.value = False
603 obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged())
604 else:
605 # use default configuration
606 obj.conn.send_gc_config(obj.jid, obj.form_node)
607 # invite contacts
608 # check if it is necessary to add <continue />
609 continue_tag = False
610 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
611 continue_tag = True
612 if 'invities' in gajim.automatic_rooms[account][obj.jid]:
613 for jid in gajim.automatic_rooms[account][obj.jid]['invities']:
614 obj.conn.send_invite(obj.jid, jid,
615 continue_tag=continue_tag)
616 del gajim.automatic_rooms[account][obj.jid]
617 elif obj.jid not in self.instances[account]['gc_config']:
618 self.instances[account]['gc_config'][obj.jid] = \
619 config.GroupchatConfigWindow(account, obj.jid, obj.dataform)
621 def handle_event_gc_affiliation(self, obj):
622 #('GC_AFFILIATION', account, (room_jid, users_dict))
623 account = obj.conn.name
624 if obj.jid in self.instances[account]['gc_config']:
625 self.instances[account]['gc_config'][obj.jid].\
626 affiliation_list_received(obj.users_dict)
628 def handle_event_gc_invitation(self, obj):
629 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
630 jid = gajim.get_jid_without_resource(obj.jid_from)
631 account = obj.conn.name
632 if helpers.allow_popup_window(account) or not self.systray_enabled:
633 dialogs.InvitationReceivedDialog(account, obj.room_jid, jid,
634 obj.password, obj.reason, is_continued=obj.is_continued)
635 return
637 self.add_event(account, jid, 'gc-invitation', (obj.room_jid,
638 obj.reason, obj.password, obj.is_continued))
640 if helpers.allow_showing_notification(account):
641 path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
642 event_type = _('Groupchat Invitation')
643 notify.popup(event_type, jid, account, 'gc-invitation', path,
644 event_type, obj.room_jid)
646 def forget_gpg_passphrase(self, keyid):
647 if keyid in self.gpg_passphrase:
648 del self.gpg_passphrase[keyid]
649 return False
651 def handle_event_bad_gpg_passphrase(self, obj):
652 #('BAD_PASSPHRASE', account, ())
653 if obj.use_gpg_agent:
654 sectext = _('You configured Gajim to use GPG agent, but there is no'
655 ' GPG agent running or it returned a wrong passphrase.\n')
656 sectext += _('You are currently connected without your OpenPGP '
657 'key.')
658 dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
659 else:
660 path = gtkgui_helpers.get_icon_path('gajim-warning', 48)
661 account = obj.conn.name
662 notify.popup('warning', account, account, 'warning', path,
663 _('OpenGPG Passphrase Incorrect'),
664 _('You are currently connected without your OpenPGP key.'))
665 self.forget_gpg_passphrase(obj.keyID)
667 def handle_event_gpg_password_required(self, obj):
668 #('GPG_PASSWORD_REQUIRED', account, (callback,))
669 if obj.keyid in self.gpg_passphrase:
670 request = self.gpg_passphrase[obj.keyid]
671 else:
672 request = PassphraseRequest(obj.keyid)
673 self.gpg_passphrase[obj.keyid] = request
674 request.add_callback(obj.conn.name, obj.callback)
676 def handle_event_gpg_trust_key(self, obj):
677 #('GPG_ALWAYS_TRUST', account, callback)
678 def on_yes(checked):
679 if checked:
680 obj.conn.gpg.always_trust = True
681 obj.callback(True)
683 def on_no():
684 obj.callback(False)
686 dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
687 'encrypt this chat is not trusted. Do you really want to encrypt '
688 'this message?'), checktext=_('_Do not ask me again'),
689 on_response_yes=on_yes, on_response_no=on_no)
691 def handle_event_password_required(self, obj):
692 #('PASSWORD_REQUIRED', account, None)
693 account = obj.conn.name
694 if account in self.pass_dialog:
695 return
696 text = _('Enter your password for account %s') % account
697 if passwords.USER_HAS_GNOMEKEYRING and \
698 not passwords.USER_USES_GNOMEKEYRING:
699 text += '\n' + _('Gnome Keyring is installed but not \
700 correctly started (environment variable probably not \
701 correctly set)')
703 def on_ok(passphrase, save):
704 if save:
705 gajim.config.set_per('accounts', account, 'savepass', True)
706 passwords.save_password(account, passphrase)
707 obj.conn.set_password(passphrase)
708 del self.pass_dialog[account]
710 def on_cancel():
711 self.roster.set_state(account, 'offline')
712 self.roster.update_status_combobox()
713 del self.pass_dialog[account]
715 self.pass_dialog[account] = dialogs.PassphraseDialog(
716 _('Password Required'), text, _('Save password'), ok_handler=on_ok,
717 cancel_handler=on_cancel)
719 def handle_event_roster_info(self, obj):
720 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
721 account = obj.conn.name
722 contacts = gajim.contacts.get_contacts(account, obj.jid)
723 if (not obj.sub or obj.sub == 'none') and \
724 (not obj.ask or obj.ask == 'none') and not obj.nickname and \
725 not obj.groups:
726 # contact removed us.
727 if contacts:
728 self.roster.remove_contact(obj.jid, account, backend=True)
729 return
730 elif not contacts:
731 if obj.sub == 'remove':
732 return
733 # Add new contact to roster
734 contact = gajim.contacts.create_contact(jid=obj.jid,
735 account=account, name=obj.nickname, groups=obj.groups,
736 show='offline', sub=obj.sub, ask=obj.ask)
737 gajim.contacts.add_contact(account, contact)
738 self.roster.add_contact(obj.jid, account)
739 else:
740 # If contact has changed (sub, ask or group) update roster
741 # Mind about observer status changes:
742 # According to xep 0162, a contact is not an observer anymore when
743 # we asked for auth, so also remove him if ask changed
744 old_groups = contacts[0].groups
745 if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\
746 or old_groups != obj.groups:
747 # c.get_shown_groups() has changed. Reflect that in
748 # roster_winodow
749 self.roster.remove_contact(obj.jid, account, force=True)
750 for contact in contacts:
751 contact.name = obj.nickname or ''
752 contact.sub = obj.sub
753 contact.ask = obj.ask
754 contact.groups = obj.groups or []
755 self.roster.add_contact(obj.jid, account)
756 # Refilter and update old groups
757 for group in old_groups:
758 self.roster.draw_group(group, account)
759 self.roster.draw_contact(obj.jid, account)
761 def handle_event_bookmarks(self, obj):
762 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
763 # We received a bookmark item from the server (JEP48)
764 # Auto join GC windows if neccessary
766 self.roster.set_actions_menu_needs_rebuild()
767 invisible_show = gajim.SHOW_LIST.index('invisible')
768 # do not autojoin if we are invisible
769 if obj.conn.connected == invisible_show:
770 return
772 self.auto_join_bookmarks(obj.conn.name)
774 def handle_event_file_send_error(self, account, array):
775 jid = array[0]
776 file_props = array[1]
777 ft = self.instances['file_transfers']
778 ft.set_status(file_props['type'], file_props['sid'], 'stop')
780 if helpers.allow_popup_window(account):
781 ft.show_send_error(file_props)
782 return
784 self.add_event(account, jid, 'file-send-error', file_props)
786 if helpers.allow_showing_notification(account):
787 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
788 event_type = _('File Transfer Error')
789 notify.popup(event_type, jid, account, 'file-send-error', path,
790 event_type, file_props['name'])
792 def handle_event_gmail_notify(self, obj):
793 jid = obj.jid
794 gmail_new_messages = int(obj.newmsgs)
795 gmail_messages_list = obj.gmail_messages_list
796 if not gajim.config.get('notify_on_new_gmail_email'):
797 return
798 path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
799 title = _('New mail on %(gmail_mail_address)s') % \
800 {'gmail_mail_address': jid}
801 text = i18n.ngettext('You have %d new mail conversation',
802 'You have %d new mail conversations', gmail_new_messages,
803 gmail_new_messages, gmail_new_messages)
805 if gajim.config.get('notify_on_new_gmail_email_extra'):
806 cnt = 0
807 for gmessage in gmail_messages_list:
808 # FIXME: emulate Gtalk client popups. find out what they
809 # parse and how they decide what to show each message has a
810 # 'From', 'Subject' and 'Snippet' field
811 if cnt >= 5:
812 break
813 senders = ',\n '.join(reversed(gmessage['From']))
814 text += _('\n\nFrom: %(from_address)s\nSubject: '
815 '%(subject)s\n%(snippet)s') % {'from_address': senders,
816 'subject': gmessage['Subject'],
817 'snippet': gmessage['Snippet']}
818 cnt += 1
820 command = gajim.config.get('notify_on_new_gmail_email_command')
821 if command:
822 Popen(command, shell=True)
824 if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
825 helpers.play_sound('gmail_received')
826 notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail',
827 path_to_image=path, title=title, text=text)
829 def handle_event_file_request_error(self, account, array):
830 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
831 jid, file_props, errmsg = array
832 jid = gajim.get_jid_without_resource(jid)
833 ft = self.instances['file_transfers']
834 ft.set_status(file_props['type'], file_props['sid'], 'stop')
835 errno = file_props['error']
837 if helpers.allow_popup_window(account):
838 if errno in (-4, -5):
839 ft.show_stopped(jid, file_props, errmsg)
840 else:
841 ft.show_request_error(file_props)
842 return
844 if errno in (-4, -5):
845 msg_type = 'file-error'
846 else:
847 msg_type = 'file-request-error'
849 self.add_event(account, jid, msg_type, file_props)
851 if helpers.allow_showing_notification(account):
852 # check if we should be notified
853 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
854 event_type = _('File Transfer Error')
855 notify.popup(event_type, jid, account, msg_type, path,
856 title = event_type, text = file_props['name'])
858 def handle_event_file_request(self, account, array):
859 jid = array[0]
860 jid = gajim.get_jid_without_resource(jid)
861 if jid not in gajim.contacts.get_jid_list(account):
862 keyID = ''
863 attached_keys = gajim.config.get_per('accounts', account,
864 'attached_gpg_keys').split()
865 if jid in attached_keys:
866 keyID = attached_keys[attached_keys.index(jid) + 1]
867 contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
868 account=account, keyID=keyID)
869 gajim.contacts.add_contact(account, contact)
870 self.roster.add_contact(contact.jid, account)
871 file_props = array[1]
872 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
874 if helpers.allow_popup_window(account):
875 self.instances['file_transfers'].show_file_request(account, contact,
876 file_props)
877 return
879 self.add_event(account, jid, 'file-request', file_props)
881 if helpers.allow_showing_notification(account):
882 path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
883 txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
884 account, jid)
885 event_type = _('File Transfer Request')
886 notify.popup(event_type, jid, account, 'file-request',
887 path_to_image = path, title = event_type, text = txt)
889 def handle_event_file_error(self, title, message):
890 dialogs.ErrorDialog(title, message)
892 def handle_event_file_progress(self, account, file_props):
893 if time.time() - self.last_ftwindow_update > 0.5:
894 # update ft window every 500ms
895 self.last_ftwindow_update = time.time()
896 self.instances['file_transfers'].set_progress(file_props['type'],
897 file_props['sid'], file_props['received-len'])
899 def handle_event_file_rcv_completed(self, account, file_props):
900 ft = self.instances['file_transfers']
901 if file_props['error'] == 0:
902 ft.set_progress(file_props['type'], file_props['sid'],
903 file_props['received-len'])
904 else:
905 ft.set_status(file_props['type'], file_props['sid'], 'stop')
906 if 'stalled' in file_props and file_props['stalled'] or \
907 'paused' in file_props and file_props['paused']:
908 return
909 if file_props['type'] == 'r': # we receive a file
910 jid = unicode(file_props['sender'])
911 else: # we send a file
912 jid = unicode(file_props['receiver'])
914 if helpers.allow_popup_window(account):
915 if file_props['error'] == 0:
916 if gajim.config.get('notify_on_file_complete'):
917 ft.show_completed(jid, file_props)
918 elif file_props['error'] == -1:
919 ft.show_stopped(jid, file_props,
920 error_msg=_('Remote contact stopped transfer'))
921 elif file_props['error'] == -6:
922 ft.show_stopped(jid, file_props,
923 error_msg=_('Error opening file'))
924 return
926 msg_type = ''
927 event_type = ''
928 if file_props['error'] == 0 and gajim.config.get(
929 'notify_on_file_complete'):
930 msg_type = 'file-completed'
931 event_type = _('File Transfer Completed')
932 elif file_props['error'] in (-1, -6):
933 msg_type = 'file-stopped'
934 event_type = _('File Transfer Stopped')
936 if event_type == '':
937 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
938 # this should never happen but it does. see process_result() in
939 # socks5.py
940 # who calls this func (sth is really wrong unless this func is also
941 # registered as progress_cb
942 return
944 if msg_type:
945 self.add_event(account, jid, msg_type, file_props)
947 if file_props is not None:
948 if file_props['type'] == 'r':
949 # get the name of the sender, as it is in the roster
950 sender = unicode(file_props['sender']).split('/')[0]
951 name = gajim.contacts.get_first_contact_from_jid(account,
952 sender).get_shown_name()
953 filename = os.path.basename(file_props['file-name'])
954 if event_type == _('File Transfer Completed'):
955 txt = _('You successfully received %(filename)s from '
956 '%(name)s.') % {'filename': filename, 'name': name}
957 img_name = 'gajim-ft_done'
958 else: # ft stopped
959 txt = _('File transfer of %(filename)s from %(name)s '
960 'stopped.') % {'filename': filename, 'name': name}
961 img_name = 'gajim-ft_stopped'
962 else:
963 receiver = file_props['receiver']
964 if hasattr(receiver, 'jid'):
965 receiver = receiver.jid
966 receiver = receiver.split('/')[0]
967 # get the name of the contact, as it is in the roster
968 name = gajim.contacts.get_first_contact_from_jid(account,
969 receiver).get_shown_name()
970 filename = os.path.basename(file_props['file-name'])
971 if event_type == _('File Transfer Completed'):
972 txt = _('You successfully sent %(filename)s to %(name)s.')\
973 % {'filename': filename, 'name': name}
974 img_name = 'gajim-ft_done'
975 else: # ft stopped
976 txt = _('File transfer of %(filename)s to %(name)s '
977 'stopped.') % {'filename': filename, 'name': name}
978 img_name = 'gajim-ft_stopped'
979 path = gtkgui_helpers.get_icon_path(img_name, 48)
980 else:
981 txt = ''
982 path = ''
984 if gajim.config.get('notify_on_file_complete') and \
985 (gajim.config.get('autopopupaway') or \
986 gajim.connections[account].connected in (2, 3)):
987 # we want to be notified and we are online/chat or we don't mind
988 # bugged when away/na/busy
989 notify.popup(event_type, jid, account, msg_type, path_to_image=path,
990 title=event_type, text=txt)
992 def ask_offline_status(self, account):
993 for contact in gajim.contacts.iter_contacts(account):
994 gajim.connections[account].request_last_status_time(contact.jid,
995 contact.resource)
997 def handle_event_signed_in(self, obj):
999 SIGNED_IN event is emitted when we sign in, so handle it
1001 # ('SIGNED_IN', account, ())
1002 # block signed in notifications for 30 seconds
1003 account = obj.conn.name
1004 gajim.block_signed_in_notifications[account] = True
1005 state = self.sleeper.getState()
1006 connected = obj.conn.connected
1007 if gajim.config.get('ask_offline_status_on_connection'):
1008 # Ask offline status in 1 minute so w'are sure we got all online
1009 # presences
1010 gobject.timeout_add_seconds(60, self.ask_offline_status, account)
1011 if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
1012 # we go online or free for chat, so we activate auto status
1013 gajim.sleeper_state[account] = 'online'
1014 elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
1015 (state == common.sleepy.STATE_XA and connected == 5)):
1016 # If we are autoaway/xa and come back after a disconnection, do
1017 # nothing
1018 # Else disable autoaway
1019 gajim.sleeper_state[account] = 'off'
1021 if obj.conn.archiving_supported:
1022 # Start merging logs from server
1023 obj.conn.request_modifications_page(gajim.config.get_per('accounts',
1024 account, 'last_archiving_time'))
1025 gajim.config.set_per('accounts', account, 'last_archiving_time',
1026 time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
1028 invisible_show = gajim.SHOW_LIST.index('invisible')
1029 # We cannot join rooms if we are invisible
1030 if connected == invisible_show:
1031 return
1032 # send currently played music
1033 if obj.conn.pep_supported and dbus_support.supported and \
1034 gajim.config.get_per('accounts', account, 'publish_tune'):
1035 self.enable_music_listener()
1036 # enable location listener
1037 if obj.conn.pep_supported and dbus_support.supported and \
1038 gajim.config.get_per('accounts', account, 'publish_location'):
1039 location_listener.enable()
1042 def handle_event_metacontacts(self, obj):
1043 gajim.contacts.define_metacontacts(obj.conn.name, obj.meta_list)
1045 def handle_atom_entry(self, obj):
1046 AtomWindow.newAtomEntry(obj.atom_entry)
1048 def handle_event_failed_decrypt(self, obj):
1049 details = _('Unable to decrypt message from %s\nIt may have been '
1050 'tampered with.') % obj.fjid
1051 dialogs.WarningDialog(_('Unable to decrypt message'), details)
1053 def handle_event_zc_name_conflict(self, obj):
1054 def on_ok(new_name):
1055 gajim.config.set_per('accounts', account, 'name', new_name)
1056 show = obj.conn.old_show
1057 status = obj.conn.status
1058 obj.conn.username = new_name
1059 obj.conn.change_status(show, status)
1060 def on_cancel():
1061 obj.conn.change_status('offline', '')
1063 dlg = dialogs.InputDialog(_('Username Conflict'),
1064 _('Please type a new username for your local account'),
1065 input_str=obj.alt_name, is_modal=True, ok_handler=on_ok,
1066 cancel_handler=on_cancel)
1068 def handle_event_resource_conflict(self, obj):
1069 # ('RESOURCE_CONFLICT', account, ())
1070 # First we go offline, but we don't overwrite status message
1071 account = obj.conn.name
1072 conn = obj.conn
1073 self.roster.send_status(account, 'offline', conn.status)
1074 def on_ok(new_resource):
1075 gajim.config.set_per('accounts', account, 'resource', new_resource)
1076 self.roster.send_status(account, conn.old_show, conn.status)
1077 proposed_resource = conn.server_resource
1078 proposed_resource += gajim.config.get('gc_proposed_nick_char')
1079 dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
1080 _('You are already connected to this account with the same '
1081 'resource. Please type a new one'), resource=proposed_resource,
1082 ok_handler=on_ok)
1084 def handle_event_jingle_incoming(self, obj):
1085 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
1086 # data...))
1087 # TODO: conditional blocking if peer is not in roster
1089 account = obj.conn.name
1090 content_types = set(c[0] for c in obj.contents)
1092 # check type of jingle session
1093 if 'audio' in content_types or 'video' in content_types:
1094 # a voip session...
1095 # we now handle only voip, so the only thing we will do here is
1096 # not to return from function
1097 pass
1098 else:
1099 # unknown session type... it should be declined in common/jingle.py
1100 return
1102 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1103 or self.msg_win_mgr.get_control(obj.jid, account))
1104 if ctrl:
1105 if 'audio' in content_types:
1106 ctrl.set_audio_state('connection_received', obj.sid)
1107 if 'video' in content_types:
1108 ctrl.set_video_state('connection_received', obj.sid)
1110 dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1111 if dlg:
1112 dlg.add_contents(content_types)
1113 return
1115 if helpers.allow_popup_window(account):
1116 dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid,
1117 content_types)
1118 return
1120 self.add_event(account, obj.jid, 'jingle-incoming', (obj.fjid, obj.sid,
1121 content_types))
1123 if helpers.allow_showing_notification(account):
1124 # TODO: we should use another pixmap ;-)
1125 txt = _('%s wants to start a voice chat.') % \
1126 gajim.get_name_from_jid(account, obj.fjid)
1127 path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
1128 event_type = _('Voice Chat Request')
1129 notify.popup(event_type, obj.fjid, account, 'jingle-incoming',
1130 path_to_image=path, title=event_type, text=txt)
1132 def handle_event_jingle_connected(self, obj):
1133 # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
1134 if obj.media in ('audio', 'video'):
1135 account = obj.conn.name
1136 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1137 or self.msg_win_mgr.get_control(obj.jid, account))
1138 if ctrl:
1139 if obj.media == 'audio':
1140 ctrl.set_audio_state('connected', obj.sid)
1141 else:
1142 ctrl.set_video_state('connected', obj.sid)
1144 def handle_event_jingle_disconnected(self, obj):
1145 # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
1146 account = obj.conn.name
1147 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1148 or self.msg_win_mgr.get_control(obj.jid, account))
1149 if ctrl:
1150 if obj.media is None:
1151 ctrl.stop_jingle(sid=obj.sid, reason=obj.reason)
1152 elif obj.media == 'audio':
1153 ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason)
1154 elif obj.media == 'video':
1155 ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason)
1156 dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1157 if dialog:
1158 if obj.media is None:
1159 dialog.dialog.destroy()
1160 else:
1161 dialog.remove_contents((obj.media, ))
1163 def handle_event_jingle_error(self, obj):
1164 # ('JINGLE_ERROR', account, (peerjid, sid, reason))
1165 account = obj.conn.name
1166 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1167 or self.msg_win_mgr.get_control(obj.jid, account))
1168 if ctrl:
1169 ctrl.set_audio_state('error', reason=obj.reason)
1171 def handle_event_roster_item_exchange(self, obj):
1172 # data = (action in [add, delete, modify], exchange_list, jid_from)
1173 dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action,
1174 obj.exchange_items_list, obj.fjid)
1176 def handle_event_ssl_error(self, obj):
1177 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
1178 account = obj.conn.name
1179 server = gajim.config.get_per('accounts', account, 'hostname')
1181 def on_ok(is_checked):
1182 del self.instances[account]['online_dialog']['ssl_error']
1183 if is_checked[0]:
1184 # Check if cert is already in file
1185 certs = ''
1186 if os.path.isfile(gajim.MY_CACERTS):
1187 f = open(gajim.MY_CACERTS)
1188 certs = f.read()
1189 f.close()
1190 if obj.cert in certs:
1191 dialogs.ErrorDialog(_('Certificate Already in File'),
1192 _('This certificate is already in file %s, so it\'s '
1193 'not added again.') % gajim.MY_CACERTS)
1194 else:
1195 f = open(gajim.MY_CACERTS, 'a')
1196 f.write(server + '\n')
1197 f.write(obj.cert + '\n\n')
1198 f.close()
1199 gajim.config.set_per('accounts', account,
1200 'ssl_fingerprint_sha1', obj.fingerprint)
1201 if is_checked[1]:
1202 ignore_ssl_errors = gajim.config.get_per('accounts', account,
1203 'ignore_ssl_errors').split()
1204 ignore_ssl_errors.append(str(obj.error_num))
1205 gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
1206 ' '.join(ignore_ssl_errors))
1207 obj.conn.ssl_certificate_accepted()
1209 def on_cancel():
1210 del self.instances[account]['online_dialog']['ssl_error']
1211 iobj.conn.disconnect(on_purpose=True)
1212 self.handle_event_status(account, 'offline')
1214 pritext = _('Error verifying SSL certificate')
1215 sectext = _('There was an error verifying the SSL certificate of your '
1216 'jabber server: %(error)s\nDo you still want to connect to this '
1217 'server?') % {'error': obj.error_text}
1218 if obj.error_num in (18, 27):
1219 checktext1 = _('Add this certificate to the list of trusted '
1220 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % \
1221 obj.fingerprint
1222 else:
1223 checktext1 = ''
1224 checktext2 = _('Ignore this error for this certificate.')
1225 if 'ssl_error' in self.instances[account]['online_dialog']:
1226 self.instances[account]['online_dialog']['ssl_error'].destroy()
1227 self.instances[account]['online_dialog']['ssl_error'] = \
1228 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1229 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
1231 def handle_event_fingerprint_error(self, obj):
1232 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
1233 account = obj.conn.name
1234 def on_yes(is_checked):
1235 del self.instances[account]['online_dialog']['fingerprint_error']
1236 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
1237 obj.new_fingerprint)
1238 # Reset the ignored ssl errors
1239 gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
1240 obj.conn.ssl_certificate_accepted()
1242 def on_no():
1243 del self.instances[account]['online_dialog']['fingerprint_error']
1244 obj.conn.disconnect(on_purpose=True)
1245 self.handle_event_status(account, 'offline')
1247 pritext = _('SSL certificate error')
1248 sectext = _('It seems the SSL certificate of account %(account)s has '
1249 'changed or your connection is being hacked.\nOld fingerprint: '
1250 '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect '
1251 'and update the fingerprint of the certificate?') % \
1252 {'account': account, 'old': gajim.config.get_per('accounts',
1253 account, 'ssl_fingerprint_sha1'), 'new': obj.new_fingerprint}
1254 if 'fingerprint_error' in self.instances[account]['online_dialog']:
1255 self.instances[account]['online_dialog']['fingerprint_error'].\
1256 destroy()
1257 self.instances[account]['online_dialog']['fingerprint_error'] = \
1258 dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
1259 on_response_no=on_no)
1261 def handle_event_plain_connection(self, obj):
1262 # ('PLAIN_CONNECTION', account, (connection))
1263 def on_ok(is_checked):
1264 if not is_checked[0]:
1265 on_cancel()
1266 return
1267 # On cancel call del self.instances, so don't call it another time
1268 # before
1269 del self.instances[obj.conn.name]['online_dialog']\
1270 ['plain_connection']
1271 if is_checked[1]:
1272 gajim.config.set_per('accounts', obj.conn.name,
1273 'warn_when_plaintext_connection', False)
1274 obj.conn.connection_accepted(obj.xmpp_client, 'plain')
1276 def on_cancel():
1277 del self.instances[obj.conn.name]['online_dialog']\
1278 ['plain_connection']
1279 obj.conn.disconnect(on_purpose=True)
1280 self.handle_event_status(obj.conn.name, 'offline')
1282 pritext = _('Insecure connection')
1283 sectext = _('You are about to connect to the server with an insecure '
1284 'connection. This means all your conversations will be '
1285 'exchanged unencrypted. Are you sure you want to do that?')
1286 checktext1 = _('Yes, I really want to connect insecurely')
1287 checktext2 = _('_Do not ask me again')
1288 if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']:
1289 self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
1290 destroy()
1291 self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
1292 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1293 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1294 is_modal=False)
1296 def handle_event_insecure_ssl_connection(self, obj):
1297 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1298 def on_ok(is_checked):
1299 if not is_checked[0]:
1300 on_cancel()
1301 return
1302 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1303 if is_checked[1]:
1304 gajim.config.set_per('accounts', obj.conn.name,
1305 'warn_when_insecure_ssl_connection', False)
1306 if obj.conn.connected == 0:
1307 # We have been disconnecting (too long time since window is
1308 # opened)
1309 # re-connect with auto-accept
1310 obj.conn.connection_auto_accepted = True
1311 show, msg = obj.conn.continue_connect_info[:2]
1312 self.roster.send_status(obj.conn.name, show, msg)
1313 return
1314 obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type)
1316 def on_cancel():
1317 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1318 obj.conn.disconnect(on_purpose=True)
1319 self.handle_event_status(obj.conn.name, '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 self.handle_event_status(obj.conn.name, 'offline')
1362 pritext = _('Insecure connection')
1363 sectext = _('You are about to send your password unencrypted on an '
1364 'insecure connection. Are you sure you want to do that?')
1365 checktext1 = _('Yes, I really want to connect insecurely')
1366 checktext2 = _('_Do not ask me again')
1367 if 'insecure_password' in self.instances[obj.conn.name]\
1368 ['online_dialog']:
1369 self.instances[obj.conn.name]['online_dialog']\
1370 ['insecure_password'].destroy()
1371 self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
1372 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1373 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1374 is_modal=False)
1376 def create_core_handlers_list(self):
1377 self.handlers = {
1378 'WARNING': [self.handle_event_warning],
1379 'ERROR': [self.handle_event_error],
1380 'DB_ERROR': [self.handle_event_db_error],
1381 'INFORMATION': [self.handle_event_information],
1382 'MSGERROR': [self.handle_event_msgerror],
1383 'FILE_REQUEST': [self.handle_event_file_request],
1384 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
1385 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
1386 'atom-entry-received': [self.handle_atom_entry],
1387 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
1388 'bookmarks-received': [self.handle_event_bookmarks],
1389 'connection-lost': [self.handle_event_connection_lost],
1390 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
1391 'fingerprint-error': [self.handle_event_fingerprint_error],
1392 'gc-invitation-received': [self.handle_event_gc_invitation],
1393 'gc-presence-received': [self.handle_event_gc_presence],
1394 'gmail-notify': [self.handle_event_gmail_notify],
1395 'gpg-password-required': [self.handle_event_gpg_password_required],
1396 'gpg-trust-key': [self.handle_event_gpg_trust_key],
1397 'http-auth-received': [self.handle_event_http_auth],
1398 'insecure-password': [self.handle_event_insecure_password],
1399 'insecure-ssl-connection': \
1400 [self.handle_event_insecure_ssl_connection],
1401 'iq-error-received': [self.handle_event_iq_error],
1402 'jingle-connected-received': [self.handle_event_jingle_connected],
1403 'jingle-disconnected-received': [
1404 self.handle_event_jingle_disconnected],
1405 'jingle-error-received': [self.handle_event_jingle_error],
1406 'jingle-request-received': [self.handle_event_jingle_incoming],
1407 'last-result-received': [self.handle_event_last_status_time],
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-\x19]|'\
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 #FIXME: add tooltip with ascii
1820 menu.attach(item, counter % size, counter % size + 1,
1821 counter / size, counter / size + 1)
1822 counter += 1
1823 menu.connect('selection-done', selection_done)
1824 menu.show_all()
1825 return menu
1827 def _init_emoticons(self, path, need_reload = False):
1828 #initialize emoticons dictionary and unique images list
1829 self.emoticons_images = list()
1830 self.emoticons = dict()
1831 self.emoticons_animations = dict()
1833 sys.path.append(path)
1834 import emoticons
1835 if need_reload:
1836 # we need to reload else that doesn't work when changing emoticon
1837 # set
1838 reload(emoticons)
1839 emots = emoticons.emoticons
1840 for emot_filename in emots:
1841 emot_file = os.path.join(path, emot_filename)
1842 if not self.image_is_ok(emot_file):
1843 continue
1844 for emot in emots[emot_filename]:
1845 emot = emot.decode('utf-8')
1846 # This avoids duplicated emoticons with the same image eg. :)
1847 # and :-)
1848 if not emot_file in self.emoticons.values():
1849 if emot_file.endswith('.gif'):
1850 pix = gtk.gdk.PixbufAnimation(emot_file)
1851 else:
1852 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file,
1853 16, 16)
1854 self.emoticons_images.append((emot, pix))
1855 self.emoticons[emot.upper()] = emot_file
1856 del emoticons
1857 sys.path.remove(path)
1859 def init_emoticons(self, need_reload = False):
1860 emot_theme = gajim.config.get('emoticons_theme')
1861 if not emot_theme:
1862 return
1864 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
1865 if not os.path.exists(path):
1866 # It's maybe a user theme
1867 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
1868 if not os.path.exists(path):
1869 # theme doesn't exist, disable emoticons
1870 dialogs.WarningDialog(_('Emoticons disabled'),
1871 _('Your configured emoticons theme has not been found, so '
1872 'emoticons have been disabled.'))
1873 gajim.config.set('emoticons_theme', '')
1874 return
1875 self._init_emoticons(path, need_reload)
1876 if len(self.emoticons) == 0:
1877 # maybe old format of emoticons file, try to convert it
1878 try:
1879 import pprint
1880 import emoticons
1881 emots = emoticons.emoticons
1882 fd = open(os.path.join(path, 'emoticons.py'), 'w')
1883 fd.write('emoticons = ')
1884 pprint.pprint( dict([
1885 (file_, [i for i in emots.keys() if emots[i] == file_])
1886 for file_ in set(emots.values())]), fd)
1887 fd.close()
1888 del emoticons
1889 self._init_emoticons(path, need_reload=True)
1890 except Exception:
1891 pass
1892 if len(self.emoticons) == 0:
1893 dialogs.WarningDialog(_('Emoticons disabled'),
1894 _('Your configured emoticons theme cannot been loaded. You '
1895 'maybe need to update the format of emoticons.py file. See '
1896 'http://trac.gajim.org/wiki/Emoticons for more details.'))
1897 if self.emoticons_menu:
1898 self.emoticons_menu.destroy()
1899 self.emoticons_menu = self.prepare_emoticons_menu()
1901 ################################################################################
1902 ### Methods for opening new messages controls
1903 ################################################################################
1905 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
1906 is_continued=False):
1908 Join the room immediately
1910 if not nick:
1911 nick = gajim.nicks[account]
1913 if self.msg_win_mgr.has_window(room_jid, account) and \
1914 gajim.gc_connected[account][room_jid]:
1915 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
1916 win = gc_ctrl.parent_win
1917 win.set_active_tab(gc_ctrl)
1918 dialogs.ErrorDialog(_('You are already in group chat %s') % \
1919 room_jid)
1920 return
1922 invisible_show = gajim.SHOW_LIST.index('invisible')
1923 if gajim.connections[account].connected == invisible_show:
1924 dialogs.ErrorDialog(
1925 _('You cannot join a group chat while you are invisible'))
1926 return
1928 minimized_control = gajim.interface.minimized_controls[account].get(
1929 room_jid, None)
1931 if minimized_control is None and not self.msg_win_mgr.has_window(
1932 room_jid, account):
1933 # Join new groupchat
1934 if minimize:
1935 # GCMIN
1936 contact = gajim.contacts.create_contact(jid=room_jid,
1937 account=account, name=nick)
1938 gc_control = GroupchatControl(None, contact, account)
1939 gajim.interface.minimized_controls[account][room_jid] = \
1940 gc_control
1941 self.roster.add_groupchat(room_jid, account)
1942 else:
1943 self.new_room(room_jid, nick, account,
1944 is_continued=is_continued)
1945 elif minimized_control is None:
1946 # We are already in that groupchat
1947 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
1948 gc_control.nick = nick
1949 gc_control.parent_win.set_active_tab(gc_control)
1950 else:
1951 # We are already in this groupchat and it is minimized
1952 minimized_control.nick = nick
1953 self.roster.add_groupchat(room_jid, account)
1955 # Connect
1956 gajim.connections[account].join_gc(nick, room_jid, password)
1957 if password:
1958 gajim.gc_passwords[room_jid] = password
1960 def new_room(self, room_jid, nick, account, is_continued=False):
1961 # Get target window, create a control, and associate it with the window
1962 # GCMIN
1963 contact = gajim.contacts.create_contact(jid=room_jid, account=account,
1964 name=nick)
1965 mw = self.msg_win_mgr.get_window(contact.jid, account)
1966 if not mw:
1967 mw = self.msg_win_mgr.create_window(contact, account,
1968 GroupchatControl.TYPE_ID)
1969 gc_control = GroupchatControl(mw, contact, account,
1970 is_continued=is_continued)
1971 mw.new_tab(gc_control)
1973 def new_private_chat(self, gc_contact, account, session=None):
1974 conn = gajim.connections[account]
1975 if not session and gc_contact.get_full_jid() in conn.sessions:
1976 sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
1977 values() if isinstance(s, ChatControlSession)]
1979 # look for an existing session with a chat control
1980 for s in sessions:
1981 if s.control:
1982 session = s
1983 break
1984 if not session and not len(sessions) == 0:
1985 # there are no sessions with chat controls, just take the first
1986 # one
1987 session = sessions[0]
1988 if not session:
1989 # couldn't find an existing ChatControlSession, just make a new one
1990 session = conn.make_new_session(gc_contact.get_full_jid(), None,
1991 'pm')
1993 contact = gc_contact.as_contact()
1994 if not session.control:
1995 message_window = self.msg_win_mgr.get_window(
1996 gc_contact.get_full_jid(), account)
1997 if not message_window:
1998 message_window = self.msg_win_mgr.create_window(contact,
1999 account, message_control.TYPE_PM)
2001 session.control = PrivateChatControl(message_window, gc_contact,
2002 contact, account, session)
2003 message_window.new_tab(session.control)
2005 if gajim.events.get_events(account, gc_contact.get_full_jid()):
2006 # We call this here to avoid race conditions with widget validation
2007 session.control.read_queue()
2009 return session.control
2011 def new_chat(self, contact, account, resource=None, session=None):
2012 # Get target window, create a control, and associate it with the window
2013 type_ = message_control.TYPE_CHAT
2015 fjid = contact.jid
2016 if resource:
2017 fjid += '/' + resource
2019 mw = self.msg_win_mgr.get_window(fjid, account)
2020 if not mw:
2021 mw = self.msg_win_mgr.create_window(contact, account, type_,
2022 resource)
2024 chat_control = ChatControl(mw, contact, account, session, resource)
2026 mw.new_tab(chat_control)
2028 if len(gajim.events.get_events(account, fjid)):
2029 # We call this here to avoid race conditions with widget validation
2030 chat_control.read_queue()
2032 return chat_control
2034 def new_chat_from_jid(self, account, fjid, message=None):
2035 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2036 contact = gajim.contacts.get_contact(account, jid, resource)
2037 added_to_roster = False
2038 if not contact:
2039 added_to_roster = True
2040 contact = self.roster.add_to_not_in_the_roster(account, jid,
2041 resource=resource)
2043 ctrl = self.msg_win_mgr.get_control(fjid, account)
2045 if not ctrl:
2046 ctrl = self.new_chat(contact, account,
2047 resource=resource)
2048 if len(gajim.events.get_events(account, fjid)):
2049 ctrl.read_queue()
2051 if message:
2052 buffer_ = ctrl.msg_textview.get_buffer()
2053 buffer_.set_text(message)
2054 mw = ctrl.parent_win
2055 mw.set_active_tab(ctrl)
2056 # For JEP-0172
2057 if added_to_roster:
2058 ctrl.user_nick = gajim.nicks[account]
2059 gobject.idle_add(mw.window.grab_focus)
2061 return ctrl
2063 def on_open_chat_window(self, widget, contact, account, resource=None,
2064 session=None):
2065 # Get the window containing the chat
2066 fjid = contact.jid
2068 if resource:
2069 fjid += '/' + resource
2071 ctrl = None
2073 if session:
2074 ctrl = session.control
2075 if not ctrl:
2076 win = self.msg_win_mgr.get_window(fjid, account)
2078 if win:
2079 ctrl = win.get_control(fjid, account)
2081 if not ctrl:
2082 ctrl = self.new_chat(contact, account, resource=resource,
2083 session=session)
2084 # last message is long time ago
2085 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2087 win = ctrl.parent_win
2089 win.set_active_tab(ctrl)
2091 if gajim.connections[account].is_zeroconf and \
2092 gajim.connections[account].status in ('offline', 'invisible'):
2093 ctrl = win.get_control(fjid, account)
2094 if ctrl:
2095 ctrl.got_disconnected()
2097 ################################################################################
2098 ### Other Methods
2099 ################################################################################
2101 def change_awn_icon_status(self, status):
2102 if not dbus_support.supported:
2103 # do nothing if user doesn't have D-Bus bindings
2104 return
2105 try:
2106 bus = dbus.SessionBus()
2107 if not 'com.google.code.Awn' in bus.list_names():
2108 # Awn is not installed
2109 return
2110 except Exception:
2111 return
2112 iconset = gajim.config.get('iconset')
2113 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
2114 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2115 status = status + '.png'
2116 elif status == 'online':
2117 prefix = ''
2118 status = gtkgui_helpers.get_icon_path('gajim', 32)
2119 path = os.path.join(prefix, status)
2120 try:
2121 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
2122 awn = dbus.Interface(obj, 'com.google.code.Awn')
2123 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
2124 except Exception:
2125 pass
2127 def enable_music_listener(self):
2128 listener = MusicTrackListener.get()
2129 if not self.music_track_changed_signal:
2130 self.music_track_changed_signal = listener.connect(
2131 'music-track-changed', self.music_track_changed)
2132 track = listener.get_playing_track()
2133 self.music_track_changed(listener, track)
2135 def disable_music_listener(self):
2136 listener = MusicTrackListener.get()
2137 listener.disconnect(self.music_track_changed_signal)
2138 self.music_track_changed_signal = None
2140 def music_track_changed(self, unused_listener, music_track_info,
2141 account=None):
2142 if not account:
2143 accounts = gajim.connections.keys()
2144 else:
2145 accounts = [account]
2147 is_paused = hasattr(music_track_info, 'paused') and \
2148 music_track_info.paused == 0
2149 if not music_track_info or is_paused:
2150 artist = title = source = ''
2151 else:
2152 artist = music_track_info.artist
2153 title = music_track_info.title
2154 source = music_track_info.album
2155 for acct in accounts:
2156 if not gajim.account_is_connected(acct):
2157 continue
2158 if not gajim.connections[acct].pep_supported:
2159 continue
2160 if not gajim.config.get_per('accounts', acct, 'publish_tune'):
2161 continue
2162 if gajim.connections[acct].music_track_info == music_track_info:
2163 continue
2164 gajim.connections[acct].send_tune(artist, title, source)
2165 gajim.connections[acct].music_track_info = music_track_info
2167 def get_bg_fg_colors(self):
2168 def gdkcolor_to_rgb (gdkcolor):
2169 return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
2170 gdkcolor.blue)]
2172 def format_rgb (r, g, b):
2173 return ' '.join([str(c) for c in ('rgb', r, g, b)])
2175 def format_gdkcolor (gdkcolor):
2176 return format_rgb (*gdkcolor_to_rgb (gdkcolor))
2178 # get style colors and create string for dvipng
2179 dummy = gtk.Invisible()
2180 dummy.ensure_style()
2181 style = dummy.get_style()
2182 bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
2183 fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
2184 return (bg_str, fg_str)
2186 def get_fg_color(self, fmt='hex'):
2187 def format_gdkcolor (c):
2188 if fmt == 'tex':
2189 return ' '.join([str(s) for s in
2190 ('rgb', c.red_float, c.green_float, c.blue_float)])
2191 elif fmt == 'hex':
2192 return str(c)
2194 # get foreground style color and create string
2195 dummy = gtk.Invisible()
2196 dummy.ensure_style()
2197 return format_gdkcolor(dummy.get_style().text[gtk.STATE_NORMAL])
2199 def read_sleepy(self):
2201 Check idle status and change that status if needed
2203 if not self.sleeper.poll():
2204 # idle detection is not supported in that OS
2205 return False # stop looping in vain
2206 state = self.sleeper.getState()
2207 for account in gajim.connections:
2208 if account not in gajim.sleeper_state or \
2209 not gajim.sleeper_state[account]:
2210 continue
2211 if state == common.sleepy.STATE_AWAKE and \
2212 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2213 # we go online
2214 self.roster.send_status(account, 'online',
2215 gajim.status_before_autoaway[account])
2216 gajim.status_before_autoaway[account] = ''
2217 gajim.sleeper_state[account] = 'online'
2218 elif state == common.sleepy.STATE_AWAY and \
2219 gajim.sleeper_state[account] == 'online' and \
2220 gajim.config.get('autoaway'):
2221 # we save out online status
2222 gajim.status_before_autoaway[account] = \
2223 gajim.connections[account].status
2224 # we go away (no auto status) [we pass True to auto param]
2225 auto_message = gajim.config.get('autoaway_message')
2226 if not auto_message:
2227 auto_message = gajim.connections[account].status
2228 else:
2229 auto_message = auto_message.replace('$S', '%(status)s')
2230 auto_message = auto_message.replace('$T', '%(time)s')
2231 auto_message = auto_message % {
2232 'status': gajim.status_before_autoaway[account],
2233 'time': gajim.config.get('autoawaytime')
2235 self.roster.send_status(account, 'away', auto_message,
2236 auto=True)
2237 gajim.sleeper_state[account] = 'autoaway'
2238 elif state == common.sleepy.STATE_XA and \
2239 gajim.sleeper_state[account] in ('online', 'autoaway',
2240 'autoaway-forced') and gajim.config.get('autoxa'):
2241 # we go extended away [we pass True to auto param]
2242 auto_message = gajim.config.get('autoxa_message')
2243 if not auto_message:
2244 auto_message = gajim.connections[account].status
2245 else:
2246 auto_message = auto_message.replace('$S', '%(status)s')
2247 auto_message = auto_message.replace('$T', '%(time)s')
2248 auto_message = auto_message % {
2249 'status': gajim.status_before_autoaway[account],
2250 'time': gajim.config.get('autoxatime')
2252 self.roster.send_status(account, 'xa', auto_message, auto=True)
2253 gajim.sleeper_state[account] = 'autoxa'
2254 return True # renew timeout (loop for ever)
2256 def autoconnect(self):
2258 Auto connect at startup
2260 # dict of account that want to connect sorted by status
2261 shows = {}
2262 for a in gajim.connections:
2263 if gajim.config.get_per('accounts', a, 'autoconnect'):
2264 if gajim.config.get_per('accounts', a, 'restore_last_status'):
2265 self.roster.send_status(a, gajim.config.get_per('accounts',
2266 a, 'last_status'), helpers.from_one_line(
2267 gajim.config.get_per('accounts', a, 'last_status_msg')))
2268 continue
2269 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
2270 if not show in gajim.SHOW_LIST:
2271 continue
2272 if not show in shows:
2273 shows[show] = [a]
2274 else:
2275 shows[show].append(a)
2276 def on_message(message, pep_dict):
2277 if message is None:
2278 return
2279 for a in shows[show]:
2280 self.roster.send_status(a, show, message)
2281 self.roster.send_pep(a, pep_dict)
2282 for show in shows:
2283 message = self.roster.get_status_message(show, on_message)
2284 return False
2286 def show_systray(self):
2287 self.systray_enabled = True
2288 self.systray.show_icon()
2290 def hide_systray(self):
2291 self.systray_enabled = False
2292 self.systray.hide_icon()
2294 def on_launch_browser_mailer(self, widget, url, kind):
2295 helpers.launch_browser_mailer(kind, url)
2297 def process_connections(self):
2299 Called each foo (200) miliseconds. Check for idlequeue timeouts
2301 try:
2302 gajim.idlequeue.process()
2303 except Exception:
2304 # Otherwise, an exception will stop our loop
2305 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2306 if in_seconds:
2307 gobject.timeout_add_seconds(timeout, self.process_connections)
2308 else:
2309 gobject.timeout_add(timeout, self.process_connections)
2310 raise
2311 return True # renew timeout (loop for ever)
2313 def save_config(self):
2314 err_str = parser.write()
2315 if err_str is not None:
2316 print >> sys.stderr, err_str
2317 # it is good to notify the user
2318 # in case he or she cannot see the output of the console
2319 dialogs.ErrorDialog(_('Could not save your settings and '
2320 'preferences'), err_str)
2321 sys.exit()
2323 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
2325 Save an avatar to a separate file, and generate files for dbus
2326 notifications. An avatar can be given as a pixmap directly or as an
2327 decoded image
2329 puny_jid = helpers.sanitize_filename(jid)
2330 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2331 if puny_nick:
2332 path_to_file = os.path.join(path_to_file, puny_nick)
2333 # remove old avatars
2334 for typ in ('jpeg', 'png'):
2335 if local:
2336 path_to_original_file = path_to_file + '_local'+ '.' + typ
2337 else:
2338 path_to_original_file = path_to_file + '.' + typ
2339 if os.path.isfile(path_to_original_file):
2340 os.remove(path_to_original_file)
2341 if local and photo:
2342 pixbuf = photo
2343 typ = 'png'
2344 extension = '_local.png' # save local avatars as png file
2345 else:
2346 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
2347 want_type=True)
2348 if pixbuf is None:
2349 return
2350 extension = '.' + typ
2351 if typ not in ('jpeg', 'png'):
2352 gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\
2353 'png formats. saving %s\'avatar as png file (originaly %s)'\
2354 % (jid, typ))
2355 typ = 'png'
2356 extension = '.png'
2357 path_to_original_file = path_to_file + extension
2358 try:
2359 pixbuf.save(path_to_original_file, typ)
2360 except Exception, e:
2361 log.error('Error writing avatar file %s: %s' % (
2362 path_to_original_file, str(e)))
2363 # Generate and save the resized, color avatar
2364 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
2365 if pixbuf:
2366 path_to_normal_file = path_to_file + '_notif_size_colored' + \
2367 extension
2368 try:
2369 pixbuf.save(path_to_normal_file, 'png')
2370 except Exception, e:
2371 log.error('Error writing avatar file %s: %s' % \
2372 (path_to_original_file, str(e)))
2373 # Generate and save the resized, black and white avatar
2374 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
2375 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
2376 if bwbuf:
2377 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
2378 try:
2379 bwbuf.save(path_to_bw_file, 'png')
2380 except Exception, e:
2381 log.error('Error writing avatar file %s: %s' % \
2382 (path_to_original_file, str(e)))
2384 def remove_avatar_files(self, jid, puny_nick = None, local = False):
2386 Remove avatar files of a jid
2388 puny_jid = helpers.sanitize_filename(jid)
2389 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2390 if puny_nick:
2391 path_to_file = os.path.join(path_to_file, puny_nick)
2392 for ext in ('.jpeg', '.png'):
2393 if local:
2394 ext = '_local' + ext
2395 path_to_original_file = path_to_file + ext
2396 if os.path.isfile(path_to_file + ext):
2397 os.remove(path_to_file + ext)
2398 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
2399 os.remove(path_to_file + '_notif_size_colored' + ext)
2400 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
2401 os.remove(path_to_file + '_notif_size_bw' + ext)
2403 def auto_join_bookmarks(self, account):
2405 Autojoin bookmarked GCs that have 'auto join' on for this account
2407 for bm in gajim.connections[account].bookmarks:
2408 if bm['autojoin'] in ('1', 'true'):
2409 jid = bm['jid']
2410 # Only join non-opened groupchats. Opened one are already
2411 # auto-joined on re-connection
2412 if not jid in gajim.gc_connected[account]:
2413 # we are not already connected
2414 minimize = bm['minimize'] in ('1', 'true')
2415 gajim.interface.join_gc_room(account, jid, bm['nick'],
2416 bm['password'], minimize = minimize)
2417 elif jid in self.minimized_controls[account]:
2418 # more or less a hack:
2419 # On disconnect the minimized gc contact instances
2420 # were set to offline. Reconnect them to show up in the
2421 # roster.
2422 self.roster.add_groupchat(jid, account)
2424 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
2425 nick):
2427 Add a bookmark for this account, sorted in bookmark list
2429 bm = {
2430 'name': name,
2431 'jid': jid,
2432 'autojoin': autojoin,
2433 'minimize': minimize,
2434 'password': password,
2435 'nick': nick
2437 place_found = False
2438 index = 0
2439 # check for duplicate entry and respect alpha order
2440 for bookmark in gajim.connections[account].bookmarks:
2441 if bookmark['jid'] == bm['jid']:
2442 dialogs.ErrorDialog(
2443 _('Bookmark already set'),
2444 _('Group Chat "%s" is already in your bookmarks.') % \
2445 bm['jid'])
2446 return
2447 if bookmark['name'] > bm['name']:
2448 place_found = True
2449 break
2450 index += 1
2451 if place_found:
2452 gajim.connections[account].bookmarks.insert(index, bm)
2453 else:
2454 gajim.connections[account].bookmarks.append(bm)
2455 gajim.connections[account].store_bookmarks()
2456 self.roster.set_actions_menu_needs_rebuild()
2457 dialogs.InformationDialog(
2458 _('Bookmark has been added successfully'),
2459 _('You can manage your bookmarks via Actions menu in your roster.'))
2462 # does JID exist only within a groupchat?
2463 def is_pm_contact(self, fjid, account):
2464 bare_jid = gajim.get_jid_without_resource(fjid)
2466 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
2468 if not gc_ctrl and \
2469 bare_jid in self.minimized_controls[account]:
2470 gc_ctrl = self.minimized_controls[account][bare_jid]
2472 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
2474 def create_ipython_window(self):
2475 try:
2476 from ipython_view import IPythonView
2477 except ImportError:
2478 print 'ipython_view not found'
2479 return
2480 import pango
2482 if os.name == 'nt':
2483 font = 'Lucida Console 9'
2484 else:
2485 font = 'Luxi Mono 10'
2487 window = gtk.Window()
2488 window.set_size_request(750, 550)
2489 window.set_resizable(True)
2490 sw = gtk.ScrolledWindow()
2491 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2492 view = IPythonView()
2493 view.modify_font(pango.FontDescription(font))
2494 view.set_wrap_mode(gtk.WRAP_CHAR)
2495 sw.add(view)
2496 window.add(sw)
2497 window.show_all()
2498 def on_delete(win, event):
2499 win.hide()
2500 return True
2501 window.connect('delete_event', on_delete)
2502 view.updateNamespace({'gajim': gajim})
2503 gajim.ipython_window = window
2505 def run(self):
2506 if gajim.config.get('trayicon') != 'never':
2507 self.show_systray()
2509 self.roster = roster_window.RosterWindow()
2510 # Creating plugin manager
2511 import plugins
2512 gajim.plugin_manager = plugins.PluginManager()
2514 self.roster._before_fill()
2515 for account in gajim.connections:
2516 gajim.connections[account].load_roster_from_db()
2517 self.roster._after_fill()
2519 # get instances for windows/dialogs that will show_all()/hide()
2520 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
2522 gobject.timeout_add(100, self.autoconnect)
2523 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2524 if in_seconds:
2525 gobject.timeout_add_seconds(timeout, self.process_connections)
2526 else:
2527 gobject.timeout_add(timeout, self.process_connections)
2528 gobject.timeout_add_seconds(gajim.config.get(
2529 'check_idle_every_foo_seconds'), self.read_sleepy)
2531 # when using libasyncns we need to process resolver in regular intervals
2532 if resolver.USE_LIBASYNCNS:
2533 gobject.timeout_add(200, gajim.resolver.process)
2535 def remote_init():
2536 if gajim.config.get('remote_control'):
2537 try:
2538 import remote_control
2539 self.remote_ctrl = remote_control.Remote()
2540 except Exception:
2541 pass
2542 gobject.timeout_add_seconds(5, remote_init)
2544 def __init__(self):
2545 gajim.interface = self
2546 gajim.thread_interface = ThreadInterface
2547 # This is the manager and factory of message windows set by the module
2548 self.msg_win_mgr = None
2549 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
2550 'closed': {}}
2551 self.emoticons_menu = None
2552 # handler when an emoticon is clicked in emoticons_menu
2553 self.emoticon_menuitem_clicked = None
2554 self.minimized_controls = {}
2555 self.status_sent_to_users = {}
2556 self.status_sent_to_groups = {}
2557 self.gpg_passphrase = {}
2558 self.pass_dialog = {}
2559 self.db_error_dialog = None
2560 self.default_colors = {
2561 'inmsgcolor': gajim.config.get('inmsgcolor'),
2562 'outmsgcolor': gajim.config.get('outmsgcolor'),
2563 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
2564 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
2565 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
2566 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
2569 self.handlers = {}
2570 self.roster = None
2571 self._invalid_XML_chars_re = None
2572 self._basic_pattern_re = None
2573 self._emot_and_basic_re = None
2574 self._sth_at_sth_dot_sth_re = None
2575 self.link_pattern_re = None
2576 self.invalid_XML_chars = None
2577 self.basic_pattern = None
2578 self.emot_and_basic = None
2579 self.sth_at_sth_dot_sth = None
2580 self.emot_only = None
2581 self.emoticons = []
2582 self.emoticons_animations = {}
2583 self.emoticons_images = {}
2585 cfg_was_read = parser.read()
2587 from common import latex
2588 gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
2589 latex.check_for_latex_support()
2591 gajim.logger.reset_shown_unread_messages()
2592 # override logging settings from config (don't take care of '-q' option)
2593 if gajim.config.get('verbose'):
2594 logging_helpers.set_verbose()
2596 # Is Gajim default app?
2597 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
2598 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
2600 for account in gajim.config.get_per('accounts'):
2601 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
2602 gajim.ZEROCONF_ACC_NAME = account
2603 break
2604 # Is gnome configured to activate row on single click ?
2605 try:
2606 import gconf
2607 client = gconf.client_get_default()
2608 click_policy = client.get_string(
2609 '/apps/nautilus/preferences/click_policy')
2610 if click_policy == 'single':
2611 gajim.single_click = True
2612 except Exception:
2613 pass
2614 # add default status messages if there is not in the config file
2615 if len(gajim.config.get_per('statusmsg')) == 0:
2616 default = gajim.config.statusmsg_default
2617 for msg in default:
2618 gajim.config.add_per('statusmsg', msg)
2619 gajim.config.set_per('statusmsg', msg, 'message',
2620 default[msg][0])
2621 gajim.config.set_per('statusmsg', msg, 'activity',
2622 default[msg][1])
2623 gajim.config.set_per('statusmsg', msg, 'subactivity',
2624 default[msg][2])
2625 gajim.config.set_per('statusmsg', msg, 'activity_text',
2626 default[msg][3])
2627 gajim.config.set_per('statusmsg', msg, 'mood',
2628 default[msg][4])
2629 gajim.config.set_per('statusmsg', msg, 'mood_text',
2630 default[msg][5])
2631 #add default themes if there is not in the config file
2632 theme = gajim.config.get('roster_theme')
2633 if not theme in gajim.config.get_per('themes'):
2634 gajim.config.set('roster_theme', _('default'))
2635 if len(gajim.config.get_per('themes')) == 0:
2636 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
2637 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2638 'groupfont', 'groupfontattrs', 'contacttextcolor',
2639 'contactbgcolor', 'contactfont', 'contactfontattrs',
2640 'bannertextcolor', 'bannerbgcolor']
2642 default = gajim.config.themes_default
2643 for theme_name in default:
2644 gajim.config.add_per('themes', theme_name)
2645 theme = default[theme_name]
2646 for o in d:
2647 gajim.config.set_per('themes', theme_name, o,
2648 theme[d.index(o)])
2650 if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
2651 gtkgui_helpers.autodetect_browser_mailer()
2653 gajim.idlequeue = idlequeue.get_idlequeue()
2654 # resolve and keep current record of resolved hosts
2655 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
2656 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
2657 self.handle_event_file_rcv_completed,
2658 self.handle_event_file_progress,
2659 self.handle_event_file_error)
2660 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
2661 gajim.default_session_type = ChatControlSession
2663 # Creating Network Events Controller
2664 from common import nec
2665 gajim.nec = nec.NetworkEventsController()
2667 self.create_core_handlers_list()
2668 self.register_core_handlers()
2670 if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
2671 and gajim.HAVE_ZEROCONF:
2672 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
2673 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
2674 for account in gajim.config.get_per('accounts'):
2675 if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
2676 gajim.config.get_per('accounts', account, 'active'):
2677 gajim.connections[account] = common.connection.Connection(
2678 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 # set the icon to all windows
2787 gtk.window_set_default_icon(pix)
2789 self.init_emoticons()
2790 self.make_regexps()
2792 # get transports type from DB
2793 gajim.transport_type = gajim.logger.get_transports_type()
2795 # test is dictionnary is present for speller
2796 if gajim.config.get('use_speller'):
2797 lang = gajim.config.get('speller_language')
2798 if not lang:
2799 lang = gajim.LANG
2800 tv = gtk.TextView()
2801 try:
2802 import gtkspell
2803 spell = gtkspell.Spell(tv, lang)
2804 except (ImportError, TypeError, RuntimeError, OSError):
2805 dialogs.AspellDictError(lang)
2807 if gajim.config.get('soundplayer') == '':
2808 # only on first time Gajim starts
2809 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
2810 for command in commands:
2811 if helpers.is_in_path(command):
2812 if command == 'aplay':
2813 command += ' -q'
2814 gajim.config.set('soundplayer', command)
2815 break
2817 self.last_ftwindow_update = 0
2819 self.music_track_changed_signal = None
2822 class PassphraseRequest:
2823 def __init__(self, keyid):
2824 self.keyid = keyid
2825 self.callbacks = []
2826 self.dialog_created = False
2827 self.dialog = None
2828 self.passphrase = None
2829 self.completed = False
2831 def interrupt(self, account=None):
2832 if account:
2833 for (acct, cb) in self.callbacks:
2834 if acct == account:
2835 self.callbacks.remove((acct, cb))
2836 else:
2837 self.callbacks = []
2838 if not len(self.callbacks):
2839 self.dialog.window.destroy()
2841 def run_callback(self, account, callback):
2842 gajim.connections[account].gpg_passphrase(self.passphrase)
2843 callback()
2845 def add_callback(self, account, cb):
2846 if self.completed:
2847 self.run_callback(account, cb)
2848 else:
2849 self.callbacks.append((account, cb))
2850 if not self.dialog_created:
2851 self.create_dialog(account)
2853 def complete(self, passphrase):
2854 self.passphrase = passphrase
2855 self.completed = True
2856 if passphrase is not None:
2857 gobject.timeout_add_seconds(30,
2858 gajim.interface.forget_gpg_passphrase, self.keyid)
2859 for (account, cb) in self.callbacks:
2860 self.run_callback(account, cb)
2861 self.callbacks = []
2863 def create_dialog(self, account):
2864 title = _('Passphrase Required')
2865 second = _('Enter GPG key passphrase for key %(keyid)s (account '
2866 '%(account)s).') % {'keyid': self.keyid, 'account': account}
2868 def _cancel():
2869 # user cancelled, continue without GPG
2870 self.complete(None)
2872 def _ok(passphrase, checked, count):
2873 result = gajim.connections[account].test_gpg_passphrase(passphrase)
2874 if result == 'ok':
2875 # passphrase is good
2876 self.complete(passphrase)
2877 return
2878 elif result == 'expired':
2879 dialogs.ErrorDialog(_('GPG key expired'),
2880 _('Your GPG key has expired, you will be connected to %s '
2881 'without OpenPGP.') % account)
2882 # Don't try to connect with GPG
2883 gajim.connections[account].continue_connect_info[2] = False
2884 self.complete(None)
2885 return
2887 if count < 3:
2888 # ask again
2889 dialogs.PassphraseDialog(_('Wrong Passphrase'),
2890 _('Please retype your GPG passphrase or press Cancel.'),
2891 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
2892 else:
2893 # user failed 3 times, continue without GPG
2894 self.complete(None)
2896 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
2897 1), cancel_handler=_cancel)
2898 self.dialog_created = True
2901 class ThreadInterface:
2902 def __init__(self, func, func_args, callback, callback_args):
2904 Call a function in a thread
2906 def thread_function(func, func_args, callback, callback_args):
2907 output = func(*func_args)
2908 gobject.idle_add(callback, output, *callback_args)
2910 Thread(target=thread_function, args=(func, func_args, callback,
2911 callback_args)).start()