use NEC to handle PEP / ATOM messages
[gajim.git] / src / gui_interface.py
blobe78c280093df3c5dbb7278b362b98c742db6d74f
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_privacy_lists_received(self, account, data):
1214 # ('PRIVACY_LISTS_RECEIVED', account, list)
1215 if account not in self.instances:
1216 return
1217 if 'privacy_lists' in self.instances[account]:
1218 self.instances[account]['privacy_lists'].privacy_lists_received(
1219 data)
1221 def handle_event_privacy_list_received(self, account, data):
1222 # ('PRIVACY_LIST_RECEIVED', account, (name, rules))
1223 if account not in self.instances:
1224 return
1225 name = data[0]
1226 rules = data[1]
1227 if 'privacy_list_%s' % name in self.instances[account]:
1228 self.instances[account]['privacy_list_%s' % name].\
1229 privacy_list_received(rules)
1230 if name == 'block':
1231 con = gajim.connections[account]
1232 con.blocked_contacts = []
1233 con.blocked_groups = []
1234 con.blocked_list = []
1235 gajim.connections[account].blocked_all = False
1236 for rule in rules:
1237 if rule['action'] == 'allow':
1238 if not 'type' in rule:
1239 con.blocked_all = False
1240 elif rule['type'] == 'jid' and rule['value'] in \
1241 con.blocked_contacts:
1242 con.blocked_contacts.remove(rule['value'])
1243 elif rule['type'] == 'group' and rule['value'] in \
1244 con.blocked_groups:
1245 con.blocked_groups.remove(rule['value'])
1246 elif rule['action'] == 'deny':
1247 if not 'type' in rule:
1248 con.blocked_all = True
1249 elif rule['type'] == 'jid' and rule['value'] not in \
1250 con.blocked_contacts:
1251 con.blocked_contacts.append(rule['value'])
1252 elif rule['type'] == 'group' and rule['value'] not in \
1253 con.blocked_groups:
1254 con.blocked_groups.append(rule['value'])
1255 con.blocked_list.append(rule)
1256 if 'blocked_contacts' in self.instances[account]:
1257 self.instances[account]['blocked_contacts'].\
1258 privacy_list_received(rules)
1260 def handle_event_privacy_lists_active_default(self, account, data):
1261 if not data:
1262 return
1263 # Send to all privacy_list_* windows as we can't know which one asked
1264 for win in self.instances[account]:
1265 if win.startswith('privacy_list_'):
1266 self.instances[account][win].check_active_default(data)
1268 def handle_event_privacy_list_removed(self, account, name):
1269 # ('PRIVACY_LISTS_REMOVED', account, name)
1270 if account not in self.instances:
1271 return
1272 if 'privacy_lists' in self.instances[account]:
1273 self.instances[account]['privacy_lists'].privacy_list_removed(name)
1275 def handle_event_zc_name_conflict(self, account, data):
1276 def on_ok(new_name):
1277 gajim.config.set_per('accounts', account, 'name', new_name)
1278 show = gajim.connections[account].old_show
1279 status = gajim.connections[account].status
1280 gajim.connections[account].username = new_name
1281 gajim.connections[account].change_status(show, status)
1282 def on_cancel():
1283 gajim.connections[account].change_status('offline', '')
1285 dlg = dialogs.InputDialog(_('Username Conflict'),
1286 _('Please type a new username for your local account'),
1287 input_str=data, is_modal=True, ok_handler=on_ok,
1288 cancel_handler=on_cancel)
1290 def handle_event_resource_conflict(self, obj):
1291 # ('RESOURCE_CONFLICT', account, ())
1292 # First we go offline, but we don't overwrite status message
1293 account = obj.conn.name
1294 conn = obj.conn
1295 self.roster.send_status(account, 'offline', conn.status)
1296 def on_ok(new_resource):
1297 gajim.config.set_per('accounts', account, 'resource', new_resource)
1298 self.roster.send_status(account, conn.old_show, conn.status)
1299 proposed_resource = conn.server_resource
1300 proposed_resource += gajim.config.get('gc_proposed_nick_char')
1301 dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
1302 _('You are already connected to this account with the same '
1303 'resource. Please type a new one'), resource=proposed_resource,
1304 ok_handler=on_ok)
1306 def handle_event_jingle_incoming(self, obj):
1307 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
1308 # data...))
1309 # TODO: conditional blocking if peer is not in roster
1311 account = obj.conn.name
1312 content_types = set(c[0] for c in obj.contents)
1314 # check type of jingle session
1315 if 'audio' in content_types or 'video' in content_types:
1316 # a voip session...
1317 # we now handle only voip, so the only thing we will do here is
1318 # not to return from function
1319 pass
1320 else:
1321 # unknown session type... it should be declined in common/jingle.py
1322 return
1324 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1325 or self.msg_win_mgr.get_control(obj.jid, account))
1326 if ctrl:
1327 if 'audio' in content_types:
1328 ctrl.set_audio_state('connection_received', obj.sid)
1329 if 'video' in content_types:
1330 ctrl.set_video_state('connection_received', obj.sid)
1332 dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1333 if dlg:
1334 dlg.add_contents(content_types)
1335 return
1337 if helpers.allow_popup_window(account):
1338 dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid,
1339 content_types)
1340 return
1342 self.add_event(account, obj.jid, 'jingle-incoming', (obj.fjid, obj.sid,
1343 content_types))
1345 if helpers.allow_showing_notification(account):
1346 # TODO: we should use another pixmap ;-)
1347 txt = _('%s wants to start a voice chat.') % \
1348 gajim.get_name_from_jid(account, obj.fjid)
1349 path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
1350 event_type = _('Voice Chat Request')
1351 notify.popup(event_type, obj.fjid, account, 'jingle-incoming',
1352 path_to_image=path, title=event_type, text=txt)
1354 def handle_event_jingle_connected(self, obj):
1355 # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
1356 if obj.media in ('audio', 'video'):
1357 account = obj.conn.name
1358 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1359 or self.msg_win_mgr.get_control(obj.jid, account))
1360 if ctrl:
1361 if obj.media == 'audio':
1362 ctrl.set_audio_state('connected', obj.sid)
1363 else:
1364 ctrl.set_video_state('connected', obj.sid)
1366 def handle_event_jingle_disconnected(self, obj):
1367 # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
1368 account = obj.conn.name
1369 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1370 or self.msg_win_mgr.get_control(obj.jid, account))
1371 if ctrl:
1372 if obj.media is None:
1373 ctrl.stop_jingle(sid=obj.sid, reason=obj.reason)
1374 elif obj.media == 'audio':
1375 ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason)
1376 elif obj.media == 'video':
1377 ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason)
1378 dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1379 if dialog:
1380 if obj.media is None:
1381 dialog.dialog.destroy()
1382 else:
1383 dialog.remove_contents((obj.media, ))
1385 def handle_event_jingle_error(self, obj):
1386 # ('JINGLE_ERROR', account, (peerjid, sid, reason))
1387 account = obj.conn.name
1388 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1389 or self.msg_win_mgr.get_control(obj.jid, account))
1390 if ctrl:
1391 ctrl.set_audio_state('error', reason=obj.reason)
1393 def handle_event_pep_config(self, account, data):
1394 # ('PEP_CONFIG', account, (node, form))
1395 if 'pep_services' in self.instances[account]:
1396 self.instances[account]['pep_services'].config(data[0], data[1])
1398 def handle_event_roster_item_exchange(self, obj):
1399 # data = (action in [add, delete, modify], exchange_list, jid_from)
1400 dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action,
1401 obj.exchange_items_list, obj.fjid)
1403 def handle_event_unique_room_id_supported(self, account, data):
1405 Receive confirmation that unique_room_id are supported
1407 # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
1408 instance = data[1]
1409 instance.unique_room_id_supported(data[0], data[2])
1411 def handle_event_unique_room_id_unsupported(self, account, data):
1412 # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance)
1413 instance = data[1]
1414 instance.unique_room_id_error(data[0])
1416 def handle_event_ssl_error(self, account, data):
1417 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
1418 server = gajim.config.get_per('accounts', account, 'hostname')
1420 def on_ok(is_checked):
1421 del self.instances[account]['online_dialog']['ssl_error']
1422 if is_checked[0]:
1423 # Check if cert is already in file
1424 certs = ''
1425 if os.path.isfile(gajim.MY_CACERTS):
1426 f = open(gajim.MY_CACERTS)
1427 certs = f.read()
1428 f.close()
1429 if data[2] in certs:
1430 dialogs.ErrorDialog(_('Certificate Already in File'),
1431 _('This certificate is already in file %s, so it\'s '
1432 'not added again.') % gajim.MY_CACERTS)
1433 else:
1434 f = open(gajim.MY_CACERTS, 'a')
1435 f.write(server + '\n')
1436 f.write(data[2] + '\n\n')
1437 f.close()
1438 gajim.config.set_per('accounts', account,
1439 'ssl_fingerprint_sha1', data[3])
1440 if is_checked[1]:
1441 ignore_ssl_errors = gajim.config.get_per('accounts', account,
1442 'ignore_ssl_errors').split()
1443 ignore_ssl_errors.append(str(data[1]))
1444 gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
1445 ' '.join(ignore_ssl_errors))
1446 gajim.connections[account].ssl_certificate_accepted()
1448 def on_cancel():
1449 del self.instances[account]['online_dialog']['ssl_error']
1450 gajim.connections[account].disconnect(on_purpose=True)
1451 self.handle_event_status(account, 'offline')
1453 pritext = _('Error verifying SSL certificate')
1454 sectext = _('There was an error verifying the SSL certificate of your '
1455 'jabber server: %(error)s\nDo you still want to connect to this '
1456 'server?') % {'error': data[0]}
1457 if data[1] in (18, 27):
1458 checktext1 = _('Add this certificate to the list of trusted '
1459 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3]
1460 else:
1461 checktext1 = ''
1462 checktext2 = _('Ignore this error for this certificate.')
1463 if 'ssl_error' in self.instances[account]['online_dialog']:
1464 self.instances[account]['online_dialog']['ssl_error'].destroy()
1465 self.instances[account]['online_dialog']['ssl_error'] = \
1466 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1467 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel)
1469 def handle_event_fingerprint_error(self, account, data):
1470 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
1471 def on_yes(is_checked):
1472 del self.instances[account]['online_dialog']['fingerprint_error']
1473 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
1474 data[0])
1475 # Reset the ignored ssl errors
1476 gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
1477 gajim.connections[account].ssl_certificate_accepted()
1478 def on_no():
1479 del self.instances[account]['online_dialog']['fingerprint_error']
1480 gajim.connections[account].disconnect(on_purpose=True)
1481 self.handle_event_status(account, 'offline')
1482 pritext = _('SSL certificate error')
1483 sectext = _('It seems the SSL certificate of account %(account)s has '
1484 'changed or your connection is being hacked.\nOld fingerprint: '
1485 '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect '
1486 'and update the fingerprint of the certificate?') % \
1487 {'account': account, 'old': gajim.config.get_per('accounts',
1488 account, 'ssl_fingerprint_sha1'), 'new': data[0]}
1489 if 'fingerprint_error' in self.instances[account]['online_dialog']:
1490 self.instances[account]['online_dialog']['fingerprint_error'].\
1491 destroy()
1492 self.instances[account]['online_dialog']['fingerprint_error'] = \
1493 dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
1494 on_response_no=on_no)
1496 def handle_event_plain_connection(self, account, data):
1497 # ('PLAIN_CONNECTION', account, (connection))
1498 def on_ok(is_checked):
1499 if not is_checked[0]:
1500 on_cancel()
1501 return
1502 # On cancel call del self.instances, so don't call it another time
1503 # before
1504 del self.instances[account]['online_dialog']['plain_connection']
1505 if is_checked[1]:
1506 gajim.config.set_per('accounts', account,
1507 'warn_when_plaintext_connection', False)
1508 gajim.connections[account].connection_accepted(data[0], 'plain')
1509 def on_cancel():
1510 del self.instances[account]['online_dialog']['plain_connection']
1511 gajim.connections[account].disconnect(on_purpose=True)
1512 self.handle_event_status(account, 'offline')
1513 pritext = _('Insecure connection')
1514 sectext = _('You are about to connect to the server with an insecure '
1515 'connection. This means all your conversations will be '
1516 'exchanged unencrypted. Are you sure you want to do that?')
1517 checktext1 = _('Yes, I really want to connect insecurely')
1518 checktext2 = _('_Do not ask me again')
1519 if 'plain_connection' in self.instances[account]['online_dialog']:
1520 self.instances[account]['online_dialog']['plain_connection'].\
1521 destroy()
1522 self.instances[account]['online_dialog']['plain_connection'] = \
1523 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1524 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1525 is_modal=False)
1527 def handle_event_insecure_ssl_connection(self, account, data):
1528 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1529 def on_ok(is_checked):
1530 if not is_checked[0]:
1531 on_cancel()
1532 return
1533 del self.instances[account]['online_dialog']['insecure_ssl']
1534 if is_checked[1]:
1535 gajim.config.set_per('accounts', account,
1536 'warn_when_insecure_ssl_connection', False)
1537 if gajim.connections[account].connected == 0:
1538 # We have been disconnecting (too long time since window is
1539 # opened)
1540 # re-connect with auto-accept
1541 gajim.connections[account].connection_auto_accepted = True
1542 show, msg = gajim.connections[account].continue_connect_info[:2]
1543 self.roster.send_status(account, show, msg)
1544 return
1545 gajim.connections[account].connection_accepted(data[0], data[1])
1546 def on_cancel():
1547 del self.instances[account]['online_dialog']['insecure_ssl']
1548 gajim.connections[account].disconnect(on_purpose=True)
1549 self.handle_event_status(account, 'offline')
1550 pritext = _('Insecure connection')
1551 sectext = _('You are about to send your password on an insecure '
1552 'connection. You should install PyOpenSSL to prevent that. Are you '
1553 'sure you want to do that?')
1554 checktext1 = _('Yes, I really want to connect insecurely')
1555 checktext2 = _('_Do not ask me again')
1556 if 'insecure_ssl' in self.instances[account]['online_dialog']:
1557 self.instances[account]['online_dialog']['insecure_ssl'].destroy()
1558 self.instances[account]['online_dialog']['insecure_ssl'] = \
1559 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1560 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1561 is_modal=False)
1563 def handle_event_insecure_password(self, account, data):
1564 # ('INSECURE_PASSWORD', account, ())
1565 def on_ok(is_checked):
1566 if not is_checked[0]:
1567 on_cancel()
1568 return
1569 del self.instances[account]['online_dialog']['insecure_password']
1570 if is_checked[1]:
1571 gajim.config.set_per('accounts', account,
1572 'warn_when_insecure_password', False)
1573 if gajim.connections[account].connected == 0:
1574 # We have been disconnecting (too long time since window is
1575 # opened)
1576 # re-connect with auto-accept
1577 gajim.connections[account].connection_auto_accepted = True
1578 show, msg = gajim.connections[account].continue_connect_info[:2]
1579 self.roster.send_status(account, show, msg)
1580 return
1581 gajim.connections[account].accept_insecure_password()
1582 def on_cancel():
1583 del self.instances[account]['online_dialog']['insecure_password']
1584 gajim.connections[account].disconnect(on_purpose=True)
1585 self.handle_event_status(account, 'offline')
1586 pritext = _('Insecure connection')
1587 sectext = _('You are about to send your password unencrypted on an '
1588 'insecure connection. Are you sure you want to do that?')
1589 checktext1 = _('Yes, I really want to connect insecurely')
1590 checktext2 = _('_Do not ask me again')
1591 if 'insecure_password' in self.instances[account]['online_dialog']:
1592 self.instances[account]['online_dialog']['insecure_password'].\
1593 destroy()
1594 self.instances[account]['online_dialog']['insecure_password'] = \
1595 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1596 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1597 is_modal=False)
1599 def create_core_handlers_list(self):
1600 self.handlers = {
1601 'WARNING': [self.handle_event_warning],
1602 'ERROR': [self.handle_event_error],
1603 'DB_ERROR': [self.handle_event_db_error],
1604 'INFORMATION': [self.handle_event_information],
1605 'MSGERROR': [self.handle_event_msgerror],
1606 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info],
1607 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items],
1608 'MYVCARD': [self.handle_event_myvcard],
1609 'VCARD': [self.handle_event_vcard],
1610 'GC_SUBJECT': [self.handle_event_gc_subject],
1611 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change],
1612 'FILE_REQUEST': [self.handle_event_file_request],
1613 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
1614 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
1615 'SIGNED_IN': [self.handle_event_signed_in],
1616 'METACONTACTS': [self.handle_event_metacontacts],
1617 'FAILED_DECRYPT': [self.handle_event_failed_decrypt],
1618 'PRIVACY_LISTS_RECEIVED': \
1619 [self.handle_event_privacy_lists_received],
1620 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received],
1621 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
1622 [self.handle_event_privacy_lists_active_default],
1623 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed],
1624 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict],
1625 'PEP_CONFIG': [self.handle_event_pep_config],
1626 'UNIQUE_ROOM_ID_UNSUPPORTED': \
1627 [self.handle_event_unique_room_id_unsupported],
1628 'UNIQUE_ROOM_ID_SUPPORTED': \
1629 [self.handle_event_unique_room_id_supported],
1630 'PASSWORD_REQUIRED': [self.handle_event_password_required],
1631 'SSL_ERROR': [self.handle_event_ssl_error],
1632 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error],
1633 'PLAIN_CONNECTION': [self.handle_event_plain_connection],
1634 'INSECURE_SSL_CONNECTION': \
1635 [self.handle_event_insecure_ssl_connection],
1636 'INSECURE_PASSWORD': [self.handle_event_insecure_password],
1637 'atom-entry-received': [self.handle_atom_entry],
1638 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
1639 'bookmarks-received': [self.handle_event_bookmarks],
1640 'connection-lost': [self.handle_event_connection_lost],
1641 'gc-invitation-received': [self.handle_event_gc_invitation],
1642 'gc-presence-received': [self.handle_event_gc_presence],
1643 'gmail-notify': [self.handle_event_gmail_notify],
1644 'gpg-password-required': [self.handle_event_gpg_password_required],
1645 'gpg-trust-key': [self.handle_event_gpg_trust_key],
1646 'http-auth-received': [self.handle_event_http_auth],
1647 'iq-error-received': [self.handle_event_iq_error],
1648 'jingle-connected-received': [self.handle_event_jingle_connected],
1649 'jingle-disconnected-received': [
1650 self.handle_event_jingle_disconnected],
1651 'jingle-error-received': [self.handle_event_jingle_error],
1652 'jingle-request-received': [self.handle_event_jingle_incoming],
1653 'last-result-received': [self.handle_event_last_status_time],
1654 'message-not-sent': [self.handle_event_msgnotsent],
1655 'message-sent': [self.handle_event_msgsent],
1656 'muc-admin-received': [self.handle_event_gc_affiliation],
1657 'muc-owner-received': [self.handle_event_gc_config],
1658 'our-show': [self.handle_event_status],
1659 'presence-received': [self.handle_event_presence],
1660 'roster-info': [self.handle_event_roster_info],
1661 'roster-item-exchange-received': \
1662 [self.handle_event_roster_item_exchange],
1663 'stream-conflict-received': [self.handle_event_resource_conflict],
1664 'subscribe-presence-received': [
1665 self.handle_event_subscribe_presence],
1666 'subscribed-presence-received': [
1667 self.handle_event_subscribed_presence],
1668 'unsubscribed-presence-received': [
1669 self.handle_event_unsubscribed_presence],
1672 def register_core_handlers(self):
1674 Register core handlers in Global Events Dispatcher (GED).
1676 This is part of rewriting whole events handling system to use GED.
1678 for event_name, event_handlers in self.handlers.iteritems():
1679 for event_handler in event_handlers:
1680 gajim.ged.register_event_handler(event_name, ged.GUI1,
1681 event_handler)
1683 ################################################################################
1684 ### Methods dealing with gajim.events
1685 ################################################################################
1687 def add_event(self, account, jid, type_, event_args):
1689 Add an event to the gajim.events var
1691 # We add it to the gajim.events queue
1692 # Do we have a queue?
1693 jid = gajim.get_jid_without_resource(jid)
1694 no_queue = len(gajim.events.get_events(account, jid)) == 0
1695 # type_ can be gc-invitation file-send-error file-error
1696 # file-request-error file-request file-completed file-stopped
1697 # jingle-incoming
1698 # event_type can be in advancedNotificationWindow.events_list
1699 event_types = {'file-request': 'ft_request',
1700 'file-completed': 'ft_finished'}
1701 event_type = event_types.get(type_)
1702 show_in_roster = notify.get_show_in_roster(event_type, account, jid)
1703 show_in_systray = notify.get_show_in_systray(event_type, account, jid)
1704 event = gajim.events.create_event(type_, event_args,
1705 show_in_roster=show_in_roster,
1706 show_in_systray=show_in_systray)
1707 gajim.events.add_event(account, jid, event)
1709 self.roster.show_title()
1710 if no_queue: # We didn't have a queue: we change icons
1711 if not gajim.contacts.get_contact_with_highest_priority(account,
1712 jid):
1713 if type_ == 'gc-invitation':
1714 self.roster.add_groupchat(jid, account, status='offline')
1715 else:
1716 # add contact to roster ("Not In The Roster") if he is not
1717 self.roster.add_to_not_in_the_roster(account, jid)
1718 else:
1719 self.roster.draw_contact(jid, account)
1721 # Select the big brother contact in roster, it's visible because it has
1722 # events.
1723 family = gajim.contacts.get_metacontacts_family(account, jid)
1724 if family:
1725 nearby_family, bb_jid, bb_account = \
1726 gajim.contacts.get_nearby_family_and_big_brother(family,
1727 account)
1728 else:
1729 bb_jid, bb_account = jid, account
1730 self.roster.select_contact(bb_jid, bb_account)
1732 def handle_event(self, account, fjid, type_):
1733 w = None
1734 ctrl = None
1735 session = None
1737 resource = gajim.get_resource_from_jid(fjid)
1738 jid = gajim.get_jid_without_resource(fjid)
1740 if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
1741 w = self.msg_win_mgr.get_window(jid, account)
1742 if jid in self.minimized_controls[account]:
1743 self.roster.on_groupchat_maximized(None, jid, account)
1744 return
1745 else:
1746 ctrl = self.msg_win_mgr.get_gc_control(jid, account)
1748 elif type_ in ('printed_chat', 'chat', ''):
1749 # '' is for log in/out notifications
1751 if type_ != '':
1752 event = gajim.events.get_first_event(account, fjid, type_)
1753 if not event:
1754 event = gajim.events.get_first_event(account, jid, type_)
1755 if not event:
1756 return
1758 if type_ == 'printed_chat':
1759 ctrl = event.parameters[0]
1760 elif type_ == 'chat':
1761 session = event.parameters[8]
1762 ctrl = session.control
1763 elif type_ == '':
1764 ctrl = self.msg_win_mgr.get_control(fjid, account)
1766 if not ctrl:
1767 highest_contact = gajim.contacts.\
1768 get_contact_with_highest_priority(account, jid)
1769 # jid can have a window if this resource was lower when he sent
1770 # message and is now higher because the other one is offline
1771 if resource and highest_contact.resource == resource and \
1772 not self.msg_win_mgr.has_window(jid, account):
1773 # remove resource of events too
1774 gajim.events.change_jid(account, fjid, jid)
1775 resource = None
1776 fjid = jid
1777 contact = None
1778 if resource:
1779 contact = gajim.contacts.get_contact(account, jid, resource)
1780 if not contact:
1781 contact = highest_contact
1783 ctrl = self.new_chat(contact, account, resource=resource,
1784 session=session)
1786 gajim.last_message_time[account][jid] = 0 # long time ago
1788 w = ctrl.parent_win
1789 elif type_ in ('printed_pm', 'pm'):
1790 # assume that the most recently updated control we have for this
1791 # party is the one that this event was in
1792 event = gajim.events.get_first_event(account, fjid, type_)
1793 if not event:
1794 event = gajim.events.get_first_event(account, jid, type_)
1796 if type_ == 'printed_pm':
1797 ctrl = event.parameters[0]
1798 elif type_ == 'pm':
1799 session = event.parameters[8]
1801 if session and session.control:
1802 ctrl = session.control
1803 elif not ctrl:
1804 room_jid = jid
1805 nick = resource
1806 gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
1807 nick)
1808 if gc_contact:
1809 show = gc_contact.show
1810 else:
1811 show = 'offline'
1812 gc_contact = gajim.contacts.create_gc_contact(
1813 room_jid=room_jid, account=account, name=nick,
1814 show=show)
1816 if not session:
1817 session = gajim.connections[account].make_new_session(
1818 fjid, None, type_='pm')
1820 self.new_private_chat(gc_contact, account, session=session)
1821 ctrl = session.control
1823 w = ctrl.parent_win
1824 elif type_ in ('normal', 'file-request', 'file-request-error',
1825 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
1826 'jingle-incoming'):
1827 # Get the first single message event
1828 event = gajim.events.get_first_event(account, fjid, type_)
1829 if not event:
1830 # default to jid without resource
1831 event = gajim.events.get_first_event(account, jid, type_)
1832 if not event:
1833 return
1834 # Open the window
1835 self.roster.open_event(account, jid, event)
1836 else:
1837 # Open the window
1838 self.roster.open_event(account, fjid, event)
1839 elif type_ == 'gmail':
1840 url = gajim.connections[account].gmail_url
1841 if url:
1842 helpers.launch_browser_mailer('url', url)
1843 elif type_ == 'gc-invitation':
1844 event = gajim.events.get_first_event(account, jid, type_)
1845 data = event.parameters
1846 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1847 data[1], data[3])
1848 gajim.events.remove_events(account, jid, event)
1849 self.roster.draw_contact(jid, account)
1850 elif type_ == 'subscription_request':
1851 event = gajim.events.get_first_event(account, jid, type_)
1852 data = event.parameters
1853 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1854 gajim.events.remove_events(account, jid, event)
1855 self.roster.draw_contact(jid, account)
1856 elif type_ == 'unsubscribed':
1857 event = gajim.events.get_first_event(account, jid, type_)
1858 contact = event.parameters
1859 self.show_unsubscribed_dialog(account, contact)
1860 gajim.events.remove_events(account, jid, event)
1861 self.roster.draw_contact(jid, account)
1862 if w:
1863 w.set_active_tab(ctrl)
1864 w.window.window.focus(gtk.get_current_event_time())
1865 # Using isinstance here because we want to catch all derived types
1866 if isinstance(ctrl, ChatControlBase):
1867 tv = ctrl.conv_textview
1868 tv.scroll_to_end()
1870 ################################################################################
1871 ### Methods dealing with emoticons
1872 ################################################################################
1874 def image_is_ok(self, image):
1875 if not os.path.exists(image):
1876 return False
1877 img = gtk.Image()
1878 try:
1879 img.set_from_file(image)
1880 except Exception:
1881 return False
1882 t = img.get_storage_type()
1883 if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
1884 return False
1885 return True
1887 @property
1888 def basic_pattern_re(self):
1889 if not self._basic_pattern_re:
1890 self._basic_pattern_re = re.compile(self.basic_pattern,
1891 re.IGNORECASE)
1892 return self._basic_pattern_re
1894 @property
1895 def emot_and_basic_re(self):
1896 if not self._emot_and_basic_re:
1897 self._emot_and_basic_re = re.compile(self.emot_and_basic,
1898 re.IGNORECASE + re.UNICODE)
1899 return self._emot_and_basic_re
1901 @property
1902 def sth_at_sth_dot_sth_re(self):
1903 if not self._sth_at_sth_dot_sth_re:
1904 self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
1905 return self._sth_at_sth_dot_sth_re
1907 @property
1908 def invalid_XML_chars_re(self):
1909 if not self._invalid_XML_chars_re:
1910 self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
1911 return self._invalid_XML_chars_re
1913 def make_regexps(self):
1914 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
1915 # one escapes the metachars with \
1916 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
1917 # \s matches any whitespace character
1918 # \w any alphanumeric character
1919 # \W any non-alphanumeric character
1920 # \b means word boundary. This is a zero-width assertion that
1921 # matches only at the beginning or end of a word.
1922 # ^ matches at the beginning of lines
1924 # * means 0 or more times
1925 # + means 1 or more times
1926 # ? means 0 or 1 time
1927 # | means or
1928 # [^*] anything but '*' (inside [] you don't have to escape metachars)
1929 # [^\s*] anything but whitespaces and '*'
1930 # (?<!\S) is a one char lookbehind assertion and asks for any leading
1931 # whitespace
1932 # and mathces beginning of lines so we have correct formatting detection
1933 # even if the the text is just '*foo*'
1934 # (?!\S) is the same thing but it's a lookahead assertion
1935 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
1936 # the end
1937 # so http://be) will match http://be and http://be)be) will match
1938 # http://be)be
1940 legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
1941 r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
1942 r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
1943 r"|%[A-Fa-f0-9]{2})+"\
1944 r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
1945 # NOTE: it's ok to catch www.gr such stuff exist!
1947 # FIXME: recognize xmpp: and treat it specially
1948 links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
1949 r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
1950 r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
1952 # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
1953 mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
1955 # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
1956 # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
1957 formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
1958 r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
1959 r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
1961 latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\
1962 r'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
1964 basic_pattern = links + '|' + mail + '|' + legacy_prefixes
1966 link_pattern = basic_pattern
1967 self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
1969 if gajim.config.get('use_latex'):
1970 basic_pattern += latex
1972 if gajim.config.get('ascii_formatting'):
1973 basic_pattern += formatting
1974 self.basic_pattern = basic_pattern
1976 emoticons_pattern = ''
1977 if gajim.config.get('emoticons_theme'):
1978 # When an emoticon is bordered by an alpha-numeric character it is
1979 # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
1980 # We still allow multiple emoticons side-by-side like :P:P:P
1981 # sort keys by length so :qwe emot is checked before :q
1982 keys = sorted(self.emoticons, key=len, reverse=True)
1983 emoticons_pattern_prematch = ''
1984 emoticons_pattern_postmatch = ''
1985 emoticon_length = 0
1986 for emoticon in keys: # travel thru emoticons list
1987 emoticon = emoticon.decode('utf-8')
1988 emoticon_escaped = re.escape(emoticon) # espace regexp metachars
1989 # | means or in regexp
1990 emoticons_pattern += emoticon_escaped + '|'
1991 if (emoticon_length != len(emoticon)):
1992 # Build up expressions to match emoticons next to others
1993 emoticons_pattern_prematch = \
1994 emoticons_pattern_prematch[:-1] + ')|(?<='
1995 emoticons_pattern_postmatch = \
1996 emoticons_pattern_postmatch[:-1] + ')|(?='
1997 emoticon_length = len(emoticon)
1998 emoticons_pattern_prematch += emoticon_escaped + '|'
1999 emoticons_pattern_postmatch += emoticon_escaped + '|'
2000 # We match from our list of emoticons, but they must either have
2001 # whitespace, or another emoticon next to it to match successfully
2002 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
2003 emoticons_pattern = '|' + '(?:(?<![\w.]' + \
2004 emoticons_pattern_prematch[:-1] + '))' + '(?:' + \
2005 emoticons_pattern[:-1] + ')' + '(?:(?![\w]' + \
2006 emoticons_pattern_postmatch[:-1] + '))'
2008 # because emoticons match later (in the string) they need to be after
2009 # basic matches that may occur earlier
2010 self.emot_and_basic = basic_pattern + emoticons_pattern
2012 # needed for xhtml display
2013 self.emot_only = emoticons_pattern
2015 # at least one character in 3 parts (before @, after @, after .)
2016 self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
2018 # Invalid XML chars
2019 self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|'\
2020 u'[\ud800-\udfff]|[\ufffe-\uffff]'
2022 def popup_emoticons_under_button(self, button, parent_win):
2024 Popup the emoticons menu under button, located in parent_win
2026 gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
2027 button, parent_win)
2029 def prepare_emoticons_menu(self):
2030 menu = gtk.Menu()
2031 def emoticon_clicked(w, str_):
2032 if self.emoticon_menuitem_clicked:
2033 self.emoticon_menuitem_clicked(str_)
2034 # don't keep reference to CB of object
2035 # this will prevent making it uncollectable
2036 self.emoticon_menuitem_clicked = None
2037 def selection_done(widget):
2038 # remove reference to CB of object, which will
2039 # make it uncollectable
2040 self.emoticon_menuitem_clicked = None
2041 counter = 0
2042 # Calculate the side lenght of the popup to make it a square
2043 size = int(round(math.sqrt(len(self.emoticons_images))))
2044 for image in self.emoticons_images:
2045 item = gtk.MenuItem()
2046 img = gtk.Image()
2047 if isinstance(image[1], gtk.gdk.PixbufAnimation):
2048 img.set_from_animation(image[1])
2049 else:
2050 img.set_from_pixbuf(image[1])
2051 item.add(img)
2052 item.connect('activate', emoticon_clicked, image[0])
2053 #FIXME: add tooltip with ascii
2054 menu.attach(item, counter % size, counter % size + 1,
2055 counter / size, counter / size + 1)
2056 counter += 1
2057 menu.connect('selection-done', selection_done)
2058 menu.show_all()
2059 return menu
2061 def _init_emoticons(self, path, need_reload = False):
2062 #initialize emoticons dictionary and unique images list
2063 self.emoticons_images = list()
2064 self.emoticons = dict()
2065 self.emoticons_animations = dict()
2067 sys.path.append(path)
2068 import emoticons
2069 if need_reload:
2070 # we need to reload else that doesn't work when changing emoticon
2071 # set
2072 reload(emoticons)
2073 emots = emoticons.emoticons
2074 for emot_filename in emots:
2075 emot_file = os.path.join(path, emot_filename)
2076 if not self.image_is_ok(emot_file):
2077 continue
2078 for emot in emots[emot_filename]:
2079 emot = emot.decode('utf-8')
2080 # This avoids duplicated emoticons with the same image eg. :)
2081 # and :-)
2082 if not emot_file in self.emoticons.values():
2083 if emot_file.endswith('.gif'):
2084 pix = gtk.gdk.PixbufAnimation(emot_file)
2085 else:
2086 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file,
2087 16, 16)
2088 self.emoticons_images.append((emot, pix))
2089 self.emoticons[emot.upper()] = emot_file
2090 del emoticons
2091 sys.path.remove(path)
2093 def init_emoticons(self, need_reload = False):
2094 emot_theme = gajim.config.get('emoticons_theme')
2095 if not emot_theme:
2096 return
2098 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
2099 if not os.path.exists(path):
2100 # It's maybe a user theme
2101 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
2102 if not os.path.exists(path):
2103 # theme doesn't exist, disable emoticons
2104 dialogs.WarningDialog(_('Emoticons disabled'),
2105 _('Your configured emoticons theme has not been found, so '
2106 'emoticons have been disabled.'))
2107 gajim.config.set('emoticons_theme', '')
2108 return
2109 self._init_emoticons(path, need_reload)
2110 if len(self.emoticons) == 0:
2111 # maybe old format of emoticons file, try to convert it
2112 try:
2113 import pprint
2114 import emoticons
2115 emots = emoticons.emoticons
2116 fd = open(os.path.join(path, 'emoticons.py'), 'w')
2117 fd.write('emoticons = ')
2118 pprint.pprint( dict([
2119 (file_, [i for i in emots.keys() if emots[i] == file_])
2120 for file_ in set(emots.values())]), fd)
2121 fd.close()
2122 del emoticons
2123 self._init_emoticons(path, need_reload=True)
2124 except Exception:
2125 pass
2126 if len(self.emoticons) == 0:
2127 dialogs.WarningDialog(_('Emoticons disabled'),
2128 _('Your configured emoticons theme cannot been loaded. You '
2129 'maybe need to update the format of emoticons.py file. See '
2130 'http://trac.gajim.org/wiki/Emoticons for more details.'))
2131 if self.emoticons_menu:
2132 self.emoticons_menu.destroy()
2133 self.emoticons_menu = self.prepare_emoticons_menu()
2135 ################################################################################
2136 ### Methods for opening new messages controls
2137 ################################################################################
2139 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
2140 is_continued=False):
2142 Join the room immediately
2144 if not nick:
2145 nick = gajim.nicks[account]
2147 if self.msg_win_mgr.has_window(room_jid, account) and \
2148 gajim.gc_connected[account][room_jid]:
2149 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
2150 win = gc_ctrl.parent_win
2151 win.set_active_tab(gc_ctrl)
2152 dialogs.ErrorDialog(_('You are already in group chat %s') % \
2153 room_jid)
2154 return
2156 invisible_show = gajim.SHOW_LIST.index('invisible')
2157 if gajim.connections[account].connected == invisible_show:
2158 dialogs.ErrorDialog(
2159 _('You cannot join a group chat while you are invisible'))
2160 return
2162 minimized_control = gajim.interface.minimized_controls[account].get(
2163 room_jid, None)
2165 if minimized_control is None and not self.msg_win_mgr.has_window(
2166 room_jid, account):
2167 # Join new groupchat
2168 if minimize:
2169 # GCMIN
2170 contact = gajim.contacts.create_contact(jid=room_jid,
2171 account=account, name=nick)
2172 gc_control = GroupchatControl(None, contact, account)
2173 gajim.interface.minimized_controls[account][room_jid] = \
2174 gc_control
2175 self.roster.add_groupchat(room_jid, account)
2176 else:
2177 self.new_room(room_jid, nick, account,
2178 is_continued=is_continued)
2179 elif minimized_control is None:
2180 # We are already in that groupchat
2181 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
2182 gc_control.nick = nick
2183 gc_control.parent_win.set_active_tab(gc_control)
2184 else:
2185 # We are already in this groupchat and it is minimized
2186 minimized_control.nick = nick
2187 self.roster.add_groupchat(room_jid, account)
2189 # Connect
2190 gajim.connections[account].join_gc(nick, room_jid, password)
2191 if password:
2192 gajim.gc_passwords[room_jid] = password
2194 def new_room(self, room_jid, nick, account, is_continued=False):
2195 # Get target window, create a control, and associate it with the window
2196 # GCMIN
2197 contact = gajim.contacts.create_contact(jid=room_jid, account=account,
2198 name=nick)
2199 mw = self.msg_win_mgr.get_window(contact.jid, account)
2200 if not mw:
2201 mw = self.msg_win_mgr.create_window(contact, account,
2202 GroupchatControl.TYPE_ID)
2203 gc_control = GroupchatControl(mw, contact, account,
2204 is_continued=is_continued)
2205 mw.new_tab(gc_control)
2207 def new_private_chat(self, gc_contact, account, session=None):
2208 conn = gajim.connections[account]
2209 if not session and gc_contact.get_full_jid() in conn.sessions:
2210 sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
2211 values() if isinstance(s, ChatControlSession)]
2213 # look for an existing session with a chat control
2214 for s in sessions:
2215 if s.control:
2216 session = s
2217 break
2218 if not session and not len(sessions) == 0:
2219 # there are no sessions with chat controls, just take the first
2220 # one
2221 session = sessions[0]
2222 if not session:
2223 # couldn't find an existing ChatControlSession, just make a new one
2224 session = conn.make_new_session(gc_contact.get_full_jid(), None,
2225 'pm')
2227 contact = gc_contact.as_contact()
2228 if not session.control:
2229 message_window = self.msg_win_mgr.get_window(
2230 gc_contact.get_full_jid(), account)
2231 if not message_window:
2232 message_window = self.msg_win_mgr.create_window(contact,
2233 account, message_control.TYPE_PM)
2235 session.control = PrivateChatControl(message_window, gc_contact,
2236 contact, account, session)
2237 message_window.new_tab(session.control)
2239 if gajim.events.get_events(account, gc_contact.get_full_jid()):
2240 # We call this here to avoid race conditions with widget validation
2241 session.control.read_queue()
2243 return session.control
2245 def new_chat(self, contact, account, resource=None, session=None):
2246 # Get target window, create a control, and associate it with the window
2247 type_ = message_control.TYPE_CHAT
2249 fjid = contact.jid
2250 if resource:
2251 fjid += '/' + resource
2253 mw = self.msg_win_mgr.get_window(fjid, account)
2254 if not mw:
2255 mw = self.msg_win_mgr.create_window(contact, account, type_,
2256 resource)
2258 chat_control = ChatControl(mw, contact, account, session, resource)
2260 mw.new_tab(chat_control)
2262 if len(gajim.events.get_events(account, fjid)):
2263 # We call this here to avoid race conditions with widget validation
2264 chat_control.read_queue()
2266 return chat_control
2268 def new_chat_from_jid(self, account, fjid, message=None):
2269 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2270 contact = gajim.contacts.get_contact(account, jid, resource)
2271 added_to_roster = False
2272 if not contact:
2273 added_to_roster = True
2274 contact = self.roster.add_to_not_in_the_roster(account, jid,
2275 resource=resource)
2277 ctrl = self.msg_win_mgr.get_control(fjid, account)
2279 if not ctrl:
2280 ctrl = self.new_chat(contact, account,
2281 resource=resource)
2282 if len(gajim.events.get_events(account, fjid)):
2283 ctrl.read_queue()
2285 if message:
2286 buffer_ = ctrl.msg_textview.get_buffer()
2287 buffer_.set_text(message)
2288 mw = ctrl.parent_win
2289 mw.set_active_tab(ctrl)
2290 # For JEP-0172
2291 if added_to_roster:
2292 ctrl.user_nick = gajim.nicks[account]
2293 gobject.idle_add(mw.window.grab_focus)
2295 return ctrl
2297 def on_open_chat_window(self, widget, contact, account, resource=None,
2298 session=None):
2299 # Get the window containing the chat
2300 fjid = contact.jid
2302 if resource:
2303 fjid += '/' + resource
2305 ctrl = None
2307 if session:
2308 ctrl = session.control
2309 if not ctrl:
2310 win = self.msg_win_mgr.get_window(fjid, account)
2312 if win:
2313 ctrl = win.get_control(fjid, account)
2315 if not ctrl:
2316 ctrl = self.new_chat(contact, account, resource=resource,
2317 session=session)
2318 # last message is long time ago
2319 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2321 win = ctrl.parent_win
2323 win.set_active_tab(ctrl)
2325 if gajim.connections[account].is_zeroconf and \
2326 gajim.connections[account].status in ('offline', 'invisible'):
2327 ctrl = win.get_control(fjid, account)
2328 if ctrl:
2329 ctrl.got_disconnected()
2331 ################################################################################
2332 ### Other Methods
2333 ################################################################################
2335 def change_awn_icon_status(self, status):
2336 if not dbus_support.supported:
2337 # do nothing if user doesn't have D-Bus bindings
2338 return
2339 try:
2340 bus = dbus.SessionBus()
2341 if not 'com.google.code.Awn' in bus.list_names():
2342 # Awn is not installed
2343 return
2344 except Exception:
2345 return
2346 iconset = gajim.config.get('iconset')
2347 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
2348 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2349 status = status + '.png'
2350 elif status == 'online':
2351 prefix = ''
2352 status = gtkgui_helpers.get_icon_path('gajim', 32)
2353 path = os.path.join(prefix, status)
2354 try:
2355 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
2356 awn = dbus.Interface(obj, 'com.google.code.Awn')
2357 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
2358 except Exception:
2359 pass
2361 def enable_music_listener(self):
2362 listener = MusicTrackListener.get()
2363 if not self.music_track_changed_signal:
2364 self.music_track_changed_signal = listener.connect(
2365 'music-track-changed', self.music_track_changed)
2366 track = listener.get_playing_track()
2367 self.music_track_changed(listener, track)
2369 def disable_music_listener(self):
2370 listener = MusicTrackListener.get()
2371 listener.disconnect(self.music_track_changed_signal)
2372 self.music_track_changed_signal = None
2374 def music_track_changed(self, unused_listener, music_track_info,
2375 account=None):
2376 if not account:
2377 accounts = gajim.connections.keys()
2378 else:
2379 accounts = [account]
2381 is_paused = hasattr(music_track_info, 'paused') and \
2382 music_track_info.paused == 0
2383 if not music_track_info or is_paused:
2384 artist = title = source = ''
2385 else:
2386 artist = music_track_info.artist
2387 title = music_track_info.title
2388 source = music_track_info.album
2389 for acct in accounts:
2390 if not gajim.account_is_connected(acct):
2391 continue
2392 if not gajim.connections[acct].pep_supported:
2393 continue
2394 if not gajim.config.get_per('accounts', acct, 'publish_tune'):
2395 continue
2396 if gajim.connections[acct].music_track_info == music_track_info:
2397 continue
2398 gajim.connections[acct].send_tune(artist, title, source)
2399 gajim.connections[acct].music_track_info = music_track_info
2401 def get_bg_fg_colors(self):
2402 def gdkcolor_to_rgb (gdkcolor):
2403 return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
2404 gdkcolor.blue)]
2406 def format_rgb (r, g, b):
2407 return ' '.join([str(c) for c in ('rgb', r, g, b)])
2409 def format_gdkcolor (gdkcolor):
2410 return format_rgb (*gdkcolor_to_rgb (gdkcolor))
2412 # get style colors and create string for dvipng
2413 dummy = gtk.Invisible()
2414 dummy.ensure_style()
2415 style = dummy.get_style()
2416 bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
2417 fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
2418 return (bg_str, fg_str)
2420 def get_fg_color(self, fmt='hex'):
2421 def format_gdkcolor (c):
2422 if fmt == 'tex':
2423 return ' '.join([str(s) for s in
2424 ('rgb', c.red_float, c.green_float, c.blue_float)])
2425 elif fmt == 'hex':
2426 return str(c)
2428 # get foreground style color and create string
2429 dummy = gtk.Invisible()
2430 dummy.ensure_style()
2431 return format_gdkcolor(dummy.get_style().text[gtk.STATE_NORMAL])
2433 def read_sleepy(self):
2435 Check idle status and change that status if needed
2437 if not self.sleeper.poll():
2438 # idle detection is not supported in that OS
2439 return False # stop looping in vain
2440 state = self.sleeper.getState()
2441 for account in gajim.connections:
2442 if account not in gajim.sleeper_state or \
2443 not gajim.sleeper_state[account]:
2444 continue
2445 if state == common.sleepy.STATE_AWAKE and \
2446 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2447 # we go online
2448 self.roster.send_status(account, 'online',
2449 gajim.status_before_autoaway[account])
2450 gajim.status_before_autoaway[account] = ''
2451 gajim.sleeper_state[account] = 'online'
2452 elif state == common.sleepy.STATE_AWAY and \
2453 gajim.sleeper_state[account] == 'online' and \
2454 gajim.config.get('autoaway'):
2455 # we save out online status
2456 gajim.status_before_autoaway[account] = \
2457 gajim.connections[account].status
2458 # we go away (no auto status) [we pass True to auto param]
2459 auto_message = gajim.config.get('autoaway_message')
2460 if not auto_message:
2461 auto_message = gajim.connections[account].status
2462 else:
2463 auto_message = auto_message.replace('$S', '%(status)s')
2464 auto_message = auto_message.replace('$T', '%(time)s')
2465 auto_message = auto_message % {
2466 'status': gajim.status_before_autoaway[account],
2467 'time': gajim.config.get('autoawaytime')
2469 self.roster.send_status(account, 'away', auto_message,
2470 auto=True)
2471 gajim.sleeper_state[account] = 'autoaway'
2472 elif state == common.sleepy.STATE_XA and \
2473 gajim.sleeper_state[account] in ('online', 'autoaway',
2474 'autoaway-forced') and gajim.config.get('autoxa'):
2475 # we go extended away [we pass True to auto param]
2476 auto_message = gajim.config.get('autoxa_message')
2477 if not auto_message:
2478 auto_message = gajim.connections[account].status
2479 else:
2480 auto_message = auto_message.replace('$S', '%(status)s')
2481 auto_message = auto_message.replace('$T', '%(time)s')
2482 auto_message = auto_message % {
2483 'status': gajim.status_before_autoaway[account],
2484 'time': gajim.config.get('autoxatime')
2486 self.roster.send_status(account, 'xa', auto_message, auto=True)
2487 gajim.sleeper_state[account] = 'autoxa'
2488 return True # renew timeout (loop for ever)
2490 def autoconnect(self):
2492 Auto connect at startup
2494 # dict of account that want to connect sorted by status
2495 shows = {}
2496 for a in gajim.connections:
2497 if gajim.config.get_per('accounts', a, 'autoconnect'):
2498 if gajim.config.get_per('accounts', a, 'restore_last_status'):
2499 self.roster.send_status(a, gajim.config.get_per('accounts',
2500 a, 'last_status'), helpers.from_one_line(
2501 gajim.config.get_per('accounts', a, 'last_status_msg')))
2502 continue
2503 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
2504 if not show in gajim.SHOW_LIST:
2505 continue
2506 if not show in shows:
2507 shows[show] = [a]
2508 else:
2509 shows[show].append(a)
2510 def on_message(message, pep_dict):
2511 if message is None:
2512 return
2513 for a in shows[show]:
2514 self.roster.send_status(a, show, message)
2515 self.roster.send_pep(a, pep_dict)
2516 for show in shows:
2517 message = self.roster.get_status_message(show, on_message)
2518 return False
2520 def show_systray(self):
2521 self.systray_enabled = True
2522 self.systray.show_icon()
2524 def hide_systray(self):
2525 self.systray_enabled = False
2526 self.systray.hide_icon()
2528 def on_launch_browser_mailer(self, widget, url, kind):
2529 helpers.launch_browser_mailer(kind, url)
2531 def process_connections(self):
2533 Called each foo (200) miliseconds. Check for idlequeue timeouts
2535 try:
2536 gajim.idlequeue.process()
2537 except Exception:
2538 # Otherwise, an exception will stop our loop
2539 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2540 if in_seconds:
2541 gobject.timeout_add_seconds(timeout, self.process_connections)
2542 else:
2543 gobject.timeout_add(timeout, self.process_connections)
2544 raise
2545 return True # renew timeout (loop for ever)
2547 def save_config(self):
2548 err_str = parser.write()
2549 if err_str is not None:
2550 print >> sys.stderr, err_str
2551 # it is good to notify the user
2552 # in case he or she cannot see the output of the console
2553 dialogs.ErrorDialog(_('Could not save your settings and '
2554 'preferences'), err_str)
2555 sys.exit()
2557 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
2559 Save an avatar to a separate file, and generate files for dbus
2560 notifications. An avatar can be given as a pixmap directly or as an
2561 decoded image
2563 puny_jid = helpers.sanitize_filename(jid)
2564 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2565 if puny_nick:
2566 path_to_file = os.path.join(path_to_file, puny_nick)
2567 # remove old avatars
2568 for typ in ('jpeg', 'png'):
2569 if local:
2570 path_to_original_file = path_to_file + '_local'+ '.' + typ
2571 else:
2572 path_to_original_file = path_to_file + '.' + typ
2573 if os.path.isfile(path_to_original_file):
2574 os.remove(path_to_original_file)
2575 if local and photo:
2576 pixbuf = photo
2577 typ = 'png'
2578 extension = '_local.png' # save local avatars as png file
2579 else:
2580 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
2581 want_type=True)
2582 if pixbuf is None:
2583 return
2584 extension = '.' + typ
2585 if typ not in ('jpeg', 'png'):
2586 gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\
2587 'png formats. saving %s\'avatar as png file (originaly %s)'\
2588 % (jid, typ))
2589 typ = 'png'
2590 extension = '.png'
2591 path_to_original_file = path_to_file + extension
2592 try:
2593 pixbuf.save(path_to_original_file, typ)
2594 except Exception, e:
2595 log.error('Error writing avatar file %s: %s' % (
2596 path_to_original_file, str(e)))
2597 # Generate and save the resized, color avatar
2598 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
2599 if pixbuf:
2600 path_to_normal_file = path_to_file + '_notif_size_colored' + \
2601 extension
2602 try:
2603 pixbuf.save(path_to_normal_file, 'png')
2604 except Exception, e:
2605 log.error('Error writing avatar file %s: %s' % \
2606 (path_to_original_file, str(e)))
2607 # Generate and save the resized, black and white avatar
2608 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
2609 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
2610 if bwbuf:
2611 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
2612 try:
2613 bwbuf.save(path_to_bw_file, 'png')
2614 except Exception, e:
2615 log.error('Error writing avatar file %s: %s' % \
2616 (path_to_original_file, str(e)))
2618 def remove_avatar_files(self, jid, puny_nick = None, local = False):
2620 Remove avatar files of a jid
2622 puny_jid = helpers.sanitize_filename(jid)
2623 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2624 if puny_nick:
2625 path_to_file = os.path.join(path_to_file, puny_nick)
2626 for ext in ('.jpeg', '.png'):
2627 if local:
2628 ext = '_local' + ext
2629 path_to_original_file = path_to_file + ext
2630 if os.path.isfile(path_to_file + ext):
2631 os.remove(path_to_file + ext)
2632 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
2633 os.remove(path_to_file + '_notif_size_colored' + ext)
2634 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
2635 os.remove(path_to_file + '_notif_size_bw' + ext)
2637 def auto_join_bookmarks(self, account):
2639 Autojoin bookmarked GCs that have 'auto join' on for this account
2641 for bm in gajim.connections[account].bookmarks:
2642 if bm['autojoin'] in ('1', 'true'):
2643 jid = bm['jid']
2644 # Only join non-opened groupchats. Opened one are already
2645 # auto-joined on re-connection
2646 if not jid in gajim.gc_connected[account]:
2647 # we are not already connected
2648 minimize = bm['minimize'] in ('1', 'true')
2649 gajim.interface.join_gc_room(account, jid, bm['nick'],
2650 bm['password'], minimize = minimize)
2651 elif jid in self.minimized_controls[account]:
2652 # more or less a hack:
2653 # On disconnect the minimized gc contact instances
2654 # were set to offline. Reconnect them to show up in the
2655 # roster.
2656 self.roster.add_groupchat(jid, account)
2658 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
2659 nick):
2661 Add a bookmark for this account, sorted in bookmark list
2663 bm = {
2664 'name': name,
2665 'jid': jid,
2666 'autojoin': autojoin,
2667 'minimize': minimize,
2668 'password': password,
2669 'nick': nick
2671 place_found = False
2672 index = 0
2673 # check for duplicate entry and respect alpha order
2674 for bookmark in gajim.connections[account].bookmarks:
2675 if bookmark['jid'] == bm['jid']:
2676 dialogs.ErrorDialog(
2677 _('Bookmark already set'),
2678 _('Group Chat "%s" is already in your bookmarks.') % \
2679 bm['jid'])
2680 return
2681 if bookmark['name'] > bm['name']:
2682 place_found = True
2683 break
2684 index += 1
2685 if place_found:
2686 gajim.connections[account].bookmarks.insert(index, bm)
2687 else:
2688 gajim.connections[account].bookmarks.append(bm)
2689 gajim.connections[account].store_bookmarks()
2690 self.roster.set_actions_menu_needs_rebuild()
2691 dialogs.InformationDialog(
2692 _('Bookmark has been added successfully'),
2693 _('You can manage your bookmarks via Actions menu in your roster.'))
2696 # does JID exist only within a groupchat?
2697 def is_pm_contact(self, fjid, account):
2698 bare_jid = gajim.get_jid_without_resource(fjid)
2700 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
2702 if not gc_ctrl and \
2703 bare_jid in self.minimized_controls[account]:
2704 gc_ctrl = self.minimized_controls[account][bare_jid]
2706 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
2708 def create_ipython_window(self):
2709 try:
2710 from ipython_view import IPythonView
2711 except ImportError:
2712 print 'ipython_view not found'
2713 return
2714 import pango
2716 if os.name == 'nt':
2717 font = 'Lucida Console 9'
2718 else:
2719 font = 'Luxi Mono 10'
2721 window = gtk.Window()
2722 window.set_size_request(750, 550)
2723 window.set_resizable(True)
2724 sw = gtk.ScrolledWindow()
2725 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2726 view = IPythonView()
2727 view.modify_font(pango.FontDescription(font))
2728 view.set_wrap_mode(gtk.WRAP_CHAR)
2729 sw.add(view)
2730 window.add(sw)
2731 window.show_all()
2732 def on_delete(win, event):
2733 win.hide()
2734 return True
2735 window.connect('delete_event', on_delete)
2736 view.updateNamespace({'gajim': gajim})
2737 gajim.ipython_window = window
2739 def run(self):
2740 if gajim.config.get('trayicon') != 'never':
2741 self.show_systray()
2743 self.roster = roster_window.RosterWindow()
2744 # Creating plugin manager
2745 import plugins
2746 gajim.plugin_manager = plugins.PluginManager()
2748 self.roster._before_fill()
2749 for account in gajim.connections:
2750 gajim.connections[account].load_roster_from_db()
2751 self.roster._after_fill()
2753 # get instances for windows/dialogs that will show_all()/hide()
2754 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
2756 gobject.timeout_add(100, self.autoconnect)
2757 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2758 if in_seconds:
2759 gobject.timeout_add_seconds(timeout, self.process_connections)
2760 else:
2761 gobject.timeout_add(timeout, self.process_connections)
2762 gobject.timeout_add_seconds(gajim.config.get(
2763 'check_idle_every_foo_seconds'), self.read_sleepy)
2765 # when using libasyncns we need to process resolver in regular intervals
2766 if resolver.USE_LIBASYNCNS:
2767 gobject.timeout_add(200, gajim.resolver.process)
2769 def remote_init():
2770 if gajim.config.get('remote_control'):
2771 try:
2772 import remote_control
2773 self.remote_ctrl = remote_control.Remote()
2774 except Exception:
2775 pass
2776 gobject.timeout_add_seconds(5, remote_init)
2778 def __init__(self):
2779 gajim.interface = self
2780 gajim.thread_interface = ThreadInterface
2781 # This is the manager and factory of message windows set by the module
2782 self.msg_win_mgr = None
2783 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
2784 'closed': {}}
2785 self.emoticons_menu = None
2786 # handler when an emoticon is clicked in emoticons_menu
2787 self.emoticon_menuitem_clicked = None
2788 self.minimized_controls = {}
2789 self.status_sent_to_users = {}
2790 self.status_sent_to_groups = {}
2791 self.gpg_passphrase = {}
2792 self.pass_dialog = {}
2793 self.db_error_dialog = None
2794 self.default_colors = {
2795 'inmsgcolor': gajim.config.get('inmsgcolor'),
2796 'outmsgcolor': gajim.config.get('outmsgcolor'),
2797 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
2798 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
2799 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
2800 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
2803 self.handlers = {}
2804 self.roster = None
2805 self._invalid_XML_chars_re = None
2806 self._basic_pattern_re = None
2807 self._emot_and_basic_re = None
2808 self._sth_at_sth_dot_sth_re = None
2809 self.link_pattern_re = None
2810 self.invalid_XML_chars = None
2811 self.basic_pattern = None
2812 self.emot_and_basic = None
2813 self.sth_at_sth_dot_sth = None
2814 self.emot_only = None
2815 self.emoticons = []
2816 self.emoticons_animations = {}
2817 self.emoticons_images = {}
2819 cfg_was_read = parser.read()
2821 from common import latex
2822 gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
2823 latex.check_for_latex_support()
2825 gajim.logger.reset_shown_unread_messages()
2826 # override logging settings from config (don't take care of '-q' option)
2827 if gajim.config.get('verbose'):
2828 logging_helpers.set_verbose()
2830 # Is Gajim default app?
2831 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
2832 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
2834 for account in gajim.config.get_per('accounts'):
2835 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
2836 gajim.ZEROCONF_ACC_NAME = account
2837 break
2838 # Is gnome configured to activate row on single click ?
2839 try:
2840 import gconf
2841 client = gconf.client_get_default()
2842 click_policy = client.get_string(
2843 '/apps/nautilus/preferences/click_policy')
2844 if click_policy == 'single':
2845 gajim.single_click = True
2846 except Exception:
2847 pass
2848 # add default status messages if there is not in the config file
2849 if len(gajim.config.get_per('statusmsg')) == 0:
2850 default = gajim.config.statusmsg_default
2851 for msg in default:
2852 gajim.config.add_per('statusmsg', msg)
2853 gajim.config.set_per('statusmsg', msg, 'message',
2854 default[msg][0])
2855 gajim.config.set_per('statusmsg', msg, 'activity',
2856 default[msg][1])
2857 gajim.config.set_per('statusmsg', msg, 'subactivity',
2858 default[msg][2])
2859 gajim.config.set_per('statusmsg', msg, 'activity_text',
2860 default[msg][3])
2861 gajim.config.set_per('statusmsg', msg, 'mood',
2862 default[msg][4])
2863 gajim.config.set_per('statusmsg', msg, 'mood_text',
2864 default[msg][5])
2865 #add default themes if there is not in the config file
2866 theme = gajim.config.get('roster_theme')
2867 if not theme in gajim.config.get_per('themes'):
2868 gajim.config.set('roster_theme', _('default'))
2869 if len(gajim.config.get_per('themes')) == 0:
2870 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
2871 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2872 'groupfont', 'groupfontattrs', 'contacttextcolor',
2873 'contactbgcolor', 'contactfont', 'contactfontattrs',
2874 'bannertextcolor', 'bannerbgcolor']
2876 default = gajim.config.themes_default
2877 for theme_name in default:
2878 gajim.config.add_per('themes', theme_name)
2879 theme = default[theme_name]
2880 for o in d:
2881 gajim.config.set_per('themes', theme_name, o,
2882 theme[d.index(o)])
2884 if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read:
2885 gtkgui_helpers.autodetect_browser_mailer()
2887 gajim.idlequeue = idlequeue.get_idlequeue()
2888 # resolve and keep current record of resolved hosts
2889 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
2890 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
2891 self.handle_event_file_rcv_completed,
2892 self.handle_event_file_progress,
2893 self.handle_event_file_error)
2894 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
2895 gajim.default_session_type = ChatControlSession
2897 # Creating Network Events Controller
2898 from common import nec
2899 gajim.nec = nec.NetworkEventsController()
2901 self.create_core_handlers_list()
2902 self.register_core_handlers()
2904 if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
2905 and gajim.HAVE_ZEROCONF:
2906 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
2907 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
2908 for account in gajim.config.get_per('accounts'):
2909 if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
2910 gajim.config.get_per('accounts', account, 'active'):
2911 gajim.connections[account] = common.connection.Connection(
2912 account)
2914 # gtk hooks
2915 gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
2916 gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
2917 gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
2919 self.instances = {}
2921 for a in gajim.connections:
2922 self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
2923 'search': {}, 'online_dialog': {}}
2924 # online_dialog contains all dialogs that have a meaning only when
2925 # we are not disconnected
2926 self.minimized_controls[a] = {}
2927 gajim.contacts.add_account(a)
2928 gajim.groups[a] = {}
2929 gajim.gc_connected[a] = {}
2930 gajim.automatic_rooms[a] = {}
2931 gajim.newly_added[a] = []
2932 gajim.to_be_removed[a] = []
2933 gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
2934 gajim.block_signed_in_notifications[a] = True
2935 gajim.sleeper_state[a] = 0
2936 gajim.encrypted_chats[a] = []
2937 gajim.last_message_time[a] = {}
2938 gajim.status_before_autoaway[a] = ''
2939 gajim.transport_avatar[a] = {}
2940 gajim.gajim_optional_features[a] = []
2941 gajim.caps_hash[a] = ''
2943 helpers.update_optional_features()
2944 # prepopulate data which we are sure of; note: we do not log these info
2945 for account in gajim.connections:
2946 gajimcaps = caps_cache.capscache[('sha-1',
2947 gajim.caps_hash[account])]
2948 gajimcaps.identities = [gajim.gajim_identity]
2949 gajimcaps.features = gajim.gajim_common_features + \
2950 gajim.gajim_optional_features[account]
2952 self.remote_ctrl = None
2954 if gajim.config.get('networkmanager_support') and \
2955 dbus_support.supported:
2956 import network_manager_listener
2958 # Handle gnome screensaver
2959 if dbus_support.supported:
2960 def gnome_screensaver_ActiveChanged_cb(active):
2961 if not active:
2962 for account in gajim.connections:
2963 if gajim.sleeper_state[account] == 'autoaway-forced':
2964 # We came back online ofter gnome-screensaver
2965 # autoaway
2966 self.roster.send_status(account, 'online',
2967 gajim.status_before_autoaway[account])
2968 gajim.status_before_autoaway[account] = ''
2969 gajim.sleeper_state[account] = 'online'
2970 return
2971 if not gajim.config.get('autoaway'):
2972 # Don't go auto away if user disabled the option
2973 return
2974 for account in gajim.connections:
2975 if account not in gajim.sleeper_state or \
2976 not gajim.sleeper_state[account]:
2977 continue
2978 if gajim.sleeper_state[account] == 'online':
2979 # we save out online status
2980 gajim.status_before_autoaway[account] = \
2981 gajim.connections[account].status
2982 # we go away (no auto status) [we pass True to auto
2983 # param]
2984 auto_message = gajim.config.get('autoaway_message')
2985 if not auto_message:
2986 auto_message = gajim.connections[account].status
2987 else:
2988 auto_message = auto_message.replace('$S',
2989 '%(status)s')
2990 auto_message = auto_message.replace('$T',
2991 '%(time)s')
2992 auto_message = auto_message % {
2993 'status': gajim.status_before_autoaway[account],
2994 'time': gajim.config.get('autoxatime')}
2995 self.roster.send_status(account, 'away', auto_message,
2996 auto=True)
2997 gajim.sleeper_state[account] = 'autoaway-forced'
2999 try:
3000 bus = dbus.SessionBus()
3001 bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
3002 'ActiveChanged', 'org.gnome.ScreenSaver')
3003 except Exception:
3004 pass
3006 self.show_vcard_when_connect = []
3008 self.sleeper = common.sleepy.Sleepy(
3009 gajim.config.get('autoawaytime') * 60, # make minutes to seconds
3010 gajim.config.get('autoxatime') * 60)
3012 gtkgui_helpers.make_jabber_state_images()
3014 self.systray_enabled = False
3016 import statusicon
3017 self.systray = statusicon.StatusIcon()
3019 pix = gtkgui_helpers.get_icon_pixmap('gajim', 32)
3020 # set the icon to all windows
3021 gtk.window_set_default_icon(pix)
3023 self.init_emoticons()
3024 self.make_regexps()
3026 # get transports type from DB
3027 gajim.transport_type = gajim.logger.get_transports_type()
3029 # test is dictionnary is present for speller
3030 if gajim.config.get('use_speller'):
3031 lang = gajim.config.get('speller_language')
3032 if not lang:
3033 lang = gajim.LANG
3034 tv = gtk.TextView()
3035 try:
3036 import gtkspell
3037 spell = gtkspell.Spell(tv, lang)
3038 except (ImportError, TypeError, RuntimeError, OSError):
3039 dialogs.AspellDictError(lang)
3041 if gajim.config.get('soundplayer') == '':
3042 # only on first time Gajim starts
3043 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
3044 for command in commands:
3045 if helpers.is_in_path(command):
3046 if command == 'aplay':
3047 command += ' -q'
3048 gajim.config.set('soundplayer', command)
3049 break
3051 self.last_ftwindow_update = 0
3053 self.music_track_changed_signal = None
3056 class PassphraseRequest:
3057 def __init__(self, keyid):
3058 self.keyid = keyid
3059 self.callbacks = []
3060 self.dialog_created = False
3061 self.dialog = None
3062 self.passphrase = None
3063 self.completed = False
3065 def interrupt(self, account=None):
3066 if account:
3067 for (acct, cb) in self.callbacks:
3068 if acct == account:
3069 self.callbacks.remove((acct, cb))
3070 else:
3071 self.callbacks = []
3072 if not len(self.callbacks):
3073 self.dialog.window.destroy()
3075 def run_callback(self, account, callback):
3076 gajim.connections[account].gpg_passphrase(self.passphrase)
3077 callback()
3079 def add_callback(self, account, cb):
3080 if self.completed:
3081 self.run_callback(account, cb)
3082 else:
3083 self.callbacks.append((account, cb))
3084 if not self.dialog_created:
3085 self.create_dialog(account)
3087 def complete(self, passphrase):
3088 self.passphrase = passphrase
3089 self.completed = True
3090 if passphrase is not None:
3091 gobject.timeout_add_seconds(30,
3092 gajim.interface.forget_gpg_passphrase, self.keyid)
3093 for (account, cb) in self.callbacks:
3094 self.run_callback(account, cb)
3095 self.callbacks = []
3097 def create_dialog(self, account):
3098 title = _('Passphrase Required')
3099 second = _('Enter GPG key passphrase for key %(keyid)s (account '
3100 '%(account)s).') % {'keyid': self.keyid, 'account': account}
3102 def _cancel():
3103 # user cancelled, continue without GPG
3104 self.complete(None)
3106 def _ok(passphrase, checked, count):
3107 result = gajim.connections[account].test_gpg_passphrase(passphrase)
3108 if result == 'ok':
3109 # passphrase is good
3110 self.complete(passphrase)
3111 return
3112 elif result == 'expired':
3113 dialogs.ErrorDialog(_('GPG key expired'),
3114 _('Your GPG key has expired, you will be connected to %s '
3115 'without OpenPGP.') % account)
3116 # Don't try to connect with GPG
3117 gajim.connections[account].continue_connect_info[2] = False
3118 self.complete(None)
3119 return
3121 if count < 3:
3122 # ask again
3123 dialogs.PassphraseDialog(_('Wrong Passphrase'),
3124 _('Please retype your GPG passphrase or press Cancel.'),
3125 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
3126 else:
3127 # user failed 3 times, continue without GPG
3128 self.complete(None)
3130 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
3131 1), cancel_handler=_cancel)
3132 self.dialog_created = True
3135 class ThreadInterface:
3136 def __init__(self, func, func_args, callback, callback_args):
3138 Call a function in a thread
3140 def thread_function(func, func_args, callback, callback_args):
3141 output = func(*func_args)
3142 gobject.idle_add(callback, output, *callback_args)
3144 Thread(target=thread_function, args=(func, func_args, callback,
3145 callback_args)).start()