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