Skip to content
Snippets Groups Projects
gui_interface.py 134 KiB
Newer Older
steve-e's avatar
steve-e committed
# -*- coding:utf-8 -*-
## src/gajim.py
##
Yann Leboulanger's avatar
Yann Leboulanger committed
## Copyright (C) 2003-2013 Yann Leboulanger <asterix AT lagaule.org>
steve-e's avatar
steve-e committed
## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
##                    Norman Rasmussen <norman AT rasmussen.co.za>
##                    Stéphan Kochen <stephan AT kochen.nl>
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
##                         Alex Mauer <hawke AT hawkesnest.net>
## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
##                         Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
##                    Stefan Bethge <stefan AT lanpartei.de>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
##                    James Newton <redshodan AT gmail.com>
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
##                         Julien Pivotto <roidelapluie AT gmail.com>
##                         Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
##
## 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 <http://www.gnu.org/licenses/>.
##
import os
import sys
import re
import time
import math
steve-e's avatar
steve-e committed

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
steve-e's avatar
steve-e committed

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 nbxmpp import idlequeue
from nbxmpp import Hashes
steve-e's avatar
steve-e committed
from common.zeroconf import connection_zeroconf
from common import resolver
steve-e's avatar
steve-e committed
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
Dicson's avatar
Dicson committed
from common.connection_handlers_events import OurShowEvent, \
    FileRequestErrorEvent, InformationEvent
from common.connection import Connection
from common import jingle
zimio's avatar
zimio committed
from common.file_props import FilesProp
from common import pep
steve-e's avatar
steve-e committed

import roster_window
import profile_window
import config
from threading import Thread
from common import ged
steve-e's avatar
steve-e committed

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_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, 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, obj.sec_txt)
    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,
        else:
            self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
                account, room_jid, title, prompt)
        #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
            obj.conn.build_http_auth_answer(obj.stanza, answer)
        def on_yes(is_checked, obj):
            response(obj, 'yes')
        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:]
zimio's avatar
zimio committed
            file_props = FilesProp.getFileProp(obj.conn.name, sid)
            if file_props :
                if unicode(obj.errcode) == '400':
zimio's avatar
zimio committed
                    file_props.error = -3
zimio's avatar
zimio committed
                    file_props.error = -4
                gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
Yann Leboulanger's avatar
Yann Leboulanger committed
                    conn=obj.conn, jid=obj.jid, file_props=file_props,
                    error_msg=obj.errmsg))
                obj.conn.disconnect_transfer(file_props)
        elif unicode(obj.errcode) == '404':
            sid = obj.id_
            if len(obj.id_) > 3 and obj.id_[2] == '_':
                sid = obj.id_[3:]
zimio's avatar
zimio committed
            file_props = FilesProp.getFileProp(obj.conn.name, sid)
Yann Leboulanger's avatar
Yann Leboulanger committed
            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))
    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:
            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.interface.roster.window)
            gajim.connections[account].request_vcard(jid)

    def handle_gc_error(self, gc_control, pritext, sectext):
Yann Leboulanger's avatar
Yann Leboulanger committed
        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)
            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):
            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)
        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()

Yann Leboulanger's avatar
Yann Leboulanger committed
            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
        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 <b>%s</b> 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 <b>%s</b>.') % \
                    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 <b>%s</b> 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 '
            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))
            if gc_control and gc_control.autorejoin:
                gc_control.autorejoin = False
    def handle_event_gc_message(self, obj):
        if not obj.stanza.getTag('body'): # no <body>
            # 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 = 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)

        highest = gajim.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:
    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)
        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 = 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': 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 gajim.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}
            session.roster_message(jid, msg, obj.time_, 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',
            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
        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,
        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.')

    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, 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)
            dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \
                obj.agent, _('Check your connection or try again later.'))
    def handle_event_vcard(self, obj):
        # ('VCARD', account, data)
        '''vcard holds the vcard data'''
        our_jid = gajim.get_jid_from_account(obj.conn.name)
        if obj.jid == our_jid:
            if obj.nickname:
                gajim.nicks[obj.conn.name] = obj.nickname
            if obj.conn.name in self.show_vcard_when_connect:
                self.show_vcard_when_connect.remove(obj.conn.name)
    def handle_event_last_status_time(self, obj):
        # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
        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_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 />
            continue_tag = False
            if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
            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)
                    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 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_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)
        self.add_event(account, obj.jid_from, 'gc-invitation', (obj.room_jid,
            obj.reason, obj.password, obj.is_continued, obj.jid_from))

        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(_('Your passphrase is incorrect'), sectext)
        else:
            path = gtkgui_helpers.get_icon_path('gtk-dialog-warning', 48)
            account = obj.conn.name
            notify.popup('warning', account, account, 'warning', path,
                _('OpenPGP Passphrase Incorrect'),
                _('You are currently connected without your OpenPGP key.'))
        self.forget_gpg_passphrase(obj.keyID)
    def handle_event_client_cert_passphrase(self, 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 passphrase for the certificate 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]
            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)
            obj.callback(False)
        dialogs.YesNoDialog(_('OpenPGP key not trusted'), _('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
        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)
            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):
            gajim.config.set_per('accounts', account, 'oauth2_refresh_token',
                refresh)
            st = gajim.config.get_per('accounts', account, 'last_status')
            msg = helpers.from_one_line(gajim.config.get_per('accounts',
                account, 'last_status_msg'))
            gajim.interface.roster.send_status(account, st, msg)
            del self.pass_dialog[account]

        def on_cancel():
            gajim.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 = 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)
            if obj.sub == 'remove':
                return
            # Add new contact to roster
            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]
            contact = gajim.contacts.create_contact(jid=obj.jid,
                account=account, name=obj.nickname, groups=obj.groups,
                show='offline', sub=obj.sub, ask=obj.ask, keyID=keyID)
            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 obj.sub == 'remove':
                # another of our instance removed a contact. Remove it here too
                self.roster.remove_contact(obj.jid, account, backend=True)
                return
            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
                self.roster.remove_contact(obj.jid, account, force=True)
                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

        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:
        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']
