remove unused function
[gajim.git] / src / gui_interface.py
blob5e1d1dddff103e01d227c25df6c0a7dfbda63fe2
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, account, array):
550 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
551 # info in a dataform if is_form is True
552 if array[2] or 'instructions' in array[1]:
553 config.ServiceRegistrationWindow(array[0], array[1], account,
554 array[2])
555 else:
556 dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
557 % array[0], _('Check your connection or try again later.'))
559 def handle_event_agent_info_items(self, account, array):
560 #('AGENT_INFO_ITEMS', account, (agent, node, items))
561 our_jid = gajim.get_jid_from_account(account)
562 if 'pep_services' in gajim.interface.instances[account] and \
563 array[0] == our_jid:
564 gajim.interface.instances[account]['pep_services'].items_received(
565 array[2])
567 def handle_event_vcard(self, obj):
568 # ('VCARD', account, data)
569 '''vcard holds the vcard data'''
570 our_jid = gajim.get_jid_from_account(obj.conn.name)
571 if obj.jid == our_jid:
572 if obj.nickname:
573 gajim.nicks[obj.conn.name] = obj.nickname
574 if obj.conn.name in self.show_vcard_when_connect:
575 self.show_vcard_when_connect.remove(obj.conn.name)
577 def handle_event_last_status_time(self, obj):
578 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
579 if obj.seconds < 0:
580 # Ann error occured
581 return
582 account = obj.conn.name
583 c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
584 if c: # c can be none if it's a gc contact
585 if obj.status:
586 c.status = obj.status
587 self.roster.draw_contact(c.jid, account) # draw offline status
588 last_time = time.localtime(time.time() - obj.seconds)
589 if c.show == 'offline':
590 c.last_status_time = last_time
591 else:
592 c.last_activity_time = last_time
593 if self.roster.tooltip.id and self.roster.tooltip.win:
594 self.roster.tooltip.update_last_time(last_time)
596 def handle_event_gc_config(self, obj):
597 #('GC_CONFIG', account, (jid, form_node)) config is a dict
598 account = obj.conn.name
599 if obj.jid in gajim.automatic_rooms[account]:
600 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
601 # We're converting chat to muc. allow participants to invite
602 for f in obj.dataform.iter_fields():
603 if f.var == 'muc#roomconfig_allowinvites':
604 f.value = True
605 elif f.var == 'muc#roomconfig_publicroom':
606 f.value = False
607 elif f.var == 'muc#roomconfig_membersonly':
608 f.value = True
609 elif f.var == 'public_list':
610 f.value = False
611 obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged())
612 else:
613 # use default configuration
614 obj.conn.send_gc_config(obj.jid, obj.form_node)
615 # invite contacts
616 # check if it is necessary to add <continue />
617 continue_tag = False
618 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
619 continue_tag = True
620 if 'invities' in gajim.automatic_rooms[account][obj.jid]:
621 for jid in gajim.automatic_rooms[account][obj.jid]['invities']:
622 obj.conn.send_invite(obj.jid, jid,
623 continue_tag=continue_tag)
624 del gajim.automatic_rooms[account][obj.jid]
625 elif obj.jid not in self.instances[account]['gc_config']:
626 self.instances[account]['gc_config'][obj.jid] = \
627 config.GroupchatConfigWindow(account, obj.jid, obj.dataform)
629 def handle_event_gc_affiliation(self, obj):
630 #('GC_AFFILIATION', account, (room_jid, users_dict))
631 account = obj.conn.name
632 if obj.jid in self.instances[account]['gc_config']:
633 self.instances[account]['gc_config'][obj.jid].\
634 affiliation_list_received(obj.users_dict)
636 def handle_event_gc_invitation(self, obj):
637 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
638 jid = gajim.get_jid_without_resource(obj.jid_from)
639 account = obj.conn.name
640 if helpers.allow_popup_window(account) or not self.systray_enabled:
641 dialogs.InvitationReceivedDialog(account, obj.room_jid, jid,
642 obj.password, obj.reason, is_continued=obj.is_continued)
643 return
645 self.add_event(account, jid, 'gc-invitation', (obj.room_jid,
646 obj.reason, obj.password, obj.is_continued))
648 if helpers.allow_showing_notification(account):
649 path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
650 event_type = _('Groupchat Invitation')
651 notify.popup(event_type, jid, account, 'gc-invitation', path,
652 event_type, obj.room_jid)
654 def forget_gpg_passphrase(self, keyid):
655 if keyid in self.gpg_passphrase:
656 del self.gpg_passphrase[keyid]
657 return False
659 def handle_event_bad_gpg_passphrase(self, obj):
660 #('BAD_PASSPHRASE', account, ())
661 if obj.use_gpg_agent:
662 sectext = _('You configured Gajim to use GPG agent, but there is no'
663 ' GPG agent running or it returned a wrong passphrase.\n')
664 sectext += _('You are currently connected without your OpenPGP '
665 'key.')
666 dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
667 else:
668 path = gtkgui_helpers.get_icon_path('gajim-warning', 48)
669 account = obj.conn.name
670 notify.popup('warning', account, account, 'warning', path,
671 _('OpenGPG Passphrase Incorrect'),
672 _('You are currently connected without your OpenPGP key.'))
673 self.forget_gpg_passphrase(obj.keyID)
675 def handle_event_gpg_password_required(self, obj):
676 #('GPG_PASSWORD_REQUIRED', account, (callback,))
677 if obj.keyid in self.gpg_passphrase:
678 request = self.gpg_passphrase[obj.keyid]
679 else:
680 request = PassphraseRequest(obj.keyid)
681 self.gpg_passphrase[obj.keyid] = request
682 request.add_callback(obj.conn.name, obj.callback)
684 def handle_event_gpg_trust_key(self, obj):
685 #('GPG_ALWAYS_TRUST', account, callback)
686 def on_yes(checked):
687 if checked:
688 obj.conn.gpg.always_trust = True
689 obj.callback(True)
691 def on_no():
692 obj.callback(False)
694 dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
695 'encrypt this chat is not trusted. Do you really want to encrypt '
696 'this message?'), checktext=_('_Do not ask me again'),
697 on_response_yes=on_yes, on_response_no=on_no)
699 def handle_event_password_required(self, account, array):
700 #('PASSWORD_REQUIRED', account, None)
701 if account in self.pass_dialog:
702 return
703 text = _('Enter your password for account %s') % account
704 if passwords.USER_HAS_GNOMEKEYRING and \
705 not passwords.USER_USES_GNOMEKEYRING:
706 text += '\n' + _('Gnome Keyring is installed but not \
707 correctly started (environment variable probably not \
708 correctly set)')
710 def on_ok(passphrase, save):
711 if save:
712 gajim.config.set_per('accounts', account, 'savepass', True)
713 passwords.save_password(account, passphrase)
714 gajim.connections[account].set_password(passphrase)
715 del self.pass_dialog[account]
717 def on_cancel():
718 self.roster.set_state(account, 'offline')
719 self.roster.update_status_combobox()
720 del self.pass_dialog[account]
722 self.pass_dialog[account] = dialogs.PassphraseDialog(
723 _('Password Required'), text, _('Save password'), ok_handler=on_ok,
724 cancel_handler=on_cancel)
726 def handle_event_roster_info(self, obj):
727 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
728 account = obj.conn.name
729 contacts = gajim.contacts.get_contacts(account, obj.jid)
730 if (not obj.sub or obj.sub == 'none') and \
731 (not obj.ask or obj.ask == 'none') and not obj.nickname and \
732 not obj.groups:
733 # contact removed us.
734 if contacts:
735 self.roster.remove_contact(obj.jid, account, backend=True)
736 return
737 elif not contacts:
738 if obj.sub == 'remove':
739 return
740 # Add new contact to roster
741 contact = gajim.contacts.create_contact(jid=obj.jid,
742 account=account, name=obj.nickname, groups=obj.groups,
743 show='offline', sub=obj.sub, ask=obj.ask)
744 gajim.contacts.add_contact(account, contact)
745 self.roster.add_contact(obj.jid, account)
746 else:
747 # If contact has changed (sub, ask or group) update roster
748 # Mind about observer status changes:
749 # According to xep 0162, a contact is not an observer anymore when
750 # we asked for auth, so also remove him if ask changed
751 old_groups = contacts[0].groups
752 if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\
753 or old_groups != obj.groups:
754 # c.get_shown_groups() has changed. Reflect that in
755 # roster_winodow
756 self.roster.remove_contact(obj.jid, account, force=True)
757 for contact in contacts:
758 contact.name = obj.nickname or ''
759 contact.sub = obj.sub
760 contact.ask = obj.ask
761 contact.groups = obj.groups or []
762 self.roster.add_contact(obj.jid, account)
763 # Refilter and update old groups
764 for group in old_groups:
765 self.roster.draw_group(group, account)
766 self.roster.draw_contact(obj.jid, account)
768 def handle_event_bookmarks(self, obj):
769 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
770 # We received a bookmark item from the server (JEP48)
771 # Auto join GC windows if neccessary
773 self.roster.set_actions_menu_needs_rebuild()
774 invisible_show = gajim.SHOW_LIST.index('invisible')
775 # do not autojoin if we are invisible
776 if obj.conn.connected == invisible_show:
777 return
779 self.auto_join_bookmarks(obj.conn.name)
781 def handle_event_file_send_error(self, account, array):
782 jid = array[0]
783 file_props = array[1]
784 ft = self.instances['file_transfers']
785 ft.set_status(file_props['type'], file_props['sid'], 'stop')
787 if helpers.allow_popup_window(account):
788 ft.show_send_error(file_props)
789 return
791 self.add_event(account, jid, 'file-send-error', file_props)
793 if helpers.allow_showing_notification(account):
794 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
795 event_type = _('File Transfer Error')
796 notify.popup(event_type, jid, account, 'file-send-error', path,
797 event_type, file_props['name'])
799 def handle_event_gmail_notify(self, obj):
800 jid = obj.jid
801 gmail_new_messages = int(obj.newmsgs)
802 gmail_messages_list = obj.gmail_messages_list
803 if not gajim.config.get('notify_on_new_gmail_email'):
804 return
805 path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
806 title = _('New mail on %(gmail_mail_address)s') % \
807 {'gmail_mail_address': jid}
808 text = i18n.ngettext('You have %d new mail conversation',
809 'You have %d new mail conversations', gmail_new_messages,
810 gmail_new_messages, gmail_new_messages)
812 if gajim.config.get('notify_on_new_gmail_email_extra'):
813 cnt = 0
814 for gmessage in gmail_messages_list:
815 # FIXME: emulate Gtalk client popups. find out what they
816 # parse and how they decide what to show each message has a
817 # 'From', 'Subject' and 'Snippet' field
818 if cnt >= 5:
819 break
820 senders = ',\n '.join(reversed(gmessage['From']))
821 text += _('\n\nFrom: %(from_address)s\nSubject: '
822 '%(subject)s\n%(snippet)s') % {'from_address': senders,
823 'subject': gmessage['Subject'],
824 'snippet': gmessage['Snippet']}
825 cnt += 1
827 command = gajim.config.get('notify_on_new_gmail_email_command')
828 if command:
829 Popen(command, shell=True)
831 if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
832 helpers.play_sound('gmail_received')
833 notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail',
834 path_to_image=path, title=title, text=text)
836 def handle_event_file_request_error(self, account, array):
837 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
838 jid, file_props, errmsg = array
839 jid = gajim.get_jid_without_resource(jid)
840 ft = self.instances['file_transfers']
841 ft.set_status(file_props['type'], file_props['sid'], 'stop')
842 errno = file_props['error']
844 if helpers.allow_popup_window(account):
845 if errno in (-4, -5):
846 ft.show_stopped(jid, file_props, errmsg)
847 else:
848 ft.show_request_error(file_props)
849 return
851 if errno in (-4, -5):
852 msg_type = 'file-error'
853 else:
854 msg_type = 'file-request-error'
856 self.add_event(account, jid, msg_type, file_props)
858 if helpers.allow_showing_notification(account):
859 # check if we should be notified
860 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
861 event_type = _('File Transfer Error')
862 notify.popup(event_type, jid, account, msg_type, path,
863 title = event_type, text = file_props['name'])
865 def handle_event_file_request(self, account, array):
866 jid = array[0]
867 jid = gajim.get_jid_without_resource(jid)
868 if jid not in gajim.contacts.get_jid_list(account):
869 keyID = ''
870 attached_keys = gajim.config.get_per('accounts', account,
871 'attached_gpg_keys').split()
872 if jid in attached_keys:
873 keyID = attached_keys[attached_keys.index(jid) + 1]
874 contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
875 account=account, keyID=keyID)
876 gajim.contacts.add_contact(account, contact)
877 self.roster.add_contact(contact.jid, account)
878 file_props = array[1]
879 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
881 if helpers.allow_popup_window(account):
882 self.instances['file_transfers'].show_file_request(account, contact,
883 file_props)
884 return
886 self.add_event(account, jid, 'file-request', file_props)
888 if helpers.allow_showing_notification(account):
889 path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
890 txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
891 account, jid)
892 event_type = _('File Transfer Request')
893 notify.popup(event_type, jid, account, 'file-request',
894 path_to_image = path, title = event_type, text = txt)
896 def handle_event_file_error(self, title, message):
897 dialogs.ErrorDialog(title, message)
899 def handle_event_file_progress(self, account, file_props):
900 if time.time() - self.last_ftwindow_update > 0.5:
901 # update ft window every 500ms
902 self.last_ftwindow_update = time.time()
903 self.instances['file_transfers'].set_progress(file_props['type'],
904 file_props['sid'], file_props['received-len'])
906 def handle_event_file_rcv_completed(self, account, file_props):
907 ft = self.instances['file_transfers']
908 if file_props['error'] == 0:
909 ft.set_progress(file_props['type'], file_props['sid'],
910 file_props['received-len'])
911 else:
912 ft.set_status(file_props['type'], file_props['sid'], 'stop')
913 if 'stalled' in file_props and file_props['stalled'] or \
914 'paused' in file_props and file_props['paused']:
915 return
916 if file_props['type'] == 'r': # we receive a file
917 jid = unicode(file_props['sender'])
918 else: # we send a file
919 jid = unicode(file_props['receiver'])
921 if helpers.allow_popup_window(account):
922 if file_props['error'] == 0:
923 if gajim.config.get('notify_on_file_complete'):
924 ft.show_completed(jid, file_props)
925 elif file_props['error'] == -1:
926 ft.show_stopped(jid, file_props,
927 error_msg=_('Remote contact stopped transfer'))
928 elif file_props['error'] == -6:
929 ft.show_stopped(jid, file_props,
930 error_msg=_('Error opening file'))
931 return
933 msg_type = ''
934 event_type = ''
935 if file_props['error'] == 0 and gajim.config.get(
936 'notify_on_file_complete'):
937 msg_type = 'file-completed'
938 event_type = _('File Transfer Completed')
939 elif file_props['error'] in (-1, -6):
940 msg_type = 'file-stopped'
941 event_type = _('File Transfer Stopped')
943 if event_type == '':
944 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
945 # this should never happen but it does. see process_result() in
946 # socks5.py
947 # who calls this func (sth is really wrong unless this func is also
948 # registered as progress_cb
949 return
951 if msg_type:
952 self.add_event(account, jid, msg_type, file_props)
954 if file_props is not None:
955 if file_props['type'] == 'r':
956 # get the name of the sender, as it is in the roster
957 sender = unicode(file_props['sender']).split('/')[0]
958 name = gajim.contacts.get_first_contact_from_jid(account,
959 sender).get_shown_name()
960 filename = os.path.basename(file_props['file-name'])
961 if event_type == _('File Transfer Completed'):
962 txt = _('You successfully received %(filename)s from '
963 '%(name)s.') % {'filename': filename, 'name': name}
964 img_name = 'gajim-ft_done'
965 else: # ft stopped
966 txt = _('File transfer of %(filename)s from %(name)s '
967 'stopped.') % {'filename': filename, 'name': name}
968 img_name = 'gajim-ft_stopped'
969 else:
970 receiver = file_props['receiver']
971 if hasattr(receiver, 'jid'):
972 receiver = receiver.jid
973 receiver = receiver.split('/')[0]
974 # get the name of the contact, as it is in the roster
975 name = gajim.contacts.get_first_contact_from_jid(account,
976 receiver).get_shown_name()
977 filename = os.path.basename(file_props['file-name'])
978 if event_type == _('File Transfer Completed'):
979 txt = _('You successfully sent %(filename)s to %(name)s.')\
980 % {'filename': filename, 'name': name}
981 img_name = 'gajim-ft_done'
982 else: # ft stopped
983 txt = _('File transfer of %(filename)s to %(name)s '
984 'stopped.') % {'filename': filename, 'name': name}
985 img_name = 'gajim-ft_stopped'
986 path = gtkgui_helpers.get_icon_path(img_name, 48)
987 else:
988 txt = ''
989 path = ''
991 if gajim.config.get('notify_on_file_complete') and \
992 (gajim.config.get('autopopupaway') or \
993 gajim.connections[account].connected in (2, 3)):
994 # we want to be notified and we are online/chat or we don't mind
995 # bugged when away/na/busy
996 notify.popup(event_type, jid, account, msg_type, path_to_image=path,
997 title=event_type, text=txt)
999 def ask_offline_status(self, account):
1000 for contact in gajim.contacts.iter_contacts(account):
1001 gajim.connections[account].request_last_status_time(contact.jid,
1002 contact.resource)
1004 def handle_event_signed_in(self, account, empty):
1006 SIGNED_IN event is emitted when we sign in, so handle it
1008 # ('SIGNED_IN', account, ())
1009 # block signed in notifications for 30 seconds
1010 gajim.block_signed_in_notifications[account] = True
1011 self.roster.set_actions_menu_needs_rebuild()
1012 self.roster.draw_account(account)
1013 state = self.sleeper.getState()
1014 connected = gajim.connections[account].connected
1015 if gajim.config.get('ask_offline_status_on_connection'):
1016 # Ask offline status in 1 minute so w'are sure we got all online
1017 # presences
1018 gobject.timeout_add_seconds(60, self.ask_offline_status, account)
1019 if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
1020 # we go online or free for chat, so we activate auto status
1021 gajim.sleeper_state[account] = 'online'
1022 elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
1023 (state == common.sleepy.STATE_XA and connected == 5)):
1024 # If we are autoaway/xa and come back after a disconnection, do
1025 # nothing
1026 # Else disable autoaway
1027 gajim.sleeper_state[account] = 'off'
1028 invisible_show = gajim.SHOW_LIST.index('invisible')
1029 # We cannot join rooms if we are invisible
1030 if gajim.connections[account].connected == invisible_show:
1031 return
1032 # join already open groupchats
1033 for gc_control in self.msg_win_mgr.get_controls(
1034 message_control.TYPE_GC) + self.minimized_controls[account].values():
1035 if account != gc_control.account:
1036 continue
1037 room_jid = gc_control.room_jid
1038 if room_jid in gajim.gc_connected[account] and \
1039 gajim.gc_connected[account][room_jid]:
1040 continue
1041 nick = gc_control.nick
1042 password = gajim.gc_passwords.get(room_jid, '')
1043 gajim.connections[account].join_gc(nick, room_jid, password)
1044 # send currently played music
1045 if gajim.connections[account].pep_supported and dbus_support.supported \
1046 and gajim.config.get_per('accounts', account, 'publish_tune'):
1047 self.enable_music_listener()
1048 # enable location listener
1049 if gajim.connections[account].pep_supported and dbus_support.supported \
1050 and gajim.config.get_per('accounts', account, 'publish_location'):
1051 location_listener.enable()
1052 if gajim.connections[account].archiving_supported:
1053 # Start merging logs from server
1054 gajim.connections[account].request_modifications_page(
1055 gajim.config.get_per('accounts', account, 'last_archiving_time'))
1056 gajim.config.set_per('accounts', account, 'last_archiving_time',
1057 time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
1059 def handle_event_metacontacts(self, account, tags_list):
1060 gajim.contacts.define_metacontacts(account, tags_list)
1061 self.roster.redraw_metacontacts(account)
1063 def handle_atom_entry(self, obj):
1064 AtomWindow.newAtomEntry(obj.atom_entry)
1066 def handle_event_failed_decrypt(self, account, data):
1067 jid, tim, session = data
1069 details = _('Unable to decrypt message from '
1070 '%s\nIt may have been tampered with.') % jid
1072 ctrl = session.control
1073 if ctrl:
1074 ctrl.print_conversation_line(details, 'status', '', tim)
1075 else:
1076 dialogs.WarningDialog(_('Unable to decrypt message'),
1077 details)
1079 # terminate the session
1080 session.terminate_e2e()
1081 session.conn.delete_session(jid, session.thread_id)
1083 # restart the session
1084 if ctrl:
1085 ctrl.begin_e2e_negotiation()
1087 def handle_event_zc_name_conflict(self, account, data):
1088 def on_ok(new_name):
1089 gajim.config.set_per('accounts', account, 'name', new_name)
1090 show = gajim.connections[account].old_show
1091 status = gajim.connections[account].status
1092 gajim.connections[account].username = new_name
1093 gajim.connections[account].change_status(show, status)
1094 def on_cancel():
1095 gajim.connections[account].change_status('offline', '')
1097 dlg = dialogs.InputDialog(_('Username Conflict'),
1098 _('Please type a new username for your local account'),
1099 input_str=data, is_modal=True, ok_handler=on_ok,
1100 cancel_handler=on_cancel)
1102 def handle_event_resource_conflict(self, obj):
1103 # ('RESOURCE_CONFLICT', account, ())
1104 # First we go offline, but we don't overwrite status message
1105 account = obj.conn.name
1106 conn = obj.conn
1107 self.roster.send_status(account, 'offline', conn.status)
1108 def on_ok(new_resource):
1109 gajim.config.set_per('accounts', account, 'resource', new_resource)
1110 self.roster.send_status(account, conn.old_show, conn.status)
1111 proposed_resource = conn.server_resource
1112 proposed_resource += gajim.config.get('gc_proposed_nick_char')
1113 dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
1114 _('You are already connected to this account with the same '
1115 'resource. Please type a new one'), resource=proposed_resource,
1116 ok_handler=on_ok)
1118 def handle_event_jingle_incoming(self, obj):
1119 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
1120 # data...))
1121 # TODO: conditional blocking if peer is not in roster
1123 account = obj.conn.name
1124 content_types = set(c[0] for c in obj.contents)
1126 # check type of jingle session
1127 if 'audio' in content_types or 'video' in content_types:
1128 # a voip session...
1129 # we now handle only voip, so the only thing we will do here is
1130 # not to return from function
1131 pass
1132 else:
1133 # unknown session type... it should be declined in common/jingle.py
1134 return
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 'audio' in content_types:
1140 ctrl.set_audio_state('connection_received', obj.sid)
1141 if 'video' in content_types:
1142 ctrl.set_video_state('connection_received', obj.sid)
1144 dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1145 if dlg:
1146 dlg.add_contents(content_types)
1147 return
1149 if helpers.allow_popup_window(account):
1150 dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid,
1151 content_types)
1152 return
1154 self.add_event(account, obj.jid, 'jingle-incoming', (obj.fjid, obj.sid,
1155 content_types))
1157 if helpers.allow_showing_notification(account):
1158 # TODO: we should use another pixmap ;-)
1159 txt = _('%s wants to start a voice chat.') % \
1160 gajim.get_name_from_jid(account, obj.fjid)
1161 path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
1162 event_type = _('Voice Chat Request')
1163 notify.popup(event_type, obj.fjid, account, 'jingle-incoming',
1164 path_to_image=path, title=event_type, text=txt)
1166 def handle_event_jingle_connected(self, obj):
1167 # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
1168 if obj.media in ('audio', 'video'):
1169 account = obj.conn.name
1170 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1171 or self.msg_win_mgr.get_control(obj.jid, account))
1172 if ctrl:
1173 if obj.media == 'audio':
1174 ctrl.set_audio_state('connected', obj.sid)
1175 else:
1176 ctrl.set_video_state('connected', obj.sid)
1178 def handle_event_jingle_disconnected(self, obj):
1179 # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
1180 account = obj.conn.name
1181 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1182 or self.msg_win_mgr.get_control(obj.jid, account))
1183 if ctrl:
1184 if obj.media is None:
1185 ctrl.stop_jingle(sid=obj.sid, reason=obj.reason)
1186 elif obj.media == 'audio':
1187 ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason)
1188 elif obj.media == 'video':
1189 ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason)
1190 dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1191 if dialog:
1192 if obj.media is None:
1193 dialog.dialog.destroy()
1194 else:
1195 dialog.remove_contents((obj.media, ))
1197 def handle_event_jingle_error(self, obj):
1198 # ('JINGLE_ERROR', account, (peerjid, sid, reason))
1199 account = obj.conn.name
1200 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1201 or self.msg_win_mgr.get_control(obj.jid, account))
1202 if ctrl:
1203 ctrl.set_audio_state('error', reason=obj.reason)
1205 def handle_event_roster_item_exchange(self, obj):
1206 # data = (action in [add, delete, modify], exchange_list, jid_from)
1207 dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action,
1208 obj.exchange_items_list, obj.fjid)
1210 def handle_event_ssl_error(self, obj):
1211 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
1212 account = obj.conn.name
1213 server = gajim.config.get_per('accounts', account, 'hostname')
1215 def on_ok(is_checked):
1216 del self.instances[account]['online_dialog']['ssl_error']
1217 if is_checked[0]:
1218 # Check if cert is already in file
1219 certs = ''
1220 if os.path.isfile(gajim.MY_CACERTS):
1221 f = open(gajim.MY_CACERTS)
1222 certs = f.read()
1223 f.close()
1224 if obj.cert in certs:
1225 dialogs.ErrorDialog(_('Certificate Already in File'),
1226 _('This certificate is already in file %s, so it\'s '
1227 'not added again.') % gajim.MY_CACERTS)
1228 else:
1229 f = open(gajim.MY_CACERTS, 'a')
1230 f.write(server + '\n')
1231 f.write(obj.cert + '\n\n')
1232 f.close()
1233 gajim.config.set_per('accounts', account,
1234 'ssl_fingerprint_sha1', obj.fingerprint)
1235 if is_checked[1]:
1236 ignore_ssl_errors = gajim.config.get_per('accounts', account,
1237 'ignore_ssl_errors').split()
1238 ignore_ssl_errors.append(str(obj.error_num))
1239 gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
1240 ' '.join(ignore_ssl_errors))
1241 obj.conn.ssl_certificate_accepted()
1243 def on_cancel():
1244 del self.instances[account]['online_dialog']['ssl_error']
1245 iobj.conn.disconnect(on_purpose=True)
1246 self.handle_event_status(account, 'offline')
1248 pritext = _('Error verifying SSL certificate')
1249 sectext = _('There was an error verifying the SSL certificate of your '
1250 'jabber server: %(error)s\nDo you still want to connect to this '
1251 'server?') % {'error': obj.error_text}
1252 if obj.error_num in (18, 27):
1253 checktext1 = _('Add this certificate to the list of trusted '
1254 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % \
1255 obj.fingerprint
1256 else:
1257 checktext1 = ''
1258 checktext2 = _('Ignore this error for this certificate.')
1259 if 'ssl_error' in self.instances[account]['online_dialog']:
1260 self.instances[account]['online_dialog']['ssl_error'].destroy()
1261 self.instances[account]['online_dialog']['ssl_error'] = \
1262 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1263 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
1265 def handle_event_fingerprint_error(self, obj):
1266 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
1267 account = obj.conn.name
1268 def on_yes(is_checked):
1269 del self.instances[account]['online_dialog']['fingerprint_error']
1270 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
1271 obj.new_fingerprint)
1272 # Reset the ignored ssl errors
1273 gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
1274 obj.conn.ssl_certificate_accepted()
1276 def on_no():
1277 del self.instances[account]['online_dialog']['fingerprint_error']
1278 obj.conn.disconnect(on_purpose=True)
1279 self.handle_event_status(account, 'offline')
1281 pritext = _('SSL certificate error')
1282 sectext = _('It seems the SSL certificate of account %(account)s has '
1283 'changed or your connection is being hacked.\nOld fingerprint: '
1284 '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect '
1285 'and update the fingerprint of the certificate?') % \
1286 {'account': account, 'old': gajim.config.get_per('accounts',
1287 account, 'ssl_fingerprint_sha1'), 'new': obj.new_fingerprint}
1288 if 'fingerprint_error' in self.instances[account]['online_dialog']:
1289 self.instances[account]['online_dialog']['fingerprint_error'].\
1290 destroy()
1291 self.instances[account]['online_dialog']['fingerprint_error'] = \
1292 dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
1293 on_response_no=on_no)
1295 def handle_event_plain_connection(self, obj):
1296 # ('PLAIN_CONNECTION', account, (connection))
1297 def on_ok(is_checked):
1298 if not is_checked[0]:
1299 on_cancel()
1300 return
1301 # On cancel call del self.instances, so don't call it another time
1302 # before
1303 del self.instances[obj.conn.name]['online_dialog']\
1304 ['plain_connection']
1305 if is_checked[1]:
1306 gajim.config.set_per('accounts', obj.conn.name,
1307 'warn_when_plaintext_connection', False)
1308 obj.conn.connection_accepted(obj.xmpp_client, 'plain')
1310 def on_cancel():
1311 del self.instances[obj.conn.name]['online_dialog']\
1312 ['plain_connection']
1313 obj.conn.disconnect(on_purpose=True)
1314 self.handle_event_status(obj.conn.name, 'offline')
1316 pritext = _('Insecure connection')
1317 sectext = _('You are about to connect to the server with an insecure '
1318 'connection. This means all your conversations will be '
1319 'exchanged unencrypted. Are you sure you want to do that?')
1320 checktext1 = _('Yes, I really want to connect insecurely')
1321 checktext2 = _('_Do not ask me again')
1322 if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']:
1323 self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
1324 destroy()
1325 self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
1326 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1327 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1328 is_modal=False)
1330 def handle_event_insecure_ssl_connection(self, obj):
1331 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1332 def on_ok(is_checked):
1333 if not is_checked[0]:
1334 on_cancel()
1335 return
1336 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1337 if is_checked[1]:
1338 gajim.config.set_per('accounts', obj.conn.name,
1339 'warn_when_insecure_ssl_connection', False)
1340 if obj.conn.connected == 0:
1341 # We have been disconnecting (too long time since window is
1342 # opened)
1343 # re-connect with auto-accept
1344 obj.conn.connection_auto_accepted = True
1345 show, msg = obj.conn.continue_connect_info[:2]
1346 self.roster.send_status(obj.conn.name, show, msg)
1347 return
1348 obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type)
1350 def on_cancel():
1351 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1352 obj.conn.disconnect(on_purpose=True)
1353 self.handle_event_status(obj.conn.name, 'offline')
1355 pritext = _('Insecure connection')
1356 sectext = _('You are about to send your password on an insecure '
1357 'connection. You should install PyOpenSSL to prevent that. Are you '
1358 'sure you want to do that?')
1359 checktext1 = _('Yes, I really want to connect insecurely')
1360 checktext2 = _('_Do not ask me again')
1361 if 'insecure_ssl' in self.instances[obj.conn.name]['online_dialog']:
1362 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\
1363 destroy()
1364 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \
1365 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1366 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1367 is_modal=False)
1369 def handle_event_insecure_password(self, obj):
1370 # ('INSECURE_PASSWORD', account, ())
1371 def on_ok(is_checked):
1372 if not is_checked[0]:
1373 on_cancel()
1374 return
1375 del self.instances[obj.conn.name]['online_dialog']\
1376 ['insecure_password']
1377 if is_checked[1]:
1378 gajim.config.set_per('accounts', obj.conn.name,
1379 'warn_when_insecure_password', False)
1380 if obj.conn.connected == 0:
1381 # We have been disconnecting (too long time since window is
1382 # opened)
1383 # re-connect with auto-accept
1384 obj.conn.connection_auto_accepted = True
1385 show, msg = obj.conn.continue_connect_info[:2]
1386 self.roster.send_status(obj.conn.name, show, msg)
1387 return
1388 obj.conn.accept_insecure_password()
1390 def on_cancel():
1391 del self.instances[obj.conn.name]['online_dialog']\
1392 ['insecure_password']
1393 obj.conn.disconnect(on_purpose=True)
1394 self.handle_event_status(obj.conn.name, 'offline')
1396 pritext = _('Insecure connection')
1397 sectext = _('You are about to send your password unencrypted on an '
1398 'insecure connection. Are you sure you want to do that?')
1399 checktext1 = _('Yes, I really want to connect insecurely')
1400 checktext2 = _('_Do not ask me again')
1401 if 'insecure_password' in self.instances[obj.conn.name]\
1402 ['online_dialog']:
1403 self.instances[obj.conn.name]['online_dialog']\
1404 ['insecure_password'].destroy()
1405 self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
1406 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1407 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1408 is_modal=False)
1410 def create_core_handlers_list(self):
1411 self.handlers = {
1412 'WARNING': [self.handle_event_warning],
1413 'ERROR': [self.handle_event_error],
1414 'DB_ERROR': [self.handle_event_db_error],
1415 'INFORMATION': [self.handle_event_information],
1416 'MSGERROR': [self.handle_event_msgerror],
1417 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info],
1418 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items],
1419 'FILE_REQUEST': [self.handle_event_file_request],
1420 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
1421 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
1422 'SIGNED_IN': [self.handle_event_signed_in],
1423 'METACONTACTS': [self.handle_event_metacontacts],
1424 'FAILED_DECRYPT': [self.handle_event_failed_decrypt],
1425 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict],
1426 'PASSWORD_REQUIRED': [self.handle_event_password_required],
1427 'atom-entry-received': [self.handle_atom_entry],
1428 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
1429 'bookmarks-received': [self.handle_event_bookmarks],
1430 'connection-lost': [self.handle_event_connection_lost],
1431 'fingerprint-error': [self.handle_event_fingerprint_error],
1432 'gc-invitation-received': [self.handle_event_gc_invitation],
1433 'gc-presence-received': [self.handle_event_gc_presence],
1434 'gmail-notify': [self.handle_event_gmail_notify],
1435 'gpg-password-required': [self.handle_event_gpg_password_required],
1436 'gpg-trust-key': [self.handle_event_gpg_trust_key],
1437 'http-auth-received': [self.handle_event_http_auth],
1438 'insecure-password': [self.handle_event_insecure_password],
1439 'insecure-ssl-connection': \
1440 [self.handle_event_insecure_ssl_connection],
1441 'iq-error-received': [self.handle_event_iq_error],
1442 'jingle-connected-received': [self.handle_event_jingle_connected],
1443 'jingle-disconnected-received': [
1444 self.handle_event_jingle_disconnected],
1445 'jingle-error-received': [self.handle_event_jingle_error],
1446 'jingle-request-received': [self.handle_event_jingle_incoming],
1447 'last-result-received': [self.handle_event_last_status_time],
1448 'message-not-sent': [self.handle_event_msgnotsent],
1449 'message-sent': [self.handle_event_msgsent],
1450 'muc-admin-received': [self.handle_event_gc_affiliation],
1451 'muc-owner-received': [self.handle_event_gc_config],
1452 'our-show': [self.handle_event_status],
1453 'plain-connection': [self.handle_event_plain_connection],
1454 'presence-received': [self.handle_event_presence],
1455 'roster-info': [self.handle_event_roster_info],
1456 'roster-item-exchange-received': \
1457 [self.handle_event_roster_item_exchange],
1458 'ssl-error': [self.handle_event_ssl_error],
1459 'stream-conflict-received': [self.handle_event_resource_conflict],
1460 'subscribe-presence-received': [
1461 self.handle_event_subscribe_presence],
1462 'subscribed-presence-received': [
1463 self.handle_event_subscribed_presence],
1464 'unsubscribed-presence-received': [
1465 self.handle_event_unsubscribed_presence],
1466 'vcard-received': [self.handle_event_vcard],
1469 def register_core_handlers(self):
1471 Register core handlers in Global Events Dispatcher (GED).
1473 This is part of rewriting whole events handling system to use GED.
1475 for event_name, event_handlers in self.handlers.iteritems():
1476 for event_handler in event_handlers:
1477 gajim.ged.register_event_handler(event_name, ged.GUI1,
1478 event_handler)
1480 ################################################################################
1481 ### Methods dealing with gajim.events
1482 ################################################################################
1484 def add_event(self, account, jid, type_, event_args):
1486 Add an event to the gajim.events var
1488 # We add it to the gajim.events queue
1489 # Do we have a queue?
1490 jid = gajim.get_jid_without_resource(jid)
1491 no_queue = len(gajim.events.get_events(account, jid)) == 0
1492 # type_ can be gc-invitation file-send-error file-error
1493 # file-request-error file-request file-completed file-stopped
1494 # jingle-incoming
1495 # event_type can be in advancedNotificationWindow.events_list
1496 event_types = {'file-request': 'ft_request',
1497 'file-completed': 'ft_finished'}
1498 event_type = event_types.get(type_)
1499 show_in_roster = notify.get_show_in_roster(event_type, account, jid)
1500 show_in_systray = notify.get_show_in_systray(event_type, account, jid)
1501 event = gajim.events.create_event(type_, event_args,
1502 show_in_roster=show_in_roster,
1503 show_in_systray=show_in_systray)
1504 gajim.events.add_event(account, jid, event)
1506 self.roster.show_title()
1507 if no_queue: # We didn't have a queue: we change icons
1508 if not gajim.contacts.get_contact_with_highest_priority(account,
1509 jid):
1510 if type_ == 'gc-invitation':
1511 self.roster.add_groupchat(jid, account, status='offline')
1512 else:
1513 # add contact to roster ("Not In The Roster") if he is not
1514 self.roster.add_to_not_in_the_roster(account, jid)
1515 else:
1516 self.roster.draw_contact(jid, account)
1518 # Select the big brother contact in roster, it's visible because it has
1519 # events.
1520 family = gajim.contacts.get_metacontacts_family(account, jid)
1521 if family:
1522 nearby_family, bb_jid, bb_account = \
1523 gajim.contacts.get_nearby_family_and_big_brother(family,
1524 account)
1525 else:
1526 bb_jid, bb_account = jid, account
1527 self.roster.select_contact(bb_jid, bb_account)
1529 def handle_event(self, account, fjid, type_):
1530 w = None
1531 ctrl = None
1532 session = None
1534 resource = gajim.get_resource_from_jid(fjid)
1535 jid = gajim.get_jid_without_resource(fjid)
1537 if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
1538 w = self.msg_win_mgr.get_window(jid, account)
1539 if jid in self.minimized_controls[account]:
1540 self.roster.on_groupchat_maximized(None, jid, account)
1541 return
1542 else:
1543 ctrl = self.msg_win_mgr.get_gc_control(jid, account)
1545 elif type_ in ('printed_chat', 'chat', ''):
1546 # '' is for log in/out notifications
1548 if type_ != '':
1549 event = gajim.events.get_first_event(account, fjid, type_)
1550 if not event:
1551 event = gajim.events.get_first_event(account, jid, type_)
1552 if not event:
1553 return
1555 if type_ == 'printed_chat':
1556 ctrl = event.parameters[0]
1557 elif type_ == 'chat':
1558 session = event.parameters[8]
1559 ctrl = session.control
1560 elif type_ == '':
1561 ctrl = self.msg_win_mgr.get_control(fjid, account)
1563 if not ctrl:
1564 highest_contact = gajim.contacts.\
1565 get_contact_with_highest_priority(account, jid)
1566 # jid can have a window if this resource was lower when he sent
1567 # message and is now higher because the other one is offline
1568 if resource and highest_contact.resource == resource and \
1569 not self.msg_win_mgr.has_window(jid, account):
1570 # remove resource of events too
1571 gajim.events.change_jid(account, fjid, jid)
1572 resource = None
1573 fjid = jid
1574 contact = None
1575 if resource:
1576 contact = gajim.contacts.get_contact(account, jid, resource)
1577 if not contact:
1578 contact = highest_contact
1580 ctrl = self.new_chat(contact, account, resource=resource,
1581 session=session)
1583 gajim.last_message_time[account][jid] = 0 # long time ago
1585 w = ctrl.parent_win
1586 elif type_ in ('printed_pm', 'pm'):
1587 # assume that the most recently updated control we have for this
1588 # party is the one that this event was in
1589 event = gajim.events.get_first_event(account, fjid, type_)
1590 if not event:
1591 event = gajim.events.get_first_event(account, jid, type_)
1593 if type_ == 'printed_pm':
1594 ctrl = event.parameters[0]
1595 elif type_ == 'pm':
1596 session = event.parameters[8]
1598 if session and session.control:
1599 ctrl = session.control
1600 elif not ctrl:
1601 room_jid = jid
1602 nick = resource
1603 gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
1604 nick)
1605 if gc_contact:
1606 show = gc_contact.show
1607 else:
1608 show = 'offline'
1609 gc_contact = gajim.contacts.create_gc_contact(
1610 room_jid=room_jid, account=account, name=nick,
1611 show=show)
1613 if not session:
1614 session = gajim.connections[account].make_new_session(
1615 fjid, None, type_='pm')
1617 self.new_private_chat(gc_contact, account, session=session)
1618 ctrl = session.control
1620 w = ctrl.parent_win
1621 elif type_ in ('normal', 'file-request', 'file-request-error',
1622 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
1623 'jingle-incoming'):
1624 # Get the first single message event
1625 event = gajim.events.get_first_event(account, fjid, type_)
1626 if not event:
1627 # default to jid without resource
1628 event = gajim.events.get_first_event(account, jid, type_)
1629 if not event:
1630 return
1631 # Open the window
1632 self.roster.open_event(account, jid, event)
1633 else:
1634 # Open the window
1635 self.roster.open_event(account, fjid, event)
1636 elif type_ == 'gmail':
1637 url = gajim.connections[account].gmail_url
1638 if url:
1639 helpers.launch_browser_mailer('url', url)
1640 elif type_ == 'gc-invitation':
1641 event = gajim.events.get_first_event(account, jid, type_)
1642 data = event.parameters
1643 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1644 data[1], data[3])
1645 gajim.events.remove_events(account, jid, event)
1646 self.roster.draw_contact(jid, account)
1647 elif type_ == 'subscription_request':
1648 event = gajim.events.get_first_event(account, jid, type_)
1649 data = event.parameters
1650 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1651 gajim.events.remove_events(account, jid, event)
1652 self.roster.draw_contact(jid, account)
1653 elif type_ == 'unsubscribed':
1654 event = gajim.events.get_first_event(account, jid, type_)
1655 contact = event.parameters
1656 self.show_unsubscribed_dialog(account, contact)
1657 gajim.events.remove_events(account, jid, event)
1658 self.roster.draw_contact(jid, account)
1659 if w:
1660 w.set_active_tab(ctrl)
1661 w.window.window.focus(gtk.get_current_event_time())
1662 # Using isinstance here because we want to catch all derived types
1663 if isinstance(ctrl, ChatControlBase):
1664 tv = ctrl.conv_textview
1665 tv.scroll_to_end()
1667 ################################################################################
1668 ### Methods dealing with emoticons
1669 ################################################################################
1671 def image_is_ok(self, image):
1672 if not os.path.exists(image):
1673 return False
1674 img = gtk.Image()
1675 try:
1676 img.set_from_file(image)
1677 except Exception:
1678 return False
1679 t = img.get_storage_type()
1680 if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
1681 return False
1682 return True
1684 @property
1685 def basic_pattern_re(self):
1686 if not self._basic_pattern_re:
1687 self._basic_pattern_re = re.compile(self.basic_pattern,
1688 re.IGNORECASE)
1689 return self._basic_pattern_re
1691 @property
1692 def emot_and_basic_re(self):
1693 if not self._emot_and_basic_re:
1694 self._emot_and_basic_re = re.compile(self.emot_and_basic,
1695 re.IGNORECASE + re.UNICODE)
1696 return self._emot_and_basic_re
1698 @property
1699 def sth_at_sth_dot_sth_re(self):
1700 if not self._sth_at_sth_dot_sth_re:
1701 self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
1702 return self._sth_at_sth_dot_sth_re
1704 @property
1705 def invalid_XML_chars_re(self):
1706 if not self._invalid_XML_chars_re:
1707 self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
1708 return self._invalid_XML_chars_re
1710 def make_regexps(self):
1711 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
1712 # one escapes the metachars with \
1713 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
1714 # \s matches any whitespace character
1715 # \w any alphanumeric character
1716 # \W any non-alphanumeric character
1717 # \b means word boundary. This is a zero-width assertion that
1718 # matches only at the beginning or end of a word.
1719 # ^ matches at the beginning of lines
1721 # * means 0 or more times
1722 # + means 1 or more times
1723 # ? means 0 or 1 time
1724 # | means or
1725 # [^*] anything but '*' (inside [] you don't have to escape metachars)
1726 # [^\s*] anything but whitespaces and '*'
1727 # (?<!\S) is a one char lookbehind assertion and asks for any leading
1728 # whitespace
1729 # and mathces beginning of lines so we have correct formatting detection
1730 # even if the the text is just '*foo*'
1731 # (?!\S) is the same thing but it's a lookahead assertion
1732 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
1733 # the end
1734 # so http://be) will match http://be and http://be)be) will match
1735 # http://be)be
1737 legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
1738 r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
1739 r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
1740 r"|%[A-Fa-f0-9]{2})+"\
1741 r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
1742 # NOTE: it's ok to catch www.gr such stuff exist!
1744 # FIXME: recognize xmpp: and treat it specially
1745 links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
1746 r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
1747 r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
1749 # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
1750 mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
1752 # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
1753 # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
1754 formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
1755 r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
1756 r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
1758 latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\
1759 r'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
1761 basic_pattern = links + '|' + mail + '|' + legacy_prefixes
1763 link_pattern = basic_pattern
1764 self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
1766 if gajim.config.get('use_latex'):
1767 basic_pattern += latex
1769 if gajim.config.get('ascii_formatting'):
1770 basic_pattern += formatting
1771 self.basic_pattern = basic_pattern
1773 emoticons_pattern = ''
1774 if gajim.config.get('emoticons_theme'):
1775 # When an emoticon is bordered by an alpha-numeric character it is
1776 # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
1777 # We still allow multiple emoticons side-by-side like :P:P:P
1778 # sort keys by length so :qwe emot is checked before :q
1779 keys = sorted(self.emoticons, key=len, reverse=True)
1780 emoticons_pattern_prematch = ''
1781 emoticons_pattern_postmatch = ''
1782 emoticon_length = 0
1783 for emoticon in keys: # travel thru emoticons list
1784 emoticon = emoticon.decode('utf-8')
1785 emoticon_escaped = re.escape(emoticon) # espace regexp metachars
1786 # | means or in regexp
1787 emoticons_pattern += emoticon_escaped + '|'
1788 if (emoticon_length != len(emoticon)):
1789 # Build up expressions to match emoticons next to others
1790 emoticons_pattern_prematch = \
1791 emoticons_pattern_prematch[:-1] + ')|(?<='
1792 emoticons_pattern_postmatch = \
1793 emoticons_pattern_postmatch[:-1] + ')|(?='
1794 emoticon_length = len(emoticon)
1795 emoticons_pattern_prematch += emoticon_escaped + '|'
1796 emoticons_pattern_postmatch += emoticon_escaped + '|'
1797 # We match from our list of emoticons, but they must either have
1798 # whitespace, or another emoticon next to it to match successfully
1799 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
1800 emoticons_pattern = '|' + '(?:(?<![\w.]' + \
1801 emoticons_pattern_prematch[:-1] + '))' + '(?:' + \
1802 emoticons_pattern[:-1] + ')' + '(?:(?![\w]' + \
1803 emoticons_pattern_postmatch[:-1] + '))'
1805 # because emoticons match later (in the string) they need to be after
1806 # basic matches that may occur earlier
1807 self.emot_and_basic = basic_pattern + emoticons_pattern
1809 # needed for xhtml display
1810 self.emot_only = emoticons_pattern
1812 # at least one character in 3 parts (before @, after @, after .)
1813 self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
1815 # Invalid XML chars
1816 self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|'\
1817 u'[\ud800-\udfff]|[\ufffe-\uffff]'
1819 def popup_emoticons_under_button(self, button, parent_win):
1821 Popup the emoticons menu under button, located in parent_win
1823 gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
1824 button, parent_win)
1826 def prepare_emoticons_menu(self):
1827 menu = gtk.Menu()
1828 def emoticon_clicked(w, str_):
1829 if self.emoticon_menuitem_clicked:
1830 self.emoticon_menuitem_clicked(str_)
1831 # don't keep reference to CB of object
1832 # this will prevent making it uncollectable
1833 self.emoticon_menuitem_clicked = None
1834 def selection_done(widget):
1835 # remove reference to CB of object, which will
1836 # make it uncollectable
1837 self.emoticon_menuitem_clicked = None
1838 counter = 0
1839 # Calculate the side lenght of the popup to make it a square
1840 size = int(round(math.sqrt(len(self.emoticons_images))))
1841 for image in self.emoticons_images:
1842 item = gtk.MenuItem()
1843 img = gtk.Image()
1844 if isinstance(image[1], gtk.gdk.PixbufAnimation):
1845 img.set_from_animation(image[1])
1846 else:
1847 img.set_from_pixbuf(image[1])
1848 item.add(img)
1849 item.connect('activate', emoticon_clicked, image[0])
1850 #FIXME: add tooltip with ascii
1851 menu.attach(item, counter % size, counter % size + 1,
1852 counter / size, counter / size + 1)
1853 counter += 1
1854 menu.connect('selection-done', selection_done)
1855 menu.show_all()
1856 return menu
1858 def _init_emoticons(self, path, need_reload = False):
1859 #initialize emoticons dictionary and unique images list
1860 self.emoticons_images = list()
1861 self.emoticons = dict()
1862 self.emoticons_animations = dict()
1864 sys.path.append(path)
1865 import emoticons
1866 if need_reload:
1867 # we need to reload else that doesn't work when changing emoticon
1868 # set
1869 reload(emoticons)
1870 emots = emoticons.emoticons
1871 for emot_filename in emots:
1872 emot_file = os.path.join(path, emot_filename)
1873 if not self.image_is_ok(emot_file):
1874 continue
1875 for emot in emots[emot_filename]:
1876 emot = emot.decode('utf-8')
1877 # This avoids duplicated emoticons with the same image eg. :)
1878 # and :-)
1879 if not emot_file in self.emoticons.values():
1880 if emot_file.endswith('.gif'):
1881 pix = gtk.gdk.PixbufAnimation(emot_file)
1882 else:
1883 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file,
1884 16, 16)
1885 self.emoticons_images.append((emot, pix))
1886 self.emoticons[emot.upper()] = emot_file
1887 del emoticons
1888 sys.path.remove(path)
1890 def init_emoticons(self, need_reload = False):
1891 emot_theme = gajim.config.get('emoticons_theme')
1892 if not emot_theme:
1893 return
1895 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
1896 if not os.path.exists(path):
1897 # It's maybe a user theme
1898 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
1899 if not os.path.exists(path):
1900 # theme doesn't exist, disable emoticons
1901 dialogs.WarningDialog(_('Emoticons disabled'),
1902 _('Your configured emoticons theme has not been found, so '
1903 'emoticons have been disabled.'))
1904 gajim.config.set('emoticons_theme', '')
1905 return
1906 self._init_emoticons(path, need_reload)
1907 if len(self.emoticons) == 0:
1908 # maybe old format of emoticons file, try to convert it
1909 try:
1910 import pprint
1911 import emoticons
1912 emots = emoticons.emoticons
1913 fd = open(os.path.join(path, 'emoticons.py'), 'w')
1914 fd.write('emoticons = ')
1915 pprint.pprint( dict([
1916 (file_, [i for i in emots.keys() if emots[i] == file_])
1917 for file_ in set(emots.values())]), fd)
1918 fd.close()
1919 del emoticons
1920 self._init_emoticons(path, need_reload=True)
1921 except Exception:
1922 pass
1923 if len(self.emoticons) == 0:
1924 dialogs.WarningDialog(_('Emoticons disabled'),
1925 _('Your configured emoticons theme cannot been loaded. You '
1926 'maybe need to update the format of emoticons.py file. See '
1927 'http://trac.gajim.org/wiki/Emoticons for more details.'))
1928 if self.emoticons_menu:
1929 self.emoticons_menu.destroy()
1930 self.emoticons_menu = self.prepare_emoticons_menu()
1932 ################################################################################
1933 ### Methods for opening new messages controls
1934 ################################################################################
1936 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
1937 is_continued=False):
1939 Join the room immediately
1941 if not nick:
1942 nick = gajim.nicks[account]
1944 if self.msg_win_mgr.has_window(room_jid, account) and \
1945 gajim.gc_connected[account][room_jid]:
1946 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
1947 win = gc_ctrl.parent_win
1948 win.set_active_tab(gc_ctrl)
1949 dialogs.ErrorDialog(_('You are already in group chat %s') % \
1950 room_jid)
1951 return
1953 invisible_show = gajim.SHOW_LIST.index('invisible')
1954 if gajim.connections[account].connected == invisible_show:
1955 dialogs.ErrorDialog(
1956 _('You cannot join a group chat while you are invisible'))
1957 return
1959 minimized_control = gajim.interface.minimized_controls[account].get(
1960 room_jid, None)
1962 if minimized_control is None and not self.msg_win_mgr.has_window(
1963 room_jid, account):
1964 # Join new groupchat
1965 if minimize:
1966 # GCMIN
1967 contact = gajim.contacts.create_contact(jid=room_jid,
1968 account=account, name=nick)
1969 gc_control = GroupchatControl(None, contact, account)
1970 gajim.interface.minimized_controls[account][room_jid] = \
1971 gc_control
1972 self.roster.add_groupchat(room_jid, account)
1973 else:
1974 self.new_room(room_jid, nick, account,
1975 is_continued=is_continued)
1976 elif minimized_control is None:
1977 # We are already in that groupchat
1978 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
1979 gc_control.nick = nick
1980 gc_control.parent_win.set_active_tab(gc_control)
1981 else:
1982 # We are already in this groupchat and it is minimized
1983 minimized_control.nick = nick
1984 self.roster.add_groupchat(room_jid, account)
1986 # Connect
1987 gajim.connections[account].join_gc(nick, room_jid, password)
1988 if password:
1989 gajim.gc_passwords[room_jid] = password
1991 def new_room(self, room_jid, nick, account, is_continued=False):
1992 # Get target window, create a control, and associate it with the window
1993 # GCMIN
1994 contact = gajim.contacts.create_contact(jid=room_jid, account=account,
1995 name=nick)
1996 mw = self.msg_win_mgr.get_window(contact.jid, account)
1997 if not mw:
1998 mw = self.msg_win_mgr.create_window(contact, account,
1999 GroupchatControl.TYPE_ID)
2000 gc_control = GroupchatControl(mw, contact, account,
2001 is_continued=is_continued)
2002 mw.new_tab(gc_control)
2004 def new_private_chat(self, gc_contact, account, session=None):
2005 conn = gajim.connections[account]
2006 if not session and gc_contact.get_full_jid() in conn.sessions:
2007 sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
2008 values() if isinstance(s, ChatControlSession)]
2010 # look for an existing session with a chat control
2011 for s in sessions:
2012 if s.control:
2013 session = s
2014 break
2015 if not session and not len(sessions) == 0:
2016 # there are no sessions with chat controls, just take the first
2017 # one
2018 session = sessions[0]
2019 if not session:
2020 # couldn't find an existing ChatControlSession, just make a new one
2021 session = conn.make_new_session(gc_contact.get_full_jid(), None,
2022 'pm')
2024 contact = gc_contact.as_contact()
2025 if not session.control:
2026 message_window = self.msg_win_mgr.get_window(
2027 gc_contact.get_full_jid(), account)
2028 if not message_window:
2029 message_window = self.msg_win_mgr.create_window(contact,
2030 account, message_control.TYPE_PM)
2032 session.control = PrivateChatControl(message_window, gc_contact,
2033 contact, account, session)
2034 message_window.new_tab(session.control)
2036 if gajim.events.get_events(account, gc_contact.get_full_jid()):
2037 # We call this here to avoid race conditions with widget validation
2038 session.control.read_queue()
2040 return session.control
2042 def new_chat(self, contact, account, resource=None, session=None):
2043 # Get target window, create a control, and associate it with the window
2044 type_ = message_control.TYPE_CHAT
2046 fjid = contact.jid
2047 if resource:
2048 fjid += '/' + resource
2050 mw = self.msg_win_mgr.get_window(fjid, account)
2051 if not mw:
2052 mw = self.msg_win_mgr.create_window(contact, account, type_,
2053 resource)
2055 chat_control = ChatControl(mw, contact, account, session, resource)
2057 mw.new_tab(chat_control)
2059 if len(gajim.events.get_events(account, fjid)):
2060 # We call this here to avoid race conditions with widget validation
2061 chat_control.read_queue()
2063 return chat_control
2065 def new_chat_from_jid(self, account, fjid, message=None):
2066 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2067 contact = gajim.contacts.get_contact(account, jid, resource)
2068 added_to_roster = False
2069 if not contact:
2070 added_to_roster = True
2071 contact = self.roster.add_to_not_in_the_roster(account, jid,
2072 resource=resource)
2074 ctrl = self.msg_win_mgr.get_control(fjid, account)
2076 if not ctrl:
2077 ctrl = self.new_chat(contact, account,
2078 resource=resource)
2079 if len(gajim.events.get_events(account, fjid)):
2080 ctrl.read_queue()
2082 if message:
2083 buffer_ = ctrl.msg_textview.get_buffer()
2084 buffer_.set_text(message)
2085 mw = ctrl.parent_win
2086 mw.set_active_tab(ctrl)
2087 # For JEP-0172
2088 if added_to_roster:
2089 ctrl.user_nick = gajim.nicks[account]
2090 gobject.idle_add(mw.window.grab_focus)
2092 return ctrl
2094 def on_open_chat_window(self, widget, contact, account, resource=None,
2095 session=None):
2096 # Get the window containing the chat
2097 fjid = contact.jid
2099 if resource:
2100 fjid += '/' + resource
2102 ctrl = None
2104 if session:
2105 ctrl = session.control
2106 if not ctrl:
2107 win = self.msg_win_mgr.get_window(fjid, account)
2109 if win:
2110 ctrl = win.get_control(fjid, account)
2112 if not ctrl:
2113 ctrl = self.new_chat(contact, account, resource=resource,
2114 session=session)
2115 # last message is long time ago
2116 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2118 win = ctrl.parent_win
2120 win.set_active_tab(ctrl)
2122 if gajim.connections[account].is_zeroconf and \
2123 gajim.connections[account].status in ('offline', 'invisible'):
2124 ctrl = win.get_control(fjid, account)
2125 if ctrl:
2126 ctrl.got_disconnected()
2128 ################################################################################
2129 ### Other Methods
2130 ################################################################################
2132 def change_awn_icon_status(self, status):
2133 if not dbus_support.supported:
2134 # do nothing if user doesn't have D-Bus bindings
2135 return
2136 try:
2137 bus = dbus.SessionBus()
2138 if not 'com.google.code.Awn' in bus.list_names():
2139 # Awn is not installed
2140 return
2141 except Exception:
2142 return
2143 iconset = gajim.config.get('iconset')
2144 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
2145 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2146 status = status + '.png'
2147 elif status == 'online':
2148 prefix = ''
2149 status = gtkgui_helpers.get_icon_path('gajim', 32)
2150 path = os.path.join(prefix, status)
2151 try:
2152 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
2153 awn = dbus.Interface(obj, 'com.google.code.Awn')
2154 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
2155 except Exception:
2156 pass
2158 def enable_music_listener(self):
2159 listener = MusicTrackListener.get()
2160 if not self.music_track_changed_signal:
2161 self.music_track_changed_signal = listener.connect(
2162 'music-track-changed', self.music_track_changed)
2163 track = listener.get_playing_track()
2164 self.music_track_changed(listener, track)
2166 def disable_music_listener(self):
2167 listener = MusicTrackListener.get()
2168 listener.disconnect(self.music_track_changed_signal)
2169 self.music_track_changed_signal = None
2171 def music_track_changed(self, unused_listener, music_track_info,
2172 account=None):
2173 if not account:
2174 accounts = gajim.connections.keys()
2175 else:
2176 accounts = [account]
2178 is_paused = hasattr(music_track_info, 'paused') and \
2179 music_track_info.paused == 0
2180 if not music_track_info or is_paused:
2181 artist = title = source = ''
2182 else:
2183 artist = music_track_info.artist
2184 title = music_track_info.title
2185 source = music_track_info.album
2186 for acct in accounts:
2187 if not gajim.account_is_connected(acct):
2188 continue
2189 if not gajim.connections[acct].pep_supported:
2190 continue
2191 if not gajim.config.get_per('accounts', acct, 'publish_tune'):
2192 continue
2193 if gajim.connections[acct].music_track_info == music_track_info:
2194 continue
2195 gajim.connections[acct].send_tune(artist, title, source)
2196 gajim.connections[acct].music_track_info = music_track_info
2198 def get_bg_fg_colors(self):
2199 def gdkcolor_to_rgb (gdkcolor):
2200 return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
2201 gdkcolor.blue)]
2203 def format_rgb (r, g, b):
2204 return ' '.join([str(c) for c in ('rgb', r, g, b)])
2206 def format_gdkcolor (gdkcolor):
2207 return format_rgb (*gdkcolor_to_rgb (gdkcolor))
2209 # get style colors and create string for dvipng
2210 dummy = gtk.Invisible()
2211 dummy.ensure_style()
2212 style = dummy.get_style()
2213 bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
2214 fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
2215 return (bg_str, fg_str)
2217 def get_fg_color(self, fmt='hex'):
2218 def format_gdkcolor (c):
2219 if fmt == 'tex':
2220 return ' '.join([str(s) for s in
2221 ('rgb', c.red_float, c.green_float, c.blue_float)])
2222 elif fmt == 'hex':
2223 return str(c)
2225 # get foreground style color and create string
2226 dummy = gtk.Invisible()
2227 dummy.ensure_style()
2228 return format_gdkcolor(dummy.get_style().text[gtk.STATE_NORMAL])
2230 def read_sleepy(self):
2232 Check idle status and change that status if needed
2234 if not self.sleeper.poll():
2235 # idle detection is not supported in that OS
2236 return False # stop looping in vain
2237 state = self.sleeper.getState()
2238 for account in gajim.connections:
2239 if account not in gajim.sleeper_state or \
2240 not gajim.sleeper_state[account]:
2241 continue
2242 if state == common.sleepy.STATE_AWAKE and \
2243 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2244 # we go online
2245 self.roster.send_status(account, 'online',
2246 gajim.status_before_autoaway[account])
2247 gajim.status_before_autoaway[account] = ''
2248 gajim.sleeper_state[account] = 'online'
2249 elif state == common.sleepy.STATE_AWAY and \
2250 gajim.sleeper_state[account] == 'online' and \
2251 gajim.config.get('autoaway'):
2252 # we save out online status
2253 gajim.status_before_autoaway[account] = \
2254 gajim.connections[account].status
2255 # we go away (no auto status) [we pass True to auto param]
2256 auto_message = gajim.config.get('autoaway_message')
2257 if not auto_message:
2258 auto_message = gajim.connections[account].status
2259 else:
2260 auto_message = auto_message.replace('$S', '%(status)s')
2261 auto_message = auto_message.replace('$T', '%(time)s')
2262 auto_message = auto_message % {
2263 'status': gajim.status_before_autoaway[account],
2264 'time': gajim.config.get('autoawaytime')
2266 self.roster.send_status(account, 'away', auto_message,
2267 auto=True)
2268 gajim.sleeper_state[account] = 'autoaway'
2269 elif state == common.sleepy.STATE_XA and \
2270 gajim.sleeper_state[account] in ('online', 'autoaway',
2271 'autoaway-forced') and gajim.config.get('autoxa'):
2272 # we go extended away [we pass True to auto param]
2273 auto_message = gajim.config.get('autoxa_message')
2274 if not auto_message:
2275 auto_message = gajim.connections[account].status
2276 else:
2277 auto_message = auto_message.replace('$S', '%(status)s')
2278 auto_message = auto_message.replace('$T', '%(time)s')
2279 auto_message = auto_message % {
2280 'status': gajim.status_before_autoaway[account],
2281 'time': gajim.config.get('autoxatime')
2283 self.roster.send_status(account, 'xa', auto_message, auto=True)
2284 gajim.sleeper_state[account] = 'autoxa'
2285 return True # renew timeout (loop for ever)
2287 def autoconnect(self):
2289 Auto connect at startup
2291 # dict of account that want to connect sorted by status
2292 shows = {}
2293 for a in gajim.connections:
2294 if gajim.config.get_per('accounts', a, 'autoconnect'):
2295 if gajim.config.get_per('accounts', a, 'restore_last_status'):
2296 self.roster.send_status(a, gajim.config.get_per('accounts',
2297 a, 'last_status'), helpers.from_one_line(
2298 gajim.config.get_per('accounts', a, 'last_status_msg')))
2299 continue
2300 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
2301 if not show in gajim.SHOW_LIST:
2302 continue
2303 if not show in shows:
2304 shows[show] = [a]
2305 else:
2306 shows[show].append(a)
2307 def on_message(message, pep_dict):
2308 if message is None:
2309 return
2310 for a in shows[show]:
2311 self.roster.send_status(a, show, message)
2312 self.roster.send_pep(a, pep_dict)
2313 for show in shows:
2314 message = self.roster.get_status_message(show, on_message)
2315 return False
2317 def show_systray(self):
2318 self.systray_enabled = True
2319 self.systray.show_icon()
2321 def hide_systray(self):
2322 self.systray_enabled = False
2323 self.systray.hide_icon()
2325 def on_launch_browser_mailer(self, widget, url, kind):
2326 helpers.launch_browser_mailer(kind, url)
2328 def process_connections(self):
2330 Called each foo (200) miliseconds. Check for idlequeue timeouts
2332 try:
2333 gajim.idlequeue.process()
2334 except Exception:
2335 # Otherwise, an exception will stop our loop
2336 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2337 if in_seconds:
2338 gobject.timeout_add_seconds(timeout, self.process_connections)
2339 else:
2340 gobject.timeout_add(timeout, self.process_connections)
2341 raise
2342 return True # renew timeout (loop for ever)
2344 def save_config(self):
2345 err_str = parser.write()
2346 if err_str is not None:
2347 print >> sys.stderr, err_str
2348 # it is good to notify the user
2349 # in case he or she cannot see the output of the console
2350 dialogs.ErrorDialog(_('Could not save your settings and '
2351 'preferences'), err_str)
2352 sys.exit()
2354 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
2356 Save an avatar to a separate file, and generate files for dbus
2357 notifications. An avatar can be given as a pixmap directly or as an
2358 decoded image
2360 puny_jid = helpers.sanitize_filename(jid)
2361 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2362 if puny_nick:
2363 path_to_file = os.path.join(path_to_file, puny_nick)
2364 # remove old avatars
2365 for typ in ('jpeg', 'png'):
2366 if local:
2367 path_to_original_file = path_to_file + '_local'+ '.' + typ
2368 else:
2369 path_to_original_file = path_to_file + '.' + typ
2370 if os.path.isfile(path_to_original_file):
2371 os.remove(path_to_original_file)
2372 if local and photo:
2373 pixbuf = photo
2374 typ = 'png'
2375 extension = '_local.png' # save local avatars as png file
2376 else:
2377 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
2378 want_type=True)
2379 if pixbuf is None:
2380 return
2381 extension = '.' + typ
2382 if typ not in ('jpeg', 'png'):
2383 gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\
2384 'png formats. saving %s\'avatar as png file (originaly %s)'\
2385 % (jid, typ))
2386 typ = 'png'
2387 extension = '.png'
2388 path_to_original_file = path_to_file + extension
2389 try:
2390 pixbuf.save(path_to_original_file, typ)
2391 except Exception, e:
2392 log.error('Error writing avatar file %s: %s' % (
2393 path_to_original_file, str(e)))
2394 # Generate and save the resized, color avatar
2395 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
2396 if pixbuf:
2397 path_to_normal_file = path_to_file + '_notif_size_colored' + \
2398 extension
2399 try:
2400 pixbuf.save(path_to_normal_file, 'png')
2401 except Exception, e:
2402 log.error('Error writing avatar file %s: %s' % \
2403 (path_to_original_file, str(e)))
2404 # Generate and save the resized, black and white avatar
2405 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
2406 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
2407 if bwbuf:
2408 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
2409 try:
2410 bwbuf.save(path_to_bw_file, 'png')
2411 except Exception, e:
2412 log.error('Error writing avatar file %s: %s' % \
2413 (path_to_original_file, str(e)))
2415 def remove_avatar_files(self, jid, puny_nick = None, local = False):
2417 Remove avatar files of a jid
2419 puny_jid = helpers.sanitize_filename(jid)
2420 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2421 if puny_nick:
2422 path_to_file = os.path.join(path_to_file, puny_nick)
2423 for ext in ('.jpeg', '.png'):
2424 if local:
2425 ext = '_local' + ext
2426 path_to_original_file = path_to_file + ext
2427 if os.path.isfile(path_to_file + ext):
2428 os.remove(path_to_file + ext)
2429 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
2430 os.remove(path_to_file + '_notif_size_colored' + ext)
2431 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
2432 os.remove(path_to_file + '_notif_size_bw' + ext)
2434 def auto_join_bookmarks(self, account):
2436 Autojoin bookmarked GCs that have 'auto join' on for this account
2438 for bm in gajim.connections[account].bookmarks:
2439 if bm['autojoin'] in ('1', 'true'):
2440 jid = bm['jid']
2441 # Only join non-opened groupchats. Opened one are already
2442 # auto-joined on re-connection
2443 if not jid in gajim.gc_connected[account]:
2444 # we are not already connected
2445 minimize = bm['minimize'] in ('1', 'true')
2446 gajim.interface.join_gc_room(account, jid, bm['nick'],
2447 bm['password'], minimize = minimize)
2448 elif jid in self.minimized_controls[account]:
2449 # more or less a hack:
2450 # On disconnect the minimized gc contact instances
2451 # were set to offline. Reconnect them to show up in the
2452 # roster.
2453 self.roster.add_groupchat(jid, account)
2455 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
2456 nick):
2458 Add a bookmark for this account, sorted in bookmark list
2460 bm = {
2461 'name': name,
2462 'jid': jid,
2463 'autojoin': autojoin,
2464 'minimize': minimize,
2465 'password': password,
2466 'nick': nick
2468 place_found = False
2469 index = 0
2470 # check for duplicate entry and respect alpha order
2471 for bookmark in gajim.connections[account].bookmarks:
2472 if bookmark['jid'] == bm['jid']:
2473 dialogs.ErrorDialog(
2474 _('Bookmark already set'),
2475 _('Group Chat "%s" is already in your bookmarks.') % \
2476 bm['jid'])
2477 return
2478 if bookmark['name'] > bm['name']:
2479 place_found = True
2480 break
2481 index += 1
2482 if place_found:
2483 gajim.connections[account].bookmarks.insert(index, bm)
2484 else:
2485 gajim.connections[account].bookmarks.append(bm)
2486 gajim.connections[account].store_bookmarks()
2487 self.roster.set_actions_menu_needs_rebuild()
2488 dialogs.InformationDialog(
2489 _('Bookmark has been added successfully'),
2490 _('You can manage your bookmarks via Actions menu in your roster.'))
2493 # does JID exist only within a groupchat?
2494 def is_pm_contact(self, fjid, account):
2495 bare_jid = gajim.get_jid_without_resource(fjid)
2497 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
2499 if not gc_ctrl and \
2500 bare_jid in self.minimized_controls[account]:
2501 gc_ctrl = self.minimized_controls[account][bare_jid]
2503 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
2505 def create_ipython_window(self):
2506 try:
2507 from ipython_view import IPythonView
2508 except ImportError:
2509 print 'ipython_view not found'
2510 return
2511 import pango
2513 if os.name == 'nt':
2514 font = 'Lucida Console 9'
2515 else:
2516 font = 'Luxi Mono 10'
2518 window = gtk.Window()
2519 window.set_size_request(750, 550)
2520 window.set_resizable(True)
2521 sw = gtk.ScrolledWindow()
2522 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2523 view = IPythonView()
2524 view.modify_font(pango.FontDescription(font))
2525 view.set_wrap_mode(gtk.WRAP_CHAR)
2526 sw.add(view)
2527 window.add(sw)
2528 window.show_all()
2529 def on_delete(win, event):
2530 win.hide()
2531 return True
2532 window.connect('delete_event', on_delete)
2533 view.updateNamespace({'gajim': gajim})
2534 gajim.ipython_window = window
2536 def run(self):
2537 if gajim.config.get('trayicon') != 'never':
2538 self.show_systray()
2540 self.roster = roster_window.RosterWindow()
2541 # Creating plugin manager
2542 import plugins
2543 gajim.plugin_manager = plugins.PluginManager()
2545 self.roster._before_fill()
2546 for account in gajim.connections:
2547 gajim.connections[account].load_roster_from_db()
2548 self.roster._after_fill()
2550 # get instances for windows/dialogs that will show_all()/hide()
2551 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
2553 gobject.timeout_add(100, self.autoconnect)
2554 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2555 if in_seconds:
2556 gobject.timeout_add_seconds(timeout, self.process_connections)
2557 else:
2558 gobject.timeout_add(timeout, self.process_connections)
2559 gobject.timeout_add_seconds(gajim.config.get(
2560 'check_idle_every_foo_seconds'), self.read_sleepy)
2562 # when using libasyncns we need to process resolver in regular intervals
2563 if resolver.USE_LIBASYNCNS:
2564 gobject.timeout_add(200, gajim.resolver.process)
2566 def remote_init():
2567 if gajim.config.get('remote_control'):
2568 try:
2569 import remote_control
2570 self.remote_ctrl = remote_control.Remote()
2571 except Exception:
2572 pass
2573 gobject.timeout_add_seconds(5, remote_init)
2575 def __init__(self):
2576 gajim.interface = self
2577 gajim.thread_interface = ThreadInterface
2578 # This is the manager and factory of message windows set by the module
2579 self.msg_win_mgr = None
2580 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
2581 'closed': {}}
2582 self.emoticons_menu = None
2583 # handler when an emoticon is clicked in emoticons_menu
2584 self.emoticon_menuitem_clicked = None
2585 self.minimized_controls = {}
2586 self.status_sent_to_users = {}
2587 self.status_sent_to_groups = {}
2588 self.gpg_passphrase = {}
2589 self.pass_dialog = {}
2590 self.db_error_dialog = None
2591 self.default_colors = {
2592 'inmsgcolor': gajim.config.get('inmsgcolor'),
2593 'outmsgcolor': gajim.config.get('outmsgcolor'),
2594 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
2595 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
2596 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
2597 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
2600 self.handlers = {}
2601 self.roster = None
2602 self._invalid_XML_chars_re = None
2603 self._basic_pattern_re = None
2604 self._emot_and_basic_re = None
2605 self._sth_at_sth_dot_sth_re = None
2606 self.link_pattern_re = None
2607 self.invalid_XML_chars = None
2608 self.basic_pattern = None
2609 self.emot_and_basic = None
2610 self.sth_at_sth_dot_sth = None
2611 self.emot_only = None
2612 self.emoticons = []
2613 self.emoticons_animations = {}
2614 self.emoticons_images = {}
2616 cfg_was_read = parser.read()
2618 from common import latex
2619 gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
2620 latex.check_for_latex_support()
2622 gajim.logger.reset_shown_unread_messages()
2623 # override logging settings from config (don't take care of '-q' option)
2624 if gajim.config.get('verbose'):
2625 logging_helpers.set_verbose()
2627 # Is Gajim default app?
2628 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
2629 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
2631 for account in gajim.config.get_per('accounts'):
2632 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
2633 gajim.ZEROCONF_ACC_NAME = account
2634 break
2635 # Is gnome configured to activate row on single click ?
2636 try:
2637 import gconf
2638 client = gconf.client_get_default()
2639 click_policy = client.get_string(
2640 '/apps/nautilus/preferences/click_policy')
2641 if click_policy == 'single':
2642 gajim.single_click = True
2643 except Exception:
2644 pass
2645 # add default status messages if there is not in the config file
2646 if len(gajim.config.get_per('statusmsg')) == 0:
2647 default = gajim.config.statusmsg_default
2648 for msg in default:
2649 gajim.config.add_per('statusmsg', msg)
2650 gajim.config.set_per('statusmsg', msg, 'message',
2651 default[msg][0])
2652 gajim.config.set_per('statusmsg', msg, 'activity',
2653 default[msg][1])
2654 gajim.config.set_per('statusmsg', msg, 'subactivity',
2655 default[msg][2])
2656 gajim.config.set_per('statusmsg', msg, 'activity_text',
2657 default[msg][3])
2658 gajim.config.set_per('statusmsg', msg, 'mood',
2659 default[msg][4])
2660 gajim.config.set_per('statusmsg', msg, 'mood_text',
2661 default[msg][5])
2662 #add default themes if there is not in the config file
2663 theme = gajim.config.get('roster_theme')
2664 if not theme in gajim.config.get_per('themes'):
2665 gajim.config.set('roster_theme', _('default'))
2666 if len(gajim.config.get_per('themes')) == 0:
2667 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
2668 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2669 'groupfont', 'groupfontattrs', 'contacttextcolor',
2670 'contactbgcolor', 'contactfont', 'contactfontattrs',
2671 'bannertextcolor', 'bannerbgcolor']
2673 default = gajim.config.themes_default
2674 for theme_name in default:
2675 gajim.config.add_per('themes', theme_name)
2676 theme = default[theme_name]
2677 for o in d:
2678 gajim.config.set_per('themes', theme_name, o,
2679 theme[d.index(o)])
2681 if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
2682 gtkgui_helpers.autodetect_browser_mailer()
2684 gajim.idlequeue = idlequeue.get_idlequeue()
2685 # resolve and keep current record of resolved hosts
2686 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
2687 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
2688 self.handle_event_file_rcv_completed,
2689 self.handle_event_file_progress,
2690 self.handle_event_file_error)
2691 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
2692 gajim.default_session_type = ChatControlSession
2694 # Creating Network Events Controller
2695 from common import nec
2696 gajim.nec = nec.NetworkEventsController()
2698 self.create_core_handlers_list()
2699 self.register_core_handlers()
2701 if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
2702 and gajim.HAVE_ZEROCONF:
2703 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
2704 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
2705 for account in gajim.config.get_per('accounts'):
2706 if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
2707 gajim.config.get_per('accounts', account, 'active'):
2708 gajim.connections[account] = common.connection.Connection(
2709 account)
2711 # gtk hooks
2712 gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
2713 gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
2714 gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
2716 self.instances = {}
2718 for a in gajim.connections:
2719 self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
2720 'search': {}, 'online_dialog': {}}
2721 # online_dialog contains all dialogs that have a meaning only when
2722 # we are not disconnected
2723 self.minimized_controls[a] = {}
2724 gajim.contacts.add_account(a)
2725 gajim.groups[a] = {}
2726 gajim.gc_connected[a] = {}
2727 gajim.automatic_rooms[a] = {}
2728 gajim.newly_added[a] = []
2729 gajim.to_be_removed[a] = []
2730 gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
2731 gajim.block_signed_in_notifications[a] = True
2732 gajim.sleeper_state[a] = 0
2733 gajim.encrypted_chats[a] = []
2734 gajim.last_message_time[a] = {}
2735 gajim.status_before_autoaway[a] = ''
2736 gajim.transport_avatar[a] = {}
2737 gajim.gajim_optional_features[a] = []
2738 gajim.caps_hash[a] = ''
2740 helpers.update_optional_features()
2741 # prepopulate data which we are sure of; note: we do not log these info
2742 for account in gajim.connections:
2743 gajimcaps = caps_cache.capscache[('sha-1',
2744 gajim.caps_hash[account])]
2745 gajimcaps.identities = [gajim.gajim_identity]
2746 gajimcaps.features = gajim.gajim_common_features + \
2747 gajim.gajim_optional_features[account]
2749 self.remote_ctrl = None
2751 if gajim.config.get('networkmanager_support') and \
2752 dbus_support.supported:
2753 import network_manager_listener
2755 # Handle gnome screensaver
2756 if dbus_support.supported:
2757 def gnome_screensaver_ActiveChanged_cb(active):
2758 if not active:
2759 for account in gajim.connections:
2760 if gajim.sleeper_state[account] == 'autoaway-forced':
2761 # We came back online ofter gnome-screensaver
2762 # autoaway
2763 self.roster.send_status(account, 'online',
2764 gajim.status_before_autoaway[account])
2765 gajim.status_before_autoaway[account] = ''
2766 gajim.sleeper_state[account] = 'online'
2767 return
2768 if not gajim.config.get('autoaway'):
2769 # Don't go auto away if user disabled the option
2770 return
2771 for account in gajim.connections:
2772 if account not in gajim.sleeper_state or \
2773 not gajim.sleeper_state[account]:
2774 continue
2775 if gajim.sleeper_state[account] == 'online':
2776 # we save out online status
2777 gajim.status_before_autoaway[account] = \
2778 gajim.connections[account].status
2779 # we go away (no auto status) [we pass True to auto
2780 # param]
2781 auto_message = gajim.config.get('autoaway_message')
2782 if not auto_message:
2783 auto_message = gajim.connections[account].status
2784 else:
2785 auto_message = auto_message.replace('$S',
2786 '%(status)s')
2787 auto_message = auto_message.replace('$T',
2788 '%(time)s')
2789 auto_message = auto_message % {
2790 'status': gajim.status_before_autoaway[account],
2791 'time': gajim.config.get('autoxatime')}
2792 self.roster.send_status(account, 'away', auto_message,
2793 auto=True)
2794 gajim.sleeper_state[account] = 'autoaway-forced'
2796 try:
2797 bus = dbus.SessionBus()
2798 bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
2799 'ActiveChanged', 'org.gnome.ScreenSaver')
2800 except Exception:
2801 pass
2803 self.show_vcard_when_connect = []
2805 self.sleeper = common.sleepy.Sleepy(
2806 gajim.config.get('autoawaytime') * 60, # make minutes to seconds
2807 gajim.config.get('autoxatime') * 60)
2809 gtkgui_helpers.make_jabber_state_images()
2811 self.systray_enabled = False
2813 import statusicon
2814 self.systray = statusicon.StatusIcon()
2816 pix = gtkgui_helpers.get_icon_pixmap('gajim', 32)
2817 # set the icon to all windows
2818 gtk.window_set_default_icon(pix)
2820 self.init_emoticons()
2821 self.make_regexps()
2823 # get transports type from DB
2824 gajim.transport_type = gajim.logger.get_transports_type()
2826 # test is dictionnary is present for speller
2827 if gajim.config.get('use_speller'):
2828 lang = gajim.config.get('speller_language')
2829 if not lang:
2830 lang = gajim.LANG
2831 tv = gtk.TextView()
2832 try:
2833 import gtkspell
2834 spell = gtkspell.Spell(tv, lang)
2835 except (ImportError, TypeError, RuntimeError, OSError):
2836 dialogs.AspellDictError(lang)
2838 if gajim.config.get('soundplayer') == '':
2839 # only on first time Gajim starts
2840 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
2841 for command in commands:
2842 if helpers.is_in_path(command):
2843 if command == 'aplay':
2844 command += ' -q'
2845 gajim.config.set('soundplayer', command)
2846 break
2848 self.last_ftwindow_update = 0
2850 self.music_track_changed_signal = None
2853 class PassphraseRequest:
2854 def __init__(self, keyid):
2855 self.keyid = keyid
2856 self.callbacks = []
2857 self.dialog_created = False
2858 self.dialog = None
2859 self.passphrase = None
2860 self.completed = False
2862 def interrupt(self, account=None):
2863 if account:
2864 for (acct, cb) in self.callbacks:
2865 if acct == account:
2866 self.callbacks.remove((acct, cb))
2867 else:
2868 self.callbacks = []
2869 if not len(self.callbacks):
2870 self.dialog.window.destroy()
2872 def run_callback(self, account, callback):
2873 gajim.connections[account].gpg_passphrase(self.passphrase)
2874 callback()
2876 def add_callback(self, account, cb):
2877 if self.completed:
2878 self.run_callback(account, cb)
2879 else:
2880 self.callbacks.append((account, cb))
2881 if not self.dialog_created:
2882 self.create_dialog(account)
2884 def complete(self, passphrase):
2885 self.passphrase = passphrase
2886 self.completed = True
2887 if passphrase is not None:
2888 gobject.timeout_add_seconds(30,
2889 gajim.interface.forget_gpg_passphrase, self.keyid)
2890 for (account, cb) in self.callbacks:
2891 self.run_callback(account, cb)
2892 self.callbacks = []
2894 def create_dialog(self, account):
2895 title = _('Passphrase Required')
2896 second = _('Enter GPG key passphrase for key %(keyid)s (account '
2897 '%(account)s).') % {'keyid': self.keyid, 'account': account}
2899 def _cancel():
2900 # user cancelled, continue without GPG
2901 self.complete(None)
2903 def _ok(passphrase, checked, count):
2904 result = gajim.connections[account].test_gpg_passphrase(passphrase)
2905 if result == 'ok':
2906 # passphrase is good
2907 self.complete(passphrase)
2908 return
2909 elif result == 'expired':
2910 dialogs.ErrorDialog(_('GPG key expired'),
2911 _('Your GPG key has expired, you will be connected to %s '
2912 'without OpenPGP.') % account)
2913 # Don't try to connect with GPG
2914 gajim.connections[account].continue_connect_info[2] = False
2915 self.complete(None)
2916 return
2918 if count < 3:
2919 # ask again
2920 dialogs.PassphraseDialog(_('Wrong Passphrase'),
2921 _('Please retype your GPG passphrase or press Cancel.'),
2922 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
2923 else:
2924 # user failed 3 times, continue without GPG
2925 self.complete(None)
2927 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
2928 1), cancel_handler=_cancel)
2929 self.dialog_created = True
2932 class ThreadInterface:
2933 def __init__(self, func, func_args, callback, callback_args):
2935 Call a function in a thread
2937 def thread_function(func, func_args, callback, callback_args):
2938 output = func(*func_args)
2939 gobject.idle_add(callback, output, *callback_args)
2941 Thread(target=thread_function, args=(func, func_args, callback,
2942 callback_args)).start()