# -*- coding:utf-8 -*- ## src/gajim.py ## ## Copyright (C) 2003-2010 Yann Leboulanger ## Copyright (C) 2004-2005 Vincent Hanquez ## Copyright (C) 2005 Alex Podaras ## Norman Rasmussen ## Stéphan Kochen ## Copyright (C) 2005-2006 Dimitur Kirov ## Alex Mauer ## Copyright (C) 2005-2007 Travis Shirk ## Nikos Kouremenos ## Copyright (C) 2006 Junglecow J ## Stefan Bethge ## Copyright (C) 2006-2008 Jean-Marie Traissard ## Copyright (C) 2007 Lukas Petrovicky ## James Newton ## Copyright (C) 2007-2008 Brendan Taylor ## Julien Pivotto ## Stephan Erb ## Copyright (C) 2008 Jonathan Schleifer ## ## This file is part of Gajim. ## ## Gajim is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published ## by the Free Software Foundation; version 3 only. ## ## Gajim is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Gajim. If not, see . ## import os import sys import re import time import math from subprocess import Popen import gtk import gobject from common import i18n from common import gajim from common import dbus_support if dbus_support.supported: from music_track_listener import MusicTrackListener from common import location_listener import dbus import gtkgui_helpers import dialogs import notify import message_control from chat_control import ChatControlBase from chat_control import ChatControl from groupchat_control import GroupchatControl from groupchat_control import PrivateChatControl from atom_window import AtomWindow from session import ChatControlSession import common.sleepy from common.xmpp import idlequeue from common.zeroconf import connection_zeroconf from common import resolver from common import caps_cache from common import proxy65_manager from common import socks5 from common import helpers from common import dataforms from common import passwords from common import logging_helpers import roster_window import profile_window import config from threading import Thread from common import ged gajimpaths = common.configpaths.gajimpaths config_filename = gajimpaths['CONFIG_FILE'] from common import optparser parser = optparser.OptionsParser(config_filename) import logging log = logging.getLogger('gajim.interface') class Interface: ################################################################################ ### Methods handling events from connection ################################################################################ def handle_event_warning(self, unused, data): #('WARNING', account, (title_text, section_text)) dialogs.WarningDialog(data[0], data[1]) def handle_event_error(self, unused, data): #('ERROR', account, (title_text, section_text)) dialogs.ErrorDialog(data[0], data[1]) def handle_event_db_error(self, unused, data): #('DB_ERROR', account, (title_text, section_text)) if self.db_error_dialog: return self.db_error_dialog = dialogs.ErrorDialog(data[0], data[1]) def destroyed(win): self.db_error_dialog = None self.db_error_dialog.connect('destroy', destroyed) def handle_event_information(self, unused, data): #('INFORMATION', account, (title_text, section_text)) dialogs.InformationDialog(data[0], data[1]) def handle_ask_new_nick(self, account, room_jid): title = _('Unable to join group chat') prompt = _('Your desired nickname in group chat %s is in use or ' 'registered by another occupant.\nPlease specify another nickname ' 'below:') % room_jid check_text = _('Always use this nickname when there is a conflict') if 'change_nick_dialog' in self.instances: self.instances['change_nick_dialog'].add_room(account, room_jid, prompt) else: self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( account, room_jid, title, prompt) def handle_event_http_auth(self, obj): #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) def response(account, answer): obj.conn.build_http_auth_answer(obj.stanza, answer) def on_yes(is_checked, obj): response(obj, 'yes') account = obj.conn.name sec_msg = _('Do you accept this request?') if gajim.get_number_of_connected_accounts() > 1: sec_msg = _('Do you accept this request on account %s?') % account if obj.msg: sec_msg = obj.msg + '\n' + sec_msg dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' '%(url)s (id: %(id)s)') % {'method': obj.method, 'url': obj.url, 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj), on_response_no=(response, obj, 'no')) def handle_event_iq_error(self, obj): #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode)) if unicode(obj.errcode) in ('400', '403', '406') and obj.id_: # show the error dialog ft = self.instances['file_transfers'] sid = obj.id_ if len(obj.id_) > 3 and obj.id_[2] == '_': sid = obj.id_[3:] if sid in ft.files_props['s']: file_props = ft.files_props['s'][sid] if unicode(obj.errcode) == '400': file_props['error'] = -3 else: file_props['error'] = -4 self.handle_event_file_request_error(obj.conn.name, (obj.fjid, file_props, obj.errmsg)) obj.conn.disconnect_transfer(file_props) return elif unicode(obj.errcode) == '404': sid = obj.id_ if len(obj.id_) > 3 and obj.id_[2] == '_': sid = obj.id_[3:] if sid in obj.conn.files_props: file_props = obj.conn.files_props[sid] self.handle_event_file_send_error(obj.conn.name, (obj.fjid, file_props)) obj.conn.disconnect_transfer(file_props) return ctrl = self.msg_win_mgr.get_control(obj.fjid, obj.conn.name) if ctrl and ctrl.type_id == message_control.TYPE_GC: ctrl.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg)) def handle_event_connection_lost(self, obj): # ('CONNECTION_LOST', account, [title, text]) path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48) account = obj.conn.name notify.popup(_('Connection Failed'), account, account, 'connection_failed', path, obj.title, obj.msg) def unblock_signed_in_notifications(self, account): gajim.block_signed_in_notifications[account] = False def handle_event_status(self, obj): # OUR status #('STATUS', account, show) account = obj.conn.name if obj.show in ('offline', 'error'): for name in self.instances[account]['online_dialog'].keys(): # .keys() is needed to not have a dictionary length changed # during iteration error self.instances[account]['online_dialog'][name].destroy() if name in self.instances[account]['online_dialog']: # destroy handler may have already removed it del self.instances[account]['online_dialog'][name] for request in self.gpg_passphrase.values(): if request: request.interrupt(account=account) if account in self.pass_dialog: self.pass_dialog[account].window.destroy() if obj.show == 'offline': gajim.block_signed_in_notifications[account] = True else: # 30 seconds after we change our status to sth else than offline # we stop blocking notifications of any kind # this prevents from getting the roster items as 'just signed in' # contacts. 30 seconds should be enough time gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account) if account in self.show_vcard_when_connect and obj.show not in ( 'offline', 'error'): self.edit_own_details(account) def edit_own_details(self, account): jid = gajim.get_jid_from_account(account) if 'profile' not in self.instances[account]: self.instances[account]['profile'] = \ profile_window.ProfileWindow(account) gajim.connections[account].request_vcard(jid) def handle_gc_error(self, gc_control, pritext, sectext): if gc_control and gc_control.autorejoin is not None: if gc_control.error_dialog: gc_control.error_dialog.destroy() def on_close(dummy): gc_control.error_dialog.destroy() gc_control.error_dialog = None gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext, on_response_ok=on_close, on_response_cancel=on_close) else: dialogs.ErrorDialog(pritext, sectext) def handle_gc_password_required(self, account, room_jid, nick): def on_ok(text): gajim.connections[account].join_gc(nick, room_jid, text) gajim.gc_passwords[room_jid] = text def on_cancel(): # get and destroy window if room_jid in gajim.interface.minimized_controls[account]: self.roster.on_disconnect(None, room_jid, account) else: win = self.msg_win_mgr.get_window(room_jid, account) ctrl = self.msg_win_mgr.get_gc_control(room_jid, account) win.remove_tab(ctrl, 3) dlg = dialogs.InputDialog(_('Password Required'), _('A Password is required to join the room %s. Please type it.') % \ room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) dlg.input_entry.set_visibility(False) def handle_event_gc_presence(self, obj): gc_control = obj.gc_control if obj.ptype == 'error': if obj.errcode == '503': # maximum user number reached self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Maximum number of users for %s has been reached') % \ obj.room_jid) elif (obj.errcode == '401') or (obj.errcon == 'not-authorized'): # password required to join self.handle_gc_password_required(obj.conn.name, obj.room_jid, obj.nick) elif (obj.errcode == '403') or (obj.errcon == 'forbidden'): # we are banned self.handle_gc_error(gc_control, _('Unable to join group chat'), _('You are banned from group chat %s.') % obj.room_jid) elif (obj.errcode == '404') or (obj.errcon in ('item-not-found', 'remote-server-not-found')): # group chat does not exist self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Group chat %s does not exist.') % obj.room_jid) elif (obj.errcode == '405') or (obj.errcon == 'not-allowed'): self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Group chat creation is restricted.')) elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'): self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Your registered nickname must be used in group chat ' '%s.') % obj.room_jid) elif (obj.errcode == '407') or (obj.errcon == \ 'registration-required'): self.handle_gc_error(gc_control, _('Unable to join group chat'), _('You are not in the members list in groupchat %s.') % \ obj.room_jid) elif (obj.errcode == '409') or (obj.errcon == 'conflict'): self.handle_ask_new_nick(obj.conn.name, obj.room_jid) elif gc_control: gc_control.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg)) return def handle_event_presence(self, obj): # 'NOTIFY' (account, (jid, status, status message, resource, # priority, # keyID, timestamp, contact_nickname)) # # Contact changed show account = obj.conn.name jid = obj.jid show = obj.show status = obj.status resource = obj.resource or '' jid_list = gajim.contacts.get_jid_list(account) # unset custom status if (obj.old_show == 0 and obj.new_show > 1) or \ (obj.old_show > 1 and obj.new_show == 0 and obj.conn.connected > 1): if account in self.status_sent_to_users and \ jid in self.status_sent_to_users[account]: del self.status_sent_to_users[account][jid] if gajim.jid_is_transport(jid): # It must be an agent # transport just signed in/out, don't show # popup notifications for 30s account_jid = account + '/' + jid gajim.block_signed_in_notifications[account_jid] = True gobject.timeout_add_seconds(30, self.unblock_signed_in_notifications, account_jid) else: # It isn't an agent # Notifications obj.show_notif = True for c in obj.contact_list: if c.resource == resource: # we look for other connected resources continue if c.show not in ('offline', 'error'): obj.show_notif = False break if obj.show_notif: # no other resource is connected, let's look in metacontacts family = gajim.contacts.get_metacontacts_family(account, jid) for info in family: acct_ = info['account'] jid_ = info['jid'] c_ = gajim.contacts.get_contact_with_highest_priority( acct_, jid_) if not c_: continue if c_.show not in ('offline', 'error'): obj.show_notif = False break if obj.show_notif: if obj.old_show < 2 and obj.new_show > 1: notify.notify('contact_connected', jid, account, status) elif obj.old_show > 1 and obj.new_show < 2: notify.notify('contact_disconnected', jid, account, status) # Status change (not connected/disconnected or # error (<1)) elif obj.new_show > 1: notify.notify('status_change', jid, account, [obj.new_show, status]) highest = gajim.contacts.get_contact_with_highest_priority(account, jid) is_highest = (highest and highest.resource == resource) # disconnect the session from the ctrl if the highest resource has # changed if (obj.was_highest and not is_highest) or \ (not obj.was_highest and is_highest): ctrl = self.msg_win_mgr.get_control(jid, account) if ctrl: ctrl.no_autonegotiation = False ctrl.set_session(None) ctrl.contact = highest def handle_event_msgerror(self, account, array): #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[, # session])) full_jid_with_resource = array[0] jids = full_jid_with_resource.split('/', 1) jid = jids[0] if array[1] == '503': # If we get server-not-found error, stop sending chatstates for contact in gajim.contacts.get_contacts(account, jid): contact.composing_xep = False session = None if len(array) > 5: session = array[5] gc_control = self.msg_win_mgr.get_gc_control(jid, account) if not gc_control and \ jid in self.minimized_controls[account]: gc_control = self.minimized_controls[account][jid] if gc_control and gc_control.type_id != message_control.TYPE_GC: gc_control = None if gc_control: if len(jids) > 1: # it's a pm nick = jids[1] if session: ctrl = session.control else: ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account) if not ctrl: tv = gc_control.list_treeview model = tv.get_model() iter_ = gc_control.get_contact_iter(nick) if iter_: show = model[iter_][3] else: show = 'offline' gc_c = gajim.contacts.create_gc_contact(room_jid=jid, account=account, name=nick, show=show) ctrl = self.new_private_chat(gc_c, account, session) ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { 'code': array[1], 'msg': array[2]}, 'status') return gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { 'code': array[1], 'msg': array[2]}, 'status') if gc_control.parent_win and \ gc_control.parent_win.get_active_jid() == jid: gc_control.set_subject(gc_control.subject) return if gajim.jid_is_transport(jid): jid = jid.replace('@', '') msg = array[2] if array[3]: msg = _('error while sending %(message)s ( %(error)s )') % { 'message': array[3], 'error': msg} if session: session.roster_message(jid, msg, array[4], msg_type='error') def handle_event_msgsent(self, obj): #('MSGSENT', account, (jid, msg, keyID)) # do not play sound when standalone chatstate message (eg no msg) if obj.message and gajim.config.get_per('soundevents', 'message_sent', 'enabled'): helpers.play_sound('message_sent') def handle_event_msgnotsent(self, obj): #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session)) msg = _('error while sending %(message)s ( %(error)s )') % { 'message': obj.message, 'error': obj.error} if not obj.session: # No session. This can happen when sending a message from # gajim-remote log.warn(msg) return obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name, msg_type='error') def handle_event_subscribe_presence(self, obj): #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 account = obj.conn.name if helpers.allow_popup_window(account) or not self.systray_enabled: dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account, obj.user_nick) return self.add_event(account, obj.jid, 'subscription_request', (obj.status, obj.user_nick)) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-subscription_request', 48) event_type = _('Subscription request') notify.popup(event_type, obj.jid, account, 'subscription_request', path, event_type, obj.jid) def handle_event_subscribed_presence(self, obj): #('SUBSCRIBED', account, (jid, resource)) account = obj.conn.name if obj.jid in gajim.contacts.get_jid_list(account): c = gajim.contacts.get_first_contact_from_jid(account, obj.jid) c.resource = obj.resource self.roster.remove_contact_from_groups(c.jid, account, [_('Not in Roster'), _('Observers')], update=False) else: keyID = '' attached_keys = gajim.config.get_per('accounts', account, 'attached_gpg_keys').split() if obj.jid in attached_keys: keyID = attached_keys[attached_keys.index(obj.jid) + 1] name = obj.jid.split('@', 1)[0] name = name.split('%', 1)[0] contact1 = gajim.contacts.create_contact(jid=obj.jid, account=account, name=name, groups=[], show='online', status='online', ask='to', resource=obj.resource, keyID=keyID) gajim.contacts.add_contact(account, contact1) self.roster.add_contact(obj.jid, account) dialogs.InformationDialog(_('Authorization accepted'), _('The contact "%s" has authorized you to see his or her status.') % obj.jid) def show_unsubscribed_dialog(self, account, contact): def on_yes(is_checked, list_): self.roster.on_req_usub(None, list_) list_ = [(contact, account)] dialogs.YesNoDialog( _('Contact "%s" removed subscription from you') % contact.jid, _('You will always see him or her as offline.\nDo you want to ' 'remove him or her from your contact list?'), on_response_yes=(on_yes, list_)) # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does # not show deny def handle_event_unsubscribed_presence(self, obj): #('UNSUBSCRIBED', account, jid) account = obj.conn.name contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid) if not contact: return if helpers.allow_popup_window(account) or not self.systray_enabled: self.show_unsubscribed_dialog(account, contact) return self.add_event(account, obj.jid, 'unsubscribed', contact) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48) event_type = _('Unsubscribed') notify.popup(event_type, obj.jid, account, 'unsubscribed', path, event_type, obj.jid) def handle_event_register_agent_info(self, account, array): # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) # info in a dataform if is_form is True if array[2] or 'instructions' in array[1]: config.ServiceRegistrationWindow(array[0], array[1], account, array[2]) else: dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \ % array[0], _('Check your connection or try again later.')) def handle_event_agent_info_items(self, account, array): #('AGENT_INFO_ITEMS', account, (agent, node, items)) our_jid = gajim.get_jid_from_account(account) if 'pep_services' in gajim.interface.instances[account] and \ array[0] == our_jid: gajim.interface.instances[account]['pep_services'].items_received( array[2]) def handle_event_myvcard(self, account, array): nick = '' if 'NICKNAME' in array and array['NICKNAME']: gajim.nicks[account] = array['NICKNAME'] elif 'FN' in array and array['FN']: gajim.nicks[account] = array['FN'] if 'profile' in self.instances[account]: win = self.instances[account]['profile'] win.set_values(array) if account in self.show_vcard_when_connect: self.show_vcard_when_connect.remove(account) jid = array['jid'] if jid in self.instances[account]['infos']: self.instances[account]['infos'][jid].set_values(array) def handle_event_vcard(self, account, vcard): # ('VCARD', account, data) '''vcard holds the vcard data''' jid = vcard['jid'] resource = vcard.get('resource', '') fjid = jid + '/' + str(resource) # vcard window win = None if jid in self.instances[account]['infos']: win = self.instances[account]['infos'][jid] elif resource and fjid in self.instances[account]['infos']: win = self.instances[account]['infos'][fjid] if win: win.set_values(vcard) # show avatar in chat ctrl = None if resource and self.msg_win_mgr.has_window(fjid, account): win = self.msg_win_mgr.get_window(fjid, account) ctrl = win.get_control(fjid, account) elif self.msg_win_mgr.has_window(jid, account): win = self.msg_win_mgr.get_window(jid, account) ctrl = win.get_control(jid, account) if ctrl and ctrl.type_id != message_control.TYPE_GC: ctrl.show_avatar() # Show avatar in roster or gc_roster gc_ctrl = self.msg_win_mgr.get_gc_control(jid, account) if not gc_ctrl and \ jid in self.minimized_controls[account]: gc_ctrl = self.minimized_controls[account][jid] if gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC: gc_ctrl.draw_avatar(resource) else: self.roster.draw_avatar(jid, account) if self.remote_ctrl: self.remote_ctrl.raise_signal('VcardInfo', (account, vcard)) def handle_event_last_status_time(self, obj): # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status)) if obj.seconds < 0: # Ann error occured return account = obj.conn.name c = gajim.contacts.get_contact(account, obj.jid, obj.resource) if c: # c can be none if it's a gc contact if obj.status: c.status = obj.status self.roster.draw_contact(c.jid, account) # draw offline status last_time = time.localtime(time.time() - obj.seconds) if c.show == 'offline': c.last_status_time = last_time else: c.last_activity_time = last_time if self.roster.tooltip.id and self.roster.tooltip.win: self.roster.tooltip.update_last_time(last_time) def handle_event_gc_subject(self, account, array): #('GC_SUBJECT', account, (jid, subject, body, has_timestamp)) jids = array[0].split('/', 1) jid = jids[0] gc_control = self.msg_win_mgr.get_gc_control(jid, account) if not gc_control and \ jid in self.minimized_controls[account]: gc_control = self.minimized_controls[account][jid] contact = gajim.contacts.\ get_contact_with_highest_priority(account, jid) if contact: contact.status = array[1] self.roster.draw_contact(jid, account) if not gc_control: return gc_control.set_subject(array[1]) # Standard way, the message comes from the occupant who set the subject text = None if len(jids) > 1: text = _('%(jid)s has set the subject to %(subject)s') % { 'jid': jids[1], 'subject': array[1]} # Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be # deleted one day. We can receive a subject with a body that contains # "X has set the subject to Y" ... elif array[2]: text = array[2] if text is not None: if array[3]: gc_control.print_old_conversation(text) else: gc_control.print_conversation(text) def handle_event_gc_config(self, obj): #('GC_CONFIG', account, (jid, form_node)) config is a dict account = obj.conn.name if obj.jid in gajim.automatic_rooms[account]: if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]: # We're converting chat to muc. allow participants to invite for f in obj.dataform.iter_fields(): if f.var == 'muc#roomconfig_allowinvites': f.value = True elif f.var == 'muc#roomconfig_publicroom': f.value = False elif f.var == 'muc#roomconfig_membersonly': f.value = True elif f.var == 'public_list': f.value = False obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged()) else: # use default configuration obj.conn.send_gc_config(obj.jid, obj.form_node) # invite contacts # check if it is necessary to add continue_tag = False if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]: continue_tag = True if 'invities' in gajim.automatic_rooms[account][obj.jid]: for jid in gajim.automatic_rooms[account][obj.jid]['invities']: obj.conn.send_invite(obj.jid, jid, continue_tag=continue_tag) del gajim.automatic_rooms[account][obj.jid] elif obj.jid not in self.instances[account]['gc_config']: self.instances[account]['gc_config'][obj.jid] = \ config.GroupchatConfigWindow(account, obj.jid, obj.dataform) def handle_event_gc_config_change(self, account, array): #('GC_CONFIG_CHANGE', account, (jid, statusCode)) statuscode is a list # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes... # -init jid = array[0] statusCode = array[1] gc_control = self.msg_win_mgr.get_gc_control(jid, account) if not gc_control and \ jid in self.minimized_controls[account]: gc_control = self.minimized_controls[account][jid] if not gc_control: return changes = [] if '100' in statusCode: # Can be a presence (see chg_contact_status in groupchat_control.py) changes.append(_('Any occupant is allowed to see your full JID')) gc_control.is_anonymous = False if '102' in statusCode: changes.append(_('Room now shows unavailable member')) if '103' in statusCode: changes.append(_('room now does not show unavailable members')) if '104' in statusCode: changes.append(_('A non-privacy-related room configuration change ' 'has occurred')) if '170' in statusCode: # Can be a presence (see chg_contact_status in groupchat_control.py) changes.append(_('Room logging is now enabled')) if '171' in statusCode: changes.append(_('Room logging is now disabled')) if '172' in statusCode: changes.append(_('Room is now non-anonymous')) gc_control.is_anonymous = False if '173' in statusCode: changes.append(_('Room is now semi-anonymous')) gc_control.is_anonymous = True if '174' in statusCode: changes.append(_('Room is now fully-anonymous')) gc_control.is_anonymous = True for change in changes: gc_control.print_conversation(change) def handle_event_gc_affiliation(self, obj): #('GC_AFFILIATION', account, (room_jid, users_dict)) account = obj.conn.name if obj.jid in self.instances[account]['gc_config']: self.instances[account]['gc_config'][obj.jid].\ affiliation_list_received(obj.users_dict) def handle_event_gc_invitation(self, obj): #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) jid = gajim.get_jid_without_resource(obj.jid_from) account = obj.conn.name if helpers.allow_popup_window(account) or not self.systray_enabled: dialogs.InvitationReceivedDialog(account, obj.room_jid, jid, obj.password, obj.reason, is_continued=obj.is_continued) return self.add_event(account, jid, 'gc-invitation', (obj.room_jid, obj.reason, obj.password, obj.is_continued)) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48) event_type = _('Groupchat Invitation') notify.popup(event_type, jid, account, 'gc-invitation', path, event_type, obj.room_jid) def forget_gpg_passphrase(self, keyid): if keyid in self.gpg_passphrase: del self.gpg_passphrase[keyid] return False def handle_event_bad_gpg_passphrase(self, obj): #('BAD_PASSPHRASE', account, ()) if obj.use_gpg_agent: sectext = _('You configured Gajim to use GPG agent, but there is no' ' GPG agent running or it returned a wrong passphrase.\n') sectext += _('You are currently connected without your OpenPGP ' 'key.') dialogs.WarningDialog(_('Your passphrase is incorrect'), sectext) else: path = gtkgui_helpers.get_icon_path('gajim-warning', 48) account = obj.conn.name notify.popup('warning', account, account, 'warning', path, _('OpenGPG Passphrase Incorrect'), _('You are currently connected without your OpenPGP key.')) self.forget_gpg_passphrase(obj.keyID) def handle_event_gpg_password_required(self, obj): #('GPG_PASSWORD_REQUIRED', account, (callback,)) if obj.keyid in self.gpg_passphrase: request = self.gpg_passphrase[obj.keyid] else: request = PassphraseRequest(obj.keyid) self.gpg_passphrase[obj.keyid] = request request.add_callback(obj.conn.name, obj.callback) def handle_event_gpg_trust_key(self, obj): #('GPG_ALWAYS_TRUST', account, callback) def on_yes(checked): if checked: obj.conn.gpg.always_trust = True obj.callback(True) def on_no(): obj.callback(False) dialogs.YesNoDialog(_('GPG key not trusted'), _('The GPG key used to ' 'encrypt this chat is not trusted. Do you really want to encrypt ' 'this message?'), checktext=_('_Do not ask me again'), on_response_yes=on_yes, on_response_no=on_no) def handle_event_password_required(self, account, array): #('PASSWORD_REQUIRED', account, None) if account in self.pass_dialog: return text = _('Enter your password for account %s') % account if passwords.USER_HAS_GNOMEKEYRING and \ not passwords.USER_USES_GNOMEKEYRING: text += '\n' + _('Gnome Keyring is installed but not \ correctly started (environment variable probably not \ correctly set)') def on_ok(passphrase, save): if save: gajim.config.set_per('accounts', account, 'savepass', True) passwords.save_password(account, passphrase) gajim.connections[account].set_password(passphrase) del self.pass_dialog[account] def on_cancel(): self.roster.set_state(account, 'offline') self.roster.update_status_combobox() del self.pass_dialog[account] self.pass_dialog[account] = dialogs.PassphraseDialog( _('Password Required'), text, _('Save password'), ok_handler=on_ok, cancel_handler=on_cancel) def handle_event_roster_info(self, obj): #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) account = obj.conn.name contacts = gajim.contacts.get_contacts(account, obj.jid) if (not obj.sub or obj.sub == 'none') and \ (not obj.ask or obj.ask == 'none') and not obj.nickname and \ not obj.groups: # contact removed us. if contacts: self.roster.remove_contact(obj.jid, account, backend=True) return elif not contacts: if obj.sub == 'remove': return # Add new contact to roster contact = gajim.contacts.create_contact(jid=obj.jid, account=account, name=obj.nickname, groups=obj.groups, show='offline', sub=obj.sub, ask=obj.ask) gajim.contacts.add_contact(account, contact) self.roster.add_contact(obj.jid, account) else: # If contact has changed (sub, ask or group) update roster # Mind about observer status changes: # According to xep 0162, a contact is not an observer anymore when # we asked for auth, so also remove him if ask changed old_groups = contacts[0].groups if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\ or old_groups != obj.groups: # c.get_shown_groups() has changed. Reflect that in # roster_winodow self.roster.remove_contact(obj.jid, account, force=True) for contact in contacts: contact.name = obj.nickname or '' contact.sub = obj.sub contact.ask = obj.ask contact.groups = obj.groups or [] self.roster.add_contact(obj.jid, account) # Refilter and update old groups for group in old_groups: self.roster.draw_group(group, account) self.roster.draw_contact(obj.jid, account) def handle_event_bookmarks(self, obj): # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}]) # We received a bookmark item from the server (JEP48) # Auto join GC windows if neccessary self.roster.set_actions_menu_needs_rebuild() invisible_show = gajim.SHOW_LIST.index('invisible') # do not autojoin if we are invisible if obj.conn.connected == invisible_show: return self.auto_join_bookmarks(obj.conn.name) def handle_event_file_send_error(self, account, array): jid = array[0] file_props = array[1] ft = self.instances['file_transfers'] ft.set_status(file_props['type'], file_props['sid'], 'stop') if helpers.allow_popup_window(account): ft.show_send_error(file_props) return self.add_event(account, jid, 'file-send-error', file_props) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, jid, account, 'file-send-error', path, event_type, file_props['name']) def handle_event_gmail_notify(self, obj): jid = obj.jid gmail_new_messages = int(obj.newmsgs) gmail_messages_list = obj.gmail_messages_list if not gajim.config.get('notify_on_new_gmail_email'): return path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48) title = _('New mail on %(gmail_mail_address)s') % \ {'gmail_mail_address': jid} text = i18n.ngettext('You have %d new mail conversation', 'You have %d new mail conversations', gmail_new_messages, gmail_new_messages, gmail_new_messages) if gajim.config.get('notify_on_new_gmail_email_extra'): cnt = 0 for gmessage in gmail_messages_list: # FIXME: emulate Gtalk client popups. find out what they # parse and how they decide what to show each message has a # 'From', 'Subject' and 'Snippet' field if cnt >= 5: break senders = ',\n '.join(reversed(gmessage['From'])) text += _('\n\nFrom: %(from_address)s\nSubject: ' '%(subject)s\n%(snippet)s') % {'from_address': senders, 'subject': gmessage['Subject'], 'snippet': gmessage['Snippet']} cnt += 1 command = gajim.config.get('notify_on_new_gmail_email_command') if command: Popen(command, shell=True) if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'): helpers.play_sound('gmail_received') notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail', path_to_image=path, title=title, text=text) def handle_event_file_request_error(self, account, array): # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) jid, file_props, errmsg = array jid = gajim.get_jid_without_resource(jid) ft = self.instances['file_transfers'] ft.set_status(file_props['type'], file_props['sid'], 'stop') errno = file_props['error'] if helpers.allow_popup_window(account): if errno in (-4, -5): ft.show_stopped(jid, file_props, errmsg) else: ft.show_request_error(file_props) return if errno in (-4, -5): msg_type = 'file-error' else: msg_type = 'file-request-error' self.add_event(account, jid, msg_type, file_props) if helpers.allow_showing_notification(account): # check if we should be notified path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48) event_type = _('File Transfer Error') notify.popup(event_type, jid, account, msg_type, path, title = event_type, text = file_props['name']) def handle_event_file_request(self, account, array): jid = array[0] jid = gajim.get_jid_without_resource(jid) if jid not in gajim.contacts.get_jid_list(account): keyID = '' attached_keys = gajim.config.get_per('accounts', account, 'attached_gpg_keys').split() if jid in attached_keys: keyID = attached_keys[attached_keys.index(jid) + 1] contact = gajim.contacts.create_not_in_roster_contact(jid=jid, account=account, keyID=keyID) gajim.contacts.add_contact(account, contact) self.roster.add_contact(contact.jid, account) file_props = array[1] contact = gajim.contacts.get_first_contact_from_jid(account, jid) if helpers.allow_popup_window(account): self.instances['file_transfers'].show_file_request(account, contact, file_props) return self.add_event(account, jid, 'file-request', file_props) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48) txt = _('%s wants to send you a file.') % gajim.get_name_from_jid( account, jid) event_type = _('File Transfer Request') notify.popup(event_type, jid, account, 'file-request', path_to_image = path, title = event_type, text = txt) def handle_event_file_error(self, title, message): dialogs.ErrorDialog(title, message) def handle_event_file_progress(self, account, file_props): if time.time() - self.last_ftwindow_update > 0.5: # update ft window every 500ms self.last_ftwindow_update = time.time() self.instances['file_transfers'].set_progress(file_props['type'], file_props['sid'], file_props['received-len']) def handle_event_file_rcv_completed(self, account, file_props): ft = self.instances['file_transfers'] if file_props['error'] == 0: ft.set_progress(file_props['type'], file_props['sid'], file_props['received-len']) else: ft.set_status(file_props['type'], file_props['sid'], 'stop') if 'stalled' in file_props and file_props['stalled'] or \ 'paused' in file_props and file_props['paused']: return if file_props['type'] == 'r': # we receive a file jid = unicode(file_props['sender']) else: # we send a file jid = unicode(file_props['receiver']) if helpers.allow_popup_window(account): if file_props['error'] == 0: if gajim.config.get('notify_on_file_complete'): ft.show_completed(jid, file_props) elif file_props['error'] == -1: ft.show_stopped(jid, file_props, error_msg=_('Remote contact stopped transfer')) elif file_props['error'] == -6: ft.show_stopped(jid, file_props, error_msg=_('Error opening file')) return msg_type = '' event_type = '' if file_props['error'] == 0 and gajim.config.get( 'notify_on_file_complete'): msg_type = 'file-completed' event_type = _('File Transfer Completed') elif file_props['error'] in (-1, -6): msg_type = 'file-stopped' event_type = _('File Transfer Stopped') if event_type == '': # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs) # this should never happen but it does. see process_result() in # socks5.py # who calls this func (sth is really wrong unless this func is also # registered as progress_cb return if msg_type: self.add_event(account, jid, msg_type, file_props) if file_props is not None: if file_props['type'] == 'r': # get the name of the sender, as it is in the roster sender = unicode(file_props['sender']).split('/')[0] name = gajim.contacts.get_first_contact_from_jid(account, sender).get_shown_name() filename = os.path.basename(file_props['file-name']) if event_type == _('File Transfer Completed'): txt = _('You successfully received %(filename)s from ' '%(name)s.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' else: # ft stopped txt = _('File transfer of %(filename)s from %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' else: receiver = file_props['receiver'] if hasattr(receiver, 'jid'): receiver = receiver.jid receiver = receiver.split('/')[0] # get the name of the contact, as it is in the roster name = gajim.contacts.get_first_contact_from_jid(account, receiver).get_shown_name() filename = os.path.basename(file_props['file-name']) if event_type == _('File Transfer Completed'): txt = _('You successfully sent %(filename)s to %(name)s.')\ % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' else: # ft stopped txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' path = '' if gajim.config.get('notify_on_file_complete') and \ (gajim.config.get('autopopupaway') or \ gajim.connections[account].connected in (2, 3)): # we want to be notified and we are online/chat or we don't mind # bugged when away/na/busy notify.popup(event_type, jid, account, msg_type, path_to_image=path, title=event_type, text=txt) def ask_offline_status(self, account): for contact in gajim.contacts.iter_contacts(account): gajim.connections[account].request_last_status_time(contact.jid, contact.resource) def handle_event_signed_in(self, account, empty): """ SIGNED_IN event is emitted when we sign in, so handle it """ # ('SIGNED_IN', account, ()) # block signed in notifications for 30 seconds gajim.block_signed_in_notifications[account] = True self.roster.set_actions_menu_needs_rebuild() self.roster.draw_account(account) state = self.sleeper.getState() connected = gajim.connections[account].connected if gajim.config.get('ask_offline_status_on_connection'): # Ask offline status in 1 minute so w'are sure we got all online # presences gobject.timeout_add_seconds(60, self.ask_offline_status, account) if state != common.sleepy.STATE_UNKNOWN and connected in (2, 3): # we go online or free for chat, so we activate auto status gajim.sleeper_state[account] = 'online' elif not ((state == common.sleepy.STATE_AWAY and connected == 4) or \ (state == common.sleepy.STATE_XA and connected == 5)): # If we are autoaway/xa and come back after a disconnection, do # nothing # Else disable autoaway gajim.sleeper_state[account] = 'off' invisible_show = gajim.SHOW_LIST.index('invisible') # We cannot join rooms if we are invisible if gajim.connections[account].connected == invisible_show: return # join already open groupchats for gc_control in self.msg_win_mgr.get_controls( message_control.TYPE_GC) + self.minimized_controls[account].values(): if account != gc_control.account: continue room_jid = gc_control.room_jid if room_jid in gajim.gc_connected[account] and \ gajim.gc_connected[account][room_jid]: continue nick = gc_control.nick password = gajim.gc_passwords.get(room_jid, '') gajim.connections[account].join_gc(nick, room_jid, password) # send currently played music if gajim.connections[account].pep_supported and dbus_support.supported \ and gajim.config.get_per('accounts', account, 'publish_tune'): self.enable_music_listener() # enable location listener if gajim.connections[account].pep_supported and dbus_support.supported \ and gajim.config.get_per('accounts', account, 'publish_location'): location_listener.enable() if gajim.connections[account].archiving_supported: # Start merging logs from server gajim.connections[account].request_modifications_page( gajim.config.get_per('accounts', account, 'last_archiving_time')) gajim.config.set_per('accounts', account, 'last_archiving_time', time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())) def handle_event_metacontacts(self, account, tags_list): gajim.contacts.define_metacontacts(account, tags_list) self.roster.redraw_metacontacts(account) def handle_atom_entry(self, obj): AtomWindow.newAtomEntry(obj.atom_entry) def handle_event_failed_decrypt(self, account, data): jid, tim, session = data details = _('Unable to decrypt message from ' '%s\nIt may have been tampered with.') % jid ctrl = session.control if ctrl: ctrl.print_conversation_line(details, 'status', '', tim) else: dialogs.WarningDialog(_('Unable to decrypt message'), details) # terminate the session session.terminate_e2e() session.conn.delete_session(jid, session.thread_id) # restart the session if ctrl: ctrl.begin_e2e_negotiation() def handle_event_privacy_lists_received(self, account, data): # ('PRIVACY_LISTS_RECEIVED', account, list) if account not in self.instances: return if 'privacy_lists' in self.instances[account]: self.instances[account]['privacy_lists'].privacy_lists_received( data) def handle_event_privacy_list_received(self, account, data): # ('PRIVACY_LIST_RECEIVED', account, (name, rules)) if account not in self.instances: return name = data[0] rules = data[1] if 'privacy_list_%s' % name in self.instances[account]: self.instances[account]['privacy_list_%s' % name].\ privacy_list_received(rules) if name == 'block': con = gajim.connections[account] con.blocked_contacts = [] con.blocked_groups = [] con.blocked_list = [] gajim.connections[account].blocked_all = False for rule in rules: if rule['action'] == 'allow': if not 'type' in rule: con.blocked_all = False elif rule['type'] == 'jid' and rule['value'] in \ con.blocked_contacts: con.blocked_contacts.remove(rule['value']) elif rule['type'] == 'group' and rule['value'] in \ con.blocked_groups: con.blocked_groups.remove(rule['value']) elif rule['action'] == 'deny': if not 'type' in rule: con.blocked_all = True elif rule['type'] == 'jid' and rule['value'] not in \ con.blocked_contacts: con.blocked_contacts.append(rule['value']) elif rule['type'] == 'group' and rule['value'] not in \ con.blocked_groups: con.blocked_groups.append(rule['value']) con.blocked_list.append(rule) if 'blocked_contacts' in self.instances[account]: self.instances[account]['blocked_contacts'].\ privacy_list_received(rules) def handle_event_privacy_lists_active_default(self, account, data): if not data: return # Send to all privacy_list_* windows as we can't know which one asked for win in self.instances[account]: if win.startswith('privacy_list_'): self.instances[account][win].check_active_default(data) def handle_event_privacy_list_removed(self, account, name): # ('PRIVACY_LISTS_REMOVED', account, name) if account not in self.instances: return if 'privacy_lists' in self.instances[account]: self.instances[account]['privacy_lists'].privacy_list_removed(name) def handle_event_zc_name_conflict(self, account, data): def on_ok(new_name): gajim.config.set_per('accounts', account, 'name', new_name) show = gajim.connections[account].old_show status = gajim.connections[account].status gajim.connections[account].username = new_name gajim.connections[account].change_status(show, status) def on_cancel(): gajim.connections[account].change_status('offline', '') dlg = dialogs.InputDialog(_('Username Conflict'), _('Please type a new username for your local account'), input_str=data, is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel) def handle_event_resource_conflict(self, obj): # ('RESOURCE_CONFLICT', account, ()) # First we go offline, but we don't overwrite status message account = obj.conn.name conn = obj.conn self.roster.send_status(account, 'offline', conn.status) def on_ok(new_resource): gajim.config.set_per('accounts', account, 'resource', new_resource) self.roster.send_status(account, conn.old_show, conn.status) proposed_resource = conn.server_resource proposed_resource += gajim.config.get('gc_proposed_nick_char') dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'), _('You are already connected to this account with the same ' 'resource. Please type a new one'), resource=proposed_resource, ok_handler=on_ok) def handle_event_jingle_incoming(self, obj): # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, # data...)) # TODO: conditional blocking if peer is not in roster account = obj.conn.name content_types = set(c[0] for c in obj.contents) # check type of jingle session if 'audio' in content_types or 'video' in content_types: # a voip session... # we now handle only voip, so the only thing we will do here is # not to return from function pass else: # unknown session type... it should be declined in common/jingle.py return ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: if 'audio' in content_types: ctrl.set_audio_state('connection_received', obj.sid) if 'video' in content_types: ctrl.set_video_state('connection_received', obj.sid) dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid) if dlg: dlg.add_contents(content_types) return if helpers.allow_popup_window(account): dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid, content_types) return self.add_event(account, obj.jid, 'jingle-incoming', (obj.fjid, obj.sid, content_types)) if helpers.allow_showing_notification(account): # TODO: we should use another pixmap ;-) txt = _('%s wants to start a voice chat.') % \ gajim.get_name_from_jid(account, obj.fjid) path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48) event_type = _('Voice Chat Request') notify.popup(event_type, obj.fjid, account, 'jingle-incoming', path_to_image=path, title=event_type, text=txt) def handle_event_jingle_connected(self, obj): # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) if obj.media in ('audio', 'video'): account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: if obj.media == 'audio': ctrl.set_audio_state('connected', obj.sid) else: ctrl.set_video_state('connected', obj.sid) def handle_event_jingle_disconnected(self, obj): # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: if obj.media is None: ctrl.stop_jingle(sid=obj.sid, reason=obj.reason) elif obj.media == 'audio': ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason) elif obj.media == 'video': ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason) dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid) if dialog: if obj.media is None: dialog.dialog.destroy() else: dialog.remove_contents((obj.media, )) def handle_event_jingle_error(self, obj): # ('JINGLE_ERROR', account, (peerjid, sid, reason)) account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) if ctrl: ctrl.set_audio_state('error', reason=obj.reason) def handle_event_pep_config(self, account, data): # ('PEP_CONFIG', account, (node, form)) if 'pep_services' in self.instances[account]: self.instances[account]['pep_services'].config(data[0], data[1]) def handle_event_roster_item_exchange(self, obj): # data = (action in [add, delete, modify], exchange_list, jid_from) dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action, obj.exchange_items_list, obj.fjid) def handle_event_unique_room_id_supported(self, account, data): """ Receive confirmation that unique_room_id are supported """ # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) instance = data[1] instance.unique_room_id_supported(data[0], data[2]) def handle_event_unique_room_id_unsupported(self, account, data): # ('UNIQUE_ROOM_ID_UNSUPPORTED', server, instance) instance = data[1] instance.unique_room_id_error(data[0]) def handle_event_ssl_error(self, account, data): # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint)) server = gajim.config.get_per('accounts', account, 'hostname') def on_ok(is_checked): del self.instances[account]['online_dialog']['ssl_error'] if is_checked[0]: # Check if cert is already in file certs = '' if os.path.isfile(gajim.MY_CACERTS): f = open(gajim.MY_CACERTS) certs = f.read() f.close() if data[2] in certs: dialogs.ErrorDialog(_('Certificate Already in File'), _('This certificate is already in file %s, so it\'s ' 'not added again.') % gajim.MY_CACERTS) else: f = open(gajim.MY_CACERTS, 'a') f.write(server + '\n') f.write(data[2] + '\n\n') f.close() gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', data[3]) if is_checked[1]: ignore_ssl_errors = gajim.config.get_per('accounts', account, 'ignore_ssl_errors').split() ignore_ssl_errors.append(str(data[1])) gajim.config.set_per('accounts', account, 'ignore_ssl_errors', ' '.join(ignore_ssl_errors)) gajim.connections[account].ssl_certificate_accepted() def on_cancel(): del self.instances[account]['online_dialog']['ssl_error'] gajim.connections[account].disconnect(on_purpose=True) self.handle_event_status(account, 'offline') pritext = _('Error verifying SSL certificate') sectext = _('There was an error verifying the SSL certificate of your ' 'jabber server: %(error)s\nDo you still want to connect to this ' 'server?') % {'error': data[0]} if data[1] in (18, 27): checktext1 = _('Add this certificate to the list of trusted ' 'certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3] else: checktext1 = '' checktext2 = _('Ignore this error for this certificate.') if 'ssl_error' in self.instances[account]['online_dialog']: self.instances[account]['online_dialog']['ssl_error'].destroy() self.instances[account]['online_dialog']['ssl_error'] = \ dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) def handle_event_fingerprint_error(self, account, data): # ('FINGERPRINT_ERROR', account, (new_fingerprint,)) def on_yes(is_checked): del self.instances[account]['online_dialog']['fingerprint_error'] gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', data[0]) # Reset the ignored ssl errors gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '') gajim.connections[account].ssl_certificate_accepted() def on_no(): del self.instances[account]['online_dialog']['fingerprint_error'] gajim.connections[account].disconnect(on_purpose=True) self.handle_event_status(account, 'offline') pritext = _('SSL certificate error') sectext = _('It seems the SSL certificate of account %(account)s has ' 'changed or your connection is being hacked.\nOld fingerprint: ' '%(old)s\nNew fingerprint: %(new)s\n\nDo you still want to connect ' 'and update the fingerprint of the certificate?') % \ {'account': account, 'old': gajim.config.get_per('accounts', account, 'ssl_fingerprint_sha1'), 'new': data[0]} if 'fingerprint_error' in self.instances[account]['online_dialog']: self.instances[account]['online_dialog']['fingerprint_error'].\ destroy() self.instances[account]['online_dialog']['fingerprint_error'] = \ dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no) def handle_event_plain_connection(self, obj): # ('PLAIN_CONNECTION', account, (connection)) def on_ok(is_checked): if not is_checked[0]: on_cancel() return # On cancel call del self.instances, so don't call it another time # before del self.instances[obj.conn.name]['online_dialog']\ ['plain_connection'] if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'warn_when_plaintext_connection', False) obj.conn.connection_accepted(obj.xmpp_client, 'plain') def on_cancel(): del self.instances[obj.conn.name]['online_dialog']\ ['plain_connection'] obj.conn.disconnect(on_purpose=True) self.handle_event_status(obj.conn.name, 'offline') pritext = _('Insecure connection') sectext = _('You are about to connect to the server with an insecure ' 'connection. This means all your conversations will be ' 'exchanged unencrypted. Are you sure you want to do that?') checktext1 = _('Yes, I really want to connect insecurely') checktext2 = _('_Do not ask me again') if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']: self.instances[obj.conn.name]['online_dialog']['plain_connection'].\ destroy() self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \ dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, is_modal=False) def handle_event_insecure_ssl_connection(self, obj): # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) def on_ok(is_checked): if not is_checked[0]: on_cancel() return del self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'warn_when_insecure_ssl_connection', False) if obj.conn.connected == 0: # We have been disconnecting (too long time since window is # opened) # re-connect with auto-accept obj.conn.connection_auto_accepted = True show, msg = obj.conn.continue_connect_info[:2] self.roster.send_status(obj.conn.name, show, msg) return obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type) def on_cancel(): del self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] obj.conn.disconnect(on_purpose=True) self.handle_event_status(obj.conn.name, 'offline') pritext = _('Insecure connection') sectext = _('You are about to send your password on an insecure ' 'connection. You should install PyOpenSSL to prevent that. Are you ' 'sure you want to do that?') checktext1 = _('Yes, I really want to connect insecurely') checktext2 = _('_Do not ask me again') if 'insecure_ssl' in self.instances[obj.conn.name]['online_dialog']: self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\ destroy() self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \ dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, is_modal=False) def handle_event_insecure_password(self, obj): # ('INSECURE_PASSWORD', account, ()) def on_ok(is_checked): if not is_checked[0]: on_cancel() return del self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'] if is_checked[1]: gajim.config.set_per('accounts', obj.conn.name, 'warn_when_insecure_password', False) if obj.conn.connected == 0: # We have been disconnecting (too long time since window is # opened) # re-connect with auto-accept obj.conn.connection_auto_accepted = True show, msg = obj.conn.continue_connect_info[:2] self.roster.send_status(obj.conn.name, show, msg) return obj.conn.accept_insecure_password() def on_cancel(): del self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'] obj.conn.disconnect(on_purpose=True) self.handle_event_status(obj.conn.name, 'offline') pritext = _('Insecure connection') sectext = _('You are about to send your password unencrypted on an ' 'insecure connection. Are you sure you want to do that?') checktext1 = _('Yes, I really want to connect insecurely') checktext2 = _('_Do not ask me again') if 'insecure_password' in self.instances[obj.conn.name]\ ['online_dialog']: self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'].destroy() self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \ dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, is_modal=False) def create_core_handlers_list(self): self.handlers = { 'WARNING': [self.handle_event_warning], 'ERROR': [self.handle_event_error], 'DB_ERROR': [self.handle_event_db_error], 'INFORMATION': [self.handle_event_information], 'MSGERROR': [self.handle_event_msgerror], 'REGISTER_AGENT_INFO': [self.handle_event_register_agent_info], 'AGENT_INFO_ITEMS': [self.handle_event_agent_info_items], 'MYVCARD': [self.handle_event_myvcard], 'VCARD': [self.handle_event_vcard], 'GC_SUBJECT': [self.handle_event_gc_subject], 'GC_CONFIG_CHANGE': [self.handle_event_gc_config_change], 'FILE_REQUEST': [self.handle_event_file_request], 'FILE_REQUEST_ERROR': [self.handle_event_file_request_error], 'FILE_SEND_ERROR': [self.handle_event_file_send_error], 'SIGNED_IN': [self.handle_event_signed_in], 'METACONTACTS': [self.handle_event_metacontacts], 'FAILED_DECRYPT': [self.handle_event_failed_decrypt], 'PRIVACY_LISTS_RECEIVED': \ [self.handle_event_privacy_lists_received], 'PRIVACY_LIST_RECEIVED': [self.handle_event_privacy_list_received], 'PRIVACY_LISTS_ACTIVE_DEFAULT': \ [self.handle_event_privacy_lists_active_default], 'PRIVACY_LIST_REMOVED': [self.handle_event_privacy_list_removed], 'ZC_NAME_CONFLICT': [self.handle_event_zc_name_conflict], 'PEP_CONFIG': [self.handle_event_pep_config], 'UNIQUE_ROOM_ID_UNSUPPORTED': \ [self.handle_event_unique_room_id_unsupported], 'UNIQUE_ROOM_ID_SUPPORTED': \ [self.handle_event_unique_room_id_supported], 'PASSWORD_REQUIRED': [self.handle_event_password_required], 'SSL_ERROR': [self.handle_event_ssl_error], 'FINGERPRINT_ERROR': [self.handle_event_fingerprint_error], 'atom-entry-received': [self.handle_atom_entry], 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase], 'bookmarks-received': [self.handle_event_bookmarks], 'connection-lost': [self.handle_event_connection_lost], 'gc-invitation-received': [self.handle_event_gc_invitation], 'gc-presence-received': [self.handle_event_gc_presence], 'gmail-notify': [self.handle_event_gmail_notify], 'gpg-password-required': [self.handle_event_gpg_password_required], 'gpg-trust-key': [self.handle_event_gpg_trust_key], 'http-auth-received': [self.handle_event_http_auth], 'insecure-password': [self.handle_event_insecure_password], 'insecure-ssl-connection': \ [self.handle_event_insecure_ssl_connection], 'iq-error-received': [self.handle_event_iq_error], 'jingle-connected-received': [self.handle_event_jingle_connected], 'jingle-disconnected-received': [ self.handle_event_jingle_disconnected], 'jingle-error-received': [self.handle_event_jingle_error], 'jingle-request-received': [self.handle_event_jingle_incoming], 'last-result-received': [self.handle_event_last_status_time], 'message-not-sent': [self.handle_event_msgnotsent], 'message-sent': [self.handle_event_msgsent], 'muc-admin-received': [self.handle_event_gc_affiliation], 'muc-owner-received': [self.handle_event_gc_config], 'our-show': [self.handle_event_status], 'plain-connection': [self.handle_event_plain_connection], 'presence-received': [self.handle_event_presence], 'roster-info': [self.handle_event_roster_info], 'roster-item-exchange-received': \ [self.handle_event_roster_item_exchange], 'stream-conflict-received': [self.handle_event_resource_conflict], 'subscribe-presence-received': [ self.handle_event_subscribe_presence], 'subscribed-presence-received': [ self.handle_event_subscribed_presence], 'unsubscribed-presence-received': [ self.handle_event_unsubscribed_presence], } def register_core_handlers(self): """ Register core handlers in Global Events Dispatcher (GED). This is part of rewriting whole events handling system to use GED. """ for event_name, event_handlers in self.handlers.iteritems(): for event_handler in event_handlers: gajim.ged.register_event_handler(event_name, ged.GUI1, event_handler) ################################################################################ ### Methods dealing with gajim.events ################################################################################ def add_event(self, account, jid, type_, event_args): """ Add an event to the gajim.events var """ # We add it to the gajim.events queue # Do we have a queue? jid = gajim.get_jid_without_resource(jid) no_queue = len(gajim.events.get_events(account, jid)) == 0 # type_ can be gc-invitation file-send-error file-error # file-request-error file-request file-completed file-stopped # jingle-incoming # event_type can be in advancedNotificationWindow.events_list event_types = {'file-request': 'ft_request', 'file-completed': 'ft_finished'} event_type = event_types.get(type_) show_in_roster = notify.get_show_in_roster(event_type, account, jid) show_in_systray = notify.get_show_in_systray(event_type, account, jid) event = gajim.events.create_event(type_, event_args, show_in_roster=show_in_roster, show_in_systray=show_in_systray) gajim.events.add_event(account, jid, event) self.roster.show_title() if no_queue: # We didn't have a queue: we change icons if not gajim.contacts.get_contact_with_highest_priority(account, jid): if type_ == 'gc-invitation': self.roster.add_groupchat(jid, account, status='offline') else: # add contact to roster ("Not In The Roster") if he is not self.roster.add_to_not_in_the_roster(account, jid) else: self.roster.draw_contact(jid, account) # Select the big brother contact in roster, it's visible because it has # events. family = gajim.contacts.get_metacontacts_family(account, jid) if family: nearby_family, bb_jid, bb_account = \ gajim.contacts.get_nearby_family_and_big_brother(family, account) else: bb_jid, bb_account = jid, account self.roster.select_contact(bb_jid, bb_account) def handle_event(self, account, fjid, type_): w = None ctrl = None session = None resource = gajim.get_resource_from_jid(fjid) jid = gajim.get_jid_without_resource(fjid) if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'): w = self.msg_win_mgr.get_window(jid, account) if jid in self.minimized_controls[account]: self.roster.on_groupchat_maximized(None, jid, account) return else: ctrl = self.msg_win_mgr.get_gc_control(jid, account) elif type_ in ('printed_chat', 'chat', ''): # '' is for log in/out notifications if type_ != '': event = gajim.events.get_first_event(account, fjid, type_) if not event: event = gajim.events.get_first_event(account, jid, type_) if not event: return if type_ == 'printed_chat': ctrl = event.parameters[0] elif type_ == 'chat': session = event.parameters[8] ctrl = session.control elif type_ == '': ctrl = self.msg_win_mgr.get_control(fjid, account) if not ctrl: highest_contact = gajim.contacts.\ get_contact_with_highest_priority(account, jid) # jid can have a window if this resource was lower when he sent # message and is now higher because the other one is offline if resource and highest_contact.resource == resource and \ not self.msg_win_mgr.has_window(jid, account): # remove resource of events too gajim.events.change_jid(account, fjid, jid) resource = None fjid = jid contact = None if resource: contact = gajim.contacts.get_contact(account, jid, resource) if not contact: contact = highest_contact ctrl = self.new_chat(contact, account, resource=resource, session=session) gajim.last_message_time[account][jid] = 0 # long time ago w = ctrl.parent_win elif type_ in ('printed_pm', 'pm'): # assume that the most recently updated control we have for this # party is the one that this event was in event = gajim.events.get_first_event(account, fjid, type_) if not event: event = gajim.events.get_first_event(account, jid, type_) if type_ == 'printed_pm': ctrl = event.parameters[0] elif type_ == 'pm': session = event.parameters[8] if session and session.control: ctrl = session.control elif not ctrl: room_jid = jid nick = resource gc_contact = gajim.contacts.get_gc_contact(account, room_jid, nick) if gc_contact: show = gc_contact.show else: show = 'offline' gc_contact = gajim.contacts.create_gc_contact( room_jid=room_jid, account=account, name=nick, show=show) if not session: session = gajim.connections[account].make_new_session( fjid, None, type_='pm') self.new_private_chat(gc_contact, account, session=session) ctrl = session.control w = ctrl.parent_win elif type_ in ('normal', 'file-request', 'file-request-error', 'file-send-error', 'file-error', 'file-stopped', 'file-completed', 'jingle-incoming'): # Get the first single message event event = gajim.events.get_first_event(account, fjid, type_) if not event: # default to jid without resource event = gajim.events.get_first_event(account, jid, type_) if not event: return # Open the window self.roster.open_event(account, jid, event) else: # Open the window self.roster.open_event(account, fjid, event) elif type_ == 'gmail': url = gajim.connections[account].gmail_url if url: helpers.launch_browser_mailer('url', url) elif type_ == 'gc-invitation': event = gajim.events.get_first_event(account, jid, type_) data = event.parameters dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], data[1], data[3]) gajim.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) elif type_ == 'subscription_request': event = gajim.events.get_first_event(account, jid, type_) data = event.parameters dialogs.SubscriptionRequestWindow(jid, data[0], account, data[1]) gajim.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) elif type_ == 'unsubscribed': event = gajim.events.get_first_event(account, jid, type_) contact = event.parameters self.show_unsubscribed_dialog(account, contact) gajim.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) if w: w.set_active_tab(ctrl) w.window.window.focus(gtk.get_current_event_time()) # Using isinstance here because we want to catch all derived types if isinstance(ctrl, ChatControlBase): tv = ctrl.conv_textview tv.scroll_to_end() ################################################################################ ### Methods dealing with emoticons ################################################################################ def image_is_ok(self, image): if not os.path.exists(image): return False img = gtk.Image() try: img.set_from_file(image) except Exception: return False t = img.get_storage_type() if t != gtk.IMAGE_PIXBUF and t != gtk.IMAGE_ANIMATION: return False return True @property def basic_pattern_re(self): if not self._basic_pattern_re: self._basic_pattern_re = re.compile(self.basic_pattern, re.IGNORECASE) return self._basic_pattern_re @property def emot_and_basic_re(self): if not self._emot_and_basic_re: self._emot_and_basic_re = re.compile(self.emot_and_basic, re.IGNORECASE + re.UNICODE) return self._emot_and_basic_re @property def sth_at_sth_dot_sth_re(self): if not self._sth_at_sth_dot_sth_re: self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth) return self._sth_at_sth_dot_sth_re @property def invalid_XML_chars_re(self): if not self._invalid_XML_chars_re: self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars) return self._invalid_XML_chars_re def make_regexps(self): # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) # one escapes the metachars with \ # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' # \s matches any whitespace character # \w any alphanumeric character # \W any non-alphanumeric character # \b means word boundary. This is a zero-width assertion that # matches only at the beginning or end of a word. # ^ matches at the beginning of lines # # * means 0 or more times # + means 1 or more times # ? means 0 or 1 time # | means or # [^*] anything but '*' (inside [] you don't have to escape metachars) # [^\s*] anything but whitespaces and '*' # (? in the matching string don't match ? or ) etc.. if at # the end # so http://be) will match http://be and http://be)be) will match # http://be)be legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\ r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\ r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\ r"|%[A-Fa-f0-9]{2})+"\ r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)" # NOTE: it's ok to catch www.gr such stuff exist! # FIXME: recognize xmpp: and treat it specially links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\ r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\ r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)" # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* formatting = r'|(?> sys.stderr, err_str # it is good to notify the user # in case he or she cannot see the output of the console dialogs.ErrorDialog(_('Could not save your settings and ' 'preferences'), err_str) sys.exit() def save_avatar_files(self, jid, photo, puny_nick = None, local = False): """ Save an avatar to a separate file, and generate files for dbus notifications. An avatar can be given as a pixmap directly or as an decoded image """ puny_jid = helpers.sanitize_filename(jid) path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) if puny_nick: path_to_file = os.path.join(path_to_file, puny_nick) # remove old avatars for typ in ('jpeg', 'png'): if local: path_to_original_file = path_to_file + '_local'+ '.' + typ else: path_to_original_file = path_to_file + '.' + typ if os.path.isfile(path_to_original_file): os.remove(path_to_original_file) if local and photo: pixbuf = photo typ = 'png' extension = '_local.png' # save local avatars as png file else: pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo, want_type=True) if pixbuf is None: return extension = '.' + typ if typ not in ('jpeg', 'png'): gajim.log.debug('gtkpixbuf cannot save other than jpeg and '\ 'png formats. saving %s\'avatar as png file (originaly %s)'\ % (jid, typ)) typ = 'png' extension = '.png' path_to_original_file = path_to_file + extension try: pixbuf.save(path_to_original_file, typ) except Exception, e: log.error('Error writing avatar file %s: %s' % ( path_to_original_file, str(e))) # Generate and save the resized, color avatar pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification') if pixbuf: path_to_normal_file = path_to_file + '_notif_size_colored' + \ extension try: pixbuf.save(path_to_normal_file, 'png') except Exception, e: log.error('Error writing avatar file %s: %s' % \ (path_to_original_file, str(e))) # Generate and save the resized, black and white avatar bwbuf = gtkgui_helpers.get_scaled_pixbuf( gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification') if bwbuf: path_to_bw_file = path_to_file + '_notif_size_bw' + extension try: bwbuf.save(path_to_bw_file, 'png') except Exception, e: log.error('Error writing avatar file %s: %s' % \ (path_to_original_file, str(e))) def remove_avatar_files(self, jid, puny_nick = None, local = False): """ Remove avatar files of a jid """ puny_jid = helpers.sanitize_filename(jid) path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) if puny_nick: path_to_file = os.path.join(path_to_file, puny_nick) for ext in ('.jpeg', '.png'): if local: ext = '_local' + ext path_to_original_file = path_to_file + ext if os.path.isfile(path_to_file + ext): os.remove(path_to_file + ext) if os.path.isfile(path_to_file + '_notif_size_colored' + ext): os.remove(path_to_file + '_notif_size_colored' + ext) if os.path.isfile(path_to_file + '_notif_size_bw' + ext): os.remove(path_to_file + '_notif_size_bw' + ext) def auto_join_bookmarks(self, account): """ Autojoin bookmarked GCs that have 'auto join' on for this account """ for bm in gajim.connections[account].bookmarks: if bm['autojoin'] in ('1', 'true'): jid = bm['jid'] # Only join non-opened groupchats. Opened one are already # auto-joined on re-connection if not jid in gajim.gc_connected[account]: # we are not already connected minimize = bm['minimize'] in ('1', 'true') gajim.interface.join_gc_room(account, jid, bm['nick'], bm['password'], minimize = minimize) elif jid in self.minimized_controls[account]: # more or less a hack: # On disconnect the minimized gc contact instances # were set to offline. Reconnect them to show up in the # roster. self.roster.add_groupchat(jid, account) def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, nick): """ Add a bookmark for this account, sorted in bookmark list """ bm = { 'name': name, 'jid': jid, 'autojoin': autojoin, 'minimize': minimize, 'password': password, 'nick': nick } place_found = False index = 0 # check for duplicate entry and respect alpha order for bookmark in gajim.connections[account].bookmarks: if bookmark['jid'] == bm['jid']: dialogs.ErrorDialog( _('Bookmark already set'), _('Group Chat "%s" is already in your bookmarks.') % \ bm['jid']) return if bookmark['name'] > bm['name']: place_found = True break index += 1 if place_found: gajim.connections[account].bookmarks.insert(index, bm) else: gajim.connections[account].bookmarks.append(bm) gajim.connections[account].store_bookmarks() self.roster.set_actions_menu_needs_rebuild() dialogs.InformationDialog( _('Bookmark has been added successfully'), _('You can manage your bookmarks via Actions menu in your roster.')) # does JID exist only within a groupchat? def is_pm_contact(self, fjid, account): bare_jid = gajim.get_jid_without_resource(fjid) gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account) if not gc_ctrl and \ bare_jid in self.minimized_controls[account]: gc_ctrl = self.minimized_controls[account][bare_jid] return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC def create_ipython_window(self): try: from ipython_view import IPythonView except ImportError: print 'ipython_view not found' return import pango if os.name == 'nt': font = 'Lucida Console 9' else: font = 'Luxi Mono 10' window = gtk.Window() window.set_size_request(750, 550) window.set_resizable(True) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) view = IPythonView() view.modify_font(pango.FontDescription(font)) view.set_wrap_mode(gtk.WRAP_CHAR) sw.add(view) window.add(sw) window.show_all() def on_delete(win, event): win.hide() return True window.connect('delete_event', on_delete) view.updateNamespace({'gajim': gajim}) gajim.ipython_window = window def run(self): if gajim.config.get('trayicon') != 'never': self.show_systray() self.roster = roster_window.RosterWindow() # Creating plugin manager import plugins gajim.plugin_manager = plugins.PluginManager() self.roster._before_fill() for account in gajim.connections: gajim.connections[account].load_roster_from_db() self.roster._after_fill() # get instances for windows/dialogs that will show_all()/hide() self.instances['file_transfers'] = dialogs.FileTransfersWindow() gobject.timeout_add(100, self.autoconnect) timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT if in_seconds: gobject.timeout_add_seconds(timeout, self.process_connections) else: gobject.timeout_add(timeout, self.process_connections) gobject.timeout_add_seconds(gajim.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) # when using libasyncns we need to process resolver in regular intervals if resolver.USE_LIBASYNCNS: gobject.timeout_add(200, gajim.resolver.process) def remote_init(): if gajim.config.get('remote_control'): try: import remote_control self.remote_ctrl = remote_control.Remote() except Exception: pass gobject.timeout_add_seconds(5, remote_init) def __init__(self): gajim.interface = self gajim.thread_interface = ThreadInterface # This is the manager and factory of message windows set by the module self.msg_win_mgr = None self.jabber_state_images = {'16': {}, '32': {}, 'opened': {}, 'closed': {}} self.emoticons_menu = None # handler when an emoticon is clicked in emoticons_menu self.emoticon_menuitem_clicked = None self.minimized_controls = {} self.status_sent_to_users = {} self.status_sent_to_groups = {} self.gpg_passphrase = {} self.pass_dialog = {} self.db_error_dialog = None self.default_colors = { 'inmsgcolor': gajim.config.get('inmsgcolor'), 'outmsgcolor': gajim.config.get('outmsgcolor'), 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'), 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'), 'statusmsgcolor': gajim.config.get('statusmsgcolor'), 'urlmsgcolor': gajim.config.get('urlmsgcolor'), } self.handlers = {} self.roster = None self._invalid_XML_chars_re = None self._basic_pattern_re = None self._emot_and_basic_re = None self._sth_at_sth_dot_sth_re = None self.link_pattern_re = None self.invalid_XML_chars = None self.basic_pattern = None self.emot_and_basic = None self.sth_at_sth_dot_sth = None self.emot_only = None self.emoticons = [] self.emoticons_animations = {} self.emoticons_images = {} cfg_was_read = parser.read() from common import latex gajim.HAVE_LATEX = gajim.config.get('use_latex') and \ latex.check_for_latex_support() gajim.logger.reset_shown_unread_messages() # override logging settings from config (don't take care of '-q' option) if gajim.config.get('verbose'): logging_helpers.set_verbose() # Is Gajim default app? if os.name != 'nt' and gajim.config.get('check_if_gajim_is_default'): gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() for account in gajim.config.get_per('accounts'): if gajim.config.get_per('accounts', account, 'is_zeroconf'): gajim.ZEROCONF_ACC_NAME = account break # Is gnome configured to activate row on single click ? try: import gconf client = gconf.client_get_default() click_policy = client.get_string( '/apps/nautilus/preferences/click_policy') if click_policy == 'single': gajim.single_click = True except Exception: pass # add default status messages if there is not in the config file if len(gajim.config.get_per('statusmsg')) == 0: default = gajim.config.statusmsg_default for msg in default: gajim.config.add_per('statusmsg', msg) gajim.config.set_per('statusmsg', msg, 'message', default[msg][0]) gajim.config.set_per('statusmsg', msg, 'activity', default[msg][1]) gajim.config.set_per('statusmsg', msg, 'subactivity', default[msg][2]) gajim.config.set_per('statusmsg', msg, 'activity_text', default[msg][3]) gajim.config.set_per('statusmsg', msg, 'mood', default[msg][4]) gajim.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) #add default themes if there is not in the config file theme = gajim.config.get('roster_theme') if not theme in gajim.config.get_per('themes'): gajim.config.set('roster_theme', _('default')) if len(gajim.config.get_per('themes')) == 0: d = ['accounttextcolor', 'accountbgcolor', 'accountfont', 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor'] default = gajim.config.themes_default for theme_name in default: gajim.config.add_per('themes', theme_name) theme = default[theme_name] for o in d: gajim.config.set_per('themes', theme_name, o, theme[d.index(o)]) if gajim.config.get('autodetect_browser_mailer') or not cfg_was_read: gtkgui_helpers.autodetect_browser_mailer() gajim.idlequeue = idlequeue.get_idlequeue() # resolve and keep current record of resolved hosts gajim.resolver = resolver.get_resolver(gajim.idlequeue) gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue, self.handle_event_file_rcv_completed, self.handle_event_file_progress, self.handle_event_file_error) gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) gajim.default_session_type = ChatControlSession # Creating Network Events Controller from common import nec gajim.nec = nec.NetworkEventsController() self.create_core_handlers_list() self.register_core_handlers() if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \ and gajim.HAVE_ZEROCONF: gajim.connections[gajim.ZEROCONF_ACC_NAME] = \ connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME) for account in gajim.config.get_per('accounts'): if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\ gajim.config.get_per('accounts', account, 'active'): gajim.connections[account] = common.connection.Connection( account) # gtk hooks gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') self.instances = {} for a in gajim.connections: self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}} # online_dialog contains all dialogs that have a meaning only when # we are not disconnected self.minimized_controls[a] = {} gajim.contacts.add_account(a) gajim.groups[a] = {} gajim.gc_connected[a] = {} gajim.automatic_rooms[a] = {} gajim.newly_added[a] = [] gajim.to_be_removed[a] = [] gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name') gajim.block_signed_in_notifications[a] = True gajim.sleeper_state[a] = 0 gajim.encrypted_chats[a] = [] gajim.last_message_time[a] = {} gajim.status_before_autoaway[a] = '' gajim.transport_avatar[a] = {} gajim.gajim_optional_features[a] = [] gajim.caps_hash[a] = '' helpers.update_optional_features() # prepopulate data which we are sure of; note: we do not log these info for account in gajim.connections: gajimcaps = caps_cache.capscache[('sha-1', gajim.caps_hash[account])] gajimcaps.identities = [gajim.gajim_identity] gajimcaps.features = gajim.gajim_common_features + \ gajim.gajim_optional_features[account] self.remote_ctrl = None if gajim.config.get('networkmanager_support') and \ dbus_support.supported: import network_manager_listener # Handle gnome screensaver if dbus_support.supported: def gnome_screensaver_ActiveChanged_cb(active): if not active: for account in gajim.connections: if gajim.sleeper_state[account] == 'autoaway-forced': # We came back online ofter gnome-screensaver # autoaway self.roster.send_status(account, 'online', gajim.status_before_autoaway[account]) gajim.status_before_autoaway[account] = '' gajim.sleeper_state[account] = 'online' return if not gajim.config.get('autoaway'): # Don't go auto away if user disabled the option return for account in gajim.connections: if account not in gajim.sleeper_state or \ not gajim.sleeper_state[account]: continue if gajim.sleeper_state[account] == 'online': # we save out online status gajim.status_before_autoaway[account] = \ gajim.connections[account].status # we go away (no auto status) [we pass True to auto # param] auto_message = gajim.config.get('autoaway_message') if not auto_message: auto_message = gajim.connections[account].status else: auto_message = auto_message.replace('$S', '%(status)s') auto_message = auto_message.replace('$T', '%(time)s') auto_message = auto_message % { 'status': gajim.status_before_autoaway[account], 'time': gajim.config.get('autoxatime')} self.roster.send_status(account, 'away', auto_message, auto=True) gajim.sleeper_state[account] = 'autoaway-forced' try: bus = dbus.SessionBus() bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb, 'ActiveChanged', 'org.gnome.ScreenSaver') except Exception: pass self.show_vcard_when_connect = [] self.sleeper = common.sleepy.Sleepy( gajim.config.get('autoawaytime') * 60, # make minutes to seconds gajim.config.get('autoxatime') * 60) gtkgui_helpers.make_jabber_state_images() self.systray_enabled = False import statusicon self.systray = statusicon.StatusIcon() pix = gtkgui_helpers.get_icon_pixmap('gajim', 32) # set the icon to all windows gtk.window_set_default_icon(pix) self.init_emoticons() self.make_regexps() # get transports type from DB gajim.transport_type = gajim.logger.get_transports_type() # test is dictionnary is present for speller if gajim.config.get('use_speller'): lang = gajim.config.get('speller_language') if not lang: lang = gajim.LANG tv = gtk.TextView() try: import gtkspell spell = gtkspell.Spell(tv, lang) except (ImportError, TypeError, RuntimeError, OSError): dialogs.AspellDictError(lang) if gajim.config.get('soundplayer') == '': # only on first time Gajim starts commands = ('aplay', 'play', 'esdplay', 'artsplay', 'ossplay') for command in commands: if helpers.is_in_path(command): if command == 'aplay': command += ' -q' gajim.config.set('soundplayer', command) break self.last_ftwindow_update = 0 self.music_track_changed_signal = None class PassphraseRequest: def __init__(self, keyid): self.keyid = keyid self.callbacks = [] self.dialog_created = False self.dialog = None self.passphrase = None self.completed = False def interrupt(self, account=None): if account: for (acct, cb) in self.callbacks: if acct == account: self.callbacks.remove((acct, cb)) else: self.callbacks = [] if not len(self.callbacks): self.dialog.window.destroy() def run_callback(self, account, callback): gajim.connections[account].gpg_passphrase(self.passphrase) callback() def add_callback(self, account, cb): if self.completed: self.run_callback(account, cb) else: self.callbacks.append((account, cb)) if not self.dialog_created: self.create_dialog(account) def complete(self, passphrase): self.passphrase = passphrase self.completed = True if passphrase is not None: gobject.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase, self.keyid) for (account, cb) in self.callbacks: self.run_callback(account, cb) self.callbacks = [] def create_dialog(self, account): title = _('Passphrase Required') second = _('Enter GPG key passphrase for key %(keyid)s (account ' '%(account)s).') % {'keyid': self.keyid, 'account': account} def _cancel(): # user cancelled, continue without GPG self.complete(None) def _ok(passphrase, checked, count): result = gajim.connections[account].test_gpg_passphrase(passphrase) if result == 'ok': # passphrase is good self.complete(passphrase) return elif result == 'expired': dialogs.ErrorDialog(_('GPG key expired'), _('Your GPG key has expired, you will be connected to %s ' 'without OpenPGP.') % account) # Don't try to connect with GPG gajim.connections[account].continue_connect_info[2] = False self.complete(None) return if count < 3: # ask again dialogs.PassphraseDialog(_('Wrong Passphrase'), _('Please retype your GPG passphrase or press Cancel.'), ok_handler=(_ok, count + 1), cancel_handler=_cancel) else: # user failed 3 times, continue without GPG self.complete(None) self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok, 1), cancel_handler=_cancel) self.dialog_created = True class ThreadInterface: def __init__(self, func, func_args, callback, callback_args): """ Call a function in a thread """ def thread_function(func, func_args, callback, callback_args): output = func(*func_args) gobject.idle_add(callback, output, *callback_args) Thread(target=thread_function, args=(func, func_args, callback, callback_args)).start()