zimio's avatar
zimio committed
        ft.set_status(file_props, '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,
zimio's avatar
zimio committed
                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, obj):
        # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
        ft = self.instances['file_transfers']
zimio's avatar
zimio committed
        ft.set_status(obj.file_props, 'stop')
        errno = obj.file_props.error
        if helpers.allow_popup_window(obj.conn.name):
                ft.show_stopped(obj.jid, obj.file_props, obj.error_msg)
                ft.show_request_error(obj.file_props)
            return

        if errno in (-4, -5):
            msg_type = 'file-error'
        else:
            msg_type = 'file-request-error'

        self.add_event(obj.conn.name, obj.jid, msg_type, obj.file_props)
        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,
zimio's avatar
zimio committed
                title=event_type, text=obj.file_props.name)
    def handle_event_file_request(self, obj):
        account = obj.conn.name
        if obj.jid not in gajim.contacts.get_jid_list(account):
            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]
            contact = gajim.contacts.create_not_in_roster_contact(jid=obj.jid,
                account=account, keyID=keyID)
            gajim.contacts.add_contact(account, contact)
            self.roster.add_contact(obj.jid, account)
        contact = gajim.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)
        if helpers.allow_popup_window(account):
            self.instances['file_transfers'].show_file_request(account, contact,
        self.add_event(account, obj.jid, 'file-request', obj.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(
            event_type = _('File Transfer Request')
            notify.popup(event_type, obj.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()
zimio's avatar
zimio committed
            self.instances['file_transfers'].set_progress(file_props.type_,
                    file_props.sid, file_props.received_len)
Yann Leboulanger's avatar
Yann Leboulanger committed

    def __compare_hashes(self, account, file_props):
        session = gajim.connections[account].get_jingle_session(jid=None,
            sid=file_props.sid)
zimio's avatar
zimio committed
        if not file_props.hash_:
            # We disn't get the hash, sender probably don't support that
zimio's avatar
zimio committed
            jid = unicode(file_props.sender)
            self.popup_ft_result(account, jid, file_props)
zimio's avatar
zimio committed
            ft_win.set_status(file_props, 'ok')
        h = Hashes()
        try:
zimio's avatar
zimio committed
            file_ = open(file_props.file_name, 'r')
zimio's avatar
zimio committed
        hash_ = h.calculateHash(file_props.algo, file_)
Yann Leboulanger's avatar
Yann Leboulanger committed
        file_.close()
        # If the hash we received and the hash of the file are the same,
        # then the file is not corrupt
zimio's avatar
zimio committed
        jid = unicode(file_props.sender)
zimio's avatar
zimio committed
        if file_props.hash_ == hash_:
            gobject.idle_add(self.popup_ft_result, account, jid, file_props)
            gobject.idle_add(ft_win.set_status, file_props, 'ok')
        else:
            # wrong hash, we need to get the file again!
zimio's avatar
zimio committed
            file_props.error = -10
            gobject.idle_add(self.popup_ft_result, account, jid, file_props)
            gobject.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']
zimio's avatar
zimio committed
        if file_props.error == 0:
            ft.set_progress(file_props.type_, file_props.sid,
                file_props.received_len)
zimio's avatar
zimio committed
            ft.set_status(file_props, 'stop')
        if file_props.stalled or file_props.paused:
Yann Leboulanger's avatar
Yann Leboulanger committed

        if file_props.type_ == 'r' and file_props.hash_: # we receive a file
            gajim.socks5queue.remove_receiver(file_props.sid, True, True)
            # we compare hashes