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/>.
43 from subprocess
import Popen
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
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
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
87 from threading
import Thread
88 from common
import ged
90 gajimpaths
= common
.configpaths
.gajimpaths
91 config_filename
= gajimpaths
['CONFIG_FILE']
93 from common
import optparser
94 parser
= optparser
.OptionsParser(config_filename
)
97 log
= logging
.getLogger('gajim.interface')
101 ################################################################################
102 ### Methods handling events from connection
103 ################################################################################
105 def handle_event_warning(self
, unused
, data
):
106 #('WARNING', account, (title_text, section_text))
107 dialogs
.WarningDialog(data
[0], data
[1])
109 def handle_event_error(self
, unused
, data
):
110 #('ERROR', account, (title_text, section_text))
111 dialogs
.ErrorDialog(data
[0], data
[1])
113 def handle_event_db_error(self
, unused
, data
):
114 #('DB_ERROR', account, (title_text, section_text))
115 if self
.db_error_dialog
:
117 self
.db_error_dialog
= dialogs
.ErrorDialog(data
[0], data
[1])
119 self
.db_error_dialog
= None
120 self
.db_error_dialog
.connect('destroy', destroyed
)
122 def handle_event_information(self
, unused
, data
):
123 #('INFORMATION', account, (title_text, section_text))
124 dialogs
.InformationDialog(data
[0], data
[1])
126 def handle_ask_new_nick(self
, account
, room_jid
):
127 title
= _('Unable to join group chat')
128 prompt
= _('Your desired nickname in group chat %s is in use or '
129 'registered by another occupant.\nPlease specify another nickname '
131 check_text
= _('Always use this nickname when there is a conflict')
132 if 'change_nick_dialog' in self
.instances
:
133 self
.instances
['change_nick_dialog'].add_room(account
, room_jid
,
136 self
.instances
['change_nick_dialog'] = dialogs
.ChangeNickDialog(
137 account
, room_jid
, title
, prompt
)
139 def handle_event_http_auth(self
, obj
):
140 #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
141 def response(account
, answer
):
142 obj
.conn
.build_http_auth_answer(obj
.stanza
, answer
)
144 def on_yes(is_checked
, obj
):
147 account
= obj
.conn
.name
148 sec_msg
= _('Do you accept this request?')
149 if gajim
.get_number_of_connected_accounts() > 1:
150 sec_msg
= _('Do you accept this request on account %s?') % account
152 sec_msg
= obj
.msg
+ '\n' + sec_msg
153 dialog
= dialogs
.YesNoDialog(_('HTTP (%(method)s) Authorization for '
154 '%(url)s (id: %(id)s)') % {'method': obj
.method
, 'url': obj
.url
,
155 'id': obj
.iq_id
}, sec_msg
, on_response_yes
=(on_yes
, obj
),
156 on_response_no
=(response
, obj
, 'no'))
158 def handle_event_iq_error(self
, obj
):
159 #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode))
160 if unicode(obj
.errcode
) in ('400', '403', '406') and obj
.id_
:
161 # show the error dialog
162 ft
= self
.instances
['file_transfers']
164 if len(obj
.id_
) > 3 and obj
.id_
[2] == '_':
166 if sid
in ft
.files_props
['s']:
167 file_props
= ft
.files_props
['s'][sid
]
168 if unicode(obj
.errcode
) == '400':
169 file_props
['error'] = -3
171 file_props
['error'] = -4
172 self
.handle_event_file_request_error(obj
.conn
.name
, (obj
.fjid
,
173 file_props
, obj
.errmsg
))
174 obj
.conn
.disconnect_transfer(file_props
)
176 elif unicode(obj
.errcode
) == '404':
178 if len(obj
.id_
) > 3 and obj
.id_
[2] == '_':
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
,
184 obj
.conn
.disconnect_transfer(file_props
)
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():
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
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 (
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()
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
)
248 dialogs
.ErrorDialog(pritext
, sectext
)
250 def handle_gc_password_required(self
, account
, room_jid
, nick
):
252 gajim
.connections
[account
].join_gc(nick
, room_jid
, text
)
253 gajim
.gc_passwords
[room_jid
] = text
256 # get and destroy window
257 if room_jid
in gajim
.interface
.minimized_controls
[account
]:
258 self
.roster
.on_disconnect(None, room_jid
, account
)
260 win
= self
.msg_win_mgr
.get_window(room_jid
, account
)
261 ctrl
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
262 win
.remove_tab(ctrl
, 3)
264 dlg
= dialogs
.InputDialog(_('Password Required'),
265 _('A Password is required to join the room %s. Please type it.') % \
266 room_jid
, is_modal
=False, ok_handler
=on_ok
,
267 cancel_handler
=on_cancel
)
268 dlg
.input_entry
.set_visibility(False)
270 def handle_event_gc_presence(self
, obj
):
271 gc_control
= obj
.gc_control
272 if obj
.ptype
== 'error':
273 if obj
.errcode
== '503':
274 # maximum user number reached
275 self
.handle_gc_error(gc_control
,
276 _('Unable to join group chat'),
277 _('Maximum number of users for %s has been reached') % \
279 elif (obj
.errcode
== '401') or (obj
.errcon
== 'not-authorized'):
280 # password required to join
281 self
.handle_gc_password_required(obj
.conn
.name
, obj
.room_jid
,
283 elif (obj
.errcode
== '403') or (obj
.errcon
== 'forbidden'):
285 self
.handle_gc_error(gc_control
, _('Unable to join group chat'),
286 _('You are banned from group chat %s.') % obj
.room_jid
)
287 elif (obj
.errcode
== '404') or (obj
.errcon
in ('item-not-found',
288 'remote-server-not-found')):
289 # group chat does not exist
290 self
.handle_gc_error(gc_control
, _('Unable to join group chat'),
291 _('Group chat %s does not exist.') % obj
.room_jid
)
292 elif (obj
.errcode
== '405') or (obj
.errcon
== 'not-allowed'):
293 self
.handle_gc_error(gc_control
, _('Unable to join group chat'),
294 _('Group chat creation is restricted.'))
295 elif (obj
.errcode
== '406') or (obj
.errcon
== 'not-acceptable'):
296 self
.handle_gc_error(gc_control
, _('Unable to join group chat'),
297 _('Your registered nickname must be used in group chat '
298 '%s.') % obj
.room_jid
)
299 elif (obj
.errcode
== '407') or (obj
.errcon
== \
300 'registration-required'):
301 self
.handle_gc_error(gc_control
, _('Unable to join group chat'),
302 _('You are not in the members list in groupchat %s.') % \
304 elif (obj
.errcode
== '409') or (obj
.errcon
== 'conflict'):
305 self
.handle_ask_new_nick(obj
.conn
.name
, obj
.room_jid
)
307 gc_control
.print_conversation('Error %s: %s' % (obj
.errcode
,
311 def handle_event_presence(self
, obj
):
312 # 'NOTIFY' (account, (jid, status, status message, resource,
313 # priority, # keyID, timestamp, contact_nickname))
315 # Contact changed show
317 account
= obj
.conn
.name
321 resource
= obj
.resource
or ''
323 jid_list
= gajim
.contacts
.get_jid_list(account
)
325 # unset custom status
326 if (obj
.old_show
== 0 and obj
.new_show
> 1) or \
327 (obj
.old_show
> 1 and obj
.new_show
== 0 and obj
.conn
.connected
> 1):
328 if account
in self
.status_sent_to_users
and \
329 jid
in self
.status_sent_to_users
[account
]:
330 del self
.status_sent_to_users
[account
][jid
]
332 if gajim
.jid_is_transport(jid
):
333 # It must be an agent
335 # transport just signed in/out, don't show
336 # popup notifications for 30s
337 account_jid
= account
+ '/' + jid
338 gajim
.block_signed_in_notifications
[account_jid
] = True
339 gobject
.timeout_add_seconds(30,
340 self
.unblock_signed_in_notifications
, account_jid
)
345 obj
.show_notif
= True
346 for c
in obj
.contact_list
:
347 if c
.resource
== resource
:
348 # we look for other connected resources
350 if c
.show
not in ('offline', 'error'):
351 obj
.show_notif
= False
354 # no other resource is connected, let's look in metacontacts
355 family
= gajim
.contacts
.get_metacontacts_family(account
,
358 acct_
= info
['account']
360 c_
= gajim
.contacts
.get_contact_with_highest_priority(
364 if c_
.show
not in ('offline', 'error'):
365 obj
.show_notif
= False
368 if obj
.old_show
< 2 and obj
.new_show
> 1:
369 notify
.notify('contact_connected', jid
, account
, status
)
371 elif obj
.old_show
> 1 and obj
.new_show
< 2:
372 notify
.notify('contact_disconnected', jid
, account
, status
)
373 # Status change (not connected/disconnected or
375 elif obj
.new_show
> 1:
376 notify
.notify('status_change', jid
, account
, [obj
.new_show
,
379 highest
= gajim
.contacts
.get_contact_with_highest_priority(account
, jid
)
380 is_highest
= (highest
and highest
.resource
== resource
)
382 # disconnect the session from the ctrl if the highest resource has
384 if (obj
.was_highest
and not is_highest
) or \
385 (not obj
.was_highest
and is_highest
):
386 ctrl
= self
.msg_win_mgr
.get_control(jid
, account
)
388 ctrl
.no_autonegotiation
= False
389 ctrl
.set_session(None)
390 ctrl
.contact
= highest
392 def handle_event_msgerror(self
, account
, array
):
393 #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[,
395 full_jid_with_resource
= array
[0]
396 jids
= full_jid_with_resource
.split('/', 1)
399 if array
[1] == '503':
400 # If we get server-not-found error, stop sending chatstates
401 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
402 contact
.composing_xep
= False
408 gc_control
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
409 if not gc_control
and \
410 jid
in self
.minimized_controls
[account
]:
411 gc_control
= self
.minimized_controls
[account
][jid
]
412 if gc_control
and gc_control
.type_id
!= message_control
.TYPE_GC
:
415 if len(jids
) > 1: # it's a pm
419 ctrl
= session
.control
421 ctrl
= self
.msg_win_mgr
.get_control(full_jid_with_resource
,
425 tv
= gc_control
.list_treeview
426 model
= tv
.get_model()
427 iter_
= gc_control
.get_contact_iter(nick
)
429 show
= model
[iter_
][3]
432 gc_c
= gajim
.contacts
.create_gc_contact(room_jid
=jid
,
433 account
=account
, name
=nick
, show
=show
)
434 ctrl
= self
.new_private_chat(gc_c
, account
, session
)
436 ctrl
.print_conversation(_('Error %(code)s: %(msg)s') % {
437 'code': array
[1], 'msg': array
[2]}, 'status')
440 gc_control
.print_conversation(_('Error %(code)s: %(msg)s') % {
441 'code': array
[1], 'msg': array
[2]}, 'status')
442 if gc_control
.parent_win
and \
443 gc_control
.parent_win
.get_active_jid() == jid
:
444 gc_control
.set_subject(gc_control
.subject
)
447 if gajim
.jid_is_transport(jid
):
448 jid
= jid
.replace('@', '')
451 msg
= _('error while sending %(message)s ( %(error)s )') % {
452 'message': array
[3], 'error': msg
}
454 session
.roster_message(jid
, msg
, array
[4], msg_type
='error')
456 def handle_event_msgsent(self
, obj
):
457 #('MSGSENT', account, (jid, msg, keyID))
458 # do not play sound when standalone chatstate message (eg no msg)
459 if obj
.message
and gajim
.config
.get_per('soundevents', 'message_sent',
461 helpers
.play_sound('message_sent')
463 def handle_event_msgnotsent(self
, obj
):
464 #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
465 msg
= _('error while sending %(message)s ( %(error)s )') % {
466 'message': obj
.message
, 'error': obj
.error
}
468 # No session. This can happen when sending a message from
472 obj
.session
.roster_message(obj
.jid
, msg
, obj
.time_
, obj
.conn
.name
,
475 def handle_event_subscribe_presence(self
, obj
):
476 #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
477 account
= obj
.conn
.name
478 if helpers
.allow_popup_window(account
) or not self
.systray_enabled
:
479 dialogs
.SubscriptionRequestWindow(obj
.jid
, obj
.status
, account
,
483 self
.add_event(account
, obj
.jid
, 'subscription_request', (obj
.status
,
486 if helpers
.allow_showing_notification(account
):
487 path
= gtkgui_helpers
.get_icon_path('gajim-subscription_request',
489 event_type
= _('Subscription request')
490 notify
.popup(event_type
, obj
.jid
, account
, 'subscription_request',
491 path
, event_type
, obj
.jid
)
493 def handle_event_subscribed_presence(self
, obj
):
494 #('SUBSCRIBED', account, (jid, resource))
495 account
= obj
.conn
.name
496 if obj
.jid
in gajim
.contacts
.get_jid_list(account
):
497 c
= gajim
.contacts
.get_first_contact_from_jid(account
, obj
.jid
)
498 c
.resource
= obj
.resource
499 self
.roster
.remove_contact_from_groups(c
.jid
, account
,
500 [_('Not in Roster'), _('Observers')], update
=False)
503 attached_keys
= gajim
.config
.get_per('accounts', account
,
504 'attached_gpg_keys').split()
505 if obj
.jid
in attached_keys
:
506 keyID
= attached_keys
[attached_keys
.index(obj
.jid
) + 1]
507 name
= obj
.jid
.split('@', 1)[0]
508 name
= name
.split('%', 1)[0]
509 contact1
= gajim
.contacts
.create_contact(jid
=obj
.jid
,
510 account
=account
, name
=name
, groups
=[], show
='online',
511 status
='online', ask
='to', resource
=obj
.resource
, keyID
=keyID
)
512 gajim
.contacts
.add_contact(account
, contact1
)
513 self
.roster
.add_contact(obj
.jid
, account
)
514 dialogs
.InformationDialog(_('Authorization accepted'),
515 _('The contact "%s" has authorized you to see his or her status.')
518 def show_unsubscribed_dialog(self
, account
, contact
):
519 def on_yes(is_checked
, list_
):
520 self
.roster
.on_req_usub(None, list_
)
521 list_
= [(contact
, account
)]
523 _('Contact "%s" removed subscription from you') % contact
.jid
,
524 _('You will always see him or her as offline.\nDo you want to '
525 'remove him or her from your contact list?'),
526 on_response_yes
=(on_yes
, list_
))
527 # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
530 def handle_event_unsubscribed_presence(self
, obj
):
531 #('UNSUBSCRIBED', account, jid)
532 account
= obj
.conn
.name
533 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, obj
.jid
)
537 if helpers
.allow_popup_window(account
) or not self
.systray_enabled
:
538 self
.show_unsubscribed_dialog(account
, contact
)
541 self
.add_event(account
, obj
.jid
, 'unsubscribed', contact
)
543 if helpers
.allow_showing_notification(account
):
544 path
= gtkgui_helpers
.get_icon_path('gajim-unsubscribed', 48)
545 event_type
= _('Unsubscribed')
546 notify
.popup(event_type
, obj
.jid
, account
, 'unsubscribed', path
,
549 def handle_event_register_agent_info(self
, account
, array
):
550 # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
551 # info in a dataform if is_form is True
552 if array
[2] or 'instructions' in array
[1]:
553 config
.ServiceRegistrationWindow(array
[0], array
[1], account
,
556 dialogs
.ErrorDialog(_('Contact with "%s" cannot be established') \
557 % array
[0], _('Check your connection or try again later.'))
559 def handle_event_agent_info_items(self
, account
, array
):
560 #('AGENT_INFO_ITEMS', account, (agent, node, items))
561 our_jid
= gajim
.get_jid_from_account(account
)
562 if 'pep_services' in gajim
.interface
.instances
[account
] and \
564 gajim
.interface
.instances
[account
]['pep_services'].items_received(
567 def handle_event_myvcard(self
, account
, array
):
569 if 'NICKNAME' in array
and array
['NICKNAME']:
570 gajim
.nicks
[account
] = array
['NICKNAME']
571 elif 'FN' in array
and array
['FN']:
572 gajim
.nicks
[account
] = array
['FN']
573 if 'profile' in self
.instances
[account
]:
574 win
= self
.instances
[account
]['profile']
575 win
.set_values(array
)
576 if account
in self
.show_vcard_when_connect
:
577 self
.show_vcard_when_connect
.remove(account
)
579 if jid
in self
.instances
[account
]['infos']:
580 self
.instances
[account
]['infos'][jid
].set_values(array
)
582 def handle_event_vcard(self
, account
, vcard
):
583 # ('VCARD', account, data)
584 '''vcard holds the vcard data'''
586 resource
= vcard
.get('resource', '')
587 fjid
= jid
+ '/' + str(resource
)
591 if jid
in self
.instances
[account
]['infos']:
592 win
= self
.instances
[account
]['infos'][jid
]
593 elif resource
and fjid
in self
.instances
[account
]['infos']:
594 win
= self
.instances
[account
]['infos'][fjid
]
596 win
.set_values(vcard
)
598 # show avatar in chat
600 if resource
and self
.msg_win_mgr
.has_window(fjid
, account
):
601 win
= self
.msg_win_mgr
.get_window(fjid
, account
)
602 ctrl
= win
.get_control(fjid
, account
)
603 elif self
.msg_win_mgr
.has_window(jid
, account
):
604 win
= self
.msg_win_mgr
.get_window(jid
, account
)
605 ctrl
= win
.get_control(jid
, account
)
607 if ctrl
and ctrl
.type_id
!= message_control
.TYPE_GC
:
610 # Show avatar in roster or gc_roster
611 gc_ctrl
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
613 jid
in self
.minimized_controls
[account
]:
614 gc_ctrl
= self
.minimized_controls
[account
][jid
]
615 if gc_ctrl
and gc_ctrl
.type_id
== message_control
.TYPE_GC
:
616 gc_ctrl
.draw_avatar(resource
)
618 self
.roster
.draw_avatar(jid
, account
)
620 self
.remote_ctrl
.raise_signal('VcardInfo', (account
, vcard
))
622 def handle_event_last_status_time(self
, obj
):
623 # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
627 account
= obj
.conn
.name
628 c
= gajim
.contacts
.get_contact(account
, obj
.jid
, obj
.resource
)
629 if c
: # c can be none if it's a gc contact
631 c
.status
= obj
.status
632 self
.roster
.draw_contact(c
.jid
, account
) # draw offline status
633 last_time
= time
.localtime(time
.time() - obj
.seconds
)
634 if c
.show
== 'offline':
635 c
.last_status_time
= last_time
637 c
.last_activity_time
= last_time
638 if self
.roster
.tooltip
.id and self
.roster
.tooltip
.win
:
639 self
.roster
.tooltip
.update_last_time(last_time
)
641 def handle_event_gc_subject(self
, account
, array
):
642 #('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
643 jids
= array
[0].split('/', 1)
646 gc_control
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
648 if not gc_control
and \
649 jid
in self
.minimized_controls
[account
]:
650 gc_control
= self
.minimized_controls
[account
][jid
]
652 contact
= gajim
.contacts
.\
653 get_contact_with_highest_priority(account
, jid
)
655 contact
.status
= array
[1]
656 self
.roster
.draw_contact(jid
, account
)
660 gc_control
.set_subject(array
[1])
661 # Standard way, the message comes from the occupant who set the subject
664 text
= _('%(jid)s has set the subject to %(subject)s') % {
665 'jid': jids
[1], 'subject': array
[1]}
666 # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be
667 # deleted one day. We can receive a subject with a body that contains
668 # "X has set the subject to Y" ...
673 gc_control
.print_old_conversation(text
)
675 gc_control
.print_conversation(text
)
677 def handle_event_gc_config(self
, obj
):
678 #('GC_CONFIG', account, (jid, form_node)) config is a dict
679 account
= obj
.conn
.name
680 if obj
.jid
in gajim
.automatic_rooms
[account
]:
681 if 'continue_tag' in gajim
.automatic_rooms
[account
][obj
.jid
]:
682 # We're converting chat to muc. allow participants to invite
683 for f
in obj
.dataform
.iter_fields():
684 if f
.var
== 'muc#roomconfig_allowinvites':
686 elif f
.var
== 'muc#roomconfig_publicroom':
688 elif f
.var
== 'muc#roomconfig_membersonly':
690 elif f
.var
== 'public_list':
692 obj
.conn
.send_gc_config(obj
.jid
, obj
.dataform
.get_purged())
694 # use default configuration
695 obj
.conn
.send_gc_config(obj
.jid
, obj
.form_node
)
697 # check if it is necessary to add <continue />
699 if 'continue_tag' in gajim
.automatic_rooms
[account
][obj
.jid
]:
701 if 'invities' in gajim
.automatic_rooms
[account
][obj
.jid
]:
702 for jid
in gajim
.automatic_rooms
[account
][obj
.jid
]['invities']:
703 obj
.conn
.send_invite(obj
.jid
, jid
,
704 continue_tag
=continue_tag
)
705 del gajim
.automatic_rooms
[account
][obj
.jid
]
706 elif obj
.jid
not in self
.instances
[account
]['gc_config']:
707 self
.instances
[account
]['gc_config'][obj
.jid
] = \
708 config
.GroupchatConfigWindow(account
, obj
.jid
, obj
.dataform
)
710 def handle_event_gc_config_change(self
, account
, array
):
711 #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list
712 # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
713 # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes...
716 statusCode
= array
[1]
718 gc_control
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
719 if not gc_control
and \
720 jid
in self
.minimized_controls
[account
]:
721 gc_control
= self
.minimized_controls
[account
][jid
]
726 if '100' in statusCode
:
727 # Can be a presence (see chg_contact_status in groupchat_control.py)
728 changes
.append(_('Any occupant is allowed to see your full JID'))
729 gc_control
.is_anonymous
= False
730 if '102' in statusCode
:
731 changes
.append(_('Room now shows unavailable member'))
732 if '103' in statusCode
:
733 changes
.append(_('room now does not show unavailable members'))
734 if '104' in statusCode
:
735 changes
.append(_('A non-privacy-related room configuration change '
737 if '170' in statusCode
:
738 # Can be a presence (see chg_contact_status in groupchat_control.py)
739 changes
.append(_('Room logging is now enabled'))
740 if '171' in statusCode
:
741 changes
.append(_('Room logging is now disabled'))
742 if '172' in statusCode
:
743 changes
.append(_('Room is now non-anonymous'))
744 gc_control
.is_anonymous
= False
745 if '173' in statusCode
:
746 changes
.append(_('Room is now semi-anonymous'))
747 gc_control
.is_anonymous
= True
748 if '174' in statusCode
:
749 changes
.append(_('Room is now fully-anonymous'))
750 gc_control
.is_anonymous
= True
752 for change
in changes
:
753 gc_control
.print_conversation(change
)
755 def handle_event_gc_affiliation(self
, obj
):
756 #('GC_AFFILIATION', account, (room_jid, users_dict))
757 account
= obj
.conn
.name
758 if obj
.jid
in self
.instances
[account
]['gc_config']:
759 self
.instances
[account
]['gc_config'][obj
.jid
].\
760 affiliation_list_received(obj
.users_dict
)
762 def handle_event_gc_invitation(self
, obj
):
763 #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
764 jid
= gajim
.get_jid_without_resource(obj
.jid_from
)
765 account
= obj
.conn
.name
766 if helpers
.allow_popup_window(account
) or not self
.systray_enabled
:
767 dialogs
.InvitationReceivedDialog(account
, obj
.room_jid
, jid
,
768 obj
.password
, obj
.reason
, is_continued
=obj
.is_continued
)
771 self
.add_event(account
, jid
, 'gc-invitation', (obj
.room_jid
,
772 obj
.reason
, obj
.password
, obj
.is_continued
))
774 if helpers
.allow_showing_notification(account
):
775 path
= gtkgui_helpers
.get_icon_path('gajim-gc_invitation', 48)
776 event_type
= _('Groupchat Invitation')
777 notify
.popup(event_type
, jid
, account
, 'gc-invitation', path
,
778 event_type
, obj
.room_jid
)
780 def forget_gpg_passphrase(self
, keyid
):
781 if keyid
in self
.gpg_passphrase
:
782 del self
.gpg_passphrase
[keyid
]
785 def handle_event_bad_gpg_passphrase(self
, obj
):
786 #('BAD_PASSPHRASE', account, ())
787 if obj
.use_gpg_agent
:
788 sectext
= _('You configured Gajim to use GPG agent, but there is no'
789 ' GPG agent running or it returned a wrong passphrase.\n')
790 sectext
+= _('You are currently connected without your OpenPGP '
792 dialogs
.WarningDialog(_('Your passphrase is incorrect'), sectext
)
794 path
= gtkgui_helpers
.get_icon_path('gajim-warning', 48)
795 account
= obj
.conn
.name
796 notify
.popup('warning', account
, account
, 'warning', path
,
797 _('OpenGPG Passphrase Incorrect'),
798 _('You are currently connected without your OpenPGP key.'))
799 self
.forget_gpg_passphrase(obj
.keyID
)
801 def handle_event_gpg_password_required(self
, obj
):
802 #('GPG_PASSWORD_REQUIRED', account, (callback,))
803 if obj
.keyid
in self
.gpg_passphrase
:
804 request
= self
.gpg_passphrase
[obj
.keyid
]
806 request
= PassphraseRequest(obj
.keyid
)
807 self
.gpg_passphrase
[obj
.keyid
] = request
808 request
.add_callback(obj
.conn
.name
, obj
.callback
)
810 def handle_event_gpg_trust_key(self
, obj
):
811 #('GPG_ALWAYS_TRUST', account, callback)
814 obj
.conn
.gpg
.always_trust
= True
820 dialogs
.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to '
821 'encrypt this chat is not trusted. Do you really want to encrypt '
822 'this message?'), checktext
=_('_Do not ask me again'),
823 on_response_yes
=on_yes
, on_response_no
=on_no
)
825 def handle_event_password_required(self
, account
, array
):
826 #('PASSWORD_REQUIRED', account, None)
827 if account
in self
.pass_dialog
:
829 text
= _('Enter your password for account %s') % account
830 if passwords
.USER_HAS_GNOMEKEYRING
and \
831 not passwords
.USER_USES_GNOMEKEYRING
:
832 text
+= '\n' + _('Gnome Keyring is installed but not \
833 correctly started (environment variable probably not \
836 def on_ok(passphrase
, save
):
838 gajim
.config
.set_per('accounts', account
, 'savepass', True)
839 passwords
.save_password(account
, passphrase
)
840 gajim
.connections
[account
].set_password(passphrase
)
841 del self
.pass_dialog
[account
]
844 self
.roster
.set_state(account
, 'offline')
845 self
.roster
.update_status_combobox()
846 del self
.pass_dialog
[account
]
848 self
.pass_dialog
[account
] = dialogs
.PassphraseDialog(
849 _('Password Required'), text
, _('Save password'), ok_handler
=on_ok
,
850 cancel_handler
=on_cancel
)
852 def handle_event_roster_info(self
, obj
):
853 #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
854 account
= obj
.conn
.name
855 contacts
= gajim
.contacts
.get_contacts(account
, obj
.jid
)
856 if (not obj
.sub
or obj
.sub
== 'none') and \
857 (not obj
.ask
or obj
.ask
== 'none') and not obj
.nickname
and \
859 # contact removed us.
861 self
.roster
.remove_contact(obj
.jid
, account
, backend
=True)
864 if obj
.sub
== 'remove':
866 # Add new contact to roster
867 contact
= gajim
.contacts
.create_contact(jid
=obj
.jid
,
868 account
=account
, name
=obj
.nickname
, groups
=obj
.groups
,
869 show
='offline', sub
=obj
.sub
, ask
=obj
.ask
)
870 gajim
.contacts
.add_contact(account
, contact
)
871 self
.roster
.add_contact(obj
.jid
, account
)
873 # If contact has changed (sub, ask or group) update roster
874 # Mind about observer status changes:
875 # According to xep 0162, a contact is not an observer anymore when
876 # we asked for auth, so also remove him if ask changed
877 old_groups
= contacts
[0].groups
878 if contacts
[0].sub
!= obj
.sub
or contacts
[0].ask
!= obj
.ask\
879 or old_groups
!= obj
.groups
:
880 # c.get_shown_groups() has changed. Reflect that in
882 self
.roster
.remove_contact(obj
.jid
, account
, force
=True)
883 for contact
in contacts
:
884 contact
.name
= obj
.nickname
or ''
885 contact
.sub
= obj
.sub
886 contact
.ask
= obj
.ask
887 contact
.groups
= obj
.groups
or []
888 self
.roster
.add_contact(obj
.jid
, account
)
889 # Refilter and update old groups
890 for group
in old_groups
:
891 self
.roster
.draw_group(group
, account
)
892 self
.roster
.draw_contact(obj
.jid
, account
)
894 def handle_event_bookmarks(self
, obj
):
895 # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
896 # We received a bookmark item from the server (JEP48)
897 # Auto join GC windows if neccessary
899 self
.roster
.set_actions_menu_needs_rebuild()
900 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
901 # do not autojoin if we are invisible
902 if obj
.conn
.connected
== invisible_show
:
905 self
.auto_join_bookmarks(obj
.conn
.name
)
907 def handle_event_file_send_error(self
, account
, array
):
909 file_props
= array
[1]
910 ft
= self
.instances
['file_transfers']
911 ft
.set_status(file_props
['type'], file_props
['sid'], 'stop')
913 if helpers
.allow_popup_window(account
):
914 ft
.show_send_error(file_props
)
917 self
.add_event(account
, jid
, 'file-send-error', file_props
)
919 if helpers
.allow_showing_notification(account
):
920 path
= gtkgui_helpers
.get_icon_path('gajim-ft_error', 48)
921 event_type
= _('File Transfer Error')
922 notify
.popup(event_type
, jid
, account
, 'file-send-error', path
,
923 event_type
, file_props
['name'])
925 def handle_event_gmail_notify(self
, obj
):
927 gmail_new_messages
= int(obj
.newmsgs
)
928 gmail_messages_list
= obj
.gmail_messages_list
929 if not gajim
.config
.get('notify_on_new_gmail_email'):
931 path
= gtkgui_helpers
.get_icon_path('gajim-new_email_recv', 48)
932 title
= _('New mail on %(gmail_mail_address)s') % \
933 {'gmail_mail_address': jid
}
934 text
= i18n
.ngettext('You have %d new mail conversation',
935 'You have %d new mail conversations', gmail_new_messages
,
936 gmail_new_messages
, gmail_new_messages
)
938 if gajim
.config
.get('notify_on_new_gmail_email_extra'):
940 for gmessage
in gmail_messages_list
:
941 # FIXME: emulate Gtalk client popups. find out what they
942 # parse and how they decide what to show each message has a
943 # 'From', 'Subject' and 'Snippet' field
946 senders
= ',\n '.join(reversed(gmessage
['From']))
947 text
+= _('\n\nFrom: %(from_address)s\nSubject: '
948 '%(subject)s\n%(snippet)s') % {'from_address': senders
,
949 'subject': gmessage
['Subject'],
950 'snippet': gmessage
['Snippet']}
953 command
= gajim
.config
.get('notify_on_new_gmail_email_command')
955 Popen(command
, shell
=True)
957 if gajim
.config
.get_per('soundevents', 'gmail_received', 'enabled'):
958 helpers
.play_sound('gmail_received')
959 notify
.popup(_('New E-mail'), jid
, obj
.conn
.name
, 'gmail',
960 path_to_image
=path
, title
=title
, text
=text
)
962 def handle_event_file_request_error(self
, account
, array
):
963 # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
964 jid
, file_props
, errmsg
= array
965 jid
= gajim
.get_jid_without_resource(jid
)
966 ft
= self
.instances
['file_transfers']
967 ft
.set_status(file_props
['type'], file_props
['sid'], 'stop')
968 errno
= file_props
['error']
970 if helpers
.allow_popup_window(account
):
971 if errno
in (-4, -5):
972 ft
.show_stopped(jid
, file_props
, errmsg
)
974 ft
.show_request_error(file_props
)
977 if errno
in (-4, -5):
978 msg_type
= 'file-error'
980 msg_type
= 'file-request-error'
982 self
.add_event(account
, jid
, msg_type
, file_props
)
984 if helpers
.allow_showing_notification(account
):
985 # check if we should be notified
986 path
= gtkgui_helpers
.get_icon_path('gajim-ft_error', 48)
987 event_type
= _('File Transfer Error')
988 notify
.popup(event_type
, jid
, account
, msg_type
, path
,
989 title
= event_type
, text
= file_props
['name'])
991 def handle_event_file_request(self
, account
, array
):
993 jid
= gajim
.get_jid_without_resource(jid
)
994 if jid
not in gajim
.contacts
.get_jid_list(account
):
996 attached_keys
= gajim
.config
.get_per('accounts', account
,
997 'attached_gpg_keys').split()
998 if jid
in attached_keys
:
999 keyID
= attached_keys
[attached_keys
.index(jid
) + 1]
1000 contact
= gajim
.contacts
.create_not_in_roster_contact(jid
=jid
,
1001 account
=account
, keyID
=keyID
)
1002 gajim
.contacts
.add_contact(account
, contact
)
1003 self
.roster
.add_contact(contact
.jid
, account
)
1004 file_props
= array
[1]
1005 contact
= gajim
.contacts
.get_first_contact_from_jid(account
, jid
)
1007 if helpers
.allow_popup_window(account
):
1008 self
.instances
['file_transfers'].show_file_request(account
, contact
,
1012 self
.add_event(account
, jid
, 'file-request', file_props
)
1014 if helpers
.allow_showing_notification(account
):
1015 path
= gtkgui_helpers
.get_icon_path('gajim-ft_request', 48)
1016 txt
= _('%s wants to send you a file.') % gajim
.get_name_from_jid(
1018 event_type
= _('File Transfer Request')
1019 notify
.popup(event_type
, jid
, account
, 'file-request',
1020 path_to_image
= path
, title
= event_type
, text
= txt
)
1022 def handle_event_file_error(self
, title
, message
):
1023 dialogs
.ErrorDialog(title
, message
)
1025 def handle_event_file_progress(self
, account
, file_props
):
1026 if time
.time() - self
.last_ftwindow_update
> 0.5:
1027 # update ft window every 500ms
1028 self
.last_ftwindow_update
= time
.time()
1029 self
.instances
['file_transfers'].set_progress(file_props
['type'],
1030 file_props
['sid'], file_props
['received-len'])
1032 def handle_event_file_rcv_completed(self
, account
, file_props
):
1033 ft
= self
.instances
['file_transfers']
1034 if file_props
['error'] == 0:
1035 ft
.set_progress(file_props
['type'], file_props
['sid'],
1036 file_props
['received-len'])
1038 ft
.set_status(file_props
['type'], file_props
['sid'], 'stop')
1039 if 'stalled' in file_props
and file_props
['stalled'] or \
1040 'paused' in file_props
and file_props
['paused']:
1042 if file_props
['type'] == 'r': # we receive a file
1043 jid
= unicode(file_props
['sender'])
1044 else: # we send a file
1045 jid
= unicode(file_props
['receiver'])
1047 if helpers
.allow_popup_window(account
):
1048 if file_props
['error'] == 0:
1049 if gajim
.config
.get('notify_on_file_complete'):
1050 ft
.show_completed(jid
, file_props
)
1051 elif file_props
['error'] == -1:
1052 ft
.show_stopped(jid
, file_props
,
1053 error_msg
=_('Remote contact stopped transfer'))
1054 elif file_props
['error'] == -6:
1055 ft
.show_stopped(jid
, file_props
,
1056 error_msg
=_('Error opening file'))
1061 if file_props
['error'] == 0 and gajim
.config
.get(
1062 'notify_on_file_complete'):
1063 msg_type
= 'file-completed'
1064 event_type
= _('File Transfer Completed')
1065 elif file_props
['error'] in (-1, -6):
1066 msg_type
= 'file-stopped'
1067 event_type
= _('File Transfer Stopped')
1069 if event_type
== '':
1070 # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
1071 # this should never happen but it does. see process_result() in
1073 # who calls this func (sth is really wrong unless this func is also
1074 # registered as progress_cb
1078 self
.add_event(account
, jid
, msg_type
, file_props
)
1080 if file_props
is not None:
1081 if file_props
['type'] == 'r':
1082 # get the name of the sender, as it is in the roster
1083 sender
= unicode(file_props
['sender']).split('/')[0]
1084 name
= gajim
.contacts
.get_first_contact_from_jid(account
,
1085 sender
).get_shown_name()
1086 filename
= os
.path
.basename(file_props
['file-name'])
1087 if event_type
== _('File Transfer Completed'):
1088 txt
= _('You successfully received %(filename)s from '
1089 '%(name)s.') % {'filename': filename
, 'name': name
}
1090 img_name
= 'gajim-ft_done'
1092 txt
= _('File transfer of %(filename)s from %(name)s '
1093 'stopped.') % {'filename': filename
, 'name': name
}
1094 img_name
= 'gajim-ft_stopped'
1096 receiver
= file_props
['receiver']
1097 if hasattr(receiver
, 'jid'):
1098 receiver
= receiver
.jid
1099 receiver
= receiver
.split('/')[0]
1100 # get the name of the contact, as it is in the roster
1101 name
= gajim
.contacts
.get_first_contact_from_jid(account
,
1102 receiver
).get_shown_name()
1103 filename
= os
.path
.basename(file_props
['file-name'])
1104 if event_type
== _('File Transfer Completed'):
1105 txt
= _('You successfully sent %(filename)s to %(name)s.')\
1106 % {'filename': filename
, 'name': name
}
1107 img_name
= 'gajim-ft_done'
1109 txt
= _('File transfer of %(filename)s to %(name)s '
1110 'stopped.') % {'filename': filename
, 'name': name
}
1111 img_name
= 'gajim-ft_stopped'
1112 path
= gtkgui_helpers
.get_icon_path(img_name
, 48)
1117 if gajim
.config
.get('notify_on_file_complete') and \
1118 (gajim
.config
.get('autopopupaway') or \
1119 gajim
.connections
[account
].connected
in (2, 3)):
1120 # we want to be notified and we are online/chat or we don't mind
1121 # bugged when away/na/busy
1122 notify
.popup(event_type
, jid
, account
, msg_type
, path_to_image
=path
,
1123 title
=event_type
, text
=txt
)
1125 def ask_offline_status(self
, account
):
1126 for contact
in gajim
.contacts
.iter_contacts(account
):
1127 gajim
.connections
[account
].request_last_status_time(contact
.jid
,
1130 def handle_event_signed_in(self
, account
, empty
):
1132 SIGNED_IN event is emitted when we sign in, so handle it
1134 # ('SIGNED_IN', account, ())
1135 # block signed in notifications for 30 seconds
1136 gajim
.block_signed_in_notifications
[account
] = True
1137 self
.roster
.set_actions_menu_needs_rebuild()
1138 self
.roster
.draw_account(account
)
1139 state
= self
.sleeper
.getState()
1140 connected
= gajim
.connections
[account
].connected
1141 if gajim
.config
.get('ask_offline_status_on_connection'):
1142 # Ask offline status in 1 minute so w'are sure we got all online
1144 gobject
.timeout_add_seconds(60, self
.ask_offline_status
, account
)
1145 if state
!= common
.sleepy
.STATE_UNKNOWN
and connected
in (2, 3):
1146 # we go online or free for chat, so we activate auto status
1147 gajim
.sleeper_state
[account
] = 'online'
1148 elif not ((state
== common
.sleepy
.STATE_AWAY
and connected
== 4) or \
1149 (state
== common
.sleepy
.STATE_XA
and connected
== 5)):
1150 # If we are autoaway/xa and come back after a disconnection, do
1152 # Else disable autoaway
1153 gajim
.sleeper_state
[account
] = 'off'
1154 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
1155 # We cannot join rooms if we are invisible
1156 if gajim
.connections
[account
].connected
== invisible_show
:
1158 # join already open groupchats
1159 for gc_control
in self
.msg_win_mgr
.get_controls(
1160 message_control
.TYPE_GC
) + self
.minimized_controls
[account
].values():
1161 if account
!= gc_control
.account
:
1163 room_jid
= gc_control
.room_jid
1164 if room_jid
in gajim
.gc_connected
[account
] and \
1165 gajim
.gc_connected
[account
][room_jid
]:
1167 nick
= gc_control
.nick
1168 password
= gajim
.gc_passwords
.get(room_jid
, '')
1169 gajim
.connections
[account
].join_gc(nick
, room_jid
, password
)
1170 # send currently played music
1171 if gajim
.connections
[account
].pep_supported
and dbus_support
.supported \
1172 and gajim
.config
.get_per('accounts', account
, 'publish_tune'):
1173 self
.enable_music_listener()
1174 # enable location listener
1175 if gajim
.connections
[account
].pep_supported
and dbus_support
.supported \
1176 and gajim
.config
.get_per('accounts', account
, 'publish_location'):
1177 location_listener
.enable()
1178 if gajim
.connections
[account
].archiving_supported
:
1179 # Start merging logs from server
1180 gajim
.connections
[account
].request_modifications_page(
1181 gajim
.config
.get_per('accounts', account
, 'last_archiving_time'))
1182 gajim
.config
.set_per('accounts', account
, 'last_archiving_time',
1183 time
.strftime('%Y-%m-%dT%H:%M:%SZ', time
.gmtime()))
1185 def handle_event_metacontacts(self
, account
, tags_list
):
1186 gajim
.contacts
.define_metacontacts(account
, tags_list
)
1187 self
.roster
.redraw_metacontacts(account
)
1189 def handle_atom_entry(self
, obj
):
1190 AtomWindow
.newAtomEntry(obj
.atom_entry
)
1192 def handle_event_failed_decrypt(self
, account
, data
):
1193 jid
, tim
, session
= data
1195 details
= _('Unable to decrypt message from '
1196 '%s\nIt may have been tampered with.') % jid
1198 ctrl
= session
.control
1200 ctrl
.print_conversation_line(details
, 'status', '', tim
)
1202 dialogs
.WarningDialog(_('Unable to decrypt message'),
1205 # terminate the session
1206 session
.terminate_e2e()
1207 session
.conn
.delete_session(jid
, session
.thread_id
)
1209 # restart the session
1211 ctrl
.begin_e2e_negotiation()
1213 def handle_event_privacy_lists_received(self
, account
, data
):
1214 # ('PRIVACY_LISTS_RECEIVED', account, list)
1215 if account
not in self
.instances
:
1217 if 'privacy_lists' in self
.instances
[account
]:
1218 self
.instances
[account
]['privacy_lists'].privacy_lists_received(
1221 def handle_event_privacy_list_received(self
, account
, data
):
1222 # ('PRIVACY_LIST_RECEIVED', account, (name, rules))
1223 if account
not in self
.instances
:
1227 if 'privacy_list_%s' % name
in self
.instances
[account
]:
1228 self
.instances
[account
]['privacy_list_%s' % name
].\
1229 privacy_list_received(rules
)
1231 con
= gajim
.connections
[account
]
1232 con
.blocked_contacts
= []
1233 con
.blocked_groups
= []
1234 con
.blocked_list
= []
1235 gajim
.connections
[account
].blocked_all
= False
1237 if rule
['action'] == 'allow':
1238 if not 'type' in rule
:
1239 con
.blocked_all
= False
1240 elif rule
['type'] == 'jid' and rule
['value'] in \
1241 con
.blocked_contacts
:
1242 con
.blocked_contacts
.remove(rule
['value'])
1243 elif rule
['type'] == 'group' and rule
['value'] in \
1245 con
.blocked_groups
.remove(rule
['value'])
1246 elif rule
['action'] == 'deny':
1247 if not 'type' in rule
:
1248 con
.blocked_all
= True
1249 elif rule
['type'] == 'jid' and rule
['value'] not in \
1250 con
.blocked_contacts
:
1251 con
.blocked_contacts
.append(rule
['value'])
1252 elif rule
['type'] == 'group' and rule
['value'] not in \
1254 con
.blocked_groups
.append(rule
['value'])
1255 con
.blocked_list
.append(rule
)
1256 if 'blocked_contacts' in self
.instances
[account
]:
1257 self
.instances
[account
]['blocked_contacts'].\
1258 privacy_list_received(rules
)
1260 def handle_event_privacy_lists_active_default(self
, account
, data
):
1263 # Send to all privacy_list_* windows as we can't know which one asked
1264 for win
in self
.instances
[account
]:
1265 if win
.startswith('privacy_list_'):
1266 self
.instances
[account
][win
].check_active_default(data
)
1268 def handle_event_privacy_list_removed(self
, account
, name
):
1269 # ('PRIVACY_LISTS_REMOVED', account, name)
1270 if account
not in self
.instances
:
1272 if 'privacy_lists' in self
.instances
[account
]:
1273 self
.instances
[account
]['privacy_lists'].privacy_list_removed(name
)
1275 def handle_event_zc_name_conflict(self
, account
, data
):
1276 def on_ok(new_name
):
1277 gajim
.config
.set_per('accounts', account
, 'name', new_name
)
1278 show
= gajim
.connections
[account
].old_show
1279 status
= gajim
.connections
[account
].status
1280 gajim
.connections
[account
].username
= new_name
1281 gajim
.connections
[account
].change_status(show
, status
)
1283 gajim
.connections
[account
].change_status('offline', '')
1285 dlg
= dialogs
.InputDialog(_('Username Conflict'),
1286 _('Please type a new username for your local account'),
1287 input_str
=data
, is_modal
=True, ok_handler
=on_ok
,
1288 cancel_handler
=on_cancel
)
1290 def handle_event_resource_conflict(self
, obj
):
1291 # ('RESOURCE_CONFLICT', account, ())
1292 # First we go offline, but we don't overwrite status message
1293 account
= obj
.conn
.name
1295 self
.roster
.send_status(account
, 'offline', conn
.status
)
1296 def on_ok(new_resource
):
1297 gajim
.config
.set_per('accounts', account
, 'resource', new_resource
)
1298 self
.roster
.send_status(account
, conn
.old_show
, conn
.status
)
1299 proposed_resource
= conn
.server_resource
1300 proposed_resource
+= gajim
.config
.get('gc_proposed_nick_char')
1301 dlg
= dialogs
.ResourceConflictDialog(_('Resource Conflict'),
1302 _('You are already connected to this account with the same '
1303 'resource. Please type a new one'), resource
=proposed_resource
,
1306 def handle_event_jingle_incoming(self
, obj
):
1307 # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
1309 # TODO: conditional blocking if peer is not in roster
1311 account
= obj
.conn
.name
1312 content_types
= set(c
[0] for c
in obj
.contents
)
1314 # check type of jingle session
1315 if 'audio' in content_types
or 'video' in content_types
:
1317 # we now handle only voip, so the only thing we will do here is
1318 # not to return from function
1321 # unknown session type... it should be declined in common/jingle.py
1324 ctrl
= (self
.msg_win_mgr
.get_control(obj
.fjid
, account
)
1325 or self
.msg_win_mgr
.get_control(obj
.jid
, account
))
1327 if 'audio' in content_types
:
1328 ctrl
.set_audio_state('connection_received', obj
.sid
)
1329 if 'video' in content_types
:
1330 ctrl
.set_video_state('connection_received', obj
.sid
)
1332 dlg
= dialogs
.VoIPCallReceivedDialog
.get_dialog(obj
.fjid
, obj
.sid
)
1334 dlg
.add_contents(content_types
)
1337 if helpers
.allow_popup_window(account
):
1338 dialogs
.VoIPCallReceivedDialog(account
, obj
.fjid
, obj
.sid
,
1342 self
.add_event(account
, obj
.jid
, 'jingle-incoming', (obj
.fjid
, obj
.sid
,
1345 if helpers
.allow_showing_notification(account
):
1346 # TODO: we should use another pixmap ;-)
1347 txt
= _('%s wants to start a voice chat.') % \
1348 gajim
.get_name_from_jid(account
, obj
.fjid
)
1349 path
= gtkgui_helpers
.get_icon_path('gajim-mic_active', 48)
1350 event_type
= _('Voice Chat Request')
1351 notify
.popup(event_type
, obj
.fjid
, account
, 'jingle-incoming',
1352 path_to_image
=path
, title
=event_type
, text
=txt
)
1354 def handle_event_jingle_connected(self
, obj
):
1355 # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
1356 if obj
.media
in ('audio', 'video'):
1357 account
= obj
.conn
.name
1358 ctrl
= (self
.msg_win_mgr
.get_control(obj
.fjid
, account
)
1359 or self
.msg_win_mgr
.get_control(obj
.jid
, account
))
1361 if obj
.media
== 'audio':
1362 ctrl
.set_audio_state('connected', obj
.sid
)
1364 ctrl
.set_video_state('connected', obj
.sid
)
1366 def handle_event_jingle_disconnected(self
, obj
):
1367 # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
1368 account
= obj
.conn
.name
1369 ctrl
= (self
.msg_win_mgr
.get_control(obj
.fjid
, account
)
1370 or self
.msg_win_mgr
.get_control(obj
.jid
, account
))
1372 if obj
.media
is None:
1373 ctrl
.stop_jingle(sid
=obj
.sid
, reason
=obj
.reason
)
1374 elif obj
.media
== 'audio':
1375 ctrl
.set_audio_state('stop', sid
=obj
.sid
, reason
=obj
.reason
)
1376 elif obj
.media
== 'video':
1377 ctrl
.set_video_state('stop', sid
=obj
.sid
, reason
=obj
.reason
)
1378 dialog
= dialogs
.VoIPCallReceivedDialog
.get_dialog(obj
.fjid
, obj
.sid
)
1380 if obj
.media
is None:
1381 dialog
.dialog
.destroy()
1383 dialog
.remove_contents((obj
.media
, ))
1385 def handle_event_jingle_error(self
, obj
):
1386 # ('JINGLE_ERROR', account, (peerjid, sid, reason))
1387 account
= obj
.conn
.name
1388 ctrl
= (self
.msg_win_mgr
.get_control(obj
.fjid
, account
)
1389 or self
.msg_win_mgr
.get_control(obj
.jid
, account
))
1391 ctrl
.set_audio_state('error', reason
=obj
.reason
)
1393 def handle_event_pep_config(self
, account
, data
):
1394 # ('PEP_CONFIG', account, (node, form))
1395 if 'pep_services' in self
.instances
[account
]:
1396 self
.instances
[account
]['pep_services'].config(data
[0], data
[1])
1398 def handle_event_roster_item_exchange(self
, obj
):
1399 # data = (action in [add, delete, modify], exchange_list, jid_from)
1400 dialogs
.RosterItemExchangeWindow(obj
.conn
.name
, obj
.action
,
1401 obj
.exchange_items_list
, obj
.fjid
)
1403 def handle_event_unique_room_id_supported(self
, account
, data
):
1405 Receive confirmation that unique_room_id are supported
1407 # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
1409 instance
.unique_room_id_supported(data
[0], data
[2])
1411 def handle_event_unique_room_id_unsupported(self
, account
, data
):
1412 # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance)
1414 instance
.unique_room_id_error(data
[0])
1416 def handle_event_ssl_error(self
, account
, data
):
1417 # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
1418 server
= gajim
.config
.get_per('accounts', account
, 'hostname')
1420 def on_ok(is_checked
):
1421 del self
.instances
[account
]['online_dialog']['ssl_error']
1423 # Check if cert is already in file
1425 if os
.path
.isfile(gajim
.MY_CACERTS
):
1426 f
= open(gajim
.MY_CACERTS
)
1429 if data
[2] in certs
:
1430 dialogs
.ErrorDialog(_('Certificate Already in File'),
1431 _('This certificate is already in file %s, so it\'s '
1432 'not added again.') % gajim
.MY_CACERTS
)
1434 f
= open(gajim
.MY_CACERTS
, 'a')
1435 f
.write(server
+ '\n')
1436 f
.write(data
[2] + '\n\n')
1438 gajim
.config
.set_per('accounts', account
,
1439 'ssl_fingerprint_sha1', data
[3])
1441 ignore_ssl_errors
= gajim
.config
.get_per('accounts', account
,
1442 'ignore_ssl_errors').split()
1443 ignore_ssl_errors
.append(str(data
[1]))
1444 gajim
.config
.set_per('accounts', account
, 'ignore_ssl_errors',
1445 ' '.join(ignore_ssl_errors
))
1446 gajim
.connections
[account
].ssl_certificate_accepted()
1449 del self
.instances
[account
]['online_dialog']['ssl_error']
1450 gajim
.connections
[account
].disconnect(on_purpose
=True)
1451 self
.handle_event_status(account
, 'offline')
1453 pritext
= _('Error verifying SSL certificate')
1454 sectext
= _('There was an error verifying the SSL certificate of your '
1455 'jabber server: %(error)s\nDo you still want to connect to this '
1456 'server?') % {'error': data
[0]}
1457 if data
[1] in (18, 27):
1458 checktext1
= _('Add this certificate to the list of trusted '
1459 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % data
[3]
1462 checktext2
= _('Ignore this error for this certificate.')
1463 if 'ssl_error' in self
.instances
[account
]['online_dialog']:
1464 self
.instances
[account
]['online_dialog']['ssl_error'].destroy()
1465 self
.instances
[account
]['online_dialog']['ssl_error'] = \
1466 dialogs
.ConfirmationDialogDoubleCheck(pritext
, sectext
, checktext1
,
1467 checktext2
, on_response_ok
=on_ok
, on_response_cancel
=on_cancel
)
1469 def handle_event_fingerprint_error(self
, account
, data
):
1470 # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
1471 def on_yes(is_checked
):
1472 del self
.instances
[account
]['online_dialog']['fingerprint_error']
1473 gajim
.config
.set_per('accounts', account
, 'ssl_fingerprint_sha1',
1475 # Reset the ignored ssl errors
1476 gajim
.config
.set_per('accounts', account
, 'ignore_ssl_errors', '')
1477 gajim
.connections
[account
].ssl_certificate_accepted()
1479 del self
.instances
[account
]['online_dialog']['fingerprint_error']
1480 gajim
.connections
[account
].disconnect(on_purpose
=True)
1481 self
.handle_event_status(account
, 'offline')
1482 pritext
= _('SSL certificate error')
1483 sectext
= _('It seems the SSL certificate of account %(account)s has '
1484 'changed or your connection is being hacked.\nOld fingerprint: '
1485 '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect '
1486 'and update the fingerprint of the certificate?') % \
1487 {'account': account
, 'old': gajim
.config
.get_per('accounts',
1488 account
, 'ssl_fingerprint_sha1'), 'new': data
[0]}
1489 if 'fingerprint_error' in self
.instances
[account
]['online_dialog']:
1490 self
.instances
[account
]['online_dialog']['fingerprint_error'].\
1492 self
.instances
[account
]['online_dialog']['fingerprint_error'] = \
1493 dialogs
.YesNoDialog(pritext
, sectext
, on_response_yes
=on_yes
,
1494 on_response_no
=on_no
)
1496 def handle_event_plain_connection(self
, account
, data
):
1497 # ('PLAIN_CONNECTION', account, (connection))
1498 def on_ok(is_checked
):
1499 if not is_checked
[0]:
1502 # On cancel call del self.instances, so don't call it another time
1504 del self
.instances
[account
]['online_dialog']['plain_connection']
1506 gajim
.config
.set_per('accounts', account
,
1507 'warn_when_plaintext_connection', False)
1508 gajim
.connections
[account
].connection_accepted(data
[0], 'plain')
1510 del self
.instances
[account
]['online_dialog']['plain_connection']
1511 gajim
.connections
[account
].disconnect(on_purpose
=True)
1512 self
.handle_event_status(account
, 'offline')
1513 pritext
= _('Insecure connection')
1514 sectext
= _('You are about to connect to the server with an insecure '
1515 'connection. This means all your conversations will be '
1516 'exchanged unencrypted. Are you sure you want to do that?')
1517 checktext1
= _('Yes, I really want to connect insecurely')
1518 checktext2
= _('_Do not ask me again')
1519 if 'plain_connection' in self
.instances
[account
]['online_dialog']:
1520 self
.instances
[account
]['online_dialog']['plain_connection'].\
1522 self
.instances
[account
]['online_dialog']['plain_connection'] = \
1523 dialogs
.ConfirmationDialogDoubleCheck(pritext
, sectext
, checktext1
,
1524 checktext2
, on_response_ok
=on_ok
, on_response_cancel
=on_cancel
,
1527 def handle_event_insecure_ssl_connection(self
, account
, data
):
1528 # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
1529 def on_ok(is_checked
):
1530 if not is_checked
[0]:
1533 del self
.instances
[account
]['online_dialog']['insecure_ssl']
1535 gajim
.config
.set_per('accounts', account
,
1536 'warn_when_insecure_ssl_connection', False)
1537 if gajim
.connections
[account
].connected
== 0:
1538 # We have been disconnecting (too long time since window is
1540 # re-connect with auto-accept
1541 gajim
.connections
[account
].connection_auto_accepted
= True
1542 show
, msg
= gajim
.connections
[account
].continue_connect_info
[:2]
1543 self
.roster
.send_status(account
, show
, msg
)
1545 gajim
.connections
[account
].connection_accepted(data
[0], data
[1])
1547 del self
.instances
[account
]['online_dialog']['insecure_ssl']
1548 gajim
.connections
[account
].disconnect(on_purpose
=True)
1549 self
.handle_event_status(account
, 'offline')
1550 pritext
= _('Insecure connection')
1551 sectext
= _('You are about to send your password on an insecure '
1552 'connection. You should install PyOpenSSL to prevent that. Are you '
1553 'sure you want to do that?')
1554 checktext1
= _('Yes, I really want to connect insecurely')
1555 checktext2
= _('_Do not ask me again')
1556 if 'insecure_ssl' in self
.instances
[account
]['online_dialog']:
1557 self
.instances
[account
]['online_dialog']['insecure_ssl'].destroy()
1558 self
.instances
[account
]['online_dialog']['insecure_ssl'] = \
1559 dialogs
.ConfirmationDialogDoubleCheck(pritext
, sectext
, checktext1
,
1560 checktext2
, on_response_ok
=on_ok
, on_response_cancel
=on_cancel
,
1563 def handle_event_insecure_password(self
, account
, data
):
1564 # ('INSECURE_PASSWORD', account, ())
1565 def on_ok(is_checked
):
1566 if not is_checked
[0]:
1569 del self
.instances
[account
]['online_dialog']['insecure_password']
1571 gajim
.config
.set_per('accounts', account
,
1572 'warn_when_insecure_password', False)
1573 if gajim
.connections
[account
].connected
== 0:
1574 # We have been disconnecting (too long time since window is
1576 # re-connect with auto-accept
1577 gajim
.connections
[account
].connection_auto_accepted
= True
1578 show
, msg
= gajim
.connections
[account
].continue_connect_info
[:2]
1579 self
.roster
.send_status(account
, show
, msg
)
1581 gajim
.connections
[account
].accept_insecure_password()
1583 del self
.instances
[account
]['online_dialog']['insecure_password']
1584 gajim
.connections
[account
].disconnect(on_purpose
=True)
1585 self
.handle_event_status(account
, 'offline')
1586 pritext
= _('Insecure connection')
1587 sectext
= _('You are about to send your password unencrypted on an '
1588 'insecure connection. Are you sure you want to do that?')
1589 checktext1
= _('Yes, I really want to connect insecurely')
1590 checktext2
= _('_Do not ask me again')
1591 if 'insecure_password' in self
.instances
[account
]['online_dialog']:
1592 self
.instances
[account
]['online_dialog']['insecure_password'].\
1594 self
.instances
[account
]['online_dialog']['insecure_password'] = \
1595 dialogs
.ConfirmationDialogDoubleCheck(pritext
, sectext
, checktext1
,
1596 checktext2
, on_response_ok
=on_ok
, on_response_cancel
=on_cancel
,
1599 def create_core_handlers_list(self
):
1601 'WARNING': [self
.handle_event_warning
],
1602 'ERROR': [self
.handle_event_error
],
1603 'DB_ERROR': [self
.handle_event_db_error
],
1604 'INFORMATION': [self
.handle_event_information
],
1605 'MSGERROR': [self
.handle_event_msgerror
],
1606 'REGISTER_AGENT_INFO': [self
.handle_event_register_agent_info
],
1607 'AGENT_INFO_ITEMS': [self
.handle_event_agent_info_items
],
1608 'MYVCARD': [self
.handle_event_myvcard
],
1609 'VCARD': [self
.handle_event_vcard
],
1610 'GC_SUBJECT': [self
.handle_event_gc_subject
],
1611 'GC_CONFIG_CHANGE': [self
.handle_event_gc_config_change
],
1612 'FILE_REQUEST': [self
.handle_event_file_request
],
1613 'FILE_REQUEST_ERROR': [self
.handle_event_file_request_error
],
1614 'FILE_SEND_ERROR': [self
.handle_event_file_send_error
],
1615 'SIGNED_IN': [self
.handle_event_signed_in
],
1616 'METACONTACTS': [self
.handle_event_metacontacts
],
1617 'FAILED_DECRYPT': [self
.handle_event_failed_decrypt
],
1618 'PRIVACY_LISTS_RECEIVED': \
1619 [self
.handle_event_privacy_lists_received
],
1620 'PRIVACY_LIST_RECEIVED': [self
.handle_event_privacy_list_received
],
1621 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
1622 [self
.handle_event_privacy_lists_active_default
],
1623 'PRIVACY_LIST_REMOVED': [self
.handle_event_privacy_list_removed
],
1624 'ZC_NAME_CONFLICT': [self
.handle_event_zc_name_conflict
],
1625 'PEP_CONFIG': [self
.handle_event_pep_config
],
1626 'UNIQUE_ROOM_ID_UNSUPPORTED': \
1627 [self
.handle_event_unique_room_id_unsupported
],
1628 'UNIQUE_ROOM_ID_SUPPORTED': \
1629 [self
.handle_event_unique_room_id_supported
],
1630 'PASSWORD_REQUIRED': [self
.handle_event_password_required
],
1631 'SSL_ERROR': [self
.handle_event_ssl_error
],
1632 'FINGERPRINT_ERROR': [self
.handle_event_fingerprint_error
],
1633 'PLAIN_CONNECTION': [self
.handle_event_plain_connection
],
1634 'INSECURE_SSL_CONNECTION': \
1635 [self
.handle_event_insecure_ssl_connection
],
1636 'INSECURE_PASSWORD': [self
.handle_event_insecure_password
],
1637 'atom-entry-received': [self
.handle_atom_entry
],
1638 'bad-gpg-passphrase': [self
.handle_event_bad_gpg_passphrase
],
1639 'bookmarks-received': [self
.handle_event_bookmarks
],
1640 'connection-lost': [self
.handle_event_connection_lost
],
1641 'gc-invitation-received': [self
.handle_event_gc_invitation
],
1642 'gc-presence-received': [self
.handle_event_gc_presence
],
1643 'gmail-notify': [self
.handle_event_gmail_notify
],
1644 'gpg-password-required': [self
.handle_event_gpg_password_required
],
1645 'gpg-trust-key': [self
.handle_event_gpg_trust_key
],
1646 'http-auth-received': [self
.handle_event_http_auth
],
1647 'iq-error-received': [self
.handle_event_iq_error
],
1648 'jingle-connected-received': [self
.handle_event_jingle_connected
],
1649 'jingle-disconnected-received': [
1650 self
.handle_event_jingle_disconnected
],
1651 'jingle-error-received': [self
.handle_event_jingle_error
],
1652 'jingle-request-received': [self
.handle_event_jingle_incoming
],
1653 'last-result-received': [self
.handle_event_last_status_time
],
1654 'message-not-sent': [self
.handle_event_msgnotsent
],
1655 'message-sent': [self
.handle_event_msgsent
],
1656 'muc-admin-received': [self
.handle_event_gc_affiliation
],
1657 'muc-owner-received': [self
.handle_event_gc_config
],
1658 'our-show': [self
.handle_event_status
],
1659 'presence-received': [self
.handle_event_presence
],
1660 'roster-info': [self
.handle_event_roster_info
],
1661 'roster-item-exchange-received': \
1662 [self
.handle_event_roster_item_exchange
],
1663 'stream-conflict-received': [self
.handle_event_resource_conflict
],
1664 'subscribe-presence-received': [
1665 self
.handle_event_subscribe_presence
],
1666 'subscribed-presence-received': [
1667 self
.handle_event_subscribed_presence
],
1668 'unsubscribed-presence-received': [
1669 self
.handle_event_unsubscribed_presence
],
1672 def register_core_handlers(self
):
1674 Register core handlers in Global Events Dispatcher (GED).
1676 This is part of rewriting whole events handling system to use GED.
1678 for event_name
, event_handlers
in self
.handlers
.iteritems():
1679 for event_handler
in event_handlers
:
1680 gajim
.ged
.register_event_handler(event_name
, ged
.GUI1
,
1683 ################################################################################
1684 ### Methods dealing with gajim.events
1685 ################################################################################
1687 def add_event(self
, account
, jid
, type_
, event_args
):
1689 Add an event to the gajim.events var
1691 # We add it to the gajim.events queue
1692 # Do we have a queue?
1693 jid
= gajim
.get_jid_without_resource(jid
)
1694 no_queue
= len(gajim
.events
.get_events(account
, jid
)) == 0
1695 # type_ can be gc-invitation file-send-error file-error
1696 # file-request-error file-request file-completed file-stopped
1698 # event_type can be in advancedNotificationWindow.events_list
1699 event_types
= {'file-request': 'ft_request',
1700 'file-completed': 'ft_finished'}
1701 event_type
= event_types
.get(type_
)
1702 show_in_roster
= notify
.get_show_in_roster(event_type
, account
, jid
)
1703 show_in_systray
= notify
.get_show_in_systray(event_type
, account
, jid
)
1704 event
= gajim
.events
.create_event(type_
, event_args
,
1705 show_in_roster
=show_in_roster
,
1706 show_in_systray
=show_in_systray
)
1707 gajim
.events
.add_event(account
, jid
, event
)
1709 self
.roster
.show_title()
1710 if no_queue
: # We didn't have a queue: we change icons
1711 if not gajim
.contacts
.get_contact_with_highest_priority(account
,
1713 if type_
== 'gc-invitation':
1714 self
.roster
.add_groupchat(jid
, account
, status
='offline')
1716 # add contact to roster ("Not In The Roster") if he is not
1717 self
.roster
.add_to_not_in_the_roster(account
, jid
)
1719 self
.roster
.draw_contact(jid
, account
)
1721 # Select the big brother contact in roster, it's visible because it has
1723 family
= gajim
.contacts
.get_metacontacts_family(account
, jid
)
1725 nearby_family
, bb_jid
, bb_account
= \
1726 gajim
.contacts
.get_nearby_family_and_big_brother(family
,
1729 bb_jid
, bb_account
= jid
, account
1730 self
.roster
.select_contact(bb_jid
, bb_account
)
1732 def handle_event(self
, account
, fjid
, type_
):
1737 resource
= gajim
.get_resource_from_jid(fjid
)
1738 jid
= gajim
.get_jid_without_resource(fjid
)
1740 if type_
in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
1741 w
= self
.msg_win_mgr
.get_window(jid
, account
)
1742 if jid
in self
.minimized_controls
[account
]:
1743 self
.roster
.on_groupchat_maximized(None, jid
, account
)
1746 ctrl
= self
.msg_win_mgr
.get_gc_control(jid
, account
)
1748 elif type_
in ('printed_chat', 'chat', ''):
1749 # '' is for log in/out notifications
1752 event
= gajim
.events
.get_first_event(account
, fjid
, type_
)
1754 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
1758 if type_
== 'printed_chat':
1759 ctrl
= event
.parameters
[0]
1760 elif type_
== 'chat':
1761 session
= event
.parameters
[8]
1762 ctrl
= session
.control
1764 ctrl
= self
.msg_win_mgr
.get_control(fjid
, account
)
1767 highest_contact
= gajim
.contacts
.\
1768 get_contact_with_highest_priority(account
, jid
)
1769 # jid can have a window if this resource was lower when he sent
1770 # message and is now higher because the other one is offline
1771 if resource
and highest_contact
.resource
== resource
and \
1772 not self
.msg_win_mgr
.has_window(jid
, account
):
1773 # remove resource of events too
1774 gajim
.events
.change_jid(account
, fjid
, jid
)
1779 contact
= gajim
.contacts
.get_contact(account
, jid
, resource
)
1781 contact
= highest_contact
1783 ctrl
= self
.new_chat(contact
, account
, resource
=resource
,
1786 gajim
.last_message_time
[account
][jid
] = 0 # long time ago
1789 elif type_
in ('printed_pm', 'pm'):
1790 # assume that the most recently updated control we have for this
1791 # party is the one that this event was in
1792 event
= gajim
.events
.get_first_event(account
, fjid
, type_
)
1794 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
1796 if type_
== 'printed_pm':
1797 ctrl
= event
.parameters
[0]
1799 session
= event
.parameters
[8]
1801 if session
and session
.control
:
1802 ctrl
= session
.control
1806 gc_contact
= gajim
.contacts
.get_gc_contact(account
, room_jid
,
1809 show
= gc_contact
.show
1812 gc_contact
= gajim
.contacts
.create_gc_contact(
1813 room_jid
=room_jid
, account
=account
, name
=nick
,
1817 session
= gajim
.connections
[account
].make_new_session(
1818 fjid
, None, type_
='pm')
1820 self
.new_private_chat(gc_contact
, account
, session
=session
)
1821 ctrl
= session
.control
1824 elif type_
in ('normal', 'file-request', 'file-request-error',
1825 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
1827 # Get the first single message event
1828 event
= gajim
.events
.get_first_event(account
, fjid
, type_
)
1830 # default to jid without resource
1831 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
1835 self
.roster
.open_event(account
, jid
, event
)
1838 self
.roster
.open_event(account
, fjid
, event
)
1839 elif type_
== 'gmail':
1840 url
= gajim
.connections
[account
].gmail_url
1842 helpers
.launch_browser_mailer('url', url
)
1843 elif type_
== 'gc-invitation':
1844 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
1845 data
= event
.parameters
1846 dialogs
.InvitationReceivedDialog(account
, data
[0], jid
, data
[2],
1848 gajim
.events
.remove_events(account
, jid
, event
)
1849 self
.roster
.draw_contact(jid
, account
)
1850 elif type_
== 'subscription_request':
1851 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
1852 data
= event
.parameters
1853 dialogs
.SubscriptionRequestWindow(jid
, data
[0], account
, data
[1])
1854 gajim
.events
.remove_events(account
, jid
, event
)
1855 self
.roster
.draw_contact(jid
, account
)
1856 elif type_
== 'unsubscribed':
1857 event
= gajim
.events
.get_first_event(account
, jid
, type_
)
1858 contact
= event
.parameters
1859 self
.show_unsubscribed_dialog(account
, contact
)
1860 gajim
.events
.remove_events(account
, jid
, event
)
1861 self
.roster
.draw_contact(jid
, account
)
1863 w
.set_active_tab(ctrl
)
1864 w
.window
.window
.focus(gtk
.get_current_event_time())
1865 # Using isinstance here because we want to catch all derived types
1866 if isinstance(ctrl
, ChatControlBase
):
1867 tv
= ctrl
.conv_textview
1870 ################################################################################
1871 ### Methods dealing with emoticons
1872 ################################################################################
1874 def image_is_ok(self
, image
):
1875 if not os
.path
.exists(image
):
1879 img
.set_from_file(image
)
1882 t
= img
.get_storage_type()
1883 if t
!= gtk
.IMAGE_PIXBUF
and t
!= gtk
.IMAGE_ANIMATION
:
1888 def basic_pattern_re(self
):
1889 if not self
._basic
_pattern
_re
:
1890 self
._basic
_pattern
_re
= re
.compile(self
.basic_pattern
,
1892 return self
._basic
_pattern
_re
1895 def emot_and_basic_re(self
):
1896 if not self
._emot
_and
_basic
_re
:
1897 self
._emot
_and
_basic
_re
= re
.compile(self
.emot_and_basic
,
1898 re
.IGNORECASE
+ re
.UNICODE
)
1899 return self
._emot
_and
_basic
_re
1902 def sth_at_sth_dot_sth_re(self
):
1903 if not self
._sth
_at
_sth
_dot
_sth
_re
:
1904 self
._sth
_at
_sth
_dot
_sth
_re
= re
.compile(self
.sth_at_sth_dot_sth
)
1905 return self
._sth
_at
_sth
_dot
_sth
_re
1908 def invalid_XML_chars_re(self
):
1909 if not self
._invalid
_XML
_chars
_re
:
1910 self
._invalid
_XML
_chars
_re
= re
.compile(self
.invalid_XML_chars
)
1911 return self
._invalid
_XML
_chars
_re
1913 def make_regexps(self
):
1914 # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
1915 # one escapes the metachars with \
1916 # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
1917 # \s matches any whitespace character
1918 # \w any alphanumeric character
1919 # \W any non-alphanumeric character
1920 # \b means word boundary. This is a zero-width assertion that
1921 # matches only at the beginning or end of a word.
1922 # ^ matches at the beginning of lines
1924 # * means 0 or more times
1925 # + means 1 or more times
1926 # ? means 0 or 1 time
1928 # [^*] anything but '*' (inside [] you don't have to escape metachars)
1929 # [^\s*] anything but whitespaces and '*'
1930 # (?<!\S) is a one char lookbehind assertion and asks for any leading
1932 # and mathces beginning of lines so we have correct formatting detection
1933 # even if the the text is just '*foo*'
1934 # (?!\S) is the same thing but it's a lookahead assertion
1935 # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
1937 # so http://be) will match http://be and http://be)be) will match
1940 legacy_prefixes
= r
"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
1941 r
"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
1942 r
"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
1943 r
"|%[A-Fa-f0-9]{2})+"\
1944 r
"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
1945 # NOTE: it's ok to catch www.gr such stuff exist!
1947 # FIXME: recognize xmpp: and treat it specially
1948 links
= r
"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
1949 r
"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
1950 r
"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
1952 # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
1953 mail
= r
'\bmailto:\S*[^\s\W]|' r
'\b\S+@\S+\.\S*[^\s\W]'
1955 # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
1956 # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
1957 formatting
= r
'|(?<!\w)' r
'\*[^\s*]' r
'([^*]*[^\s*])?' r
'\*(?!\w)|'\
1958 r
'(?<!\S)' r
'/[^\s/]' r
'([^/]*[^\s/])?' r
'/(?!\S)|'\
1959 r
'(?<!\w)' r
'_[^\s_]' r
'([^_]*[^\s_])?' r
'_(?!\w)'
1961 latex
= r
'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|'\
1962 r
'[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
1964 basic_pattern
= links
+ '|' + mail
+ '|' + legacy_prefixes
1966 link_pattern
= basic_pattern
1967 self
.link_pattern_re
= re
.compile(link_pattern
, re
.I | re
.U
)
1969 if gajim
.config
.get('use_latex'):
1970 basic_pattern
+= latex
1972 if gajim
.config
.get('ascii_formatting'):
1973 basic_pattern
+= formatting
1974 self
.basic_pattern
= basic_pattern
1976 emoticons_pattern
= ''
1977 if gajim
.config
.get('emoticons_theme'):
1978 # When an emoticon is bordered by an alpha-numeric character it is
1979 # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
1980 # We still allow multiple emoticons side-by-side like :P:P:P
1981 # sort keys by length so :qwe emot is checked before :q
1982 keys
= sorted(self
.emoticons
, key
=len, reverse
=True)
1983 emoticons_pattern_prematch
= ''
1984 emoticons_pattern_postmatch
= ''
1986 for emoticon
in keys
: # travel thru emoticons list
1987 emoticon
= emoticon
.decode('utf-8')
1988 emoticon_escaped
= re
.escape(emoticon
) # espace regexp metachars
1989 # | means or in regexp
1990 emoticons_pattern
+= emoticon_escaped
+ '|'
1991 if (emoticon_length
!= len(emoticon
)):
1992 # Build up expressions to match emoticons next to others
1993 emoticons_pattern_prematch
= \
1994 emoticons_pattern_prematch
[:-1] + ')|(?<='
1995 emoticons_pattern_postmatch
= \
1996 emoticons_pattern_postmatch
[:-1] + ')|(?='
1997 emoticon_length
= len(emoticon
)
1998 emoticons_pattern_prematch
+= emoticon_escaped
+ '|'
1999 emoticons_pattern_postmatch
+= emoticon_escaped
+ '|'
2000 # We match from our list of emoticons, but they must either have
2001 # whitespace, or another emoticon next to it to match successfully
2002 # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
2003 emoticons_pattern
= '|' + '(?:(?<![\w.]' + \
2004 emoticons_pattern_prematch
[:-1] + '))' + '(?:' + \
2005 emoticons_pattern
[:-1] + ')' + '(?:(?![\w]' + \
2006 emoticons_pattern_postmatch
[:-1] + '))'
2008 # because emoticons match later (in the string) they need to be after
2009 # basic matches that may occur earlier
2010 self
.emot_and_basic
= basic_pattern
+ emoticons_pattern
2012 # needed for xhtml display
2013 self
.emot_only
= emoticons_pattern
2015 # at least one character in 3 parts (before @, after @, after .)
2016 self
.sth_at_sth_dot_sth
= r
'\S+@\S+\.\S*[^\s)?]'
2019 self
.invalid_XML_chars
= u
'[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x19]|'\
2020 u
'[\ud800-\udfff]|[\ufffe-\uffff]'
2022 def popup_emoticons_under_button(self
, button
, parent_win
):
2024 Popup the emoticons menu under button, located in parent_win
2026 gtkgui_helpers
.popup_emoticons_under_button(self
.emoticons_menu
,
2029 def prepare_emoticons_menu(self
):
2031 def emoticon_clicked(w
, str_
):
2032 if self
.emoticon_menuitem_clicked
:
2033 self
.emoticon_menuitem_clicked(str_
)
2034 # don't keep reference to CB of object
2035 # this will prevent making it uncollectable
2036 self
.emoticon_menuitem_clicked
= None
2037 def selection_done(widget
):
2038 # remove reference to CB of object, which will
2039 # make it uncollectable
2040 self
.emoticon_menuitem_clicked
= None
2042 # Calculate the side lenght of the popup to make it a square
2043 size
= int(round(math
.sqrt(len(self
.emoticons_images
))))
2044 for image
in self
.emoticons_images
:
2045 item
= gtk
.MenuItem()
2047 if isinstance(image
[1], gtk
.gdk
.PixbufAnimation
):
2048 img
.set_from_animation(image
[1])
2050 img
.set_from_pixbuf(image
[1])
2052 item
.connect('activate', emoticon_clicked
, image
[0])
2053 #FIXME: add tooltip with ascii
2054 menu
.attach(item
, counter
% size
, counter
% size
+ 1,
2055 counter
/ size
, counter
/ size
+ 1)
2057 menu
.connect('selection-done', selection_done
)
2061 def _init_emoticons(self
, path
, need_reload
= False):
2062 #initialize emoticons dictionary and unique images list
2063 self
.emoticons_images
= list()
2064 self
.emoticons
= dict()
2065 self
.emoticons_animations
= dict()
2067 sys
.path
.append(path
)
2070 # we need to reload else that doesn't work when changing emoticon
2073 emots
= emoticons
.emoticons
2074 for emot_filename
in emots
:
2075 emot_file
= os
.path
.join(path
, emot_filename
)
2076 if not self
.image_is_ok(emot_file
):
2078 for emot
in emots
[emot_filename
]:
2079 emot
= emot
.decode('utf-8')
2080 # This avoids duplicated emoticons with the same image eg. :)
2082 if not emot_file
in self
.emoticons
.values():
2083 if emot_file
.endswith('.gif'):
2084 pix
= gtk
.gdk
.PixbufAnimation(emot_file
)
2086 pix
= gtk
.gdk
.pixbuf_new_from_file_at_size(emot_file
,
2088 self
.emoticons_images
.append((emot
, pix
))
2089 self
.emoticons
[emot
.upper()] = emot_file
2091 sys
.path
.remove(path
)
2093 def init_emoticons(self
, need_reload
= False):
2094 emot_theme
= gajim
.config
.get('emoticons_theme')
2098 path
= os
.path
.join(gajim
.DATA_DIR
, 'emoticons', emot_theme
)
2099 if not os
.path
.exists(path
):
2100 # It's maybe a user theme
2101 path
= os
.path
.join(gajim
.MY_EMOTS_PATH
, emot_theme
)
2102 if not os
.path
.exists(path
):
2103 # theme doesn't exist, disable emoticons
2104 dialogs
.WarningDialog(_('Emoticons disabled'),
2105 _('Your configured emoticons theme has not been found, so '
2106 'emoticons have been disabled.'))
2107 gajim
.config
.set('emoticons_theme', '')
2109 self
._init
_emoticons
(path
, need_reload
)
2110 if len(self
.emoticons
) == 0:
2111 # maybe old format of emoticons file, try to convert it
2115 emots
= emoticons
.emoticons
2116 fd
= open(os
.path
.join(path
, 'emoticons.py'), 'w')
2117 fd
.write('emoticons = ')
2118 pprint
.pprint( dict([
2119 (file_
, [i
for i
in emots
.keys() if emots
[i
] == file_
])
2120 for file_
in set(emots
.values())]), fd
)
2123 self
._init
_emoticons
(path
, need_reload
=True)
2126 if len(self
.emoticons
) == 0:
2127 dialogs
.WarningDialog(_('Emoticons disabled'),
2128 _('Your configured emoticons theme cannot been loaded. You '
2129 'maybe need to update the format of emoticons.py file. See '
2130 'http://trac.gajim.org/wiki/Emoticons for more details.'))
2131 if self
.emoticons_menu
:
2132 self
.emoticons_menu
.destroy()
2133 self
.emoticons_menu
= self
.prepare_emoticons_menu()
2135 ################################################################################
2136 ### Methods for opening new messages controls
2137 ################################################################################
2139 def join_gc_room(self
, account
, room_jid
, nick
, password
, minimize
=False,
2140 is_continued
=False):
2142 Join the room immediately
2145 nick
= gajim
.nicks
[account
]
2147 if self
.msg_win_mgr
.has_window(room_jid
, account
) and \
2148 gajim
.gc_connected
[account
][room_jid
]:
2149 gc_ctrl
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
2150 win
= gc_ctrl
.parent_win
2151 win
.set_active_tab(gc_ctrl
)
2152 dialogs
.ErrorDialog(_('You are already in group chat %s') % \
2156 invisible_show
= gajim
.SHOW_LIST
.index('invisible')
2157 if gajim
.connections
[account
].connected
== invisible_show
:
2158 dialogs
.ErrorDialog(
2159 _('You cannot join a group chat while you are invisible'))
2162 minimized_control
= gajim
.interface
.minimized_controls
[account
].get(
2165 if minimized_control
is None and not self
.msg_win_mgr
.has_window(
2167 # Join new groupchat
2170 contact
= gajim
.contacts
.create_contact(jid
=room_jid
,
2171 account
=account
, name
=nick
)
2172 gc_control
= GroupchatControl(None, contact
, account
)
2173 gajim
.interface
.minimized_controls
[account
][room_jid
] = \
2175 self
.roster
.add_groupchat(room_jid
, account
)
2177 self
.new_room(room_jid
, nick
, account
,
2178 is_continued
=is_continued
)
2179 elif minimized_control
is None:
2180 # We are already in that groupchat
2181 gc_control
= self
.msg_win_mgr
.get_gc_control(room_jid
, account
)
2182 gc_control
.nick
= nick
2183 gc_control
.parent_win
.set_active_tab(gc_control
)
2185 # We are already in this groupchat and it is minimized
2186 minimized_control
.nick
= nick
2187 self
.roster
.add_groupchat(room_jid
, account
)
2190 gajim
.connections
[account
].join_gc(nick
, room_jid
, password
)
2192 gajim
.gc_passwords
[room_jid
] = password
2194 def new_room(self
, room_jid
, nick
, account
, is_continued
=False):
2195 # Get target window, create a control, and associate it with the window
2197 contact
= gajim
.contacts
.create_contact(jid
=room_jid
, account
=account
,
2199 mw
= self
.msg_win_mgr
.get_window(contact
.jid
, account
)
2201 mw
= self
.msg_win_mgr
.create_window(contact
, account
,
2202 GroupchatControl
.TYPE_ID
)
2203 gc_control
= GroupchatControl(mw
, contact
, account
,
2204 is_continued
=is_continued
)
2205 mw
.new_tab(gc_control
)
2207 def new_private_chat(self
, gc_contact
, account
, session
=None):
2208 conn
= gajim
.connections
[account
]
2209 if not session
and gc_contact
.get_full_jid() in conn
.sessions
:
2210 sessions
= [s
for s
in conn
.sessions
[gc_contact
.get_full_jid()].\
2211 values() if isinstance(s
, ChatControlSession
)]
2213 # look for an existing session with a chat control
2218 if not session
and not len(sessions
) == 0:
2219 # there are no sessions with chat controls, just take the first
2221 session
= sessions
[0]
2223 # couldn't find an existing ChatControlSession, just make a new one
2224 session
= conn
.make_new_session(gc_contact
.get_full_jid(), None,
2227 contact
= gc_contact
.as_contact()
2228 if not session
.control
:
2229 message_window
= self
.msg_win_mgr
.get_window(
2230 gc_contact
.get_full_jid(), account
)
2231 if not message_window
:
2232 message_window
= self
.msg_win_mgr
.create_window(contact
,
2233 account
, message_control
.TYPE_PM
)
2235 session
.control
= PrivateChatControl(message_window
, gc_contact
,
2236 contact
, account
, session
)
2237 message_window
.new_tab(session
.control
)
2239 if gajim
.events
.get_events(account
, gc_contact
.get_full_jid()):
2240 # We call this here to avoid race conditions with widget validation
2241 session
.control
.read_queue()
2243 return session
.control
2245 def new_chat(self
, contact
, account
, resource
=None, session
=None):
2246 # Get target window, create a control, and associate it with the window
2247 type_
= message_control
.TYPE_CHAT
2251 fjid
+= '/' + resource
2253 mw
= self
.msg_win_mgr
.get_window(fjid
, account
)
2255 mw
= self
.msg_win_mgr
.create_window(contact
, account
, type_
,
2258 chat_control
= ChatControl(mw
, contact
, account
, session
, resource
)
2260 mw
.new_tab(chat_control
)
2262 if len(gajim
.events
.get_events(account
, fjid
)):
2263 # We call this here to avoid race conditions with widget validation
2264 chat_control
.read_queue()
2268 def new_chat_from_jid(self
, account
, fjid
, message
=None):
2269 jid
, resource
= gajim
.get_room_and_nick_from_fjid(fjid
)
2270 contact
= gajim
.contacts
.get_contact(account
, jid
, resource
)
2271 added_to_roster
= False
2273 added_to_roster
= True
2274 contact
= self
.roster
.add_to_not_in_the_roster(account
, jid
,
2277 ctrl
= self
.msg_win_mgr
.get_control(fjid
, account
)
2280 ctrl
= self
.new_chat(contact
, account
,
2282 if len(gajim
.events
.get_events(account
, fjid
)):
2286 buffer_
= ctrl
.msg_textview
.get_buffer()
2287 buffer_
.set_text(message
)
2288 mw
= ctrl
.parent_win
2289 mw
.set_active_tab(ctrl
)
2292 ctrl
.user_nick
= gajim
.nicks
[account
]
2293 gobject
.idle_add(mw
.window
.grab_focus
)
2297 def on_open_chat_window(self
, widget
, contact
, account
, resource
=None,
2299 # Get the window containing the chat
2303 fjid
+= '/' + resource
2308 ctrl
= session
.control
2310 win
= self
.msg_win_mgr
.get_window(fjid
, account
)
2313 ctrl
= win
.get_control(fjid
, account
)
2316 ctrl
= self
.new_chat(contact
, account
, resource
=resource
,
2318 # last message is long time ago
2319 gajim
.last_message_time
[account
][ctrl
.get_full_jid()] = 0
2321 win
= ctrl
.parent_win
2323 win
.set_active_tab(ctrl
)
2325 if gajim
.connections
[account
].is_zeroconf
and \
2326 gajim
.connections
[account
].status
in ('offline', 'invisible'):
2327 ctrl
= win
.get_control(fjid
, account
)
2329 ctrl
.got_disconnected()
2331 ################################################################################
2333 ################################################################################
2335 def change_awn_icon_status(self
, status
):
2336 if not dbus_support
.supported
:
2337 # do nothing if user doesn't have D-Bus bindings
2340 bus
= dbus
.SessionBus()
2341 if not 'com.google.code.Awn' in bus
.list_names():
2342 # Awn is not installed
2346 iconset
= gajim
.config
.get('iconset')
2347 prefix
= os
.path
.join(helpers
.get_iconset_path(iconset
), '32x32')
2348 if status
in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
2349 status
= status
+ '.png'
2350 elif status
== 'online':
2352 status
= gtkgui_helpers
.get_icon_path('gajim', 32)
2353 path
= os
.path
.join(prefix
, status
)
2355 obj
= bus
.get_object('com.google.code.Awn', '/com/google/code/Awn')
2356 awn
= dbus
.Interface(obj
, 'com.google.code.Awn')
2357 awn
.SetTaskIconByName('Gajim', os
.path
.abspath(path
))
2361 def enable_music_listener(self
):
2362 listener
= MusicTrackListener
.get()
2363 if not self
.music_track_changed_signal
:
2364 self
.music_track_changed_signal
= listener
.connect(
2365 'music-track-changed', self
.music_track_changed
)
2366 track
= listener
.get_playing_track()
2367 self
.music_track_changed(listener
, track
)
2369 def disable_music_listener(self
):
2370 listener
= MusicTrackListener
.get()
2371 listener
.disconnect(self
.music_track_changed_signal
)
2372 self
.music_track_changed_signal
= None
2374 def music_track_changed(self
, unused_listener
, music_track_info
,
2377 accounts
= gajim
.connections
.keys()
2379 accounts
= [account
]
2381 is_paused
= hasattr(music_track_info
, 'paused') and \
2382 music_track_info
.paused
== 0
2383 if not music_track_info
or is_paused
:
2384 artist
= title
= source
= ''
2386 artist
= music_track_info
.artist
2387 title
= music_track_info
.title
2388 source
= music_track_info
.album
2389 for acct
in accounts
:
2390 if not gajim
.account_is_connected(acct
):
2392 if not gajim
.connections
[acct
].pep_supported
:
2394 if not gajim
.config
.get_per('accounts', acct
, 'publish_tune'):
2396 if gajim
.connections
[acct
].music_track_info
== music_track_info
:
2398 gajim
.connections
[acct
].send_tune(artist
, title
, source
)
2399 gajim
.connections
[acct
].music_track_info
= music_track_info
2401 def get_bg_fg_colors(self
):
2402 def gdkcolor_to_rgb (gdkcolor
):
2403 return [c
/ 65535. for c
in (gdkcolor
.red
, gdkcolor
.green
,
2406 def format_rgb (r
, g
, b
):
2407 return ' '.join([str(c
) for c
in ('rgb', r
, g
, b
)])
2409 def format_gdkcolor (gdkcolor
):
2410 return format_rgb (*gdkcolor_to_rgb (gdkcolor
))
2412 # get style colors and create string for dvipng
2413 dummy
= gtk
.Invisible()
2414 dummy
.ensure_style()
2415 style
= dummy
.get_style()
2416 bg_str
= format_gdkcolor(style
.base
[gtk
.STATE_NORMAL
])
2417 fg_str
= format_gdkcolor(style
.text
[gtk
.STATE_NORMAL
])
2418 return (bg_str
, fg_str
)
2420 def get_fg_color(self
, fmt
='hex'):
2421 def format_gdkcolor (c
):
2423 return ' '.join([str(s
) for s
in
2424 ('rgb', c
.red_float
, c
.green_float
, c
.blue_float
)])
2428 # get foreground style color and create string
2429 dummy
= gtk
.Invisible()
2430 dummy
.ensure_style()
2431 return format_gdkcolor(dummy
.get_style().text
[gtk
.STATE_NORMAL
])
2433 def read_sleepy(self
):
2435 Check idle status and change that status if needed
2437 if not self
.sleeper
.poll():
2438 # idle detection is not supported in that OS
2439 return False # stop looping in vain
2440 state
= self
.sleeper
.getState()
2441 for account
in gajim
.connections
:
2442 if account
not in gajim
.sleeper_state
or \
2443 not gajim
.sleeper_state
[account
]:
2445 if state
== common
.sleepy
.STATE_AWAKE
and \
2446 gajim
.sleeper_state
[account
] in ('autoaway', 'autoxa'):
2448 self
.roster
.send_status(account
, 'online',
2449 gajim
.status_before_autoaway
[account
])
2450 gajim
.status_before_autoaway
[account
] = ''
2451 gajim
.sleeper_state
[account
] = 'online'
2452 elif state
== common
.sleepy
.STATE_AWAY
and \
2453 gajim
.sleeper_state
[account
] == 'online' and \
2454 gajim
.config
.get('autoaway'):
2455 # we save out online status
2456 gajim
.status_before_autoaway
[account
] = \
2457 gajim
.connections
[account
].status
2458 # we go away (no auto status) [we pass True to auto param]
2459 auto_message
= gajim
.config
.get('autoaway_message')
2460 if not auto_message
:
2461 auto_message
= gajim
.connections
[account
].status
2463 auto_message
= auto_message
.replace('$S', '%(status)s')
2464 auto_message
= auto_message
.replace('$T', '%(time)s')
2465 auto_message
= auto_message
% {
2466 'status': gajim
.status_before_autoaway
[account
],
2467 'time': gajim
.config
.get('autoawaytime')
2469 self
.roster
.send_status(account
, 'away', auto_message
,
2471 gajim
.sleeper_state
[account
] = 'autoaway'
2472 elif state
== common
.sleepy
.STATE_XA
and \
2473 gajim
.sleeper_state
[account
] in ('online', 'autoaway',
2474 'autoaway-forced') and gajim
.config
.get('autoxa'):
2475 # we go extended away [we pass True to auto param]
2476 auto_message
= gajim
.config
.get('autoxa_message')
2477 if not auto_message
:
2478 auto_message
= gajim
.connections
[account
].status
2480 auto_message
= auto_message
.replace('$S', '%(status)s')
2481 auto_message
= auto_message
.replace('$T', '%(time)s')
2482 auto_message
= auto_message
% {
2483 'status': gajim
.status_before_autoaway
[account
],
2484 'time': gajim
.config
.get('autoxatime')
2486 self
.roster
.send_status(account
, 'xa', auto_message
, auto
=True)
2487 gajim
.sleeper_state
[account
] = 'autoxa'
2488 return True # renew timeout (loop for ever)
2490 def autoconnect(self
):
2492 Auto connect at startup
2494 # dict of account that want to connect sorted by status
2496 for a
in gajim
.connections
:
2497 if gajim
.config
.get_per('accounts', a
, 'autoconnect'):
2498 if gajim
.config
.get_per('accounts', a
, 'restore_last_status'):
2499 self
.roster
.send_status(a
, gajim
.config
.get_per('accounts',
2500 a
, 'last_status'), helpers
.from_one_line(
2501 gajim
.config
.get_per('accounts', a
, 'last_status_msg')))
2503 show
= gajim
.config
.get_per('accounts', a
, 'autoconnect_as')
2504 if not show
in gajim
.SHOW_LIST
:
2506 if not show
in shows
:
2509 shows
[show
].append(a
)
2510 def on_message(message
, pep_dict
):
2513 for a
in shows
[show
]:
2514 self
.roster
.send_status(a
, show
, message
)
2515 self
.roster
.send_pep(a
, pep_dict
)
2517 message
= self
.roster
.get_status_message(show
, on_message
)
2520 def show_systray(self
):
2521 self
.systray_enabled
= True
2522 self
.systray
.show_icon()
2524 def hide_systray(self
):
2525 self
.systray_enabled
= False
2526 self
.systray
.hide_icon()
2528 def on_launch_browser_mailer(self
, widget
, url
, kind
):
2529 helpers
.launch_browser_mailer(kind
, url
)
2531 def process_connections(self
):
2533 Called each foo (200) miliseconds. Check for idlequeue timeouts
2536 gajim
.idlequeue
.process()
2538 # Otherwise, an exception will stop our loop
2539 timeout
, in_seconds
= gajim
.idlequeue
.PROCESS_TIMEOUT
2541 gobject
.timeout_add_seconds(timeout
, self
.process_connections
)
2543 gobject
.timeout_add(timeout
, self
.process_connections
)
2545 return True # renew timeout (loop for ever)
2547 def save_config(self
):
2548 err_str
= parser
.write()
2549 if err_str
is not None:
2550 print >> sys
.stderr
, err_str
2551 # it is good to notify the user
2552 # in case he or she cannot see the output of the console
2553 dialogs
.ErrorDialog(_('Could not save your settings and '
2554 'preferences'), err_str
)
2557 def save_avatar_files(self
, jid
, photo
, puny_nick
= None, local
= False):
2559 Save an avatar to a separate file, and generate files for dbus
2560 notifications. An avatar can be given as a pixmap directly or as an
2563 puny_jid
= helpers
.sanitize_filename(jid
)
2564 path_to_file
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
)
2566 path_to_file
= os
.path
.join(path_to_file
, puny_nick
)
2567 # remove old avatars
2568 for typ
in ('jpeg', 'png'):
2570 path_to_original_file
= path_to_file
+ '_local'+ '.' + typ
2572 path_to_original_file
= path_to_file
+ '.' + typ
2573 if os
.path
.isfile(path_to_original_file
):
2574 os
.remove(path_to_original_file
)
2578 extension
= '_local.png' # save local avatars as png file
2580 pixbuf
, typ
= gtkgui_helpers
.get_pixbuf_from_data(photo
,
2584 extension
= '.' + typ
2585 if typ
not in ('jpeg', 'png'):
2586 gajim
.log
.debug('gtkpixbuf cannot save other than jpeg and '\
2587 'png formats. saving %s\'avatar as png file (originaly %s)'\
2591 path_to_original_file
= path_to_file
+ extension
2593 pixbuf
.save(path_to_original_file
, typ
)
2594 except Exception, e
:
2595 log
.error('Error writing avatar file %s: %s' % (
2596 path_to_original_file
, str(e
)))
2597 # Generate and save the resized, color avatar
2598 pixbuf
= gtkgui_helpers
.get_scaled_pixbuf(pixbuf
, 'notification')
2600 path_to_normal_file
= path_to_file
+ '_notif_size_colored' + \
2603 pixbuf
.save(path_to_normal_file
, 'png')
2604 except Exception, e
:
2605 log
.error('Error writing avatar file %s: %s' % \
2606 (path_to_original_file
, str(e
)))
2607 # Generate and save the resized, black and white avatar
2608 bwbuf
= gtkgui_helpers
.get_scaled_pixbuf(
2609 gtkgui_helpers
.make_pixbuf_grayscale(pixbuf
), 'notification')
2611 path_to_bw_file
= path_to_file
+ '_notif_size_bw' + extension
2613 bwbuf
.save(path_to_bw_file
, 'png')
2614 except Exception, e
:
2615 log
.error('Error writing avatar file %s: %s' % \
2616 (path_to_original_file
, str(e
)))
2618 def remove_avatar_files(self
, jid
, puny_nick
= None, local
= False):
2620 Remove avatar files of a jid
2622 puny_jid
= helpers
.sanitize_filename(jid
)
2623 path_to_file
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
)
2625 path_to_file
= os
.path
.join(path_to_file
, puny_nick
)
2626 for ext
in ('.jpeg', '.png'):
2628 ext
= '_local' + ext
2629 path_to_original_file
= path_to_file
+ ext
2630 if os
.path
.isfile(path_to_file
+ ext
):
2631 os
.remove(path_to_file
+ ext
)
2632 if os
.path
.isfile(path_to_file
+ '_notif_size_colored' + ext
):
2633 os
.remove(path_to_file
+ '_notif_size_colored' + ext
)
2634 if os
.path
.isfile(path_to_file
+ '_notif_size_bw' + ext
):
2635 os
.remove(path_to_file
+ '_notif_size_bw' + ext
)
2637 def auto_join_bookmarks(self
, account
):
2639 Autojoin bookmarked GCs that have 'auto join' on for this account
2641 for bm
in gajim
.connections
[account
].bookmarks
:
2642 if bm
['autojoin'] in ('1', 'true'):
2644 # Only join non-opened groupchats. Opened one are already
2645 # auto-joined on re-connection
2646 if not jid
in gajim
.gc_connected
[account
]:
2647 # we are not already connected
2648 minimize
= bm
['minimize'] in ('1', 'true')
2649 gajim
.interface
.join_gc_room(account
, jid
, bm
['nick'],
2650 bm
['password'], minimize
= minimize
)
2651 elif jid
in self
.minimized_controls
[account
]:
2652 # more or less a hack:
2653 # On disconnect the minimized gc contact instances
2654 # were set to offline. Reconnect them to show up in the
2656 self
.roster
.add_groupchat(jid
, account
)
2658 def add_gc_bookmark(self
, account
, name
, jid
, autojoin
, minimize
, password
,
2661 Add a bookmark for this account, sorted in bookmark list
2666 'autojoin': autojoin
,
2667 'minimize': minimize
,
2668 'password': password
,
2673 # check for duplicate entry and respect alpha order
2674 for bookmark
in gajim
.connections
[account
].bookmarks
:
2675 if bookmark
['jid'] == bm
['jid']:
2676 dialogs
.ErrorDialog(
2677 _('Bookmark already set'),
2678 _('Group Chat "%s" is already in your bookmarks.') % \
2681 if bookmark
['name'] > bm
['name']:
2686 gajim
.connections
[account
].bookmarks
.insert(index
, bm
)
2688 gajim
.connections
[account
].bookmarks
.append(bm
)
2689 gajim
.connections
[account
].store_bookmarks()
2690 self
.roster
.set_actions_menu_needs_rebuild()
2691 dialogs
.InformationDialog(
2692 _('Bookmark has been added successfully'),
2693 _('You can manage your bookmarks via Actions menu in your roster.'))
2696 # does JID exist only within a groupchat?
2697 def is_pm_contact(self
, fjid
, account
):
2698 bare_jid
= gajim
.get_jid_without_resource(fjid
)
2700 gc_ctrl
= self
.msg_win_mgr
.get_gc_control(bare_jid
, account
)
2702 if not gc_ctrl
and \
2703 bare_jid
in self
.minimized_controls
[account
]:
2704 gc_ctrl
= self
.minimized_controls
[account
][bare_jid
]
2706 return gc_ctrl
and gc_ctrl
.type_id
== message_control
.TYPE_GC
2708 def create_ipython_window(self
):
2710 from ipython_view
import IPythonView
2712 print 'ipython_view not found'
2717 font
= 'Lucida Console 9'
2719 font
= 'Luxi Mono 10'
2721 window
= gtk
.Window()
2722 window
.set_size_request(750, 550)
2723 window
.set_resizable(True)
2724 sw
= gtk
.ScrolledWindow()
2725 sw
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
2726 view
= IPythonView()
2727 view
.modify_font(pango
.FontDescription(font
))
2728 view
.set_wrap_mode(gtk
.WRAP_CHAR
)
2732 def on_delete(win
, event
):
2735 window
.connect('delete_event', on_delete
)
2736 view
.updateNamespace({'gajim': gajim
})
2737 gajim
.ipython_window
= window
2740 if gajim
.config
.get('trayicon') != 'never':
2743 self
.roster
= roster_window
.RosterWindow()
2744 # Creating plugin manager
2746 gajim
.plugin_manager
= plugins
.PluginManager()
2748 self
.roster
._before
_fill
()
2749 for account
in gajim
.connections
:
2750 gajim
.connections
[account
].load_roster_from_db()
2751 self
.roster
._after
_fill
()
2753 # get instances for windows/dialogs that will show_all()/hide()
2754 self
.instances
['file_transfers'] = dialogs
.FileTransfersWindow()
2756 gobject
.timeout_add(100, self
.autoconnect
)
2757 timeout
, in_seconds
= gajim
.idlequeue
.PROCESS_TIMEOUT
2759 gobject
.timeout_add_seconds(timeout
, self
.process_connections
)
2761 gobject
.timeout_add(timeout
, self
.process_connections
)
2762 gobject
.timeout_add_seconds(gajim
.config
.get(
2763 'check_idle_every_foo_seconds'), self
.read_sleepy
)
2765 # when using libasyncns we need to process resolver in regular intervals
2766 if resolver
.USE_LIBASYNCNS
:
2767 gobject
.timeout_add(200, gajim
.resolver
.process
)
2770 if gajim
.config
.get('remote_control'):
2772 import remote_control
2773 self
.remote_ctrl
= remote_control
.Remote()
2776 gobject
.timeout_add_seconds(5, remote_init
)
2779 gajim
.interface
= self
2780 gajim
.thread_interface
= ThreadInterface
2781 # This is the manager and factory of message windows set by the module
2782 self
.msg_win_mgr
= None
2783 self
.jabber_state_images
= {'16': {}, '32': {}, 'opened': {},
2785 self
.emoticons_menu
= None
2786 # handler when an emoticon is clicked in emoticons_menu
2787 self
.emoticon_menuitem_clicked
= None
2788 self
.minimized_controls
= {}
2789 self
.status_sent_to_users
= {}
2790 self
.status_sent_to_groups
= {}
2791 self
.gpg_passphrase
= {}
2792 self
.pass_dialog
= {}
2793 self
.db_error_dialog
= None
2794 self
.default_colors
= {
2795 'inmsgcolor': gajim
.config
.get('inmsgcolor'),
2796 'outmsgcolor': gajim
.config
.get('outmsgcolor'),
2797 'inmsgtxtcolor': gajim
.config
.get('inmsgtxtcolor'),
2798 'outmsgtxtcolor': gajim
.config
.get('outmsgtxtcolor'),
2799 'statusmsgcolor': gajim
.config
.get('statusmsgcolor'),
2800 'urlmsgcolor': gajim
.config
.get('urlmsgcolor'),
2805 self
._invalid
_XML
_chars
_re
= None
2806 self
._basic
_pattern
_re
= None
2807 self
._emot
_and
_basic
_re
= None
2808 self
._sth
_at
_sth
_dot
_sth
_re
= None
2809 self
.link_pattern_re
= None
2810 self
.invalid_XML_chars
= None
2811 self
.basic_pattern
= None
2812 self
.emot_and_basic
= None
2813 self
.sth_at_sth_dot_sth
= None
2814 self
.emot_only
= None
2816 self
.emoticons_animations
= {}
2817 self
.emoticons_images
= {}
2819 cfg_was_read
= parser
.read()
2821 from common
import latex
2822 gajim
.HAVE_LATEX
= gajim
.config
.get('use_latex') and \
2823 latex
.check_for_latex_support()
2825 gajim
.logger
.reset_shown_unread_messages()
2826 # override logging settings from config (don't take care of '-q' option)
2827 if gajim
.config
.get('verbose'):
2828 logging_helpers
.set_verbose()
2830 # Is Gajim default app?
2831 if os
.name
!= 'nt' and gajim
.config
.get('check_if_gajim_is_default'):
2832 gtkgui_helpers
.possibly_set_gajim_as_xmpp_handler()
2834 for account
in gajim
.config
.get_per('accounts'):
2835 if gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
2836 gajim
.ZEROCONF_ACC_NAME
= account
2838 # Is gnome configured to activate row on single click ?
2841 client
= gconf
.client_get_default()
2842 click_policy
= client
.get_string(
2843 '/apps/nautilus/preferences/click_policy')
2844 if click_policy
== 'single':
2845 gajim
.single_click
= True
2848 # add default status messages if there is not in the config file
2849 if len(gajim
.config
.get_per('statusmsg')) == 0:
2850 default
= gajim
.config
.statusmsg_default
2852 gajim
.config
.add_per('statusmsg', msg
)
2853 gajim
.config
.set_per('statusmsg', msg
, 'message',
2855 gajim
.config
.set_per('statusmsg', msg
, 'activity',
2857 gajim
.config
.set_per('statusmsg', msg
, 'subactivity',
2859 gajim
.config
.set_per('statusmsg', msg
, 'activity_text',
2861 gajim
.config
.set_per('statusmsg', msg
, 'mood',
2863 gajim
.config
.set_per('statusmsg', msg
, 'mood_text',
2865 #add default themes if there is not in the config file
2866 theme
= gajim
.config
.get('roster_theme')
2867 if not theme
in gajim
.config
.get_per('themes'):
2868 gajim
.config
.set('roster_theme', _('default'))
2869 if len(gajim
.config
.get_per('themes')) == 0:
2870 d
= ['accounttextcolor', 'accountbgcolor', 'accountfont',
2871 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
2872 'groupfont', 'groupfontattrs', 'contacttextcolor',
2873 'contactbgcolor', 'contactfont', 'contactfontattrs',
2874 'bannertextcolor', 'bannerbgcolor']
2876 default
= gajim
.config
.themes_default
2877 for theme_name
in default
:
2878 gajim
.config
.add_per('themes', theme_name
)
2879 theme
= default
[theme_name
]
2881 gajim
.config
.set_per('themes', theme_name
, o
,
2884 if gajim
.config
.get('autodetect_browser_mailer') or not cfg_was_read
:
2885 gtkgui_helpers
.autodetect_browser_mailer()
2887 gajim
.idlequeue
= idlequeue
.get_idlequeue()
2888 # resolve and keep current record of resolved hosts
2889 gajim
.resolver
= resolver
.get_resolver(gajim
.idlequeue
)
2890 gajim
.socks5queue
= socks5
.SocksQueue(gajim
.idlequeue
,
2891 self
.handle_event_file_rcv_completed
,
2892 self
.handle_event_file_progress
,
2893 self
.handle_event_file_error
)
2894 gajim
.proxy65_manager
= proxy65_manager
.Proxy65Manager(gajim
.idlequeue
)
2895 gajim
.default_session_type
= ChatControlSession
2897 # Creating Network Events Controller
2898 from common
import nec
2899 gajim
.nec
= nec
.NetworkEventsController()
2901 self
.create_core_handlers_list()
2902 self
.register_core_handlers()
2904 if gajim
.config
.get_per('accounts', gajim
.ZEROCONF_ACC_NAME
, 'active') \
2905 and gajim
.HAVE_ZEROCONF
:
2906 gajim
.connections
[gajim
.ZEROCONF_ACC_NAME
] = \
2907 connection_zeroconf
.ConnectionZeroconf(gajim
.ZEROCONF_ACC_NAME
)
2908 for account
in gajim
.config
.get_per('accounts'):
2909 if not gajim
.config
.get_per('accounts', account
, 'is_zeroconf') and\
2910 gajim
.config
.get_per('accounts', account
, 'active'):
2911 gajim
.connections
[account
] = common
.connection
.Connection(
2915 gtk
.about_dialog_set_email_hook(self
.on_launch_browser_mailer
, 'mail')
2916 gtk
.about_dialog_set_url_hook(self
.on_launch_browser_mailer
, 'url')
2917 gtk
.link_button_set_uri_hook(self
.on_launch_browser_mailer
, 'url')
2921 for a
in gajim
.connections
:
2922 self
.instances
[a
] = {'infos': {}, 'disco': {}, 'gc_config': {},
2923 'search': {}, 'online_dialog': {}}
2924 # online_dialog contains all dialogs that have a meaning only when
2925 # we are not disconnected
2926 self
.minimized_controls
[a
] = {}
2927 gajim
.contacts
.add_account(a
)
2928 gajim
.groups
[a
] = {}
2929 gajim
.gc_connected
[a
] = {}
2930 gajim
.automatic_rooms
[a
] = {}
2931 gajim
.newly_added
[a
] = []
2932 gajim
.to_be_removed
[a
] = []
2933 gajim
.nicks
[a
] = gajim
.config
.get_per('accounts', a
, 'name')
2934 gajim
.block_signed_in_notifications
[a
] = True
2935 gajim
.sleeper_state
[a
] = 0
2936 gajim
.encrypted_chats
[a
] = []
2937 gajim
.last_message_time
[a
] = {}
2938 gajim
.status_before_autoaway
[a
] = ''
2939 gajim
.transport_avatar
[a
] = {}
2940 gajim
.gajim_optional_features
[a
] = []
2941 gajim
.caps_hash
[a
] = ''
2943 helpers
.update_optional_features()
2944 # prepopulate data which we are sure of; note: we do not log these info
2945 for account
in gajim
.connections
:
2946 gajimcaps
= caps_cache
.capscache
[('sha-1',
2947 gajim
.caps_hash
[account
])]
2948 gajimcaps
.identities
= [gajim
.gajim_identity
]
2949 gajimcaps
.features
= gajim
.gajim_common_features
+ \
2950 gajim
.gajim_optional_features
[account
]
2952 self
.remote_ctrl
= None
2954 if gajim
.config
.get('networkmanager_support') and \
2955 dbus_support
.supported
:
2956 import network_manager_listener
2958 # Handle gnome screensaver
2959 if dbus_support
.supported
:
2960 def gnome_screensaver_ActiveChanged_cb(active
):
2962 for account
in gajim
.connections
:
2963 if gajim
.sleeper_state
[account
] == 'autoaway-forced':
2964 # We came back online ofter gnome-screensaver
2966 self
.roster
.send_status(account
, 'online',
2967 gajim
.status_before_autoaway
[account
])
2968 gajim
.status_before_autoaway
[account
] = ''
2969 gajim
.sleeper_state
[account
] = 'online'
2971 if not gajim
.config
.get('autoaway'):
2972 # Don't go auto away if user disabled the option
2974 for account
in gajim
.connections
:
2975 if account
not in gajim
.sleeper_state
or \
2976 not gajim
.sleeper_state
[account
]:
2978 if gajim
.sleeper_state
[account
] == 'online':
2979 # we save out online status
2980 gajim
.status_before_autoaway
[account
] = \
2981 gajim
.connections
[account
].status
2982 # we go away (no auto status) [we pass True to auto
2984 auto_message
= gajim
.config
.get('autoaway_message')
2985 if not auto_message
:
2986 auto_message
= gajim
.connections
[account
].status
2988 auto_message
= auto_message
.replace('$S',
2990 auto_message
= auto_message
.replace('$T',
2992 auto_message
= auto_message
% {
2993 'status': gajim
.status_before_autoaway
[account
],
2994 'time': gajim
.config
.get('autoxatime')}
2995 self
.roster
.send_status(account
, 'away', auto_message
,
2997 gajim
.sleeper_state
[account
] = 'autoaway-forced'
3000 bus
= dbus
.SessionBus()
3001 bus
.add_signal_receiver(gnome_screensaver_ActiveChanged_cb
,
3002 'ActiveChanged', 'org.gnome.ScreenSaver')
3006 self
.show_vcard_when_connect
= []
3008 self
.sleeper
= common
.sleepy
.Sleepy(
3009 gajim
.config
.get('autoawaytime') * 60, # make minutes to seconds
3010 gajim
.config
.get('autoxatime') * 60)
3012 gtkgui_helpers
.make_jabber_state_images()
3014 self
.systray_enabled
= False
3017 self
.systray
= statusicon
.StatusIcon()
3019 pix
= gtkgui_helpers
.get_icon_pixmap('gajim', 32)
3020 # set the icon to all windows
3021 gtk
.window_set_default_icon(pix
)
3023 self
.init_emoticons()
3026 # get transports type from DB
3027 gajim
.transport_type
= gajim
.logger
.get_transports_type()
3029 # test is dictionnary is present for speller
3030 if gajim
.config
.get('use_speller'):
3031 lang
= gajim
.config
.get('speller_language')
3037 spell
= gtkspell
.Spell(tv
, lang
)
3038 except (ImportError, TypeError, RuntimeError, OSError):
3039 dialogs
.AspellDictError(lang
)
3041 if gajim
.config
.get('soundplayer') == '':
3042 # only on first time Gajim starts
3043 commands
= ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay')
3044 for command
in commands
:
3045 if helpers
.is_in_path(command
):
3046 if command
== 'aplay':
3048 gajim
.config
.set('soundplayer', command
)
3051 self
.last_ftwindow_update
= 0
3053 self
.music_track_changed_signal
= None
3056 class PassphraseRequest
:
3057 def __init__(self
, keyid
):
3060 self
.dialog_created
= False
3062 self
.passphrase
= None
3063 self
.completed
= False
3065 def interrupt(self
, account
=None):
3067 for (acct
, cb
) in self
.callbacks
:
3069 self
.callbacks
.remove((acct
, cb
))
3072 if not len(self
.callbacks
):
3073 self
.dialog
.window
.destroy()
3075 def run_callback(self
, account
, callback
):
3076 gajim
.connections
[account
].gpg_passphrase(self
.passphrase
)
3079 def add_callback(self
, account
, cb
):
3081 self
.run_callback(account
, cb
)
3083 self
.callbacks
.append((account
, cb
))
3084 if not self
.dialog_created
:
3085 self
.create_dialog(account
)
3087 def complete(self
, passphrase
):
3088 self
.passphrase
= passphrase
3089 self
.completed
= True
3090 if passphrase
is not None:
3091 gobject
.timeout_add_seconds(30,
3092 gajim
.interface
.forget_gpg_passphrase
, self
.keyid
)
3093 for (account
, cb
) in self
.callbacks
:
3094 self
.run_callback(account
, cb
)
3097 def create_dialog(self
, account
):
3098 title
= _('Passphrase Required')
3099 second
= _('Enter GPG key passphrase for key %(keyid)s (account '
3100 '%(account)s).') % {'keyid': self
.keyid
, 'account': account
}
3103 # user cancelled, continue without GPG
3106 def _ok(passphrase
, checked
, count
):
3107 result
= gajim
.connections
[account
].test_gpg_passphrase(passphrase
)
3109 # passphrase is good
3110 self
.complete(passphrase
)
3112 elif result
== 'expired':
3113 dialogs
.ErrorDialog(_('GPG key expired'),
3114 _('Your GPG key has expired, you will be connected to %s '
3115 'without OpenPGP.') % account
)
3116 # Don't try to connect with GPG
3117 gajim
.connections
[account
].continue_connect_info
[2] = False
3123 dialogs
.PassphraseDialog(_('Wrong Passphrase'),
3124 _('Please retype your GPG passphrase or press Cancel.'),
3125 ok_handler
=(_ok
, count
+ 1), cancel_handler
=_cancel
)
3127 # user failed 3 times, continue without GPG
3130 self
.dialog
= dialogs
.PassphraseDialog(title
, second
, ok_handler
=(_ok
,
3131 1), cancel_handler
=_cancel
)
3132 self
.dialog_created
= True
3135 class ThreadInterface
:
3136 def __init__(self
, func
, func_args
, callback
, callback_args
):
3138 Call a function in a thread
3140 def thread_function(func
, func_args
, callback
, callback_args
):
3141 output
= func(*func_args
)
3142 gobject
.idle_add(callback
, output
, *callback_args
)
3144 Thread(target
=thread_function
, args
=(func
, func_args
, callback
,
3145 callback_args
)).start()