# -*- coding:utf-8 -*- ## src/gajim.py ## ## Copyright (C) 2003-2014 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 import hashlib from gi.repository import Gtk from gi.repository import GdkPixbuf from gi.repository import GLib from gi.repository import Gio try: from PIL import Image except: pass from gajim.common import app from gajim.common import events from gajim.common import dbus_support if dbus_support.supported: from gajim.music_track_listener import MusicTrackListener from gajim.common import location_listener import dbus from gajim import gtkgui_helpers from gajim import gui_menu_builder from gajim import dialogs from gajim import notify from gajim import message_control from gajim.chat_control_base import ChatControlBase from gajim.chat_control import ChatControl from gajim.groupchat_control import GroupchatControl from gajim.groupchat_control import PrivateChatControl from gajim.message_window import MessageWindowMgr from gajim.atom_window import AtomWindow from gajim.session import ChatControlSession from gajim.common import sleepy from nbxmpp import idlequeue from nbxmpp import Hashes2 from gajim.common.zeroconf import connection_zeroconf from gajim.common import resolver from gajim.common import caps_cache from gajim.common import proxy65_manager from gajim.common import socks5 from gajim.common import helpers from gajim.common import passwords from gajim.common import logging_helpers from gajim.common.connection_handlers_events import ( OurShowEvent, FileRequestErrorEvent, FileTransferCompletedEvent, UpdateRosterAvatarEvent, UpdateGCAvatarEvent) from gajim.common.connection import Connection from gajim.common.file_props import FilesProp from gajim.common import pep from gajim import emoticons from gajim.common.const import AvatarSize from gajim import roster_window from gajim import profile_window from gajim import config from threading import Thread from gajim.common import ged from gajim.common.configpaths import gajimpaths config_filename = gajimpaths['CONFIG_FILE'] from gajim.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_db_error(self, unused, error): #('DB_ERROR', account, error) if self.db_error_dialog: return self.db_error_dialog = dialogs.ErrorDialog(_('Database Error'), error) def destroyed(win): self.db_error_dialog = None self.db_error_dialog.connect('destroy', destroyed) @staticmethod def handle_event_information(obj): if obj.popup: if obj.level == 'error': cls = dialogs.ErrorDialog elif obj.level == 'warn': cls = dialogs.WarningDialog elif obj.level == 'info': cls = dialogs.InformationDialog else: return cls(obj.pri_txt, GLib.markup_escape_text(obj.sec_txt)) def handle_ask_new_nick(self, account, room_jid, parent_win): title = _('Unable to join group chat') prompt = _('Your desired nickname in group chat\n' '%s\n' 'is in use or registered by another occupant.\n' 'Please 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, transient_for=parent_win) @staticmethod def handle_event_http_auth(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 app.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 str(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:] file_props = FilesProp.getFileProp(obj.conn.name, sid) if file_props : if str(obj.errcode) == '400': file_props.error = -3 else: file_props.error = -4 app.nec.push_incoming_event(FileRequestErrorEvent(None, conn=obj.conn, jid=obj.jid, file_props=file_props, error_msg=obj.errmsg)) obj.conn.disconnect_transfer(file_props) return elif str(obj.errcode) == '404': sid = obj.id_ if len(obj.id_) > 3 and obj.id_[2] == '_': sid = obj.id_[3:] file_props = FilesProp.getFileProp(obj.conn.name, sid) if file_props: 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)) @staticmethod def handle_event_connection_lost(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, '', path, obj.title, obj.msg) @staticmethod def unblock_signed_in_notifications(account): app.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 list(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': app.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 GLib.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) self.show_vcard_when_connect.remove(self.name) def edit_own_details(self, account): jid = app.get_jid_from_account(account) if 'profile' not in self.instances[account]: self.instances[account]['profile'] = \ profile_window.ProfileWindow(account, app.interface.roster.window) @staticmethod def handle_gc_error(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) gc_control.error_dialog.set_modal(False) if gc_control.parent_win: gc_control.error_dialog.set_transient_for( gc_control.parent_win.window) else: d = dialogs.ErrorDialog(pritext, sectext) if gc_control and gc_control.parent_win: d.set_transient_for(gc_control.parent_win.window) d.set_modal(False) def handle_gc_password_required(self, account, room_jid, nick): def on_ok(text): app.connections[account].join_gc(nick, room_jid, text) app.gc_passwords[room_jid] = text gc_control.error_dialog = None def on_cancel(): # get and destroy window if room_jid in app.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) gc_control.error_dialog = None gc_control = self.msg_win_mgr.get_gc_control(room_jid, account) if gc_control: if gc_control.error_dialog: gc_control.error_dialog.destroy() gc_control.error_dialog = 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) gc_control.error_dialog.input_entry.set_visibility(False) def handle_event_gc_presence(self, obj): gc_control = obj.gc_control parent_win = None if gc_control and gc_control.parent_win: parent_win = gc_control.parent_win.window if obj.ptype == 'error': if obj.errcode == '503': # maximum user number reached self.handle_gc_error(gc_control, _('Unable to join group chat'), _('%s is full')\ % 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')): # remote server does not exist if (obj.errcon == 'remote-server-not-found'): self.handle_gc_error(gc_control, _('Unable to join group chat'), _('Remote server %s does not exist.') % obj.room_jid) # group chat does not exist else: 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 not permitted.')) elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'): self.handle_gc_error(gc_control, _('Unable to join groupchat'), _('You must use your registered nickname in %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, parent_win) elif gc_control: gc_control.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg)) if gc_control and gc_control.autorejoin: gc_control.autorejoin = False @staticmethod def handle_event_gc_message(obj): if not obj.stanza.getTag('body'): # no # It could be a voice request. See # http://www.xmpp.org/extensions/xep-0045.html#voiceapprove if obj.msg_obj.form_node: dialogs.SingleMessageWindow(obj.conn.name, obj.fjid, action='receive', from_whom=obj.fjid, subject='', message='', resource='', session=None, form_node=obj.msg_obj.form_node) 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 = app.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 app.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 app.block_signed_in_notifications[account_jid] = True GLib.timeout_add_seconds(30, self.unblock_signed_in_notifications, account_jid) highest = app.contacts.get_contact_with_highest_priority(account, jid) is_highest = (highest and highest.resource == resource) ctrl = self.msg_win_mgr.get_control(jid, account) if ctrl and ctrl.session and len(obj.contact_list) > 1: ctrl.remove_session(ctrl.session) def handle_event_msgerror(self, obj): #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session])) account = obj.conn.name jids = obj.fjid.split('/', 1) jid = jids[0] session = obj.session 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(obj.fjid, 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 = app.contacts.create_gc_contact(room_jid=jid, account=account, name=nick, show=show) ctrl = self.new_private_chat(gc_c, account, session) ctrl.contact.our_chatstate = False ctrl.print_conversation(_('Error %(code)s: %(msg)s') % { 'code': obj.error_code, 'msg': obj.error_msg}, 'status') return gc_control.print_conversation(_('Error %(code)s: %(msg)s') % { 'code': obj.error_code, 'msg': obj.error_msg}, 'status') if gc_control.parent_win and \ gc_control.parent_win.get_active_jid() == jid: gc_control.set_subject(gc_control.subject) return if app.jid_is_transport(jid): jid = jid.replace('@', '') msg = obj.error_msg if obj.msg: msg = _('error while sending %(message)s ( %(error)s )') % { 'message': obj.msg, 'error': msg} if session: session.roster_message(jid, msg, obj.time_, msg_type='error') @staticmethod def handle_event_msgsent(obj): #('MSGSENT', account, (jid, msg, keyID)) # do not play sound when standalone chatstate message (eg no msg) if obj.message and app.config.get_per('soundevents', 'message_sent', 'enabled'): helpers.play_sound('message_sent') @staticmethod def handle_event_msgnotsent(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.warning(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: if obj.jid in self.instances[account]['sub_request']: self.instances[account]['sub_request'][obj.jid].window.destroy() self.instances[account]['sub_request'][obj.jid] = \ dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account, obj.user_nick) return event = events.SubscriptionRequestEvent(obj.status, obj.user_nick) self.add_event(account, obj.jid, event) 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 app.contacts.get_jid_list(account): c = app.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 = app.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 = app.contacts.create_contact(jid=obj.jid, account=account, name=name, groups=[], show='online', status='online', ask='to', resource=obj.resource, keyID=keyID) app.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 them as offline.\nDo you want to ' 'remove them 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 = app.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 event = events.UnsubscribedEvent(contact) self.add_event(account, obj.jid, event) 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) @staticmethod def handle_event_register_agent_info(obj): # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) # info in a dataform if is_form is True if obj.is_form or 'instructions' in obj.config: config.ServiceRegistrationWindow(obj.agent, obj.config, obj.conn.name, obj.is_form) else: dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \ obj.agent, _('Check your connection or try again later.')) 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 app.automatic_rooms[account]: if 'continue_tag' in app.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()) user_list = {} for jid in app.automatic_rooms[account][obj.jid]['invities']: user_list[jid] = {'affiliation': 'member'} obj.conn.send_gc_affiliation_list(obj.jid, user_list) 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 app.automatic_rooms[account][obj.jid]: continue_tag = True if 'invities' in app.automatic_rooms[account][obj.jid]: for jid in app.automatic_rooms[account][obj.jid]['invities']: obj.conn.send_invite(obj.jid, jid, continue_tag=continue_tag) gc_control = self.msg_win_mgr.get_gc_control(obj.jid, account) if gc_control: gc_control.print_conversation( _('%(jid)s has been invited in this room') % { 'jid': jid}, graphics=False) del app.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_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_decline(self, obj): account = obj.conn.name gc_control = self.msg_win_mgr.get_gc_control(obj.room_jid, account) if gc_control: if obj.reason: gc_control.print_conversation( _('%(jid)s declined the invitation: %(reason)s') % { 'jid': obj.jid_from, 'reason': obj.reason}, graphics=False) else: gc_control.print_conversation( _('%(jid)s declined the invitation') % { 'jid': obj.jid_from}, graphics=False) def handle_event_gc_invitation(self, obj): #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued)) account = obj.conn.name if helpers.allow_popup_window(account) or not self.systray_enabled: dialogs.InvitationReceivedDialog(account, obj.room_jid, obj.jid_from, obj.password, obj.reason, is_continued=obj.is_continued) return event = events.GcInvitationtEvent(obj.room_jid, obj.reason, obj.password, obj.is_continued, obj.jid_from) self.add_event(account, obj.jid_from, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48) event_type = _('Groupchat Invitation') notify.popup(event_type, obj.jid_from, 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 OpenPGP agent, but there ' 'is no OpenPGP agent running or it returned a wrong passphrase.' '\n') sectext += _('You are currently connected without your OpenPGP ' 'key.') dialogs.WarningDialog(_('Wrong passphrase'), sectext) else: path = gtkgui_helpers.get_icon_path('gtk-dialog-warning', 48) account = obj.conn.name notify.popup('warning', account, account, '', path, _('Wrong OpenPGP passphrase'), _('You are currently connected without your OpenPGP key.')) self.forget_gpg_passphrase(obj.keyID) @staticmethod def handle_event_client_cert_passphrase(obj): def on_ok(passphrase, checked): obj.conn.on_client_cert_passphrase(passphrase, obj.con, obj.port, obj.secure_tuple) def on_cancel(): obj.conn.on_client_cert_passphrase('', obj.con, obj.port, obj.secure_tuple) dialogs.PassphraseDialog(_('Certificate Passphrase Required'), _('Enter the certificate passphrase for account %s') % \ obj.conn.name, ok_handler=on_ok, cancel_handler=on_cancel) 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) @staticmethod def handle_event_gpg_trust_key(obj): #('GPG_ALWAYS_TRUST', account, callback) def on_yes(checked): if checked: obj.conn.gpg.always_trust.append(obj.keyID) obj.callback(True) def on_no(): obj.callback(False) dialogs.YesNoDialog(_('Untrusted OpenPGP key'), _('The OpenPGP 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, obj): #('PASSWORD_REQUIRED', account, None) account = obj.conn.name if account in self.pass_dialog: return text = _('Enter your password for account %s') % account def on_ok(passphrase, save): if save: app.config.set_per('accounts', account, 'savepass', True) passwords.save_password(account, passphrase) obj.conn.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_oauth2_credentials(self, obj): account = obj.conn.name def on_ok(refresh): app.config.set_per('accounts', account, 'oauth2_refresh_token', refresh) st = app.config.get_per('accounts', account, 'last_status') msg = helpers.from_one_line(app.config.get_per('accounts', account, 'last_status_msg')) app.interface.roster.send_status(account, st, msg) del self.pass_dialog[account] def on_cancel(): app.config.set_per('accounts', account, 'oauth2_refresh_token', '') self.roster.set_state(account, 'offline') self.roster.update_status_combobox() del self.pass_dialog[account] instruction = _('Please copy / paste the refresh token from the website' ' that has just been opened.') self.pass_dialog[account] = dialogs.InputTextDialog( _('Oauth2 Credentials'), instruction, is_modal=False, 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 = app.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 keyID = '' attached_keys = app.config.get_per('accounts', account, 'attached_gpg_keys').split() if obj.jid in attached_keys: keyID = attached_keys[attached_keys.index(obj.jid) + 1] contact = app.contacts.create_contact(jid=obj.jid, account=account, name=obj.nickname, groups=obj.groups, show='offline', sub=obj.sub, ask=obj.ask, keyID=keyID, avatar_sha=obj.avatar_sha) app.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 obj.sub == 'remove': # another of our instance removed a contact. Remove it here too self.roster.remove_contact(obj.jid, account, backend=True) return update = False 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_window self.roster.remove_contact(obj.jid, account, force=True) update = True for contact in contacts: contact.name = obj.nickname or '' contact.sub = obj.sub contact.ask = obj.ask contact.groups = obj.groups or [] if update: 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) if obj.jid in self.instances[account]['sub_request'] and obj.sub in ( 'from', 'both'): self.instances[account]['sub_request'][obj.jid].window.destroy() 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 gui_menu_builder.build_bookmark_menu(obj.conn.name) invisible_show = app.SHOW_LIST.index('invisible') # do not autojoin if we are invisible if obj.conn.connected == invisible_show: return GLib.idle_add(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, 'stop') if helpers.allow_popup_window(account): ft.show_send_error(file_props) return event = events.FileSendErrorEvent(file_props) self.add_event(account, jid, event) 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_file_request_error(self, obj): # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg)) ft = self.instances['file_transfers'] ft.set_status(obj.file_props, 'stop') errno = obj.file_props.error if helpers.allow_popup_window(obj.conn.name): if errno in (-4, -5): ft.show_stopped(obj.jid, obj.file_props, obj.error_msg) else: ft.show_request_error(obj.file_props) return if errno in (-4, -5): event_class = events.FileErrorEvent msg_type = 'file-error' else: event_class = events.FileRequestErrorEvent msg_type = 'file-request-error' event = event_class(obj.file_props) self.add_event(obj.conn.name, obj.jid, event) if helpers.allow_showing_notification(obj.conn.name): # 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, obj.jid, obj.conn.name, msg_type, path, title=event_type, text=obj.file_props.name) def handle_event_file_request(self, obj): account = obj.conn.name if obj.jid not in app.contacts.get_jid_list(account): keyID = '' attached_keys = app.config.get_per('accounts', account, 'attached_gpg_keys').split() if obj.jid in attached_keys: keyID = attached_keys[attached_keys.index(obj.jid) + 1] contact = app.contacts.create_not_in_roster_contact(jid=obj.jid, account=account, keyID=keyID) app.contacts.add_contact(account, contact) self.roster.add_contact(obj.jid, account) contact = app.contacts.get_first_contact_from_jid(account, obj.jid) if obj.file_props.session_type == 'jingle': request = obj.stanza.getTag('jingle').getTag('content')\ .getTag('description').getTag('request') if request: # If we get a request instead ft_win = self.instances['file_transfers'] ft_win.add_transfer(account, contact, obj.file_props) return if helpers.allow_popup_window(account): self.instances['file_transfers'].show_file_request(account, contact, obj.file_props) return event = events.FileRequestEvent(obj.file_props) self.add_event(account, obj.jid, event) if helpers.allow_showing_notification(account): path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48) txt = _('%s wants to send you a file.') % app.get_name_from_jid( account, obj.jid) event_type = _('File Transfer Request') notify.popup(event_type, obj.jid, account, 'file-request', path_to_image=path, title=event_type, text=txt) @staticmethod def handle_event_file_error(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 __compare_hashes(self, account, file_props): session = app.connections[account].get_jingle_session(jid=None, sid=file_props.sid) ft_win = self.instances['file_transfers'] h = Hashes2() try: file_ = open(file_props.file_name, 'rb') except: return hash_ = h.calculateHash(file_props.algo, file_) file_.close() # If the hash we received and the hash of the file are the same, # then the file is not corrupt jid = file_props.sender if file_props.hash_ == hash_: GLib.idle_add(self.popup_ft_result, account, jid, file_props) GLib.idle_add(ft_win.set_status, file_props, 'ok') else: # wrong hash, we need to get the file again! file_props.error = -10 GLib.idle_add(self.popup_ft_result, account, jid, file_props) GLib.idle_add(ft_win.set_status, file_props, 'hash_error') # End jingle session if session: session.end_session() 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) app.nec.push_incoming_event(FileTransferCompletedEvent(None, file_props=file_props)) else: ft.set_status(file_props, 'stop') if not file_props.completed and (file_props.stalled or \ file_props.paused): return if file_props.type_ == 'r': # we receive a file app.socks5queue.remove_receiver(file_props.sid, True, True) if file_props.session_type == 'jingle': if file_props.hash_ and file_props.error == 0: # We compare hashes in a new thread self.hashThread = Thread(target=self.__compare_hashes, args=(account, file_props)) self.hashThread.start() else: # We disn't get the hash, sender probably don't support that jid = file_props.sender self.popup_ft_result(account, jid, file_props) if file_props.error == 0: ft.set_status(file_props, 'ok') session = app.connections[account].get_jingle_session(jid=None, sid=file_props.sid) # End jingle session # TODO: only if there are no other parallel downloads in this session if session: session.end_session() else: # we send a file jid = file_props.receiver app.socks5queue.remove_sender(file_props.sid, True, True) self.popup_ft_result(account, jid, file_props) def popup_ft_result(self, account, jid, file_props): ft = self.instances['file_transfers'] if helpers.allow_popup_window(account): if file_props.error == 0: if app.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')) elif file_props.error == -10: ft.show_hash_error(jid, file_props, account) elif file_props.error == -12: ft.show_stopped(jid, file_props, error_msg=_('SSL certificate error')) return msg_type = '' event_type = '' if file_props.error == 0 and app.config.get( 'notify_on_file_complete'): event_class = events.FileCompletedEvent msg_type = 'file-completed' event_type = _('File Transfer Completed') elif file_props.error in (-1, -6): event_class = events.FileStoppedEvent msg_type = 'file-stopped' event_type = _('File Transfer Stopped') elif file_props.error == -10: event_class = events.FileHashErrorEvent msg_type = 'file-hash-error' event_type = _('File Transfer Failed') 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: event = event_class(file_props) self.add_event(account, jid, event) if file_props is not None: if file_props.type_ == 'r': # get the name of the sender, as it is in the roster sender = file_props.sender.split('/')[0] name = app.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 = _('%(filename)s received from %(name)s.')\ % {'filename': filename, 'name': name} img_name = 'gajim-ft_done' elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s from %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' else: # ft hash error txt = _('File transfer of %(filename)s from %(name)s ' 'failed.') % {'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 = app.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' elif event_type == _('File Transfer Stopped'): txt = _('File transfer of %(filename)s to %(name)s ' 'stopped.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' else: # ft hash error txt = _('File transfer of %(filename)s to %(name)s ' 'failed.') % {'filename': filename, 'name': name} img_name = 'gajim-ft_stopped' path = gtkgui_helpers.get_icon_path(img_name, 48) else: txt = '' path = '' if app.config.get('notify_on_file_complete') and \ (app.config.get('autopopupaway') or \ app.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 handle_event_signed_in(self, obj): """ SIGNED_IN event is emitted when we sign in, so handle it """ # ('SIGNED_IN', account, ()) # block signed in notifications for 30 seconds account = obj.conn.name app.block_signed_in_notifications[account] = True state = self.sleeper.getState() connected = obj.conn.connected if state != sleepy.STATE_UNKNOWN and connected in (2, 3): # we go online or free for chat, so we activate auto status app.sleeper_state[account] = 'online' elif not ((state == sleepy.STATE_AWAY and connected == 4) or \ (state == sleepy.STATE_XA and connected == 5)): # If we are autoaway/xa and come back after a disconnection, do # nothing # Else disable autoaway app.sleeper_state[account] = 'off' if obj.conn.archiving_313_supported and app.config.get_per('accounts', account, 'sync_logs_with_server'): obj.conn.request_archive_on_signin() invisible_show = app.SHOW_LIST.index('invisible') # We cannot join rooms if we are invisible if connected == invisible_show: return # send currently played music if obj.conn.pep_supported and dbus_support.supported and \ app.config.get_per('accounts', account, 'publish_tune'): self.enable_music_listener() # enable location listener if obj.conn.pep_supported and dbus_support.supported and \ app.config.get_per('accounts', account, 'publish_location'): location_listener.enable() @staticmethod def handle_event_metacontacts(obj): app.contacts.define_metacontacts(obj.conn.name, obj.meta_list) @staticmethod def handle_atom_entry(obj): AtomWindow.newAtomEntry(obj.atom_entry) @staticmethod def handle_event_failed_decrypt(obj): details = _('Unable to decrypt message from %s\nIt may have been ' 'tampered with.') % obj.fjid dialogs.WarningDialog(_('Unable to decrypt message'), details) def handle_event_zc_name_conflict(self, obj): def on_ok(new_name): app.config.set_per('accounts', obj.conn.name, 'name', new_name) show = obj.conn.old_show status = obj.conn.status obj.conn.username = new_name obj.conn.change_status(show, status) def on_cancel(): obj.conn.change_status('offline', '') dlg = dialogs.InputDialog(_('Username Conflict'), _('Please type a new username for your local account'), input_str=obj.alt_name, is_modal=True, ok_handler=on_ok, cancel_handler=on_cancel, transient_for=self.roster.window) 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): app.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 += app.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_jingleft_cancel(self, obj): ft = self.instances['file_transfers'] file_props = None # get the file_props of our session file_props = FilesProp.getFileProp(obj.conn.name, obj.sid) if not file_props: return ft.set_status(file_props, 'stop') file_props.error = -4 # is it the right error code? ft.show_stopped(obj.jid, file_props, 'Peer cancelled ' + 'the transfer') 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 = obj.contents.media # 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 event = events.JingleIncomingEvent(obj.fjid, obj.sid, content_types) self.add_event(account, obj.jid, event) if helpers.allow_showing_notification(account): # TODO: we should use another pixmap ;-) txt = _('%s wants to start a voice chat.') % \ app.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 and obj.sid == ctrl.audio_sid: ctrl.set_audio_state('error', reason=obj.reason) @staticmethod def handle_event_roster_item_exchange(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_ssl_error(self, obj): # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint, sha256_fingerprint)) account = obj.conn.name server = app.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(app.MY_CACERTS): f = open(app.MY_CACERTS) certs = f.read() f.close() if obj.cert in certs: dialogs.ErrorDialog(_('Certificate Already in File'), _('This certificate is already in file %s, so it\'s ' 'not added again.') % app.MY_CACERTS) else: f = open(app.MY_CACERTS, 'a') f.write(server + '\n') f.write(obj.cert + '\n\n') f.close() app.config.set_per('accounts', account, 'ssl_fingerprint_sha1', obj.fingerprint_sha1) app.config.set_per('accounts', account, 'ssl_fingerprint_sha256', obj.fingerprint_sha256) if is_checked[1]: ignore_ssl_errors = app.config.get_per('accounts', account, 'ignore_ssl_errors').split() ignore_ssl_errors.append(str(obj.error_num)) app.config.set_per('accounts', account, 'ignore_ssl_errors', ' '.join(ignore_ssl_errors)) obj.conn.ssl_certificate_accepted() def on_cancel(): del self.instances[account]['online_dialog']['ssl_error'] obj.conn.disconnect(on_purpose=True) app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) pritext = _('Error verifying SSL certificate') sectext = _('There was an error verifying the SSL certificate of your ' 'XMPP server: %(error)s\nDo you still want to connect to this ' 'server?') % {'error': obj.error_text} if obj.error_num in (18, 27): checktext1 = _('Add this certificate to the list of trusted ' 'certificates.\nSHA-1 fingerprint of the certificate:\n%(sha1)s' '\nSHA256 fingerprint of the certificate:\n%(sha256)s') % \ {'sha1': obj.fingerprint_sha1, 'sha256': obj.fingerprint_sha256} 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.SSLErrorDialog(obj.conn.name, obj.certificate, pritext, sectext, checktext1, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel) self.instances[account]['online_dialog']['ssl_error'].set_title( _('SSL Certificate Verification for %s') % account) def handle_event_non_anonymous_server(self, obj): account = obj.conn.name server = app.config.get_per('accounts', account, 'hostname') dialogs.ErrorDialog(_('Non Anonymous Server'), sectext='Server "%s"' 'does not support anonymous connection' % server, transient_for=self.roster.window) def handle_event_fingerprint_error(self, obj): # ('FINGERPRINT_ERROR', account, (new_fingerprint_sha1,new_fingerprint_sha256,)) account = obj.conn.name def on_yes(is_checked): del self.instances[account]['online_dialog']['fingerprint_error'] app.config.set_per('accounts', account, 'ssl_fingerprint_sha1', obj.new_fingerprint_sha1) app.config.set_per('accounts', account, 'ssl_fingerprint_sha256', obj.new_fingerprint_sha256) # Reset the ignored ssl errors app.config.set_per('accounts', account, 'ignore_ssl_errors', '') obj.conn.ssl_certificate_accepted() def on_no(): del self.instances[account]['online_dialog']['fingerprint_error'] obj.conn.disconnect(on_purpose=True) app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) pritext = _('SSL certificate error') sectext = _('It seems the SSL certificate of account %(account)s has ' 'changed and is not valid or your connection is being compromised.\n\n' 'Old SHA-1 fingerprint: ' '%(old_sha1)s\nOld SHA-256 fingerprint: %(old_sha256)s\n\n' 'New SHA-1 fingerprint: %(new_sha1)s\nNew SHA-256 fingerprint: ' '%(new_sha256)s\n\nDo you still want to connect ' 'and update the fingerprint of the certificate?') % \ {'account': account, 'old_sha1': app.config.get_per('accounts', account, 'ssl_fingerprint_sha1'), 'old_sha256': app.config.get_per('accounts', account, 'ssl_fingerprint_sha256'), 'new_sha1': obj.new_fingerprint_sha1, 'new_sha256': obj.new_fingerprint_sha256} 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.CheckFingerprintDialog(pritext, sectext, on_response_yes=on_yes, on_response_no=on_no, account=obj.conn.name, certificate=obj.certificate) def handle_event_plain_connection(self, obj): # ('PLAIN_CONNECTION', account, (connection)) def on_ok(is_checked): if not is_checked[0]: if is_checked[1]: app.config.set_per('accounts', obj.conn.name, 'action_when_plaintext_connection', 'disconnect') 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]: app.config.set_per('accounts', obj.conn.name, 'action_when_plaintext_connection', 'connect') 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) app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='offline')) 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.PlainConnectionDialog(obj.conn.name, on_ok, on_cancel) 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]: app.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) app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='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]: app.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) app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn, show='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 = { 'DB_ERROR': [self.handle_event_db_error], 'FILE_SEND_ERROR': [self.handle_event_file_send_error], 'atom-entry-received': [self.handle_atom_entry], 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase], 'bookmarks-received': [self.handle_event_bookmarks], 'client-cert-passphrase': [ self.handle_event_client_cert_passphrase], 'connection-lost': [self.handle_event_connection_lost], 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)], 'file-request-error': [self.handle_event_file_request_error], 'file-request-received': [self.handle_event_file_request], 'fingerprint-error': [self.handle_event_fingerprint_error], 'gc-invitation-received': [self.handle_event_gc_invitation], 'gc-decline-received': [self.handle_event_gc_decline], 'gc-presence-received': [self.handle_event_gc_presence], 'gc-message-received': [self.handle_event_gc_message], '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], 'information': [self.handle_event_information], '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], 'jingleFT-cancelled-received': [self.handle_event_jingleft_cancel], 'message-error': [self.handle_event_msgerror], 'message-not-sent': [self.handle_event_msgnotsent], 'message-sent': [self.handle_event_msgsent], 'metacontacts-received': [self.handle_event_metacontacts], 'muc-admin-received': [self.handle_event_gc_affiliation], 'muc-owner-received': [self.handle_event_gc_config], 'oauth2-credentials-required': [self.handle_oauth2_credentials], 'our-show': [self.handle_event_status], 'password-required': [self.handle_event_password_required], 'plain-connection': [self.handle_event_plain_connection], 'presence-received': [self.handle_event_presence], 'register-agent-info-received': [self.handle_event_register_agent_info], 'roster-info': [self.handle_event_roster_info], 'roster-item-exchange-received': \ [self.handle_event_roster_item_exchange], 'signed-in': [self.handle_event_signed_in], 'ssl-error': [self.handle_event_ssl_error], 'non-anonymous-server-error': [self.handle_event_non_anonymous_server], '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], 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict], } 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.items(): for event_handler in event_handlers: prio = ged.GUI1 if type(event_handler) == tuple: prio = event_handler[1] event_handler = event_handler[0] app.ged.register_event_handler(event_name, prio, event_handler) ################################################################################ ### Methods dealing with app.events ################################################################################ def add_event(self, account, jid, event): """ Add an event to the app.events var """ # We add it to the app.events queue # Do we have a queue? jid = app.get_jid_without_resource(jid) no_queue = len(app.events.get_events(account, jid)) == 0 # event can be in common.events.* # event_type can be in advancedNotificationWindow.events_list event_types = {'file-request': 'ft_request', 'file-completed': 'ft_finished'} event_type = event_types.get(event.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.show_in_roster = show_in_roster event.show_in_systray = show_in_systray app.events.add_event(account, jid, event) self.roster.show_title() if no_queue: # We didn't have a queue: we change icons if app.contacts.get_contact_with_highest_priority(account, jid): self.roster.draw_contact(jid, account) else: self.roster.add_to_not_in_the_roster(account, jid) # Select the big brother contact in roster, it's visible because it has # events. family = app.contacts.get_metacontacts_family(account, jid) if family: nearby_family, bb_jid, bb_account = \ app.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 = app.get_resource_from_jid(fjid) jid = app.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 = app.events.get_first_event(account, fjid, type_) if not event: event = app.events.get_first_event(account, jid, type_) if not event: return if type_ == 'printed_chat': ctrl = event.control elif type_ == 'chat': session = event.session ctrl = session.control elif type_ == '': ctrl = self.msg_win_mgr.get_control(fjid, account) if not ctrl: highest_contact = app.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 app.events.change_jid(account, fjid, jid) resource = None fjid = jid contact = None if resource: contact = app.contacts.get_contact(account, jid, resource) if not contact: contact = highest_contact ctrl = self.new_chat(contact, account, resource=resource, session=session) app.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 = app.events.get_first_event(account, fjid, type_) if not event: event = app.events.get_first_event(account, jid, type_) if not event: return if type_ == 'printed_pm': ctrl = event.control elif type_ == 'pm': session = event.session if session and session.control: ctrl = session.control elif not ctrl: room_jid = jid nick = resource gc_contact = app.contacts.get_gc_contact(account, room_jid, nick) if gc_contact: show = gc_contact.show else: show = 'offline' gc_contact = app.contacts.create_gc_contact( room_jid=room_jid, account=account, name=nick, show=show) if not session: session = app.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', 'file-hash-error', 'jingle-incoming'): # Get the first single message event event = app.events.get_first_event(account, fjid, type_) if not event: # default to jid without resource event = app.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_ == 'gc-invitation': event = app.events.get_first_event(account, jid, type_) dialogs.InvitationReceivedDialog(account, event.room_jid, jid, event.password, event.reason, event.is_continued) app.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) elif type_ == 'subscription_request': event = app.events.get_first_event(account, jid, type_) dialogs.SubscriptionRequestWindow(jid, event.text, account, event.nick) app.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) elif type_ == 'unsubscribed': event = app.events.get_first_event(account, jid, type_) self.show_unsubscribed_dialog(account, event.contact) app.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) if w: w.set_active_tab(ctrl) w.window.get_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_iter() ################################################################################ ### Methods dealing with emoticons ################################################################################ @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'|(? bm['name']: place_found = True break index += 1 if place_found: app.connections[account].bookmarks.insert(index, bm) else: app.connections[account].bookmarks.append(bm) app.connections[account].store_bookmarks() gui_menu_builder.build_bookmark_menu(account) 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 = app.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 @staticmethod def get_pep_icon(pep_obj): if isinstance(pep_obj, pep.UserMoodPEP): received_mood = pep_obj._pep_specific_data['mood'] mood = received_mood if received_mood in pep.MOODS else 'unknown' return gtkgui_helpers.load_mood_icon(mood).get_pixbuf() elif isinstance(pep_obj, pep.UserTunePEP): path = os.path.join(app.DATA_DIR, 'emoticons', 'static', 'music.png') return GdkPixbuf.Pixbuf.new_from_file(path) elif isinstance(pep_obj, pep.UserActivityPEP): pep_ = pep_obj._pep_specific_data activity = pep_['activity'] has_known_activity = activity in pep.ACTIVITIES has_known_subactivity = (has_known_activity and ('subactivity' in pep_) and (pep_['subactivity'] in pep.ACTIVITIES[activity])) if has_known_activity: if has_known_subactivity: subactivity = pep_['subactivity'] return gtkgui_helpers.load_activity_icon(activity, subactivity).get_pixbuf() else: return gtkgui_helpers.load_activity_icon(activity).\ get_pixbuf() else: return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf() elif isinstance(pep_obj, pep.UserLocationPEP): icon = gtkgui_helpers.get_icon_pixmap('applications-internet', quiet=True) return icon @staticmethod def create_ipython_window(): try: from gajim.dev.ipython_view import IPythonView except ImportError: print('ipython_view not found') return from gi.repository 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.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) view = IPythonView() view.override_font(Pango.FontDescription(font)) view.set_wrap_mode(Gtk.WrapMode.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': app}) app.ipython_window = window def network_status_changed(self, monitor, connected): if connected == self.network_state: # This callback gets called a lot from GTK with the # same state, not only on change. return self.network_state = connected if connected: for connection in app.connections.values(): if connection.connected <= 0: log.info('Connect %s', connection.name) connection.reconnect() else: for connection in app.connections.values(): if connection.connected > 1: log.info('Disconnect %s', connection.name) connection.disconnectedReconnCB() def run(self, application): if app.config.get('trayicon') != 'never': self.show_systray() self.roster = roster_window.RosterWindow(application) if self.msg_win_mgr.mode == \ MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: self.msg_win_mgr.create_window(None, None, None) # Creating plugin manager from gajim import plugins app.plugin_manager = plugins.PluginManager() app.plugin_manager.init_plugins() helpers.update_optional_features() # prepopulate data which we are sure of; note: we do not log these info for account in app.connections: gajimcaps = caps_cache.capscache[ ('sha-1', app.caps_hash[account])] gajimcaps.identities = [app.gajim_identity] gajimcaps.features = app.gajim_common_features + \ app.gajim_optional_features[account] self.roster._before_fill() for account in app.connections: app.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() GLib.timeout_add(100, self.autoconnect) if sys.platform == 'win32': timeout, in_seconds = 20, None else: timeout, in_seconds = app.idlequeue.PROCESS_TIMEOUT if in_seconds: GLib.timeout_add_seconds(timeout, self.process_connections) else: GLib.timeout_add(timeout, self.process_connections) GLib.timeout_add_seconds(app.config.get( 'check_idle_every_foo_seconds'), self.read_sleepy) def remote_init(): if app.config.get('remote_control'): try: from gajim import remote_control self.remote_ctrl = remote_control.Remote() except Exception: pass GLib.timeout_add_seconds(5, remote_init) def __init__(self): app.interface = self app.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': {}, '24': {}, '32': {}, 'opened': {}, 'closed': {}} 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': app.config.get('inmsgcolor'), 'outmsgcolor': app.config.get('outmsgcolor'), 'inmsgtxtcolor': app.config.get('inmsgtxtcolor'), 'outmsgtxtcolor': app.config.get('outmsgtxtcolor'), 'statusmsgcolor': app.config.get('statusmsgcolor'), 'urlmsgcolor': app.config.get('urlmsgcolor'), 'markedmsgcolor': app.config.get('markedmsgcolor'), } 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 cfg_was_read = parser.read() if not cfg_was_read: # enable plugin_installer by default when creating config file app.config.set_per('plugins', 'plugin_installer', 'active', True) app.logger.reset_shown_unread_messages() # override logging settings from config (don't take care of '-q' option) if app.config.get('verbose'): logging_helpers.set_verbose() for account in app.config.get_per('accounts'): if app.config.get_per('accounts', account, 'is_zeroconf'): app.ZEROCONF_ACC_NAME = account break # Is gnome configured to activate row on single click ? # try: # gi.require_version('GConf', '2.0') # from gi.repository import GConf # client = GConf.Client.get_default() # click_policy = client.get_string( # '/apps/nautilus/preferences/click_policy') # if click_policy == 'single': # app.single_click = True # except Exception: # pass # add default status messages if there is not in the config file if len(app.config.get_per('statusmsg')) == 0: default = app.config.statusmsg_default for msg in default: app.config.add_per('statusmsg', msg) app.config.set_per('statusmsg', msg, 'message', default[msg][0]) app.config.set_per('statusmsg', msg, 'activity', default[msg][1]) app.config.set_per('statusmsg', msg, 'subactivity', default[msg][2]) app.config.set_per('statusmsg', msg, 'activity_text', default[msg][3]) app.config.set_per('statusmsg', msg, 'mood', default[msg][4]) app.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) #add default themes if there is not in the config file theme = app.config.get('roster_theme') if not theme in app.config.get_per('themes'): app.config.set('roster_theme', _('default')) if len(app.config.get_per('themes')) == 0: d = ['accounttextcolor', 'accountbgcolor', 'accountfont', 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont', 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont', 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor'] default = app.config.themes_default for theme_name in default: app.config.add_per('themes', theme_name) theme = default[theme_name] for o in d: app.config.set_per('themes', theme_name, o, theme[d.index(o)]) # Add Tor proxy if there is not in the config if len(app.config.get_per('proxies')) == 0: default = app.config.proxies_default for proxy in default: app.config.add_per('proxies', proxy) app.config.set_per('proxies', proxy, 'type', default[proxy][0]) app.config.set_per('proxies', proxy, 'host', default[proxy][1]) app.config.set_per('proxies', proxy, 'port', default[proxy][2]) app.idlequeue = idlequeue.get_idlequeue() # resolve and keep current record of resolved hosts app.resolver = resolver.get_resolver(app.idlequeue) app.socks5queue = socks5.SocksQueue(app.idlequeue, self.handle_event_file_rcv_completed, self.handle_event_file_progress, self.handle_event_file_error) app.proxy65_manager = proxy65_manager.Proxy65Manager(app.idlequeue) app.default_session_type = ChatControlSession # Creating Network Events Controller from gajim.common import nec app.nec = nec.NetworkEventsController() app.notification = notify.Notification() self.create_core_handlers_list() self.register_core_handlers() if app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, 'active') \ and app.HAVE_ZEROCONF: app.connections[app.ZEROCONF_ACC_NAME] = \ connection_zeroconf.ConnectionZeroconf(app.ZEROCONF_ACC_NAME) for account in app.config.get_per('accounts'): if not app.config.get_per('accounts', account, 'is_zeroconf') and\ app.config.get_per('accounts', account, 'active'): app.connections[account] = 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 app.connections: self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}, 'sub_request': {}} # online_dialog contains all dialogs that have a meaning only when # we are not disconnected self.minimized_controls[a] = {} app.contacts.add_account(a) app.groups[a] = {} app.gc_connected[a] = {} app.automatic_rooms[a] = {} app.newly_added[a] = [] app.to_be_removed[a] = [] app.nicks[a] = app.config.get_per('accounts', a, 'name') app.block_signed_in_notifications[a] = True app.sleeper_state[a] = 0 app.encrypted_chats[a] = [] app.last_message_time[a] = {} app.status_before_autoaway[a] = '' app.transport_avatar[a] = {} app.gajim_optional_features[a] = [] app.caps_hash[a] = '' self.remote_ctrl = None if dbus_support.supported: from gajim import upower_listener from gajim import logind_listener # Handle gnome screensaver if dbus_support.supported: def gnome_screensaver_ActiveChanged_cb(active): if not active: for account in app.connections: if app.account_is_connected(account) and \ app.sleeper_state[account] == 'autoaway-forced': # We came back online ofter gnome-screensaver # autoaway self.roster.send_status(account, 'online', app.status_before_autoaway[account]) app.status_before_autoaway[account] = '' app.sleeper_state[account] = 'online' return if not app.config.get('autoaway'): # Don't go auto away if user disabled the option return for account in app.connections: if account not in app.sleeper_state or \ not app.sleeper_state[account]: continue if app.sleeper_state[account] == 'online': if not app.account_is_connected(account): continue # we save out online status app.status_before_autoaway[account] = \ app.connections[account].status # we go away (no auto status) [we pass True to auto # param] auto_message = app.config.get('autoaway_message') if not auto_message: auto_message = app.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': app.status_before_autoaway[account], 'time': app.config.get('autoxatime')} self.roster.send_status(account, 'away', auto_message, auto=True) app.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 = sleepy.Sleepy( app.config.get('autoawaytime') * 60, # make minutes to seconds app.config.get('autoxatime') * 60) gtkgui_helpers.make_jabber_state_images() self.systray_enabled = False from gajim import statusicon self.systray = statusicon.StatusIcon() pixs = [] for size in (16, 32, 48, 64, 128): pix = gtkgui_helpers.get_icon_pixmap('org.gajim.Gajim', size) if pix: pixs.append(pix) if pixs: # set the icon to all windows Gtk.Window.set_default_icon_list(pixs) self.init_emoticons() self.make_regexps() # get transports type from DB app.transport_type = app.logger.get_transports_type() if app.config.get('soundplayer') == '': # only on first time Gajim starts commands = ('paplay', 'aplay', 'play', 'ossplay') for command in commands: if helpers.is_in_path(command): if command == 'paplay': command += ' -n gajim --property=media.role=event' if command in ('aplay', 'play'): command += ' -q' elif command == 'ossplay': command += ' -qq' app.config.set('soundplayer', command) break self.last_ftwindow_update = 0 self.music_track_changed_signal = None self.network_monitor = Gio.NetworkMonitor.get_default() self.network_monitor.connect('network-changed', self.network_status_changed) self.network_state = self.network_monitor.get_network_available() 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): app.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: GLib.timeout_add_seconds(30, app.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 OpenPGP 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 = app.connections[account].test_gpg_passphrase(passphrase) if result == 'ok': # passphrase is good self.complete(passphrase) return elif result == 'expired': dialogs.ErrorDialog(_('OpenPGP key expired'), _('Your OpenPGP key has expired, you will be connected to ' '%s without OpenPGP.') % account) # Don't try to connect with GPG app.connections[account].continue_connect_info[2] = False self.complete(None) return if count < 3: # ask again dialogs.PassphraseDialog(_('Wrong Passphrase'), _('Please retype your OpenPGP 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=None, callback_args=()): """ Call a function in a thread """ def thread_function(func, func_args, callback, callback_args): output = func(*func_args) if callback: GLib.idle_add(callback, output, *callback_args) Thread(target=thread_function, args=(func, func_args, callback, callback_args)).start()