set transient for roster windows to error / warning dialogs. Fixes #6942
[gajim.git] / src / gui_interface.py
blobb5761c5d450ea48446f41dcd94ef5a2aa7f107dd
1 # -*- coding:utf-8 -*-
2 ## src/gajim.py
3 ##
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
6 ## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
7 ## Norman Rasmussen <norman AT rasmussen.co.za>
8 ## Stéphan Kochen <stephan AT kochen.nl>
9 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
10 ## Alex Mauer <hawke AT hawkesnest.net>
11 ## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
12 ## Nikos Kouremenos <kourem AT gmail.com>
13 ## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
14 ## Stefan Bethge <stefan AT lanpartei.de>
15 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
16 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
17 ## James Newton <redshodan AT gmail.com>
18 ## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
19 ## Julien Pivotto <roidelapluie AT gmail.com>
20 ## Stephan Erb <steve-e AT h3c.de>
21 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
23 ## This file is part of Gajim.
25 ## Gajim is free software; you can redistribute it and/or modify
26 ## it under the terms of the GNU General Public License as published
27 ## by the Free Software Foundation; version 3 only.
29 ## Gajim is distributed in the hope that it will be useful,
30 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
31 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 ## GNU General Public License for more details.
34 ## You should have received a copy of the GNU General Public License
35 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
38 import os
39 import sys
40 import re
41 import time
42 import math
43 from subprocess import Popen
45 import gtk
46 import gobject
48 from common import i18n
49 from common import gajim
51 from common import dbus_support
52 if dbus_support.supported:
53 from music_track_listener import MusicTrackListener
54 from common import location_listener
55 import dbus
57 import gtkgui_helpers
59 import dialogs
60 import notify
61 import message_control
63 from chat_control import ChatControlBase
64 from chat_control import ChatControl
65 from groupchat_control import GroupchatControl
66 from groupchat_control import PrivateChatControl
68 from atom_window import AtomWindow
69 from session import ChatControlSession
71 import common.sleepy
73 from common.xmpp import idlequeue
74 from common.zeroconf import connection_zeroconf
75 from common import resolver
76 from common import caps_cache
77 from common import proxy65_manager
78 from common import socks5
79 from common import helpers
80 from common import dataforms
81 from common import passwords
82 from common import logging_helpers
83 from common.connection_handlers_events import OurShowEvent, \
84 FileRequestErrorEvent
85 from common.connection import Connection
87 import roster_window
88 import profile_window
89 import config
90 from threading import Thread
91 from common import ged
93 gajimpaths = common.configpaths.gajimpaths
94 config_filename = gajimpaths['CONFIG_FILE']
96 from common import optparser
97 parser = optparser.OptionsParser(config_filename)
99 import logging
100 log = logging.getLogger('gajim.interface')
102 class Interface:
104 ################################################################################
105 ### Methods handling events from connection
106 ################################################################################
108 def handle_event_error(self, unused, data):
109 #('ERROR', account, (title_text, section_text))
110 dialogs.ErrorDialog(data[0], data[1])
112 def handle_event_db_error(self, unused, data):
113 #('DB_ERROR', account, (title_text, section_text))
114 if self.db_error_dialog:
115 return
116 self.db_error_dialog = dialogs.ErrorDialog(data[0], data[1])
117 def destroyed(win):
118 self.db_error_dialog = None
119 self.db_error_dialog.connect('destroy', destroyed)
121 def handle_event_information(self, unused, data):
122 #('INFORMATION', account, (title_text, section_text))
123 dialogs.InformationDialog(data[0], data[1])
125 def handle_ask_new_nick(self, account, room_jid):
126 title = _('Unable to join group chat')
127 prompt = _('Your desired nickname in group chat %s is in use or '
128 'registered by another occupant.\nPlease specify another nickname '
129 'below:') % room_jid
130 check_text = _('Always use this nickname when there is a conflict')
131 if 'change_nick_dialog' in self.instances:
132 self.instances['change_nick_dialog'].add_room(account, room_jid,
133 prompt)
134 else:
135 self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
136 account, room_jid, title, prompt)
138 def handle_event_http_auth(self, obj):
139 #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
140 def response(account, answer):
141 obj.conn.build_http_auth_answer(obj.stanza, answer)
143 def on_yes(is_checked, obj):
144 response(obj, 'yes')
146 account = obj.conn.name
147 sec_msg = _('Do you accept this request?')
148 if gajim.get_number_of_connected_accounts() > 1:
149 sec_msg = _('Do you accept this request on account %s?') % account
150 if obj.msg:
151 sec_msg = obj.msg + '\n' + sec_msg
152 dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
153 '%(url)s (id: %(id)s)') % {'method': obj.method, 'url': obj.url,
154 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
155 on_response_no=(response, obj, 'no'))
157 def handle_event_iq_error(self, obj):
158 #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode))
159 if unicode(obj.errcode) in ('400', '403', '406') and obj.id_:
160 # show the error dialog
161 ft = self.instances['file_transfers']
162 sid = obj.id_
163 if len(obj.id_) > 3 and obj.id_[2] == '_':
164 sid = obj.id_[3:]
165 if sid in ft.files_props['s']:
166 file_props = ft.files_props['s'][sid]
167 if unicode(obj.errcode) == '400':
168 file_props['error'] = -3
169 else:
170 file_props['error'] = -4
171 gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
172 conn=obj.conn, jid=obj.jid, file_props=file_props,
173 error_msg=obj.errmsg))
174 obj.conn.disconnect_transfer(file_props)
175 return
176 elif unicode(obj.errcode) == '404':
177 sid = obj.id_
178 if len(obj.id_) > 3 and obj.id_[2] == '_':
179 sid = obj.id_[3:]
180 if sid in obj.conn.files_props:
181 file_props = obj.conn.files_props[sid]
182 self.handle_event_file_send_error(obj.conn.name, (obj.fjid,
183 file_props))
184 obj.conn.disconnect_transfer(file_props)
185 return
187 ctrl = self.msg_win_mgr.get_control(obj.fjid, obj.conn.name)
188 if ctrl and ctrl.type_id == message_control.TYPE_GC:
189 ctrl.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg))
191 def handle_event_connection_lost(self, obj):
192 # ('CONNECTION_LOST', account, [title, text])
193 path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48)
194 account = obj.conn.name
195 notify.popup(_('Connection Failed'), account, account,
196 'connection_failed', path, obj.title, obj.msg)
198 def unblock_signed_in_notifications(self, account):
199 gajim.block_signed_in_notifications[account] = False
201 def handle_event_status(self, obj): # OUR status
202 #('STATUS', account, show)
203 account = obj.conn.name
204 if obj.show in ('offline', 'error'):
205 for name in self.instances[account]['online_dialog'].keys():
206 # .keys() is needed to not have a dictionary length changed
207 # during iteration error
208 self.instances[account]['online_dialog'][name].destroy()
209 if name in self.instances[account]['online_dialog']:
210 # destroy handler may have already removed it
211 del self.instances[account]['online_dialog'][name]
212 for request in self.gpg_passphrase.values():
213 if request:
214 request.interrupt(account=account)
215 if account in self.pass_dialog:
216 self.pass_dialog[account].window.destroy()
217 if obj.show == 'offline':
218 gajim.block_signed_in_notifications[account] = True
219 else:
220 # 30 seconds after we change our status to sth else than offline
221 # we stop blocking notifications of any kind
222 # this prevents from getting the roster items as 'just signed in'
223 # contacts. 30 seconds should be enough time
224 gobject.timeout_add_seconds(30,
225 self.unblock_signed_in_notifications, account)
227 if account in self.show_vcard_when_connect and obj.show not in (
228 'offline', 'error'):
229 self.edit_own_details(account)
231 def edit_own_details(self, account):
232 jid = gajim.get_jid_from_account(account)
233 if 'profile' not in self.instances[account]:
234 self.instances[account]['profile'] = \
235 profile_window.ProfileWindow(account)
236 gajim.connections[account].request_vcard(jid)
238 def handle_gc_error(self, gc_control, pritext, sectext):
239 if gc_control and gc_control.autorejoin is not None:
240 if gc_control.error_dialog:
241 gc_control.error_dialog.destroy()
242 def on_close(dummy):
243 gc_control.error_dialog.destroy()
244 gc_control.error_dialog = None
245 gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext,
246 on_response_ok=on_close, on_response_cancel=on_close)
247 else:
248 dialogs.ErrorDialog(pritext, sectext)
250 def handle_gc_password_required(self, account, room_jid, nick):
251 def on_ok(text):
252 gajim.connections[account].join_gc(nick, room_jid, text)
253 gajim.gc_passwords[room_jid] = text
254 gc_control.error_dialog = None
256 def on_cancel():
257 # get and destroy window
258 if room_jid in gajim.interface.minimized_controls[account]:
259 self.roster.on_disconnect(None, room_jid, account)
260 else:
261 win = self.msg_win_mgr.get_window(room_jid, account)
262 ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
263 win.remove_tab(ctrl, 3)
264 gc_control.error_dialog = None
266 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
267 if gc_control:
268 if gc_control.error_dialog:
269 gc_control.error_dialog.destroy()
271 gc_control.error_dialog = dialogs.InputDialog(_('Password Required'),
272 _('A Password is required to join the room %s. Please type it.') % \
273 room_jid, is_modal=False, ok_handler=on_ok,
274 cancel_handler=on_cancel)
275 gc_control.error_dialog.input_entry.set_visibility(False)
277 def handle_event_gc_presence(self, obj):
278 gc_control = obj.gc_control
279 if obj.ptype == 'error':
280 if obj.errcode == '503':
281 # maximum user number reached
282 self.handle_gc_error(gc_control,
283 _('Unable to join group chat'),
284 _('Maximum number of users for %s has been reached') % \
285 obj.room_jid)
286 elif (obj.errcode == '401') or (obj.errcon == 'not-authorized'):
287 # password required to join
288 self.handle_gc_password_required(obj.conn.name, obj.room_jid,
289 obj.nick)
290 elif (obj.errcode == '403') or (obj.errcon == 'forbidden'):
291 # we are banned
292 self.handle_gc_error(gc_control, _('Unable to join group chat'),
293 _('You are banned from group chat %s.') % obj.room_jid)
294 elif (obj.errcode == '404') or (obj.errcon in ('item-not-found',
295 'remote-server-not-found')):
296 # group chat does not exist
297 self.handle_gc_error(gc_control, _('Unable to join group chat'),
298 _('Group chat %s does not exist.') % obj.room_jid)
299 elif (obj.errcode == '405') or (obj.errcon == 'not-allowed'):
300 self.handle_gc_error(gc_control, _('Unable to join group chat'),
301 _('Group chat creation is restricted.'))
302 elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'):
303 self.handle_gc_error(gc_control, _('Unable to join group chat'),
304 _('Your registered nickname must be used in group chat '
305 '%s.') % obj.room_jid)
306 elif (obj.errcode == '407') or (obj.errcon == \
307 'registration-required'):
308 self.handle_gc_error(gc_control, _('Unable to join group chat'),
309 _('You are not in the members list in groupchat %s.') % \
310 obj.room_jid)
311 elif (obj.errcode == '409') or (obj.errcon == 'conflict'):
312 self.handle_ask_new_nick(obj.conn.name, obj.room_jid)
313 elif gc_control:
314 gc_control.print_conversation('Error %s: %s' % (obj.errcode,
315 obj.errmsg))
316 if gc_control and gc_control.autorejoin:
317 gc_control.autorejoin = False
319 def handle_event_presence(self, obj):
320 # 'NOTIFY' (account, (jid, status, status message, resource,
321 # priority, # keyID, timestamp, contact_nickname))
323 # Contact changed show
325 account = obj.conn.name
326 jid = obj.jid
327 show = obj.show
328 status = obj.status
329 resource = obj.resource or ''
331 jid_list = gajim.contacts.get_jid_list(account)
333 # unset custom status
334 if (obj.old_show == 0 and obj.new_show > 1) or \
335 (obj.old_show > 1 and obj.new_show == 0 and obj.conn.connected > 1):
336 if account in self.status_sent_to_users and \
337 jid in self.status_sent_to_users[account]:
338 del self.status_sent_to_users[account][jid]
340 if gajim.jid_is_transport(jid):
341 # It must be an agent
343 # transport just signed in/out, don't show
344 # popup notifications for 30s
345 account_jid = account + '/' + jid
346 gajim.block_signed_in_notifications[account_jid] = True
347 gobject.timeout_add_seconds(30,
348 self.unblock_signed_in_notifications, account_jid)
350 highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
351 is_highest = (highest and highest.resource == resource)
353 # disconnect the session from the ctrl if the highest resource has
354 # changed
355 if (obj.was_highest and not is_highest) or \
356 (not obj.was_highest and is_highest):
357 ctrl = self.msg_win_mgr.get_control(jid, account)
358 if ctrl:
359 ctrl.no_autonegotiation = False
360 ctrl.set_session(None)
361 ctrl.contact = highest
363 def handle_event_msgerror(self, obj):
364 #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session]))
365 account = obj.conn.name
366 jids = obj.fjid.split('/', 1)
367 jid = jids[0]
369 if obj.error_code == '503':
370 # If we get server-not-found error, stop sending chatstates
371 for contact in gajim.contacts.get_contacts(account, jid):
372 contact.composing_xep = False
374 session = obj.session
376 gc_control = self.msg_win_mgr.get_gc_control(jid, account)
377 if not gc_control and \
378 jid in self.minimized_controls[account]:
379 gc_control = self.minimized_controls[account][jid]
380 if gc_control and gc_control.type_id != message_control.TYPE_GC:
381 gc_control = None
382 if gc_control:
383 if len(jids) > 1: # it's a pm
384 nick = jids[1]
386 if session:
387 ctrl = session.control
388 else:
389 ctrl = self.msg_win_mgr.get_control(obj.fjid, account)
391 if not ctrl:
392 tv = gc_control.list_treeview
393 model = tv.get_model()
394 iter_ = gc_control.get_contact_iter(nick)
395 if iter_:
396 show = model[iter_][3]
397 else:
398 show = 'offline'
399 gc_c = gajim.contacts.create_gc_contact(room_jid=jid,
400 account=account, name=nick, show=show)
401 ctrl = self.new_private_chat(gc_c, account, session)
403 ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
404 'code': obj.error_code, 'msg': obj.error_msg}, 'status')
405 return
407 gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
408 'code': obj.error_code, 'msg': obj.error_msg}, 'status')
409 if gc_control.parent_win and \
410 gc_control.parent_win.get_active_jid() == jid:
411 gc_control.set_subject(gc_control.subject)
412 return
414 if gajim.jid_is_transport(jid):
415 jid = jid.replace('@', '')
416 msg = obj.error_msg
417 if obj.msg:
418 msg = _('error while sending %(message)s ( %(error)s )') % {
419 'message': obj.msg, 'error': msg}
420 if session:
421 session.roster_message(jid, msg, obj.time_, msg_type='error')
423 def handle_event_msgsent(self, obj):
424 #('MSGSENT', account, (jid, msg, keyID))
425 # do not play sound when standalone chatstate message (eg no msg)
426 if obj.message and gajim.config.get_per('soundevents', 'message_sent',
427 'enabled'):
428 helpers.play_sound('message_sent')
430 def handle_event_msgnotsent(self, obj):
431 #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
432 msg = _('error while sending %(message)s ( %(error)s )') % {
433 'message': obj.message, 'error': obj.error}
434 if not obj.session:
435 # No session. This can happen when sending a message from
436 # gajim-remote
437 log.warn(msg)
438 return
439 obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name,
440 msg_type='error')
442 def handle_event_subscribe_presence(self, obj):
443 #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
444 account = obj.conn.name
445 if helpers.allow_popup_window(account) or not self.systray_enabled:
446 if obj.jid in self.instances[account]['sub_request']:
447 self.instances[account]['sub_request'][obj.jid].window.destroy()
448 self.instances[account]['sub_request'][obj.jid] = \
449 dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account,
450 obj.user_nick)
451 return
453 self.add_event(account, obj.jid, 'subscription_request', (obj.status,
454 obj.user_nick))
456 if helpers.allow_showing_notification(account):
457 path = gtkgui_helpers.get_icon_path('gajim-subscription_request',
459 event_type = _('Subscription request')
460 notify.popup(event_type, obj.jid, account, 'subscription_request',
461 path, event_type, obj.jid)
463 def handle_event_subscribed_presence(self, obj):
464 #('SUBSCRIBED', account, (jid, resource))
465 account = obj.conn.name
466 if obj.jid in gajim.contacts.get_jid_list(account):
467 c = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
468 c.resource = obj.resource
469 self.roster.remove_contact_from_groups(c.jid, account,
470 [_('Not in Roster'), _('Observers')], update=False)
471 else:
472 keyID = ''
473 attached_keys = gajim.config.get_per('accounts', account,
474 'attached_gpg_keys').split()
475 if obj.jid in attached_keys:
476 keyID = attached_keys[attached_keys.index(obj.jid) + 1]
477 name = obj.jid.split('@', 1)[0]
478 name = name.split('%', 1)[0]
479 contact1 = gajim.contacts.create_contact(jid=obj.jid,
480 account=account, name=name, groups=[], show='online',
481 status='online', ask='to', resource=obj.resource, keyID=keyID)
482 gajim.contacts.add_contact(account, contact1)
483 self.roster.add_contact(obj.jid, account)
484 dialogs.InformationDialog(_('Authorization accepted'),
485 _('The contact "%s" has authorized you to see his or her status.')
486 % obj.jid)
488 def show_unsubscribed_dialog(self, account, contact):
489 def on_yes(is_checked, list_):
490 self.roster.on_req_usub(None, list_)
491 list_ = [(contact, account)]
492 dialogs.YesNoDialog(
493 _('Contact "%s" removed subscription from you') % contact.jid,
494 _('You will always see him or her as offline.\nDo you want to '
495 'remove him or her from your contact list?'),
496 on_response_yes=(on_yes, list_))
497 # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
498 # not show deny
500 def handle_event_unsubscribed_presence(self, obj):
501 #('UNSUBSCRIBED', account, jid)
502 account = obj.conn.name
503 contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
504 if not contact:
505 return
507 if helpers.allow_popup_window(account) or not self.systray_enabled:
508 self.show_unsubscribed_dialog(account, contact)
509 return
511 self.add_event(account, obj.jid, 'unsubscribed', contact)
513 if helpers.allow_showing_notification(account):
514 path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
515 event_type = _('Unsubscribed')
516 notify.popup(event_type, obj.jid, account, 'unsubscribed', path,
517 event_type, obj.jid)
519 def handle_event_register_agent_info(self, obj):
520 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
521 # info in a dataform if is_form is True
522 if obj.is_form or 'instructions' in obj.config:
523 config.ServiceRegistrationWindow(obj.agent, obj.config,
524 obj.conn.name, obj.is_form)
525 else:
526 dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \
527 obj.agent, _('Check your connection or try again later.'))
529 def handle_event_vcard(self, obj):
530 # ('VCARD', account, data)
531 '''vcard holds the vcard data'''
532 our_jid = gajim.get_jid_from_account(obj.conn.name)
533 if obj.jid == our_jid:
534 if obj.nickname:
535 gajim.nicks[obj.conn.name] = obj.nickname
536 if obj.conn.name in self.show_vcard_when_connect:
537 self.show_vcard_when_connect.remove(obj.conn.name)
539 def handle_event_last_status_time(self, obj):
540 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
541 if obj.seconds < 0:
542 # Ann error occured
543 return
544 account = obj.conn.name
545 c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
546 if c: # c can be none if it's a gc contact
547 if obj.status:
548 c.status = obj.status
549 self.roster.draw_contact(c.jid, account) # draw offline status
550 last_time = time.localtime(time.time() - obj.seconds)
551 if c.show == 'offline':
552 c.last_status_time = last_time
553 else:
554 c.last_activity_time = last_time
555 if self.roster.tooltip.id and self.roster.tooltip.win:
556 self.roster.tooltip.update_last_time(last_time)
558 def handle_event_gc_config(self, obj):
559 #('GC_CONFIG', account, (jid, form_node)) config is a dict
560 account = obj.conn.name
561 if obj.jid in gajim.automatic_rooms[account]:
562 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
563 # We're converting chat to muc. allow participants to invite
564 for f in obj.dataform.iter_fields():
565 if f.var == 'muc#roomconfig_allowinvites':
566 f.value = True
567 elif f.var == 'muc#roomconfig_publicroom':
568 f.value = False
569 elif f.var == 'muc#roomconfig_membersonly':
570 f.value = True
571 elif f.var == 'public_list':
572 f.value = False
573 obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged())
574 else:
575 # use default configuration
576 obj.conn.send_gc_config(obj.jid, obj.form_node)
577 # invite contacts
578 # check if it is necessary to add <continue />
579 continue_tag = False
580 if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
581 continue_tag = True
582 if 'invities' in gajim.automatic_rooms[account][obj.jid]:
583 for jid in gajim.automatic_rooms[account][obj.jid]['invities']:
584 obj.conn.send_invite(obj.jid, jid,
585 continue_tag=continue_tag)
586 del gajim.automatic_rooms[account][obj.jid]
587 elif obj.jid not in self.instances[account]['gc_config']:
588 self.instances[account]['gc_config'][obj.jid] = \
589 config.GroupchatConfigWindow(account, obj.jid, obj.dataform)
591 def handle_event_gc_affiliation(self, obj):
592 #('GC_AFFILIATION', account, (room_jid, users_dict))
593 account = obj.conn.name
594 if obj.jid in self.instances[account]['gc_config']:
595 self.instances[account]['gc_config'][obj.jid].\
596 affiliation_list_received(obj.users_dict)
598 def handle_event_gc_invitation(self, obj):
599 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
600 jid = gajim.get_jid_without_resource(obj.jid_from)
601 account = obj.conn.name
602 if helpers.allow_popup_window(account) or not self.systray_enabled:
603 dialogs.InvitationReceivedDialog(account, obj.room_jid, jid,
604 obj.password, obj.reason, is_continued=obj.is_continued)
605 return
607 self.add_event(account, jid, 'gc-invitation', (obj.room_jid,
608 obj.reason, obj.password, obj.is_continued))
610 if helpers.allow_showing_notification(account):
611 path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
612 event_type = _('Groupchat Invitation')
613 notify.popup(event_type, jid, account, 'gc-invitation', path,
614 event_type, obj.room_jid)
616 def forget_gpg_passphrase(self, keyid):
617 if keyid in self.gpg_passphrase:
618 del self.gpg_passphrase[keyid]
619 return False
621 def handle_event_bad_gpg_passphrase(self, obj):
622 #('BAD_PASSPHRASE', account, ())
623 if obj.use_gpg_agent:
624 sectext = _('You configured Gajim to use GPG agent, but there is no'
625 ' GPG agent running or it returned a wrong passphrase.\n')
626 sectext += _('You are currently connected without your OpenPGP '
627 'key.')
628 dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext)
629 else:
630 path = gtkgui_helpers.get_icon_path('gajim-warning', 48)
631 account = obj.conn.name
632 notify.popup('warning', account, account, 'warning', path,
633 _('OpenGPG Passphrase Incorrect'),
634 _('You are currently connected without your OpenPGP key.'))
635 self.forget_gpg_passphrase(obj.keyID)
637 def handle_event_client_cert_passphrase(self, obj):
638 def on_ok(passphrase, checked):
639 obj.conn.on_client_cert_passphrase(passphrase, obj.con, obj.port,
640 obj.secure_tuple)
642 def on_cancel():
643 obj.conn.on_client_cert_passphrase('', obj.con, obj.port,
644 obj.secure_tuple)
646 dialogs.PassphraseDialog(_('Certificate Passphrase Required'),
647 _('Enter the passphrase for the certificate for account %s') % \
648 obj.conn.name, ok_handler=on_ok, cancel_handler=on_cancel)
650 def handle_event_gpg_password_required(self, obj):
651 #('GPG_PASSWORD_REQUIRED', account, (callback,))
652 if obj.keyid in self.gpg_passphrase:
653 request = self.gpg_passphrase[obj.keyid]
654 else:
655 request = PassphraseRequest(obj.keyid)
656 self.gpg_passphrase[obj.keyid] = request
657 request.add_callback(obj.conn.name, obj.callback)
659 def handle_event_gpg_trust_key(self, obj):
660 #('GPG_ALWAYS_TRUST', account, callback)
661 def on_yes(checked):
662 if checked:
663 obj.conn.gpg.always_trust = True
664 obj.callback(True)
666 def on_no():
667 obj.callback(False)
669 dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
670 'encrypt this chat is not trusted. Do you really want to encrypt '
671 'this message?'), checktext=_('_Do not ask me again'),
672 on_response_yes=on_yes, on_response_no=on_no)
674 def handle_event_password_required(self, obj):
675 #('PASSWORD_REQUIRED', account, None)
676 account = obj.conn.name
677 if account in self.pass_dialog:
678 return
679 text = _('Enter your password for account %s') % account
680 if passwords.USER_HAS_GNOMEKEYRING and \
681 not passwords.USER_USES_GNOMEKEYRING:
682 text += '\n' + _('Gnome Keyring is installed but not '
683 'correctly started (environment variable probably not '
684 'correctly set)')
686 def on_ok(passphrase, save):
687 if save:
688 gajim.config.set_per('accounts', account, 'savepass', True)
689 passwords.save_password(account, passphrase)
690 obj.conn.set_password(passphrase)
691 del self.pass_dialog[account]
693 def on_cancel():
694 self.roster.set_state(account, 'offline')
695 self.roster.update_status_combobox()
696 del self.pass_dialog[account]
698 self.pass_dialog[account] = dialogs.PassphraseDialog(
699 _('Password Required'), text, _('Save password'), ok_handler=on_ok,
700 cancel_handler=on_cancel)
702 def handle_event_roster_info(self, obj):
703 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
704 account = obj.conn.name
705 contacts = gajim.contacts.get_contacts(account, obj.jid)
706 if (not obj.sub or obj.sub == 'none') and \
707 (not obj.ask or obj.ask == 'none') and not obj.nickname and \
708 not obj.groups:
709 # contact removed us.
710 if contacts:
711 self.roster.remove_contact(obj.jid, account, backend=True)
712 return
713 elif not contacts:
714 if obj.sub == 'remove':
715 return
716 # Add new contact to roster
717 contact = gajim.contacts.create_contact(jid=obj.jid,
718 account=account, name=obj.nickname, groups=obj.groups,
719 show='offline', sub=obj.sub, ask=obj.ask)
720 gajim.contacts.add_contact(account, contact)
721 self.roster.add_contact(obj.jid, account)
722 else:
723 # If contact has changed (sub, ask or group) update roster
724 # Mind about observer status changes:
725 # According to xep 0162, a contact is not an observer anymore when
726 # we asked for auth, so also remove him if ask changed
727 old_groups = contacts[0].groups
728 if obj.sub == 'remove':
729 # another of our instance removed a contact. Remove it here too
730 self.roster.remove_contact(obj.jid, account, backend=True)
731 return
732 if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\
733 or old_groups != obj.groups:
734 # c.get_shown_groups() has changed. Reflect that in
735 # roster_window
736 self.roster.remove_contact(obj.jid, account, force=True)
737 for contact in contacts:
738 contact.name = obj.nickname or ''
739 contact.sub = obj.sub
740 contact.ask = obj.ask
741 contact.groups = obj.groups or []
742 self.roster.add_contact(obj.jid, account)
743 # Refilter and update old groups
744 for group in old_groups:
745 self.roster.draw_group(group, account)
746 self.roster.draw_contact(obj.jid, account)
747 if obj.jid in self.instances[account]['sub_request'] and obj.sub in (
748 'from', 'both'):
749 self.instances[account]['sub_request'][obj.jid].window.destroy()
751 def handle_event_bookmarks(self, obj):
752 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
753 # We received a bookmark item from the server (JEP48)
754 # Auto join GC windows if neccessary
756 self.roster.set_actions_menu_needs_rebuild()
757 invisible_show = gajim.SHOW_LIST.index('invisible')
758 # do not autojoin if we are invisible
759 if obj.conn.connected == invisible_show:
760 return
762 self.auto_join_bookmarks(obj.conn.name)
764 def handle_event_file_send_error(self, account, array):
765 jid = array[0]
766 file_props = array[1]
767 ft = self.instances['file_transfers']
768 ft.set_status(file_props['type'], file_props['sid'], 'stop')
770 if helpers.allow_popup_window(account):
771 ft.show_send_error(file_props)
772 return
774 self.add_event(account, jid, 'file-send-error', file_props)
776 if helpers.allow_showing_notification(account):
777 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
778 event_type = _('File Transfer Error')
779 notify.popup(event_type, jid, account, 'file-send-error', path,
780 event_type, file_props['name'])
782 def handle_event_gmail_notify(self, obj):
783 jid = obj.jid
784 gmail_new_messages = int(obj.newmsgs)
785 gmail_messages_list = obj.gmail_messages_list
786 if not gajim.config.get('notify_on_new_gmail_email'):
787 return
788 path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
789 title = _('New mail on %(gmail_mail_address)s') % \
790 {'gmail_mail_address': jid}
791 text = i18n.ngettext('You have %d new mail conversation',
792 'You have %d new mail conversations', gmail_new_messages,
793 gmail_new_messages, gmail_new_messages)
795 if gajim.config.get('notify_on_new_gmail_email_extra'):
796 cnt = 0
797 for gmessage in gmail_messages_list:
798 # FIXME: emulate Gtalk client popups. find out what they
799 # parse and how they decide what to show each message has a
800 # 'From', 'Subject' and 'Snippet' field
801 if cnt >= 5:
802 break
803 senders = ',\n '.join(reversed(gmessage['From']))
804 text += _('\n\nFrom: %(from_address)s\nSubject: '
805 '%(subject)s\n%(snippet)s') % {'from_address': senders,
806 'subject': gmessage['Subject'],
807 'snippet': gmessage['Snippet']}
808 cnt += 1
810 command = gajim.config.get('notify_on_new_gmail_email_command')
811 if command:
812 Popen(command, shell=True)
814 if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
815 helpers.play_sound('gmail_received')
816 notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail',
817 path_to_image=path, title=title, text=text)
819 def handle_event_file_request_error(self, obj):
820 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
821 ft = self.instances['file_transfers']
822 ft.set_status(obj.file_props['type'], obj.file_props['sid'], 'stop')
823 errno = obj.file_props['error']
825 if helpers.allow_popup_window(obj.conn.name):
826 if errno in (-4, -5):
827 ft.show_stopped(obj.jid, obj.file_props, obj.error_msg)
828 else:
829 ft.show_request_error(obj.file_props)
830 return
832 if errno in (-4, -5):
833 msg_type = 'file-error'
834 else:
835 msg_type = 'file-request-error'
837 self.add_event(obj.conn.name, obj.jid, msg_type, obj.file_props)
839 if helpers.allow_showing_notification(obj.conn.name):
840 # check if we should be notified
841 path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
842 event_type = _('File Transfer Error')
843 notify.popup(event_type, obj.jid, obj.conn.name, msg_type, path,
844 title=event_type, text=obj.file_props['name'])
846 def handle_event_file_request(self, obj):
847 account = obj.conn.name
848 if obj.jid not in gajim.contacts.get_jid_list(account):
849 keyID = ''
850 attached_keys = gajim.config.get_per('accounts', account,
851 'attached_gpg_keys').split()
852 if obj.jid in attached_keys:
853 keyID = attached_keys[attached_keys.index(obj.jid) + 1]
854 contact = gajim.contacts.create_not_in_roster_contact(jid=obj.jid,
855 account=account, keyID=keyID)
856 gajim.contacts.add_contact(account, contact)
857 self.roster.add_contact(obj.jid, account)
858 contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
860 if helpers.allow_popup_window(account):
861 self.instances['file_transfers'].show_file_request(account, contact,
862 obj.file_props)
863 return
865 self.add_event(account, obj.jid, 'file-request', obj.file_props)
867 if helpers.allow_showing_notification(account):
868 path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
869 txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
870 account, obj.jid)
871 event_type = _('File Transfer Request')
872 notify.popup(event_type, obj.jid, account, 'file-request',
873 path_to_image=path, title=event_type, text=txt)
875 def handle_event_file_error(self, title, message):
876 dialogs.ErrorDialog(title, message)
878 def handle_event_file_progress(self, account, file_props):
879 if time.time() - self.last_ftwindow_update > 0.5:
880 # update ft window every 500ms
881 self.last_ftwindow_update = time.time()
882 self.instances['file_transfers'].set_progress(file_props['type'],
883 file_props['sid'], file_props['received-len'])
885 def handle_event_file_rcv_completed(self, account, file_props):
886 ft = self.instances['file_transfers']
887 if file_props['error'] == 0:
888 ft.set_progress(file_props['type'], file_props['sid'],
889 file_props['received-len'])
890 else:
891 ft.set_status(file_props['type'], file_props['sid'], 'stop')
892 if 'stalled' in file_props and file_props['stalled'] or \
893 'paused' in file_props and file_props['paused']:
894 return
895 if file_props['type'] == 'r': # we receive a file
896 jid = unicode(file_props['sender'])
897 else: # we send a file
898 jid = unicode(file_props['receiver'])
900 if helpers.allow_popup_window(account):
901 if file_props['error'] == 0:
902 if gajim.config.get('notify_on_file_complete'):
903 ft.show_completed(jid, file_props)
904 elif file_props['error'] == -1:
905 ft.show_stopped(jid, file_props,
906 error_msg=_('Remote contact stopped transfer'))
907 elif file_props['error'] == -6:
908 ft.show_stopped(jid, file_props,
909 error_msg=_('Error opening file'))
910 return
912 msg_type = ''
913 event_type = ''
914 if file_props['error'] == 0 and gajim.config.get(
915 'notify_on_file_complete'):
916 msg_type = 'file-completed'
917 event_type = _('File Transfer Completed')
918 elif file_props['error'] in (-1, -6):
919 msg_type = 'file-stopped'
920 event_type = _('File Transfer Stopped')
922 if event_type == '':
923 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
924 # this should never happen but it does. see process_result() in
925 # socks5.py
926 # who calls this func (sth is really wrong unless this func is also
927 # registered as progress_cb
928 return
930 if msg_type:
931 self.add_event(account, jid, msg_type, file_props)
933 if file_props is not None:
934 if file_props['type'] == 'r':
935 # get the name of the sender, as it is in the roster
936 sender = unicode(file_props['sender']).split('/')[0]
937 name = gajim.contacts.get_first_contact_from_jid(account,
938 sender).get_shown_name()
939 filename = os.path.basename(file_props['file-name'])
940 if event_type == _('File Transfer Completed'):
941 txt = _('You successfully received %(filename)s from '
942 '%(name)s.') % {'filename': filename, 'name': name}
943 img_name = 'gajim-ft_done'
944 else: # ft stopped
945 txt = _('File transfer of %(filename)s from %(name)s '
946 'stopped.') % {'filename': filename, 'name': name}
947 img_name = 'gajim-ft_stopped'
948 else:
949 receiver = file_props['receiver']
950 if hasattr(receiver, 'jid'):
951 receiver = receiver.jid
952 receiver = receiver.split('/')[0]
953 # get the name of the contact, as it is in the roster
954 name = gajim.contacts.get_first_contact_from_jid(account,
955 receiver).get_shown_name()
956 filename = os.path.basename(file_props['file-name'])
957 if event_type == _('File Transfer Completed'):
958 txt = _('You successfully sent %(filename)s to %(name)s.')\
959 % {'filename': filename, 'name': name}
960 img_name = 'gajim-ft_done'
961 else: # ft stopped
962 txt = _('File transfer of %(filename)s to %(name)s '
963 'stopped.') % {'filename': filename, 'name': name}
964 img_name = 'gajim-ft_stopped'
965 path = gtkgui_helpers.get_icon_path(img_name, 48)
966 else:
967 txt = ''
968 path = ''
970 if gajim.config.get('notify_on_file_complete') and \
971 (gajim.config.get('autopopupaway') or \
972 gajim.connections[account].connected in (2, 3)):
973 # we want to be notified and we are online/chat or we don't mind
974 # bugged when away/na/busy
975 notify.popup(event_type, jid, account, msg_type, path_to_image=path,
976 title=event_type, text=txt)
978 def ask_offline_status(self, account):
979 for contact in gajim.contacts.iter_contacts(account):
980 gajim.connections[account].request_last_status_time(contact.jid,
981 contact.resource)
983 def handle_event_signed_in(self, obj):
985 SIGNED_IN event is emitted when we sign in, so handle it
987 # ('SIGNED_IN', account, ())
988 # block signed in notifications for 30 seconds
989 account = obj.conn.name
990 gajim.block_signed_in_notifications[account] = True
991 state = self.sleeper.getState()
992 connected = obj.conn.connected
993 if gajim.config.get('ask_offline_status_on_connection'):
994 # Ask offline status in 1 minute so w'are sure we got all online
995 # presences
996 gobject.timeout_add_seconds(60, self.ask_offline_status, account)
997 if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3):
998 # we go online or free for chat, so we activate auto status
999 gajim.sleeper_state[account] = 'online'
1000 elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \
1001 (state == common.sleepy.STATE_XA and connected == 5)):
1002 # If we are autoaway/xa and come back after a disconnection, do
1003 # nothing
1004 # Else disable autoaway
1005 gajim.sleeper_state[account] = 'off'
1007 if obj.conn.archiving_supported:
1008 # Start merging logs from server
1009 obj.conn.request_modifications_page(gajim.config.get_per('accounts',
1010 account, 'last_archiving_time'))
1011 gajim.config.set_per('accounts', account, 'last_archiving_time',
1012 time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
1014 invisible_show = gajim.SHOW_LIST.index('invisible')
1015 # We cannot join rooms if we are invisible
1016 if connected == invisible_show:
1017 return
1018 # send currently played music
1019 if obj.conn.pep_supported and dbus_support.supported and \
1020 gajim.config.get_per('accounts', account, 'publish_tune'):
1021 self.enable_music_listener()
1022 # enable location listener
1023 if obj.conn.pep_supported and dbus_support.supported and \
1024 gajim.config.get_per('accounts', account, 'publish_location'):
1025 location_listener.enable()
1028 def handle_event_metacontacts(self, obj):
1029 gajim.contacts.define_metacontacts(obj.conn.name, obj.meta_list)
1031 def handle_atom_entry(self, obj):
1032 AtomWindow.newAtomEntry(obj.atom_entry)
1034 def handle_event_failed_decrypt(self, obj):
1035 details = _('Unable to decrypt message from %s\nIt may have been '
1036 'tampered with.') % obj.fjid
1037 dialogs.WarningDialog(_('Unable to decrypt message'), details)
1039 def handle_event_zc_name_conflict(self, obj):
1040 def on_ok(new_name):
1041 gajim.config.set_per('accounts', obj.conn.name, 'name', new_name)
1042 show = obj.conn.old_show
1043 status = obj.conn.status
1044 obj.conn.username = new_name
1045 obj.conn.change_status(show, status)
1046 def on_cancel():
1047 obj.conn.change_status('offline', '')
1049 dlg = dialogs.InputDialog(_('Username Conflict'),
1050 _('Please type a new username for your local account'),
1051 input_str=obj.alt_name, is_modal=True, ok_handler=on_ok,
1052 cancel_handler=on_cancel)
1054 def handle_event_resource_conflict(self, obj):
1055 # ('RESOURCE_CONFLICT', account, ())
1056 # First we go offline, but we don't overwrite status message
1057 account = obj.conn.name
1058 conn = obj.conn
1059 self.roster.send_status(account, 'offline', conn.status)
1060 def on_ok(new_resource):
1061 gajim.config.set_per('accounts', account, 'resource', new_resource)
1062 self.roster.send_status(account, conn.old_show, conn.status)
1063 proposed_resource = conn.server_resource
1064 proposed_resource += gajim.config.get('gc_proposed_nick_char')
1065 dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
1066 _('You are already connected to this account with the same '
1067 'resource. Please type a new one'), resource=proposed_resource,
1068 ok_handler=on_ok)
1070 def handle_event_jingle_incoming(self, obj):
1071 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
1072 # data...))
1073 # TODO: conditional blocking if peer is not in roster
1075 account = obj.conn.name
1076 content_types = set(c[0] for c in obj.contents)
1078 # check type of jingle session
1079 if 'audio' in content_types or 'video' in content_types:
1080 # a voip session...
1081 # we now handle only voip, so the only thing we will do here is
1082 # not to return from function
1083 pass
1084 else:
1085 # unknown session type... it should be declined in common/jingle.py
1086 return
1088 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1089 or self.msg_win_mgr.get_control(obj.jid, account))
1090 if ctrl:
1091 if 'audio' in content_types:
1092 ctrl.set_audio_state('connection_received', obj.sid)
1093 if 'video' in content_types:
1094 ctrl.set_video_state('connection_received', obj.sid)
1096 dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1097 if dlg:
1098 dlg.add_contents(content_types)
1099 return
1101 if helpers.allow_popup_window(account):
1102 dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid,
1103 content_types)
1104 return
1106 self.add_event(account, obj.jid, 'jingle-incoming', (obj.fjid, obj.sid,
1107 content_types))
1109 if helpers.allow_showing_notification(account):
1110 # TODO: we should use another pixmap ;-)
1111 txt = _('%s wants to start a voice chat.') % \
1112 gajim.get_name_from_jid(account, obj.fjid)
1113 path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
1114 event_type = _('Voice Chat Request')
1115 notify.popup(event_type, obj.fjid, account, 'jingle-incoming',
1116 path_to_image=path, title=event_type, text=txt)
1118 def handle_event_jingle_connected(self, obj):
1119 # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
1120 if obj.media in ('audio', 'video'):
1121 account = obj.conn.name
1122 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1123 or self.msg_win_mgr.get_control(obj.jid, account))
1124 if ctrl:
1125 if obj.media == 'audio':
1126 ctrl.set_audio_state('connected', obj.sid)
1127 else:
1128 ctrl.set_video_state('connected', obj.sid)
1130 def handle_event_jingle_disconnected(self, obj):
1131 # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
1132 account = obj.conn.name
1133 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1134 or self.msg_win_mgr.get_control(obj.jid, account))
1135 if ctrl:
1136 if obj.media is None:
1137 ctrl.stop_jingle(sid=obj.sid, reason=obj.reason)
1138 elif obj.media == 'audio':
1139 ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason)
1140 elif obj.media == 'video':
1141 ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason)
1142 dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
1143 if dialog:
1144 if obj.media is None:
1145 dialog.dialog.destroy()
1146 else:
1147 dialog.remove_contents((obj.media, ))
1149 def handle_event_jingle_error(self, obj):
1150 # ('JINGLE_ERROR', account, (peerjid, sid, reason))
1151 account = obj.conn.name
1152 ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
1153 or self.msg_win_mgr.get_control(obj.jid, account))
1154 if ctrl:
1155 ctrl.set_audio_state('error', reason=obj.reason)
1157 def handle_event_roster_item_exchange(self, obj):
1158 # data = (action in [add, delete, modify], exchange_list, jid_from)
1159 dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action,
1160 obj.exchange_items_list, obj.fjid)
1162 def handle_event_ssl_error(self, obj):
1163 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
1164 account = obj.conn.name
1165 server = gajim.config.get_per('accounts', account, 'hostname')
1167 def on_ok(is_checked):
1168 del self.instances[account]['online_dialog']['ssl_error']
1169 if is_checked[0]:
1170 # Check if cert is already in file
1171 certs = ''
1172 if os.path.isfile(gajim.MY_CACERTS):
1173 f = open(gajim.MY_CACERTS)
1174 certs = f.read()
1175 f.close()
1176 if obj.cert in certs:
1177 dialogs.ErrorDialog(_('Certificate Already in File'),
1178 _('This certificate is already in file %s, so it\'s '
1179 'not added again.') % gajim.MY_CACERTS)
1180 else:
1181 f = open(gajim.MY_CACERTS, 'a')
1182 f.write(server + '\n')
1183 f.write(obj.cert + '\n\n')
1184 f.close()
1185 gajim.config.set_per('accounts', account,
1186 'ssl_fingerprint_sha1', obj.fingerprint)
1187 if is_checked[1]:
1188 ignore_ssl_errors = gajim.config.get_per('accounts', account,
1189 'ignore_ssl_errors').split()
1190 ignore_ssl_errors.append(str(obj.error_num))
1191 gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
1192 ' '.join(ignore_ssl_errors))
1193 obj.conn.ssl_certificate_accepted()
1195 def on_cancel():
1196 del self.instances[account]['online_dialog']['ssl_error']
1197 obj.conn.disconnect(on_purpose=True)
1198 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1199 show='offline'))
1201 pritext = _('Error verifying SSL certificate')
1202 sectext = _('There was an error verifying the SSL certificate of your '
1203 'jabber server: %(error)s\nDo you still want to connect to this '
1204 'server?') % {'error': obj.error_text}
1205 if obj.error_num in (18, 27):
1206 checktext1 = _('Add this certificate to the list of trusted '
1207 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % \
1208 obj.fingerprint
1209 else:
1210 checktext1 = ''
1211 checktext2 = _('Ignore this error for this certificate.')
1212 if 'ssl_error' in self.instances[account]['online_dialog']:
1213 self.instances[account]['online_dialog']['ssl_error'].destroy()
1214 self.instances[account]['online_dialog']['ssl_error'] = \
1215 dialogs.SSLErrorDialog(obj.conn.name, obj.certificate, pritext,
1216 sectext, checktext1, checktext2, on_response_ok=on_ok,
1217 on_response_cancel=on_cancel)
1219 def handle_event_fingerprint_error(self, obj):
1220 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
1221 account = obj.conn.name
1222 def on_yes(is_checked):
1223 del self.instances[account]['online_dialog']['fingerprint_error']
1224 gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
1225 obj.new_fingerprint)
1226 # Reset the ignored ssl errors
1227 gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
1228 obj.conn.ssl_certificate_accepted()
1230 def on_no():
1231 del self.instances[account]['online_dialog']['fingerprint_error']
1232 obj.conn.disconnect(on_purpose=True)
1233 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1234 show='offline'))
1236 pritext = _('SSL certificate error')
1237 sectext = _('It seems the SSL certificate of account %(account)s has '
1238 'changed or your connection is being hacked.\nOld fingerprint: '
1239 '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect '
1240 'and update the fingerprint of the certificate?') % \
1241 {'account': account, 'old': gajim.config.get_per('accounts',
1242 account, 'ssl_fingerprint_sha1'), 'new': obj.new_fingerprint}
1243 if 'fingerprint_error' in self.instances[account]['online_dialog']:
1244 self.instances[account]['online_dialog']['fingerprint_error'].\
1245 destroy()
1246 self.instances[account]['online_dialog']['fingerprint_error'] = \
1247 dialogs.CheckFingerprintDialog(pritext, sectext, on_response_yes=on_yes,
1248 on_response_no=on_no, account=obj.conn.name,
1249 certificate=obj.certificate)
1251 def handle_event_plain_connection(self, obj):
1252 # ('PLAIN_CONNECTION', account, (connection))
1253 def on_ok(is_checked):
1254 if not is_checked[0]:
1255 on_cancel()
1256 return
1257 # On cancel call del self.instances, so don't call it another time
1258 # before
1259 del self.instances[obj.conn.name]['online_dialog']\
1260 ['plain_connection']
1261 if is_checked[1]:
1262 gajim.config.set_per('accounts', obj.conn.name,
1263 'warn_when_plaintext_connection', False)
1264 obj.conn.connection_accepted(obj.xmpp_client, 'plain')
1266 def on_cancel():
1267 del self.instances[obj.conn.name]['online_dialog']\
1268 ['plain_connection']
1269 obj.conn.disconnect(on_purpose=True)
1270 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1271 show='offline'))
1273 pritext = _('Insecure connection')
1274 sectext = _('You are about to connect to the account %(account)s '
1275 '(%(server)s) with an insecure connection. This means all your '
1276 'conversations will be exchanged unencrypted. Are you sure you '
1277 'want to do that?') % {'account': obj.conn.name,
1278 'server': gajim.get_hostname_from_account(obj.conn.name)}
1279 checktext1 = _('Yes, I really want to connect insecurely')
1280 checktext2 = _('_Do not ask me again')
1281 if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']:
1282 self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
1283 destroy()
1284 self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
1285 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1286 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1287 is_modal=False)
1289 def handle_event_insecure_ssl_connection(self, obj):
1290 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1291 def on_ok(is_checked):
1292 if not is_checked[0]:
1293 on_cancel()
1294 return
1295 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1296 if is_checked[1]:
1297 gajim.config.set_per('accounts', obj.conn.name,
1298 'warn_when_insecure_ssl_connection', False)
1299 if obj.conn.connected == 0:
1300 # We have been disconnecting (too long time since window is
1301 # opened)
1302 # re-connect with auto-accept
1303 obj.conn.connection_auto_accepted = True
1304 show, msg = obj.conn.continue_connect_info[:2]
1305 self.roster.send_status(obj.conn.name, show, msg)
1306 return
1307 obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type)
1309 def on_cancel():
1310 del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
1311 obj.conn.disconnect(on_purpose=True)
1312 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1313 show='offline'))
1315 pritext = _('Insecure connection')
1316 sectext = _('You are about to send your password on an insecure '
1317 'connection. You should install PyOpenSSL to prevent that. Are you '
1318 'sure you want to do that?')
1319 checktext1 = _('Yes, I really want to connect insecurely')
1320 checktext2 = _('_Do not ask me again')
1321 if 'insecure_ssl' in self.instances[obj.conn.name]['online_dialog']:
1322 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\
1323 destroy()
1324 self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \
1325 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1326 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1327 is_modal=False)
1329 def handle_event_insecure_password(self, obj):
1330 # ('INSECURE_PASSWORD', account, ())
1331 def on_ok(is_checked):
1332 if not is_checked[0]:
1333 on_cancel()
1334 return
1335 del self.instances[obj.conn.name]['online_dialog']\
1336 ['insecure_password']
1337 if is_checked[1]:
1338 gajim.config.set_per('accounts', obj.conn.name,
1339 'warn_when_insecure_password', False)
1340 if obj.conn.connected == 0:
1341 # We have been disconnecting (too long time since window is
1342 # opened)
1343 # re-connect with auto-accept
1344 obj.conn.connection_auto_accepted = True
1345 show, msg = obj.conn.continue_connect_info[:2]
1346 self.roster.send_status(obj.conn.name, show, msg)
1347 return
1348 obj.conn.accept_insecure_password()
1350 def on_cancel():
1351 del self.instances[obj.conn.name]['online_dialog']\
1352 ['insecure_password']
1353 obj.conn.disconnect(on_purpose=True)
1354 gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
1355 show='offline'))
1357 pritext = _('Insecure connection')
1358 sectext = _('You are about to send your password unencrypted on an '
1359 'insecure connection. Are you sure you want to do that?')
1360 checktext1 = _('Yes, I really want to connect insecurely')
1361 checktext2 = _('_Do not ask me again')
1362 if 'insecure_password' in self.instances[obj.conn.name]\
1363 ['online_dialog']:
1364 self.instances[obj.conn.name]['online_dialog']\
1365 ['insecure_password'].destroy()
1366 self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
1367 dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
1368 checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
1369 is_modal=False)
1371 def create_core_handlers_list(self):
1372 self.handlers = {
1373 'ERROR': [self.handle_event_error],
1374 'DB_ERROR': [self.handle_event_db_error],
1375 'INFORMATION': [self.handle_event_information],
1376 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
1377 'atom-entry-received': [self.handle_atom_entry],
1378 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
1379 'bookmarks-received': [self.handle_event_bookmarks],
1380 'client-cert-passphrase': [
1381 self.handle_event_client_cert_passphrase],
1382 'connection-lost': [self.handle_event_connection_lost],
1383 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
1384 'file-request-error': [self.handle_event_file_request_error],
1385 'file-request-received': [self.handle_event_file_request],
1386 'fingerprint-error': [self.handle_event_fingerprint_error],
1387 'gc-invitation-received': [self.handle_event_gc_invitation],
1388 'gc-presence-received': [self.handle_event_gc_presence],
1389 'gmail-notify': [self.handle_event_gmail_notify],
1390 'gpg-password-required': [self.handle_event_gpg_password_required],
1391 'gpg-trust-key': [self.handle_event_gpg_trust_key],
1392 'http-auth-received': [self.handle_event_http_auth],
1393 'insecure-password': [self.handle_event_insecure_password],
1394 'insecure-ssl-connection': \
1395 [self.handle_event_insecure_ssl_connection],
1396 'iq-error-received': [self.handle_event_iq_error],
1397 'jingle-connected-received': [self.handle_event_jingle_connected],
1398 'jingle-disconnected-received': [
1399 self.handle_event_jingle_disconnected],
1400 'jingle-error-received': [self.handle_event_jingle_error],
1401 'jingle-request-received': [self.handle_event_jingle_incoming],
1402 'last-result-received': [self.handle_event_last_status_time],
1403 'message-error': [self.handle_event_msgerror],
1404 'message-not-sent': [self.handle_event_msgnotsent],
1405 'message-sent': [self.handle_event_msgsent],
1406 'metacontacts-received': [self.handle_event_metacontacts],
1407 'muc-admin-received': [self.handle_event_gc_affiliation],
1408 'muc-owner-received': [self.handle_event_gc_config],
1409 'our-show': [self.handle_event_status],
1410 'password-required': [self.handle_event_password_required],
1411 'plain-connection': [self.handle_event_plain_connection],
1412 'presence-received': [self.handle_event_presence],
1413 'register-agent-info-received': [self.handle_event_register_agent_info],
1414 'roster-info': [self.handle_event_roster_info],
1415 'roster-item-exchange-received': \
1416 [self.handle_event_roster_item_exchange],
1417 'signed-in': [self.handle_event_signed_in],
1418 'ssl-error': [self.handle_event_ssl_error],
1419 'stream-conflict-received': [self.handle_event_resource_conflict],
1420 'subscribe-presence-received': [
1421 self.handle_event_subscribe_presence],
1422 'subscribed-presence-received': [
1423 self.handle_event_subscribed_presence],
1424 'unsubscribed-presence-received': [
1425 self.handle_event_unsubscribed_presence],
1426 'vcard-received': [self.handle_event_vcard],
1427 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
1430 def register_core_handlers(self):
1432 Register core handlers in Global Events Dispatcher (GED).
1434 This is part of rewriting whole events handling system to use GED.
1436 for event_name, event_handlers in self.handlers.iteritems():
1437 for event_handler in event_handlers:
1438 prio = ged.GUI1
1439 if type(event_handler) == tuple:
1440 prio = event_handler[1]
1441 event_handler = event_handler[0]
1442 gajim.ged.register_event_handler(event_name, prio,
1443 event_handler)
1445 ################################################################################
1446 ### Methods dealing with gajim.events
1447 ################################################################################
1449 def add_event(self, account, jid, type_, event_args):
1451 Add an event to the gajim.events var
1453 # We add it to the gajim.events queue
1454 # Do we have a queue?
1455 jid = gajim.get_jid_without_resource(jid)
1456 no_queue = len(gajim.events.get_events(account, jid)) == 0
1457 # type_ can be gc-invitation file-send-error file-error
1458 # file-request-error file-request file-completed file-stopped
1459 # jingle-incoming
1460 # event_type can be in advancedNotificationWindow.events_list
1461 event_types = {'file-request': 'ft_request',
1462 'file-completed': 'ft_finished'}
1463 event_type = event_types.get(type_)
1464 show_in_roster = notify.get_show_in_roster(event_type, account, jid)
1465 show_in_systray = notify.get_show_in_systray(event_type, account, jid)
1466 event = gajim.events.create_event(type_, event_args,
1467 show_in_roster=show_in_roster,
1468 show_in_systray=show_in_systray)
1469 gajim.events.add_event(account, jid, event)
1471 self.roster.show_title()
1472 if no_queue: # We didn't have a queue: we change icons
1473 if not gajim.contacts.get_contact_with_highest_priority(account,
1474 jid):
1475 if type_ == 'gc-invitation':
1476 self.roster.add_groupchat(jid, account, status='offline')
1477 else:
1478 # add contact to roster ("Not In The Roster") if he is not
1479 self.roster.add_to_not_in_the_roster(account, jid)
1480 else:
1481 self.roster.draw_contact(jid, account)
1483 # Select the big brother contact in roster, it's visible because it has
1484 # events.
1485 family = gajim.contacts.get_metacontacts_family(account, jid)
1486 if family:
1487 nearby_family, bb_jid, bb_account = \
1488 gajim.contacts.get_nearby_family_and_big_brother(family,
1489 account)
1490 else:
1491 bb_jid, bb_account = jid, account
1492 self.roster.select_contact(bb_jid, bb_account)
1494 def handle_event(self, account, fjid, type_):
1495 w = None
1496 ctrl = None
1497 session = None
1499 resource = gajim.get_resource_from_jid(fjid)
1500 jid = gajim.get_jid_without_resource(fjid)
1502 if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
1503 w = self.msg_win_mgr.get_window(jid, account)
1504 if jid in self.minimized_controls[account]:
1505 self.roster.on_groupchat_maximized(None, jid, account)
1506 return
1507 else:
1508 ctrl = self.msg_win_mgr.get_gc_control(jid, account)
1510 elif type_ in ('printed_chat', 'chat', ''):
1511 # '' is for log in/out notifications
1513 if type_ != '':
1514 event = gajim.events.get_first_event(account, fjid, type_)
1515 if not event:
1516 event = gajim.events.get_first_event(account, jid, type_)
1517 if not event:
1518 return
1520 if type_ == 'printed_chat':
1521 ctrl = event.parameters[0]
1522 elif type_ == 'chat':
1523 session = event.parameters[8]
1524 ctrl = session.control
1525 elif type_ == '':
1526 ctrl = self.msg_win_mgr.get_control(fjid, account)
1528 if not ctrl:
1529 highest_contact = gajim.contacts.\
1530 get_contact_with_highest_priority(account, jid)
1531 # jid can have a window if this resource was lower when he sent
1532 # message and is now higher because the other one is offline
1533 if resource and highest_contact.resource == resource and \
1534 not self.msg_win_mgr.has_window(jid, account):
1535 # remove resource of events too
1536 gajim.events.change_jid(account, fjid, jid)
1537 resource = None
1538 fjid = jid
1539 contact = None
1540 if resource:
1541 contact = gajim.contacts.get_contact(account, jid, resource)
1542 if not contact:
1543 contact = highest_contact
1545 ctrl = self.new_chat(contact, account, resource=resource,
1546 session=session)
1548 gajim.last_message_time[account][jid] = 0 # long time ago
1550 w = ctrl.parent_win
1551 elif type_ in ('printed_pm', 'pm'):
1552 # assume that the most recently updated control we have for this
1553 # party is the one that this event was in
1554 event = gajim.events.get_first_event(account, fjid, type_)
1555 if not event:
1556 event = gajim.events.get_first_event(account, jid, type_)
1558 if type_ == 'printed_pm':
1559 ctrl = event.parameters[0]
1560 elif type_ == 'pm':
1561 session = event.parameters[8]
1563 if session and session.control:
1564 ctrl = session.control
1565 elif not ctrl:
1566 room_jid = jid
1567 nick = resource
1568 gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
1569 nick)
1570 if gc_contact:
1571 show = gc_contact.show
1572 else:
1573 show = 'offline'
1574 gc_contact = gajim.contacts.create_gc_contact(
1575 room_jid=room_jid, account=account, name=nick,
1576 show=show)
1578 if not session:
1579 session = gajim.connections[account].make_new_session(
1580 fjid, None, type_='pm')
1582 self.new_private_chat(gc_contact, account, session=session)
1583 ctrl = session.control
1585 w = ctrl.parent_win
1586 elif type_ in ('normal', 'file-request', 'file-request-error',
1587 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
1588 'jingle-incoming'):
1589 # Get the first single message event
1590 event = gajim.events.get_first_event(account, fjid, type_)
1591 if not event:
1592 # default to jid without resource
1593 event = gajim.events.get_first_event(account, jid, type_)
1594 if not event:
1595 return
1596 # Open the window
1597 self.roster.open_event(account, jid, event)
1598 else:
1599 # Open the window
1600 self.roster.open_event(account, fjid, event)
1601 elif type_ == 'gmail':
1602 url = gajim.connections[account].gmail_url
1603 if url:
1604 helpers.launch_browser_mailer('url', url)
1605 elif type_ == 'gc-invitation':
1606 event = gajim.events.get_first_event(account, jid, type_)
1607 data = event.parameters
1608 dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
1609 data[1], data[3])
1610 gajim.events.remove_events(account, jid, event)
1611 self.roster.draw_contact(jid, account)
1612 elif type_ == 'subscription_request':
1613 event = gajim.events.get_first_event(account, jid, type_)
1614 data = event.parameters
1615 dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1])
1616 gajim.events.remove_events(account, jid, event)
1617 self.roster.draw_contact(jid, account)
1618 elif type_ == 'unsubscribed':
1619 event = gajim.events.get_first_event(account, jid, type_)
1620 contact = event.parameters
1621 self.show_unsubscribed_dialog(account, contact)
1622 gajim.events.remove_events(account, jid, event)
1623 self.roster.draw_contact(jid, account)
1624 if w:
1625 w.set_active_tab(ctrl)
1626 w.window.window.focus(gtk.get_current_event_time())
1627 # Using isinstance here because we want to catch all derived types
1628 if isinstance(ctrl, ChatControlBase):
1629 tv = ctrl.conv_textview
1630 tv.scroll_to_end()
1632 ################################################################################
1633 ### Methods dealing with emoticons
1634 ################################################################################
1636 def image_is_ok(self, image):
1637 if not os.path.exists(image):
1638 return False
1639 img = gtk.Image()
1640 try:
1641 img.set_from_file(image)
1642 except Exception:
1643 return False
1644 t = img.get_storage_type()
1645 if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION:
1646 return False
1647 return True
1649 @property
1650 def basic_pattern_re(self):
1651 if not self._basic_pattern_re:
1652 self._basic_pattern_re = re.compile(self.basic_pattern,
1653 re.IGNORECASE)
1654 return self._basic_pattern_re
1656 @property
1657 def emot_and_basic_re(self):
1658 if not self._emot_and_basic_re:
1659 self._emot_and_basic_re = re.compile(self.emot_and_basic,
1660 re.IGNORECASE + re.UNICODE)
1661 return self._emot_and_basic_re
1663 @property
1664 def sth_at_sth_dot_sth_re(self):
1665 if not self._sth_at_sth_dot_sth_re:
1666 self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
1667 return self._sth_at_sth_dot_sth_re
1669 @property
1670 def invalid_XML_chars_re(self):
1671 if not self._invalid_XML_chars_re:
1672 self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
1673 return self._invalid_XML_chars_re
1675 def make_regexps(self):
1676 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
1677 # one escapes the metachars with \
1678 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
1679 # \s matches any whitespace character
1680 # \w any alphanumeric character
1681 # \W any non-alphanumeric character
1682 # \b means word boundary. This is a zero-width assertion that
1683 # matches only at the beginning or end of a word.
1684 # ^ matches at the beginning of lines
1686 # * means 0 or more times
1687 # + means 1 or more times
1688 # ? means 0 or 1 time
1689 # | means or
1690 # [^*] anything but '*' (inside [] you don't have to escape metachars)
1691 # [^\s*] anything but whitespaces and '*'
1692 # (?<!\S) is a one char lookbehind assertion and asks for any leading
1693 # whitespace
1694 # and mathces beginning of lines so we have correct formatting detection
1695 # even if the the text is just '*foo*'
1696 # (?!\S) is the same thing but it's a lookahead assertion
1697 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
1698 # the end
1699 # so http://be) will match http://be and http://be)be) will match
1700 # http://be)be
1702 legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
1703 r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
1704 r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
1705 r"|%[A-Fa-f0-9]{2})+"\
1706 r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
1707 # NOTE: it's ok to catch www.gr such stuff exist!
1709 # FIXME: recognize xmpp: and treat it specially
1710 links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
1711 r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
1712 r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
1714 # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
1715 mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
1717 # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
1718 # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
1719 formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
1720 r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
1721 r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
1723 latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\
1724 r'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
1726 basic_pattern = links + '|' + mail + '|' + legacy_prefixes
1728 link_pattern = basic_pattern
1729 self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
1731 if gajim.config.get('use_latex'):
1732 basic_pattern += latex
1734 if gajim.config.get('ascii_formatting'):
1735 basic_pattern += formatting
1736 self.basic_pattern = basic_pattern
1738 emoticons_pattern = ''
1739 if gajim.config.get('emoticons_theme'):
1740 # When an emoticon is bordered by an alpha-numeric character it is
1741 # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
1742 # We still allow multiple emoticons side-by-side like :P:P:P
1743 # sort keys by length so :qwe emot is checked before :q
1744 keys = sorted(self.emoticons, key=len, reverse=True)
1745 emoticons_pattern_prematch = ''
1746 emoticons_pattern_postmatch = ''
1747 emoticon_length = 0
1748 for emoticon in keys: # travel thru emoticons list
1749 emoticon = emoticon.decode('utf-8')
1750 emoticon_escaped = re.escape(emoticon) # espace regexp metachars
1751 # | means or in regexp
1752 emoticons_pattern += emoticon_escaped + '|'
1753 if (emoticon_length != len(emoticon)):
1754 # Build up expressions to match emoticons next to others
1755 emoticons_pattern_prematch = \
1756 emoticons_pattern_prematch[:-1] + ')|(?<='
1757 emoticons_pattern_postmatch = \
1758 emoticons_pattern_postmatch[:-1] + ')|(?='
1759 emoticon_length = len(emoticon)
1760 emoticons_pattern_prematch += emoticon_escaped + '|'
1761 emoticons_pattern_postmatch += emoticon_escaped + '|'
1762 # We match from our list of emoticons, but they must either have
1763 # whitespace, or another emoticon next to it to match successfully
1764 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
1765 emoticons_pattern = '|' + '(?:(?<![\w.]' + \
1766 emoticons_pattern_prematch[:-1] + '))' + '(?:' + \
1767 emoticons_pattern[:-1] + ')' + '(?:(?![\w]' + \
1768 emoticons_pattern_postmatch[:-1] + '))'
1770 # because emoticons match later (in the string) they need to be after
1771 # basic matches that may occur earlier
1772 self.emot_and_basic = basic_pattern + emoticons_pattern
1774 # needed for xhtml display
1775 self.emot_only = emoticons_pattern
1777 # at least one character in 3 parts (before @, after @, after .)
1778 self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
1780 # Invalid XML chars
1781 self.invalid_XML_chars = u'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\
1782 u'[\ud800-\udfff]|[\ufffe-\uffff]'
1784 def popup_emoticons_under_button(self, button, parent_win):
1786 Popup the emoticons menu under button, located in parent_win
1788 gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
1789 button, parent_win)
1791 def prepare_emoticons_menu(self):
1792 menu = gtk.Menu()
1793 def emoticon_clicked(w, str_):
1794 if self.emoticon_menuitem_clicked:
1795 self.emoticon_menuitem_clicked(str_)
1796 # don't keep reference to CB of object
1797 # this will prevent making it uncollectable
1798 self.emoticon_menuitem_clicked = None
1799 def selection_done(widget):
1800 # remove reference to CB of object, which will
1801 # make it uncollectable
1802 self.emoticon_menuitem_clicked = None
1803 counter = 0
1804 # Calculate the side lenght of the popup to make it a square
1805 size = int(round(math.sqrt(len(self.emoticons_images))))
1806 for image in self.emoticons_images:
1807 item = gtk.MenuItem()
1808 img = gtk.Image()
1809 if isinstance(image[1], gtk.gdk.PixbufAnimation):
1810 img.set_from_animation(image[1])
1811 else:
1812 img.set_from_pixbuf(image[1])
1813 item.add(img)
1814 item.connect('activate', emoticon_clicked, image[0])
1815 # add tooltip with ascii
1816 item.set_tooltip_text(image[0])
1817 menu.attach(item, counter % size, counter % size + 1,
1818 counter / size, counter / size + 1)
1819 counter += 1
1820 menu.connect('selection-done', selection_done)
1821 menu.show_all()
1822 return menu
1824 def _init_emoticons(self, path, need_reload = False):
1825 #initialize emoticons dictionary and unique images list
1826 self.emoticons_images = list()
1827 self.emoticons = dict()
1828 self.emoticons_animations = dict()
1830 sys.path.append(path)
1831 import emoticons
1832 if need_reload:
1833 # we need to reload else that doesn't work when changing emoticon
1834 # set
1835 reload(emoticons)
1836 emots = emoticons.emoticons
1837 for emot_filename in emots:
1838 emot_file = os.path.join(path, emot_filename)
1839 if not self.image_is_ok(emot_file):
1840 continue
1841 for emot in emots[emot_filename]:
1842 emot = emot.decode('utf-8')
1843 # This avoids duplicated emoticons with the same image eg. :)
1844 # and :-)
1845 if not emot_file in self.emoticons.values():
1846 if emot_file.endswith('.gif'):
1847 pix = gtk.gdk.PixbufAnimation(emot_file)
1848 else:
1849 pix = gtk.gdk.pixbuf_new_from_file_at_size(emot_file,
1850 16, 16)
1851 self.emoticons_images.append((emot, pix))
1852 self.emoticons[emot.upper()] = emot_file
1853 del emoticons
1854 sys.path.remove(path)
1856 def init_emoticons(self, need_reload = False):
1857 emot_theme = gajim.config.get('emoticons_theme')
1858 if not emot_theme:
1859 return
1861 path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
1862 if not os.path.exists(path):
1863 # It's maybe a user theme
1864 path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
1865 if not os.path.exists(path):
1866 # theme doesn't exist, disable emoticons
1867 dialogs.WarningDialog(_('Emoticons disabled'),
1868 _('Your configured emoticons theme has not been found, so '
1869 'emoticons have been disabled.'))
1870 gajim.config.set('emoticons_theme', '')
1871 return
1872 self._init_emoticons(path, need_reload)
1873 if len(self.emoticons) == 0:
1874 # maybe old format of emoticons file, try to convert it
1875 try:
1876 import pprint
1877 import emoticons
1878 emots = emoticons.emoticons
1879 fd = open(os.path.join(path, 'emoticons.py'), 'w')
1880 fd.write('emoticons = ')
1881 pprint.pprint( dict([
1882 (file_, [i for i in emots.keys() if emots[i] == file_])
1883 for file_ in set(emots.values())]), fd)
1884 fd.close()
1885 del emoticons
1886 self._init_emoticons(path, need_reload=True)
1887 except Exception:
1888 pass
1889 if len(self.emoticons) == 0:
1890 dialogs.WarningDialog(_('Emoticons disabled'),
1891 _('Your configured emoticons theme cannot been loaded. You '
1892 'maybe need to update the format of emoticons.py file. See '
1893 'http://trac.gajim.org/wiki/Emoticons for more details.'))
1894 if self.emoticons_menu:
1895 self.emoticons_menu.destroy()
1896 self.emoticons_menu = self.prepare_emoticons_menu()
1898 ################################################################################
1899 ### Methods for opening new messages controls
1900 ################################################################################
1902 def join_gc_room(self, account, room_jid, nick, password, minimize=False,
1903 is_continued=False):
1905 Join the room immediately
1908 if gajim.contacts.get_contact(account, room_jid) and \
1909 not gajim.contacts.get_contact(account, room_jid).is_groupchat():
1910 dialogs.ErrorDialog(_('This is not a group chat'),
1911 _('%s is not the name of a group chat.') % room_jid)
1912 return
1914 if not nick:
1915 nick = gajim.nicks[account]
1917 minimized_control = gajim.interface.minimized_controls[account].get(
1918 room_jid, None)
1920 if (self.msg_win_mgr.has_window(room_jid, account) or \
1921 minimized_control) and gajim.gc_connected[account][room_jid]:
1922 if self.msg_win_mgr.has_window(room_jid, account):
1923 gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
1924 win = gc_ctrl.parent_win
1925 win.set_active_tab(gc_ctrl)
1926 else:
1927 self.roster.on_groupchat_maximized(None, room_jid, account)
1928 dialogs.ErrorDialog(_('You are already in group chat %s') % \
1929 room_jid)
1930 return
1932 invisible_show = gajim.SHOW_LIST.index('invisible')
1933 if gajim.connections[account].connected == invisible_show:
1934 dialogs.ErrorDialog(
1935 _('You cannot join a group chat while you are invisible'))
1936 return
1938 if minimized_control is None and not self.msg_win_mgr.has_window(
1939 room_jid, account):
1940 # Join new groupchat
1941 if minimize:
1942 # GCMIN
1943 contact = gajim.contacts.create_contact(jid=room_jid,
1944 account=account, name=nick)
1945 gc_control = GroupchatControl(None, contact, account)
1946 gajim.interface.minimized_controls[account][room_jid] = \
1947 gc_control
1948 self.roster.add_groupchat(room_jid, account)
1949 else:
1950 self.new_room(room_jid, nick, account,
1951 is_continued=is_continued)
1952 elif minimized_control is None:
1953 # We are already in that groupchat
1954 gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
1955 gc_control.nick = nick
1956 gc_control.parent_win.set_active_tab(gc_control)
1958 # Connect
1959 gajim.connections[account].join_gc(nick, room_jid, password)
1960 if password:
1961 gajim.gc_passwords[room_jid] = password
1963 def new_room(self, room_jid, nick, account, is_continued=False):
1964 # Get target window, create a control, and associate it with the window
1965 # GCMIN
1966 contact = gajim.contacts.create_contact(jid=room_jid, account=account,
1967 name=nick)
1968 mw = self.msg_win_mgr.get_window(contact.jid, account)
1969 if not mw:
1970 mw = self.msg_win_mgr.create_window(contact, account,
1971 GroupchatControl.TYPE_ID)
1972 gc_control = GroupchatControl(mw, contact, account,
1973 is_continued=is_continued)
1974 mw.new_tab(gc_control)
1976 def new_private_chat(self, gc_contact, account, session=None):
1977 conn = gajim.connections[account]
1978 if not session and gc_contact.get_full_jid() in conn.sessions:
1979 sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
1980 values() if isinstance(s, ChatControlSession)]
1982 # look for an existing session with a chat control
1983 for s in sessions:
1984 if s.control:
1985 session = s
1986 break
1987 if not session and not len(sessions) == 0:
1988 # there are no sessions with chat controls, just take the first
1989 # one
1990 session = sessions[0]
1991 if not session:
1992 # couldn't find an existing ChatControlSession, just make a new one
1993 session = conn.make_new_session(gc_contact.get_full_jid(), None,
1994 'pm')
1996 contact = gc_contact.as_contact()
1997 if not session.control:
1998 message_window = self.msg_win_mgr.get_window(
1999 gc_contact.get_full_jid(), account)
2000 if not message_window:
2001 message_window = self.msg_win_mgr.create_window(contact,
2002 account, message_control.TYPE_PM)
2004 session.control = PrivateChatControl(message_window, gc_contact,
2005 contact, account, session)
2006 message_window.new_tab(session.control)
2008 if gajim.events.get_events(account, gc_contact.get_full_jid()):
2009 # We call this here to avoid race conditions with widget validation
2010 session.control.read_queue()
2012 return session.control
2014 def new_chat(self, contact, account, resource=None, session=None):
2015 # Get target window, create a control, and associate it with the window
2016 type_ = message_control.TYPE_CHAT
2018 fjid = contact.jid
2019 if resource:
2020 fjid += '/' + resource
2022 mw = self.msg_win_mgr.get_window(fjid, account)
2023 if not mw:
2024 mw = self.msg_win_mgr.create_window(contact, account, type_,
2025 resource)
2027 chat_control = ChatControl(mw, contact, account, session, resource)
2029 mw.new_tab(chat_control)
2031 if len(gajim.events.get_events(account, fjid)):
2032 # We call this here to avoid race conditions with widget validation
2033 chat_control.read_queue()
2035 return chat_control
2037 def new_chat_from_jid(self, account, fjid, message=None):
2038 jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
2039 contact = gajim.contacts.get_contact(account, jid, resource)
2040 added_to_roster = False
2041 if not contact:
2042 added_to_roster = True
2043 contact = self.roster.add_to_not_in_the_roster(account, jid,
2044 resource=resource)
2046 ctrl = self.msg_win_mgr.get_control(fjid, account)
2048 if not ctrl:
2049 ctrl = self.new_chat(contact, account,
2050 resource=resource)
2051 if len(gajim.events.get_events(account, fjid)):
2052 ctrl.read_queue()
2054 if message:
2055 buffer_ = ctrl.msg_textview.get_buffer()
2056 buffer_.set_text(message)
2057 mw = ctrl.parent_win
2058 mw.set_active_tab(ctrl)
2059 # For JEP-0172
2060 if added_to_roster:
2061 ctrl.user_nick = gajim.nicks[account]
2062 gobject.idle_add(mw.window.grab_focus)
2064 return ctrl
2066 def on_open_chat_window(self, widget, contact, account, resource=None,
2067 session=None):
2068 # Get the window containing the chat
2069 fjid = contact.jid
2071 if resource:
2072 fjid += '/' + resource
2074 ctrl = None
2076 if session:
2077 ctrl = session.control
2078 if not ctrl:
2079 win = self.msg_win_mgr.get_window(fjid, account)
2081 if win:
2082 ctrl = win.get_control(fjid, account)
2084 if not ctrl:
2085 ctrl = self.new_chat(contact, account, resource=resource,
2086 session=session)
2087 # last message is long time ago
2088 gajim.last_message_time[account][ctrl.get_full_jid()] = 0
2090 win = ctrl.parent_win
2092 win.set_active_tab(ctrl)
2094 if gajim.connections[account].is_zeroconf and \
2095 gajim.connections[account].status in ('offline', 'invisible'):
2096 ctrl = win.get_control(fjid, account)
2097 if ctrl:
2098 ctrl.got_disconnected()
2100 ################################################################################
2101 ### Other Methods
2102 ################################################################################
2104 def change_awn_icon_status(self, status):
2105 if not dbus_support.supported:
2106 # do nothing if user doesn't have D-Bus bindings
2107 return
2108 try:
2109 bus = dbus.SessionBus()
2110 if not 'com.google.code.Awn' in bus.list_names():
2111 # Awn is not installed
2112 return
2113 except Exception:
2114 return
2115 iconset = gajim.config.get('iconset')
2116 prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
2117 if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2118 status = status + '.png'
2119 elif status == 'online':
2120 prefix = ''
2121 status = gtkgui_helpers.get_icon_path('gajim', 32)
2122 path = os.path.join(prefix, status)
2123 try:
2124 obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
2125 awn = dbus.Interface(obj, 'com.google.code.Awn')
2126 awn.SetTaskIconByName('Gajim', os.path.abspath(path))
2127 except Exception:
2128 pass
2130 def enable_music_listener(self):
2131 listener = MusicTrackListener.get()
2132 if not self.music_track_changed_signal:
2133 self.music_track_changed_signal = listener.connect(
2134 'music-track-changed', self.music_track_changed)
2135 track = listener.get_playing_track()
2136 self.music_track_changed(listener, track)
2138 def disable_music_listener(self):
2139 listener = MusicTrackListener.get()
2140 listener.disconnect(self.music_track_changed_signal)
2141 self.music_track_changed_signal = None
2143 def music_track_changed(self, unused_listener, music_track_info,
2144 account=None):
2145 if not account:
2146 accounts = gajim.connections.keys()
2147 else:
2148 accounts = [account]
2150 is_paused = hasattr(music_track_info, 'paused') and \
2151 music_track_info.paused == 0
2152 if not music_track_info or is_paused:
2153 artist = title = source = ''
2154 else:
2155 artist = music_track_info.artist
2156 title = music_track_info.title
2157 source = music_track_info.album
2158 for acct in accounts:
2159 if not gajim.account_is_connected(acct):
2160 continue
2161 if not gajim.connections[acct].pep_supported:
2162 continue
2163 if not gajim.config.get_per('accounts', acct, 'publish_tune'):
2164 continue
2165 if gajim.connections[acct].music_track_info == music_track_info:
2166 continue
2167 gajim.connections[acct].send_tune(artist, title, source)
2168 gajim.connections[acct].music_track_info = music_track_info
2170 def get_bg_fg_colors(self):
2171 def gdkcolor_to_rgb (gdkcolor):
2172 return [c / 65535. for c in (gdkcolor.red, gdkcolor.green,
2173 gdkcolor.blue)]
2175 def format_rgb (r, g, b):
2176 return ' '.join([str(c) for c in ('rgb', r, g, b)])
2178 def format_gdkcolor (gdkcolor):
2179 return format_rgb (*gdkcolor_to_rgb (gdkcolor))
2181 # get style colors and create string for dvipng
2182 dummy = gtk.Invisible()
2183 dummy.ensure_style()
2184 style = dummy.get_style()
2185 bg_str = format_gdkcolor(style.base[gtk.STATE_NORMAL])
2186 fg_str = format_gdkcolor(style.text[gtk.STATE_NORMAL])
2187 return (bg_str, fg_str)
2189 def get_fg_color(self, fmt='hex'):
2190 def format_gdkcolor (c):
2191 if fmt == 'tex':
2192 return ' '.join([str(s) for s in
2193 ('rgb', c.red_float, c.green_float, c.blue_float)])
2194 elif fmt == 'hex':
2195 return str(c)
2197 # get foreground style color and create string
2198 dummy = gtk.Invisible()
2199 dummy.ensure_style()
2200 return format_gdkcolor(dummy.get_style().text[gtk.STATE_NORMAL])
2202 def read_sleepy(self):
2204 Check idle status and change that status if needed
2206 if not self.sleeper.poll():
2207 # idle detection is not supported in that OS
2208 return False # stop looping in vain
2209 state = self.sleeper.getState()
2210 for account in gajim.connections:
2211 if account not in gajim.sleeper_state or \
2212 not gajim.sleeper_state[account]:
2213 continue
2214 if state == common.sleepy.STATE_AWAKE and \
2215 gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
2216 # we go online
2217 self.roster.send_status(account, 'online',
2218 gajim.status_before_autoaway[account])
2219 gajim.status_before_autoaway[account] = ''
2220 gajim.sleeper_state[account] = 'online'
2221 elif state == common.sleepy.STATE_AWAY and \
2222 gajim.sleeper_state[account] == 'online' and \
2223 gajim.config.get('autoaway'):
2224 # we save out online status
2225 gajim.status_before_autoaway[account] = \
2226 gajim.connections[account].status
2227 # we go away (no auto status) [we pass True to auto param]
2228 auto_message = gajim.config.get('autoaway_message')
2229 if not auto_message:
2230 auto_message = gajim.connections[account].status
2231 else:
2232 auto_message = auto_message.replace('$S', '%(status)s')
2233 auto_message = auto_message.replace('$T', '%(time)s')
2234 auto_message = auto_message % {
2235 'status': gajim.status_before_autoaway[account],
2236 'time': gajim.config.get('autoawaytime')
2238 self.roster.send_status(account, 'away', auto_message,
2239 auto=True)
2240 gajim.sleeper_state[account] = 'autoaway'
2241 elif state == common.sleepy.STATE_XA and \
2242 gajim.sleeper_state[account] in ('online', 'autoaway',
2243 'autoaway-forced') and gajim.config.get('autoxa'):
2244 # we go extended away [we pass True to auto param]
2245 auto_message = gajim.config.get('autoxa_message')
2246 if not auto_message:
2247 auto_message = gajim.connections[account].status
2248 else:
2249 auto_message = auto_message.replace('$S', '%(status)s')
2250 auto_message = auto_message.replace('$T', '%(time)s')
2251 auto_message = auto_message % {
2252 'status': gajim.status_before_autoaway[account],
2253 'time': gajim.config.get('autoxatime')
2255 self.roster.send_status(account, 'xa', auto_message, auto=True)
2256 gajim.sleeper_state[account] = 'autoxa'
2257 return True # renew timeout (loop for ever)
2259 def autoconnect(self):
2261 Auto connect at startup
2263 # dict of account that want to connect sorted by status
2264 shows = {}
2265 for a in gajim.connections:
2266 if gajim.config.get_per('accounts', a, 'autoconnect'):
2267 if gajim.config.get_per('accounts', a, 'restore_last_status'):
2268 self.roster.send_status(a, gajim.config.get_per('accounts',
2269 a, 'last_status'), helpers.from_one_line(
2270 gajim.config.get_per('accounts', a, 'last_status_msg')))
2271 continue
2272 show = gajim.config.get_per('accounts', a, 'autoconnect_as')
2273 if not show in gajim.SHOW_LIST:
2274 continue
2275 if not show in shows:
2276 shows[show] = [a]
2277 else:
2278 shows[show].append(a)
2279 def on_message(message, pep_dict):
2280 if message is None:
2281 return
2282 for a in shows[show]:
2283 self.roster.send_status(a, show, message)
2284 self.roster.send_pep(a, pep_dict)
2285 for show in shows:
2286 message = self.roster.get_status_message(show, on_message)
2287 return False
2289 def show_systray(self):
2290 self.systray_enabled = True
2291 self.systray.show_icon()
2293 def hide_systray(self):
2294 self.systray_enabled = False
2295 self.systray.hide_icon()
2297 def on_launch_browser_mailer(self, widget, url, kind):
2298 helpers.launch_browser_mailer(kind, url)
2300 def process_connections(self):
2302 Called each foo (200) miliseconds. Check for idlequeue timeouts
2304 try:
2305 gajim.idlequeue.process()
2306 except Exception:
2307 # Otherwise, an exception will stop our loop
2308 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2309 if in_seconds:
2310 gobject.timeout_add_seconds(timeout, self.process_connections)
2311 else:
2312 gobject.timeout_add(timeout, self.process_connections)
2313 raise
2314 return True # renew timeout (loop for ever)
2316 def save_config(self):
2317 err_str = parser.write()
2318 if err_str is not None:
2319 print >> sys.stderr, err_str
2320 # it is good to notify the user
2321 # in case he or she cannot see the output of the console
2322 dialogs.ErrorDialog(_('Could not save your settings and '
2323 'preferences'), err_str)
2324 sys.exit()
2326 def save_avatar_files(self, jid, photo, puny_nick = None, local = False):
2328 Save an avatar to a separate file, and generate files for dbus
2329 notifications. An avatar can be given as a pixmap directly or as an
2330 decoded image
2332 puny_jid = helpers.sanitize_filename(jid)
2333 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2334 if puny_nick:
2335 path_to_file = os.path.join(path_to_file, puny_nick)
2336 # remove old avatars
2337 for typ in ('jpeg', 'png'):
2338 if local:
2339 path_to_original_file = path_to_file + '_local'+ '.' + typ
2340 else:
2341 path_to_original_file = path_to_file + '.' + typ
2342 if os.path.isfile(path_to_original_file):
2343 os.remove(path_to_original_file)
2344 if local and photo:
2345 pixbuf = photo
2346 typ = 'png'
2347 extension = '_local.png' # save local avatars as png file
2348 else:
2349 pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
2350 want_type=True)
2351 if pixbuf is None:
2352 return
2353 extension = '.' + typ
2354 if typ not in ('jpeg', 'png'):
2355 gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\
2356 'png formats. saving %s\'avatar as png file (originaly %s)'\
2357 % (jid, typ))
2358 typ = 'png'
2359 extension = '.png'
2360 path_to_original_file = path_to_file + extension
2361 try:
2362 pixbuf.save(path_to_original_file, typ)
2363 except Exception, e:
2364 log.error('Error writing avatar file %s: %s' % (
2365 path_to_original_file, str(e)))
2366 # Generate and save the resized, color avatar
2367 pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
2368 if pixbuf:
2369 path_to_normal_file = path_to_file + '_notif_size_colored' + \
2370 extension
2371 try:
2372 pixbuf.save(path_to_normal_file, 'png')
2373 except Exception, e:
2374 log.error('Error writing avatar file %s: %s' % \
2375 (path_to_original_file, str(e)))
2376 # Generate and save the resized, black and white avatar
2377 bwbuf = gtkgui_helpers.get_scaled_pixbuf(
2378 gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
2379 if bwbuf:
2380 path_to_bw_file = path_to_file + '_notif_size_bw' + extension
2381 try:
2382 bwbuf.save(path_to_bw_file, 'png')
2383 except Exception, e:
2384 log.error('Error writing avatar file %s: %s' % \
2385 (path_to_original_file, str(e)))
2387 def remove_avatar_files(self, jid, puny_nick = None, local = False):
2389 Remove avatar files of a jid
2391 puny_jid = helpers.sanitize_filename(jid)
2392 path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
2393 if puny_nick:
2394 path_to_file = os.path.join(path_to_file, puny_nick)
2395 for ext in ('.jpeg', '.png'):
2396 if local:
2397 ext = '_local' + ext
2398 path_to_original_file = path_to_file + ext
2399 if os.path.isfile(path_to_file + ext):
2400 os.remove(path_to_file + ext)
2401 if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
2402 os.remove(path_to_file + '_notif_size_colored' + ext)
2403 if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
2404 os.remove(path_to_file + '_notif_size_bw' + ext)
2406 def auto_join_bookmarks(self, account):
2408 Autojoin bookmarked GCs that have 'auto join' on for this account
2410 for bm in gajim.connections[account].bookmarks:
2411 if bm['autojoin'] in ('1', 'true'):
2412 jid = bm['jid']
2413 # Only join non-opened groupchats. Opened one are already
2414 # auto-joined on re-connection
2415 if not jid in gajim.gc_connected[account]:
2416 # we are not already connected
2417 minimize = bm['minimize'] in ('1', 'true')
2418 self.join_gc_room(account, jid, bm['nick'],
2419 bm['password'], minimize = minimize)
2420 elif jid in self.minimized_controls[account]:
2421 # more or less a hack:
2422 # On disconnect the minimized gc contact instances
2423 # were set to offline. Reconnect them to show up in the
2424 # roster.
2425 self.roster.add_groupchat(jid, account)
2427 def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
2428 nick):
2430 Add a bookmark for this account, sorted in bookmark list
2432 bm = {
2433 'name': name,
2434 'jid': jid,
2435 'autojoin': autojoin,
2436 'minimize': minimize,
2437 'password': password,
2438 'nick': nick
2440 place_found = False
2441 index = 0
2442 # check for duplicate entry and respect alpha order
2443 for bookmark in gajim.connections[account].bookmarks:
2444 if bookmark['jid'] == bm['jid']:
2445 dialogs.ErrorDialog(
2446 _('Bookmark already set'),
2447 _('Group Chat "%s" is already in your bookmarks.') % \
2448 bm['jid'])
2449 return
2450 if bookmark['name'] > bm['name']:
2451 place_found = True
2452 break
2453 index += 1
2454 if place_found:
2455 gajim.connections[account].bookmarks.insert(index, bm)
2456 else:
2457 gajim.connections[account].bookmarks.append(bm)
2458 gajim.connections[account].store_bookmarks()
2459 self.roster.set_actions_menu_needs_rebuild()
2460 dialogs.InformationDialog(
2461 _('Bookmark has been added successfully'),
2462 _('You can manage your bookmarks via Actions menu in your roster.'))
2465 # does JID exist only within a groupchat?
2466 def is_pm_contact(self, fjid, account):
2467 bare_jid = gajim.get_jid_without_resource(fjid)
2469 gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
2471 if not gc_ctrl and \
2472 bare_jid in self.minimized_controls[account]:
2473 gc_ctrl = self.minimized_controls[account][bare_jid]
2475 return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
2477 def create_ipython_window(self):
2478 try:
2479 from ipython_view import IPythonView
2480 except ImportError:
2481 print 'ipython_view not found'
2482 return
2483 import pango
2485 if os.name == 'nt':
2486 font = 'Lucida Console 9'
2487 else:
2488 font = 'Luxi Mono 10'
2490 window = gtk.Window()
2491 window.set_size_request(750, 550)
2492 window.set_resizable(True)
2493 sw = gtk.ScrolledWindow()
2494 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
2495 view = IPythonView()
2496 view.modify_font(pango.FontDescription(font))
2497 view.set_wrap_mode(gtk.WRAP_CHAR)
2498 sw.add(view)
2499 window.add(sw)
2500 window.show_all()
2501 def on_delete(win, event):
2502 win.hide()
2503 return True
2504 window.connect('delete_event', on_delete)
2505 view.updateNamespace({'gajim': gajim})
2506 gajim.ipython_window = window
2508 def run(self):
2509 if gajim.config.get('trayicon') != 'never':
2510 self.show_systray()
2512 self.roster = roster_window.RosterWindow()
2513 # Creating plugin manager
2514 import plugins
2515 gajim.plugin_manager = plugins.PluginManager()
2517 self.roster._before_fill()
2518 for account in gajim.connections:
2519 gajim.connections[account].load_roster_from_db()
2520 self.roster._after_fill()
2522 # get instances for windows/dialogs that will show_all()/hide()
2523 self.instances['file_transfers'] = dialogs.FileTransfersWindow()
2525 gobject.timeout_add(100, self.autoconnect)
2526 timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
2527 if in_seconds:
2528 gobject.timeout_add_seconds(timeout, self.process_connections)
2529 else:
2530 gobject.timeout_add(timeout, self.process_connections)
2531 gobject.timeout_add_seconds(gajim.config.get(
2532 'check_idle_every_foo_seconds'), self.read_sleepy)
2534 # when using libasyncns we need to process resolver in regular intervals
2535 if resolver.USE_LIBASYNCNS:
2536 gobject.timeout_add(200, gajim.resolver.process)
2538 def remote_init():
2539 if gajim.config.get('remote_control'):
2540 try:
2541 import remote_control
2542 self.remote_ctrl = remote_control.Remote()
2543 except Exception:
2544 pass
2545 gobject.timeout_add_seconds(5, remote_init)
2547 def __init__(self):
2548 gajim.interface = self
2549 gajim.thread_interface = ThreadInterface
2550 # This is the manager and factory of message windows set by the module
2551 self.msg_win_mgr = None
2552 self.jabber_state_images = {'16': {}, '32': {}, 'opened': {},
2553 'closed': {}}
2554 self.emoticons_menu = None
2555 # handler when an emoticon is clicked in emoticons_menu
2556 self.emoticon_menuitem_clicked = None
2557 self.minimized_controls = {}
2558 self.status_sent_to_users = {}
2559 self.status_sent_to_groups = {}
2560 self.gpg_passphrase = {}
2561 self.pass_dialog = {}
2562 self.db_error_dialog = None
2563 self.default_colors = {
2564 'inmsgcolor': gajim.config.get('inmsgcolor'),
2565 'outmsgcolor': gajim.config.get('outmsgcolor'),
2566 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
2567 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
2568 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
2569 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
2572 self.handlers = {}
2573 self.roster = None
2574 self._invalid_XML_chars_re = None
2575 self._basic_pattern_re = None
2576 self._emot_and_basic_re = None
2577 self._sth_at_sth_dot_sth_re = None
2578 self.link_pattern_re = None
2579 self.invalid_XML_chars = None
2580 self.basic_pattern = None
2581 self.emot_and_basic = None
2582 self.sth_at_sth_dot_sth = None
2583 self.emot_only = None
2584 self.emoticons = []
2585 self.emoticons_animations = {}
2586 self.emoticons_images = {}
2588 cfg_was_read = parser.read()
2590 from common import latex
2591 gajim.HAVE_LATEX = gajim.config.get('use_latex') and \
2592 latex.check_for_latex_support()
2594 gajim.logger.reset_shown_unread_messages()
2595 # override logging settings from config (don't take care of '-q' option)
2596 if gajim.config.get('verbose'):
2597 logging_helpers.set_verbose()
2599 # Is Gajim default app?
2600 if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'):
2601 gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
2603 for account in gajim.config.get_per('accounts'):
2604 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
2605 gajim.ZEROCONF_ACC_NAME = account
2606 break
2607 # Is gnome configured to activate row on single click ?
2608 try:
2609 import gconf
2610 client = gconf.client_get_default()
2611 click_policy = client.get_string(
2612 '/apps/nautilus/preferences/click_policy')
2613 if click_policy == 'single':
2614 gajim.single_click = True
2615 except Exception:
2616 pass
2617 # add default status messages if there is not in the config file
2618 if len(gajim.config.get_per('statusmsg')) == 0:
2619 default = gajim.config.statusmsg_default
2620 for msg in default:
2621 gajim.config.add_per('statusmsg', msg)
2622 gajim.config.set_per('statusmsg', msg, 'message',
2623 default[msg][0])
2624 gajim.config.set_per('statusmsg', msg, 'activity',
2625 default[msg][1])
2626 gajim.config.set_per('statusmsg', msg, 'subactivity',
2627 default[msg][2])
2628 gajim.config.set_per('statusmsg', msg, 'activity_text',
2629 default[msg][3])
2630 gajim.config.set_per('statusmsg', msg, 'mood',
2631 default[msg][4])
2632 gajim.config.set_per('statusmsg', msg, 'mood_text',
2633 default[msg][5])
2634 #add default themes if there is not in the config file
2635 theme = gajim.config.get('roster_theme')
2636 if not theme in gajim.config.get_per('themes'):
2637 gajim.config.set('roster_theme', _('default'))
2638 if len(gajim.config.get_per('themes')) == 0:
2639 d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
2640 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2641 'groupfont', 'groupfontattrs', 'contacttextcolor',
2642 'contactbgcolor', 'contactfont', 'contactfontattrs',
2643 'bannertextcolor', 'bannerbgcolor']
2645 default = gajim.config.themes_default
2646 for theme_name in default:
2647 gajim.config.add_per('themes', theme_name)
2648 theme = default[theme_name]
2649 for o in d:
2650 gajim.config.set_per('themes', theme_name, o,
2651 theme[d.index(o)])
2653 gajim.idlequeue = idlequeue.get_idlequeue()
2654 # resolve and keep current record of resolved hosts
2655 gajim.resolver = resolver.get_resolver(gajim.idlequeue)
2656 gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
2657 self.handle_event_file_rcv_completed,
2658 self.handle_event_file_progress,
2659 self.handle_event_file_error)
2660 gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
2661 gajim.default_session_type = ChatControlSession
2663 # Creating Network Events Controller
2664 from common import nec
2665 gajim.nec = nec.NetworkEventsController()
2666 gajim.notification = notify.Notification()
2668 self.create_core_handlers_list()
2669 self.register_core_handlers()
2671 if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
2672 and gajim.HAVE_ZEROCONF:
2673 gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
2674 connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
2675 for account in gajim.config.get_per('accounts'):
2676 if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
2677 gajim.config.get_per('accounts', account, 'active'):
2678 gajim.connections[account] = Connection(account)
2680 # gtk hooks
2681 gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
2682 gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
2683 gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
2685 self.instances = {}
2687 for a in gajim.connections:
2688 self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
2689 'search': {}, 'online_dialog': {}, 'sub_request': {}}
2690 # online_dialog contains all dialogs that have a meaning only when
2691 # we are not disconnected
2692 self.minimized_controls[a] = {}
2693 gajim.contacts.add_account(a)
2694 gajim.groups[a] = {}
2695 gajim.gc_connected[a] = {}
2696 gajim.automatic_rooms[a] = {}
2697 gajim.newly_added[a] = []
2698 gajim.to_be_removed[a] = []
2699 gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
2700 gajim.block_signed_in_notifications[a] = True
2701 gajim.sleeper_state[a] = 0
2702 gajim.encrypted_chats[a] = []
2703 gajim.last_message_time[a] = {}
2704 gajim.status_before_autoaway[a] = ''
2705 gajim.transport_avatar[a] = {}
2706 gajim.gajim_optional_features[a] = []
2707 gajim.caps_hash[a] = ''
2709 helpers.update_optional_features()
2710 # prepopulate data which we are sure of; note: we do not log these info
2711 for account in gajim.connections:
2712 gajimcaps = caps_cache.capscache[('sha-1',
2713 gajim.caps_hash[account])]
2714 gajimcaps.identities = [gajim.gajim_identity]
2715 gajimcaps.features = gajim.gajim_common_features + \
2716 gajim.gajim_optional_features[account]
2718 self.remote_ctrl = None
2720 if gajim.config.get('networkmanager_support') and \
2721 dbus_support.supported:
2722 import network_manager_listener
2724 # Handle gnome screensaver
2725 if dbus_support.supported:
2726 def gnome_screensaver_ActiveChanged_cb(active):
2727 if not active:
2728 for account in gajim.connections:
2729 if gajim.sleeper_state[account] == 'autoaway-forced':
2730 # We came back online ofter gnome-screensaver
2731 # autoaway
2732 self.roster.send_status(account, 'online',
2733 gajim.status_before_autoaway[account])
2734 gajim.status_before_autoaway[account] = ''
2735 gajim.sleeper_state[account] = 'online'
2736 return
2737 if not gajim.config.get('autoaway'):
2738 # Don't go auto away if user disabled the option
2739 return
2740 for account in gajim.connections:
2741 if account not in gajim.sleeper_state or \
2742 not gajim.sleeper_state[account]:
2743 continue
2744 if gajim.sleeper_state[account] == 'online':
2745 # we save out online status
2746 gajim.status_before_autoaway[account] = \
2747 gajim.connections[account].status
2748 # we go away (no auto status) [we pass True to auto
2749 # param]
2750 auto_message = gajim.config.get('autoaway_message')
2751 if not auto_message:
2752 auto_message = gajim.connections[account].status
2753 else:
2754 auto_message = auto_message.replace('$S',
2755 '%(status)s')
2756 auto_message = auto_message.replace('$T',
2757 '%(time)s')
2758 auto_message = auto_message % {
2759 'status': gajim.status_before_autoaway[account],
2760 'time': gajim.config.get('autoxatime')}
2761 self.roster.send_status(account, 'away', auto_message,
2762 auto=True)
2763 gajim.sleeper_state[account] = 'autoaway-forced'
2765 try:
2766 bus = dbus.SessionBus()
2767 bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
2768 'ActiveChanged', 'org.gnome.ScreenSaver')
2769 except Exception:
2770 pass
2772 self.show_vcard_when_connect = []
2774 self.sleeper = common.sleepy.Sleepy(
2775 gajim.config.get('autoawaytime') * 60, # make minutes to seconds
2776 gajim.config.get('autoxatime') * 60)
2778 gtkgui_helpers.make_jabber_state_images()
2780 self.systray_enabled = False
2782 import statusicon
2783 self.systray = statusicon.StatusIcon()
2785 pixs = []
2786 for size in (16, 32, 48, 64, 128):
2787 pix = gtkgui_helpers.get_icon_pixmap('gajim', size)
2788 if pix:
2789 pixs.append(pix)
2790 if pixs:
2791 # set the icon to all windows
2792 gtk.window_set_default_icon_list(*pixs)
2794 self.init_emoticons()
2795 self.make_regexps()
2797 # get transports type from DB
2798 gajim.transport_type = gajim.logger.get_transports_type()
2800 # test is dictionnary is present for speller
2801 if gajim.config.get('use_speller'):
2802 lang = gajim.config.get('speller_language')
2803 if not lang:
2804 lang = gajim.LANG
2805 tv = gtk.TextView()
2806 try:
2807 import gtkspell
2808 spell = gtkspell.Spell(tv, lang)
2809 except (ImportError, TypeError, RuntimeError, OSError):
2810 dialogs.AspellDictError(lang)
2812 if gajim.config.get('soundplayer') == '':
2813 # only on first time Gajim starts
2814 commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
2815 for command in commands:
2816 if helpers.is_in_path(command):
2817 if command == 'aplay':
2818 command += ' -q'
2819 gajim.config.set('soundplayer', command)
2820 break
2822 self.last_ftwindow_update = 0
2824 self.music_track_changed_signal = None
2827 class PassphraseRequest:
2828 def __init__(self, keyid):
2829 self.keyid = keyid
2830 self.callbacks = []
2831 self.dialog_created = False
2832 self.dialog = None
2833 self.passphrase = None
2834 self.completed = False
2836 def interrupt(self, account=None):
2837 if account:
2838 for (acct, cb) in self.callbacks:
2839 if acct == account:
2840 self.callbacks.remove((acct, cb))
2841 else:
2842 self.callbacks = []
2843 if not len(self.callbacks):
2844 self.dialog.window.destroy()
2846 def run_callback(self, account, callback):
2847 gajim.connections[account].gpg_passphrase(self.passphrase)
2848 callback()
2850 def add_callback(self, account, cb):
2851 if self.completed:
2852 self.run_callback(account, cb)
2853 else:
2854 self.callbacks.append((account, cb))
2855 if not self.dialog_created:
2856 self.create_dialog(account)
2858 def complete(self, passphrase):
2859 self.passphrase = passphrase
2860 self.completed = True
2861 if passphrase is not None:
2862 gobject.timeout_add_seconds(30,
2863 gajim.interface.forget_gpg_passphrase, self.keyid)
2864 for (account, cb) in self.callbacks:
2865 self.run_callback(account, cb)
2866 self.callbacks = []
2868 def create_dialog(self, account):
2869 title = _('Passphrase Required')
2870 second = _('Enter GPG key passphrase for key %(keyid)s (account '
2871 '%(account)s).') % {'keyid': self.keyid, 'account': account}
2873 def _cancel():
2874 # user cancelled, continue without GPG
2875 self.complete(None)
2877 def _ok(passphrase, checked, count):
2878 result = gajim.connections[account].test_gpg_passphrase(passphrase)
2879 if result == 'ok':
2880 # passphrase is good
2881 self.complete(passphrase)
2882 return
2883 elif result == 'expired':
2884 dialogs.ErrorDialog(_('GPG key expired'),
2885 _('Your GPG key has expired, you will be connected to %s '
2886 'without OpenPGP.') % account)
2887 # Don't try to connect with GPG
2888 gajim.connections[account].continue_connect_info[2] = False
2889 self.complete(None)
2890 return
2892 if count < 3:
2893 # ask again
2894 dialogs.PassphraseDialog(_('Wrong Passphrase'),
2895 _('Please retype your GPG passphrase or press Cancel.'),
2896 ok_handler=(_ok, count + 1), cancel_handler=_cancel)
2897 else:
2898 # user failed 3 times, continue without GPG
2899 self.complete(None)
2901 self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
2902 1), cancel_handler=_cancel)
2903 self.dialog_created = True
2906 class ThreadInterface:
2907 def __init__(self, func, func_args=(), callback=None, callback_args=()):
2909 Call a function in a thread
2911 def thread_function(func, func_args, callback, callback_args):
2912 output = func(*func_args)
2913 if callback:
2914 gobject.idle_add(callback, output, *callback_args)
2916 Thread(target=thread_function, args=(func, func_args, callback,
2917 callback_args)).start()