changeset 807:be4c5e24dab9

plugin XEP-0077, plugin XEP-0100, frontends: gateways have been entirely implemented in backend using the new refactored XMLUI and AdvancedListContainer. The now useless code has been removed from frontends.
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:26:03 +0100
parents 5d6c45d6ee1b
children d035c662b357
files frontends/src/primitivus/gateways.py frontends/src/primitivus/primitivus frontends/src/quick_frontend/quick_gateways.py frontends/src/wix/gateways.py frontends/src/wix/main_window.py src/core/sat_main.py src/plugins/plugin_xep_0077.py src/plugins/plugin_xep_0100.py
diffstat 8 files changed, 226 insertions(+), 521 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/primitivus/gateways.py	Tue Feb 04 18:24:27 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Primitivus: a SAT frontend
-# Copyright (C) 2009, 2010, 2011, 2012, 2013  Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.i18n import _
-import urwid
-from urwid_satext import sat_widgets
-from sat_frontends.quick_frontend.quick_gateways import QuickGatewaysManager
-from sat.tools.jid import JID
-
-
-class GatewaysManager(urwid.WidgetWrap, QuickGatewaysManager):
-
-    def __init__(self, host, gateways, title=_("Gateways manager"), server=None):
-        QuickGatewaysManager.__init__(self, host, gateways, server)
-        if server:
-            title+=" (%s)" % server
-        widget_list = urwid.SimpleListWalker([])
-        widget_list.append(urwid.Text(self.WARNING_MSG))
-        widget_list.append(urwid.Divider('-'))
-        for gateway in gateways:
-            self.addGateway(widget_list,gateway, gateways[gateway])
-        widget_list.append(urwid.Divider())
-        self.ext_serv = sat_widgets.AdvancedEdit(_("Use external XMPP server: "))
-        go_button = sat_widgets.CustomButton( _("GO !"),self.browseExternalServer)
-        ext_serv_col = urwid.Columns([self.ext_serv,('fixed',go_button.getSize(),go_button)])
-        widget_list.append(ext_serv_col)
-        list_wid = urwid.ListBox(widget_list)
-        decorated = sat_widgets.LabelLine(list_wid, sat_widgets.SurroundedText(title))
-        urwid.WidgetWrap.__init__(self, decorated)
-
-    def browseExternalServer(self, button):
-        """Open the gateway manager on given server"""
-        server = self.ext_serv.get_edit_text()
-        if not server:
-            popup = sat_widgets.Alert(_("Error"), _("You must enter an external server JID"), ok_cb=self.host.removePopUp)
-            self.host.showPopUp(popup)
-            return
-        action_id = self.host.bridge.findGateways(server, self.host.profile)
-        self.host.current_action_ids.add(action_id)
-        self.host.current_action_ids_cb[action_id] = self.host.onGatewaysFound
-        self.host.removeWindow()
-
-    def addGateway(self, widget_list, gateway, param):
-
-        widget_col = []
-        widget_col.append(('weight',4,urwid.Text(unicode(param['name'])))) #FIXME: unicode to be remove when DBus bridge will not give dbus.String anymore
-
-        #Then the transport type message
-        widget_col.append(('weight',6,urwid.Text(self.getGatewayDesc(param['type']))))
-
-        #The buttons
-
-        reg_button = sat_widgets.CustomButton( _("Register"), self.onRegister)
-        reg_button.gateway_jid = JID(gateway)
-        widget_col.append(('fixed',reg_button.getSize(),reg_button))
-        unreg_button = sat_widgets.CustomButton( _("Unregister"), self.onUnregister)
-        unreg_button.gateway_jid = JID(gateway)
-        widget_col.append(('fixed',unreg_button.getSize(),unreg_button))
-        widget_list.append(urwid.Columns(widget_col,1))
-
-    def onRegister(self, button):
-        """Called when register button is clicked"""
-        gateway_jid = button.gateway_jid
-        action_id = self.host.bridge.in_band_register(gateway_jid, self.host.profile)
-        self.host.current_action_ids.add(action_id)
-
-    def onUnregister(self, button):
-        """Called when unregister button is clicked"""
-        gateway_jid = button.gateway_jid
-        action_id = self.host.bridge.gatewayRegister("CANCEL",gateway_jid, None, self.host.profile)
-        self.host.current_action_ids.add(action_id)
--- a/frontends/src/primitivus/primitivus	Tue Feb 04 18:24:27 2014 +0100
+++ b/frontends/src/primitivus/primitivus	Tue Feb 04 18:26:03 2014 +0100
@@ -28,7 +28,6 @@
 from sat_frontends.primitivus.profile_manager import ProfileManager
 from sat_frontends.primitivus.contact_list import ContactList
 from sat_frontends.primitivus.chat import Chat
-from sat_frontends.primitivus.gateways import GatewaysManager
 from sat_frontends.primitivus.xmlui import XMLUI
 from sat_frontends.primitivus.progress import Progress
 from sat_frontends.primitivus.notify import Notify
@@ -294,7 +293,6 @@
         menu.addMenu(contact, _("Remove contact"), self.onRemoveContactRequest)
         communication = _("Communication")
         menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, 'meta j')
-        menu.addMenu(communication, _("Find Gateways"), self.onFindGatewaysRequest, 'meta g')
         #additionals menus
         #FIXME: do this in a more generic way (in quickapp)
         add_menus = self.bridge.getMenus('', Const.NO_SECURITY_LIMIT)
@@ -496,6 +494,7 @@
             self.showPopUp(pop_up_widget)
 
     def actionResult(self, type, id, data, profile):
+        # FIXME: to be removed
         if not self.check_profile(profile):
             return
 
@@ -588,12 +587,6 @@
         pop_up_widget = sat_widgets.InputDialog(_("Entering a MUC room"), _("Please enter MUC's JID"), default_txt = 'room@muc_service.server.tld', cancel_cb=self.removePopUp, ok_cb=self.onJoinRoom)
         self.showPopUp(pop_up_widget)
 
-    def onFindGatewaysRequest(self, e):
-        debug(_("Find gateways request"))
-        id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
-        self.current_action_ids.add(id)
-        self.current_action_ids_cb[id] = self.onGatewaysFound
-
     def onAddContactRequest(self, menu):
         pop_up_widget = sat_widgets.InputDialog(_("Adding a contact"), _("Please enter new contact JID"), default_txt = 'name@server.tld', cancel_cb=self.removePopUp, ok_cb=self.onAddContact)
         self.showPopUp(pop_up_widget)
@@ -611,13 +604,6 @@
 
     #MISC CALLBACKS#
 
-    def onGatewaysFound(self, data):
-        """Called when SàT has found the server gateways"""
-        target = data['__private__']['target']
-        del data['__private__']
-        gatewayManager = GatewaysManager(self, data, server=target)
-        self.addWindow(gatewayManager)
-
     def chatStateReceived(self, from_jid_s, state, profile):
         """Signal observer to display a contact chat state
         @param from_jid_s: contact who sent his new state
--- a/frontends/src/quick_frontend/quick_gateways.py	Tue Feb 04 18:24:27 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# helper class for making a SAT frontend
-# Copyright (C) 2009, 2010, 2011, 2012, 2013  Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-
-
-from sat.core.i18n import _
-class QuickGatewaysManager(object):
-
-
-    def __init__(self, host, gateways, title=_("Gateways manager"), server=None):
-        self.WARNING_MSG = _(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as jabber contacts.
-But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analyzed by the external server, most of time a private company).""")
-        self.host = host
-
-    def getGatewayDesc(self, gat_type):
-        """Return a human readable description of gateway type
-        @param gat_type: type of gateway, as given by SàT"""
-        desc = _('Unknown IM')
-
-        if gat_type == 'irc':
-            desc = "Internet Relay Chat"
-        elif gat_type == 'xmpp':
-            desc = "XMPP"
-        elif gat_type == 'qq':
-            desc = "Tencent QQ"
-        elif gat_type == 'simple':
-            desc = "SIP/SIMPLE"
-        elif gat_type == 'icq':
-            desc = "ICQ"
-        elif gat_type == 'yahoo':
-            desc = "Yahoo! Messenger"
-        elif gat_type == 'gadu-gadu':
-            desc = "Gadu-Gadu"
-        elif gat_type == 'aim':
-            desc = "AOL Instant Messenger"
-        elif gat_type == 'msn':
-            desc = 'Windows Live Messenger'
-
-        return desc
--- a/frontends/src/wix/gateways.py	Tue Feb 04 18:24:27 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# wix: a SAT frontend
-# Copyright (C) 2009, 2010, 2011, 2012, 2013  Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program 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 Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-
-
-from sat.core.i18n import _
-import wx
-import pdb
-from xml.dom import minidom
-from logging import debug, info, error
-from sat.tools.jid  import JID
-from sat_frontends.quick_frontend.quick_gateways import QuickGatewaysManager
-
-class GatewaysManager(wx.Frame,QuickGatewaysManager):
-
-    def __init__(self, host, gateways, title=_("Gateways manager"), server=None):
-        wx.Frame.__init__(self, None, title=title)
-        QuickGatewaysManager.__init__(self, host, gateways, server)
-
-        if server:
-            self.SetTitle(title+" (%s)" % server)
-
-        #Fonts
-        self.normal_font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
-        self.bold_font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.BOLD)
-        self.italic_font = wx.Font(8, wx.DEFAULT, wx.FONTSTYLE_ITALIC, wx.NORMAL)
-        self.button_font = wx.Font(6, wx.DEFAULT, wx.NORMAL, wx.BOLD)
-
-
-        self.modified = {}  # dict of modified data (i.e. what we have to save)
-        self.ctl_list = {}  # usefull to access ctrl, key = (name, category)
-
-        self.sizer = wx.BoxSizer(wx.VERTICAL)
-        warning = wx.TextCtrl(self, -1, value=self.WARNING_MSG, style = wx.TE_MULTILINE |
-                                                                       wx.TE_READONLY |
-                                                                       wx.TE_LEFT)
-        warning.SetFont(self.bold_font)
-        self.sizer.Add(warning, 0, wx.EXPAND)
-        warning.ShowPosition(0)
-        self.panel = wx.Panel(self)
-        self.panel.sizer = wx.FlexGridSizer(cols=5)
-        self.panel.SetSizer(self.panel.sizer)
-        self.panel.SetAutoLayout(True)
-        self.sizer.Add(self.panel, 1, flag=wx.EXPAND)
-        self.SetSizer(self.sizer)
-        self.SetAutoLayout(True)
-
-        #events
-        self.Bind(wx.EVT_CLOSE, self.onClose, self)
-
-        self.MakeModal()
-        self.panel.sizer.Add(wx.Window(self.panel, -1))
-        title_name = wx.StaticText(self.panel, -1, "Name")
-        title_name.SetFont(self.bold_font)
-        title_type = wx.StaticText(self.panel, -1, "Type")
-        title_type.SetFont(self.bold_font)
-        self.panel.sizer.Add(title_name)
-        self.panel.sizer.Add(title_type)
-        self.panel.sizer.Add(wx.Window(self.panel, -1))
-        self.panel.sizer.Add(wx.Window(self.panel, -1))
-
-        for gateway in gateways:
-            self.addGateway(gateway, gateways[gateway])
-
-        self.ext_server_panel = wx.Panel(self)
-        self.ext_server_panel.sizer = wx.BoxSizer(wx.HORIZONTAL)
-        self.ext_server_panel.SetSizer(self.ext_server_panel.sizer)
-        self.ext_server_panel.SetAutoLayout(True)
-        self.sizer.Add(self.ext_server_panel, 0, flag=wx.EXPAND)
-
-        ext_server_label = wx.StaticText(self.ext_server_panel, -1, _("Use external XMPP server: "))
-        ext_server_label.SetFont(wx.ITALIC_FONT)
-        self.ext_server_text = wx.TextCtrl(self.ext_server_panel, -1)
-        ext_server_button =  wx.Button(self.ext_server_panel, -1, _("GO !"))
-        self.ext_server_panel.Bind(wx.EVT_BUTTON, self.browseExternalServer, ext_server_button)
-
-        self.ext_server_panel.sizer.Add(ext_server_label)
-        self.ext_server_panel.sizer.Add(self.ext_server_text, 1, flag=wx.EXPAND)
-        self.ext_server_panel.sizer.Add(ext_server_button)
-
-        #self.panel.sizer.Fit(self)
-        self.sizer.Fit(self)
-
-        self.Show()
-
-    def browseExternalServer(self, event):
-        """Open the gateway manager on given server"""
-        server = self.ext_server_text.GetValue()
-        debug(_("Opening gateways manager on [%s]") % server)
-        id = self.host.bridge.findGateways(server, self.host.profile)
-        self.host.current_action_ids.add(id)
-        self.host.current_action_ids_cb[id] = self.host.onGatewaysFound
-        self.MakeModal(False)
-        self.Destroy()
-
-
-    def addGateway(self, gateway, param):
-
-        #First The icon
-        isz = (16,16)
-        im_icon = wx.StaticBitmap(self.panel, -1, wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD, wx.ART_TOOLBAR, isz))
-
-        #Then the name
-
-        label=wx.StaticText(self.panel, -1, param['name'])
-        label.SetFont(self.normal_font)
-
-        #Then the transport type message
-        type_label_txt = self.getGatewayDesc(param['type'])
-
-        type_label = wx.StaticText(self.panel, -1, type_label_txt)
-        type_label.SetFont(self.italic_font)
-
-        #The buttons
-        def register_cb(event):
-            """Called when register button is clicked"""
-            gateway_jid = event.GetEventObject().gateway_jid
-            id = self.host.bridge.in_band_register(gateway_jid, self.host.profile)
-            self.host.current_action_ids.add(id)
-            self.MakeModal(False)
-            self.Destroy()
-
-        def unregister_cb(event):
-            """Called when unregister button is clicked"""
-            gateway_jid = event.GetEventObject().gateway_jid
-            id = self.host.bridge.gatewayRegister("CANCEL",gateway_jid, None, self.host.profile)
-            self.host.current_action_ids.add(id)
-            self.MakeModal(False)
-            self.Destroy()
-
-        reg_button = wx.Button(self.panel, -1, _("Register"), size=wx.Size(-1, 8))
-        reg_button.SetFont(self.button_font)
-        reg_button.gateway_jid = JID(gateway)
-        self.panel.Bind(wx.EVT_BUTTON, register_cb, reg_button)
-        unreg_button = wx.Button(self.panel, -1, _("Unregister"), size=wx.Size(-1, 8))
-        unreg_button.SetFont(self.button_font)
-        unreg_button.gateway_jid = JID(gateway)
-        self.panel.Bind(wx.EVT_BUTTON, unregister_cb, unreg_button)
-
-        self.panel.sizer.Add(im_icon)
-        self.panel.sizer.Add(label)
-        self.panel.sizer.Add(type_label)
-        self.panel.sizer.Add(reg_button, 1, wx.EXPAND)
-        self.panel.sizer.Add(unreg_button, 1, wx.EXPAND)
-
-
-    def onClose(self, event):
-        """Close event"""
-        debug(_("close"))
-        self.MakeModal(False)
-        event.Skip()
-
--- a/frontends/src/wix/main_window.py	Tue Feb 04 18:24:27 2014 +0100
+++ b/frontends/src/wix/main_window.py	Tue Feb 04 18:26:03 2014 +0100
@@ -25,7 +25,6 @@
 from sat_frontends.wix.contact_list import ContactList
 from sat_frontends.wix.chat import Chat
 from sat_frontends.wix.xmlui import XMLUI
-from sat_frontends.wix.gateways import GatewaysManager
 from sat_frontends.wix.profile import Profile
 from sat_frontends.wix.profile_manager import ProfileManager
 import os.path
@@ -42,7 +41,7 @@
 idREMOVE_CONTACT,\
 idSHOW_PROFILE,\
 idJOIN_ROOM,\
-idFIND_GATEWAYS = range(10)
+= range(9)
 
 class ChatList(QuickChatList):
     """This class manage the list of chat windows"""
@@ -131,7 +130,6 @@
         contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile"))
         communicationMenu = wx.Menu()
         communicationMenu.Append(idJOIN_ROOM, _("&Join Room"),_(" Join a Multi-User Chat room"))
-        communicationMenu.Append(idFIND_GATEWAYS, _("&Find Gateways"),_(" Find gateways to legacy IM"))
         self.menuBar = wx.MenuBar()
         self.menuBar.Append(connectMenu,_("&General"))
         self.menuBar.Append(contactMenu,_("&Contacts"))
@@ -176,8 +174,6 @@
         wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact)
         wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile)
         wx.EVT_MENU(self, idJOIN_ROOM, self.onJoinRoom)
-        wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways)
-
 
     def newMessage(self, from_jid, to_jid, msg, _type, extra, profile):
         QuickApp.newMessage(self, from_jid, to_jid, msg, _type, extra, profile)
@@ -511,18 +507,6 @@
             else:
                 error (_("'%s' is an invalid JID !"), room_jid)
 
-    def onFindGateways(self, e):
-        debug(_("Find Gateways request"))
-        _id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain, self.profile)
-        self.current_action_ids.add(_id)
-        self.current_action_ids_cb[_id] = self.onGatewaysFound
-
-    def onGatewaysFound(self, data):
-        """Called when SàT has found the server gateways"""
-        target = data['__private__']['target']
-        del data['__private__']
-        gatewayManager = GatewaysManager(self, data, server=target)
-
     def onClose(self, e):
         QuickApp.onExit(self)
         info(_("Exiting..."))
--- a/src/core/sat_main.py	Tue Feb 04 18:24:27 2014 +0100
+++ b/src/core/sat_main.py	Tue Feb 04 18:26:03 2014 +0100
@@ -154,11 +154,11 @@
         self.bridge.register("getParamsCategories", self.memory.getParamsCategories)
         self.bridge.register("paramsRegisterApp", self.memory.paramsRegisterApp)
         self.bridge.register("getHistory", self.memory.getHistory)
-        self.bridge.register("setPresence", self.setPresence)
+        self.bridge.register("setPresence", self._setPresence)
         self.bridge.register("subscription", self.subscription)
-        self.bridge.register("addContact", self.addContact)
-        self.bridge.register("updateContact", self.updateContact)
-        self.bridge.register("delContact", self.delContact)
+        self.bridge.register("addContact", self._addContact)
+        self.bridge.register("updateContact", self._updateContact)
+        self.bridge.register("delContact", self._delContact)
         self.bridge.register("isConnected", self.isConnected)
         self.bridge.register("launchAction", self.launchCallback)
         self.bridge.register("confirmationAnswer", self.confirmationAnswer)
@@ -423,34 +423,6 @@
         else:
             self.actionResult(action_id, "SUPPRESS", {}, profile)
 
-    def submitForm(self, action, target, fields, profile_key):
-        """submit a form
-        @param target: target jid where we are submitting
-        @param fields: list of tuples (name, value)
-        @return: tuple: (id, deferred)
-        """
-        # FIXME: to be removed
-
-        profile = self.memory.getProfileName(profile_key)
-        assert(profile)
-        to_jid = jid.JID(target)
-
-        iq = compat.IQ(self.profiles[profile].xmlstream, 'set')
-        iq["to"] = target
-        iq["from"] = self.profiles[profile].jid.full()
-        query = iq.addElement(('jabber:iq:register', 'query'))
-        if action == 'SUBMIT':
-            form = tupleList2dataForm(fields)
-            query.addChild(form.toElement())
-        elif action == 'CANCEL':
-            query.addElement('remove')
-        else:
-            error(_("FIXME FIXME FIXME: Unmanaged action (%s) in submitForm") % action)
-            raise NotImplementedError
-
-        deferred = iq.send(target)
-        return (iq['id'], deferred)
-
     ## Client management ##
 
     def setParam(self, name, value, category, security_limit, profile_key):
@@ -591,13 +563,15 @@
                                        extra=mess_data['extra'],
                                        profile=profile)
 
-    def setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'):
+    def _setPresence(self, to="", show="", priority=0, statuses=None, profile_key='@NONE@'):
+        return self.setPresence(jid.JID(to) if to else None, show, priority, statuses, profile_key)
+
+    def setPresence(self, to_jid=None, show="", priority=0, statuses=None, profile_key='@NONE@'):
         """Send our presence information"""
         if statuses is None:
             statuses = {}
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
-        to_jid = jid.JID(to) if to else None
         self.profiles[profile].presence.available(to_jid, show, statuses, priority)
         #XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
         if '' in statuses:
@@ -624,30 +598,36 @@
         elif subs_type == "unsubscribed":
             self.profiles[profile].presence.unsubscribed(to_jid)
 
-    def addContact(self, to, profile_key):
+    def _addContact(self, to_jid_s, profile_key):
+        return self.addContact(jid.JID(to), profile_key)
+
+    def addContact(self, to_jid, profile_key):
         """Add a contact in roster list"""
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
-        to_jid = jid.JID(to)
         #self.profiles[profile].roster.addItem(to_jid)  #XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
         self.profiles[profile].presence.subscribe(to_jid)
 
-    def updateContact(self, to, name, groups, profile_key):
+    def _updateContact(self, to_jid_s, name, groups, profile_key):
+        return self.updateContact(jid.JID(to_jid_s), name, groups, profile_key)
+
+    def updateContact(self, to_jid, name, groups, profile_key):
         """update a contact in roster list"""
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
-        to_jid = jid.JID(to)
         groups = set(groups)
         roster_item = RosterItem(to_jid)
         roster_item.name = name or None
         roster_item.groups = set(groups)
         self.profiles[profile].roster.updateItem(roster_item)
 
-    def delContact(self, to, profile_key):
+    def _delContact(self, to_jid_s, profile_key):
+        return self.delContact(jid.JID(to_jid_s), profile_key)
+
+    def delContact(self, to_jid, profile_key):
         """Remove contact from roster list"""
         profile = self.memory.getProfileName(profile_key)
         assert(profile)
-        to_jid = jid.JID(to)
         self.profiles[profile].roster.removeItem(to_jid)
         self.profiles[profile].presence.unsubscribe(to_jid)
 
--- a/src/plugins/plugin_xep_0077.py	Tue Feb 04 18:24:27 2014 +0100
+++ b/src/plugins/plugin_xep_0077.py	Tue Feb 04 18:26:03 2014 +0100
@@ -18,12 +18,14 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.i18n import _
-from logging import debug, info, error
+from sat.core import exceptions
+from logging import debug, info, warning, error
 from twisted.words.protocols.jabber import jid
 from twisted.words.protocols.jabber.xmlstream import IQ
-from sat.tools.xml_tools import dataForm2XMLUI
+from sat.tools import xml_tools
+from sat.memory import memory
 
-from wokkel import data_form
+from wokkel import data_form, compat
 
 NS_REG = 'jabber:iq:register'
 
@@ -44,97 +46,70 @@
         info(_("Plugin XEP_0077 initialization"))
         self.host = host
         self.triggers = {}  # used by other protocol (e.g. XEP-0100) to finish registration. key = target_jid
-        host.bridge.addMethod("in_band_register", ".plugin", in_sign='ss', out_sign='s', method=self.in_band_register)
-        host.bridge.addMethod("in_band_submit", ".plugin", in_sign='ssa(ss)s', out_sign='s', method=self.in_band_submit)
+        host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='s',
+                              method=self._inBandRegister,
+                              async=True)
 
-    def addTrigger(self, target, cb, profile):
-        """Add a callback which is called when registration to target is successful"""
-        self.triggers[target] = (cb, profile)
-
-    def reg_ok(self, answer, profile):
+    def _regOk(self, answer, client, post_treat_cb):
         """Called after the first get IQ"""
         try:
-            x_elem = filter(lambda x: x.name == "x", answer.firstChildElement().elements())[0]  # We only want the "x" element (data form)
-        except IndexError:
-            info(_("No data form found"))
-            #TODO: manage registration without data form
-            answer_data = {"reason": "unmanaged", "message": _("This gateway can't be managed by SàT, sorry :(")}
-            answer_type = "ERROR"
-            self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
-            return
+            query_elt = answer.elements(NS_REG, 'query').next()
+        except StopIteration:
+            raise exceptions.DataError("Can't find expected query element")
+
+        try:
+            x_elem = query_elt.elements(data_form.NS_X_DATA, 'x').next()
+        except StopIteration:
+            # XXX: it seems we have an old service which doesn't manage data forms
+            warning(_("Can't find data form"))
+            raise DataError(_("This gateway can't be managed by SàT, sorry :("))
+
+        def submitForm(data, profile):
+            form_elt = xml_tools.XMLUIResultToElt(data)
+
+            iq_elt = compat.IQ(client.xmlstream, 'set')
+            iq_elt['id'] = answer['id']
+            iq_elt['to'] = answer['from']
+            query_elt = iq_elt.addElement("query", NS_REG)
+            query_elt.addChild(form_elt)
+            d = iq_elt.send()
+            d.addCallback(self._regSuccess, client, post_treat_cb)
+            d.addErrback(self._regFailure, client)
+            return d
 
         form = data_form.Form.fromElement(x_elem)
-        xml_data = dataForm2XMLUI(form, "").toXml()
-        self.host.bridge.actionResult("XMLUI", answer['id'], {"target": answer["from"], "type": "registration", "xml": xml_data}, profile)
+        submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True)
+        return xml_tools.dataForm2XMLUI(form, submit_reg_id)
 
-    def reg_err(self, failure, profile):
+    def _regErr(self, failure, client):
         """Called when something is wrong with registration"""
         info(_("Registration failure: %s") % str(failure.value))
-        answer_data = {}
-        answer_data['reason'] = 'unknown'
-        answer_data = {"message": "%s [code: %s]" % (failure.value.condition, unicode(failure.value))}
-        answer_type = "ERROR"
-        self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
+        raise failure
 
-    def unregistrationAnswer(self, answer, profile):
-        debug(_("registration answer: %s") % answer.toXml())
-        answer_type = "SUCCESS"
-        answer_data = {"message": _("Your are now unregistred")}
-        self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
-
-    def unregistrationFailure(self, failure, profile):
-        info(_("Unregistration failure: %s") % str(failure.value))
-        answer_type = "ERROR"
-        answer_data = {}
-        answer_data['reason'] = 'unknown'
-        answer_data = {"message": _("Unregistration failed: %s") % failure.value.condition}
-        self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
-
-    def registrationAnswer(self, answer, profile):
+    def _regSuccess(self, answer, client, post_treat_cb):
         debug(_("registration answer: %s") % answer.toXml())
-        answer_type = "SUCCESS"
-        answer_data = {"message": _("Registration successfull")}
-        self.host.bridge.actionResult(answer_type, answer['id'], answer_data, profile)
-        if answer["from"] in self.triggers:
-            callback, profile = self.triggers[answer["from"]]
-            callback(answer["from"], profile)
-            del self.triggers[answer["from"]]
+        if post_treat_cb is not None:
+            post_treat_cb(jid.JID(answer['from']), client.profile)
+        return {}
 
-    def registrationFailure(self, failure, profile):
+    def _regFailure(self, failure, client):
         info(_("Registration failure: %s") % str(failure.value))
-        print failure.value.stanza.toXml()
-        answer_type = "ERROR"
-        answer_data = {}
         if failure.value.condition == 'conflict':
-            answer_data['reason'] = 'conflict'
-            answer_data = {"message": _("Username already exists, please choose an other one")}
-        else:
-            answer_data['reason'] = 'unknown'
-            answer_data = {"message": _("Registration failed")}
-        self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data, profile)
-        if failure.value.stanza["from"] in self.triggers:
-            del self.triggers[failure.value.stanza["from"]]
+            raise exceptions.ConflictError( _("Username already exists, please choose an other one"))
+        raise failure
 
-    def in_band_submit(self, action, target, fields, profile):
-        """Submit a form for registration, using data_form"""
-        id, deferred = self.host.submitForm(action, target, fields, profile)
-        if action == 'CANCEL':
-            deferred.addCallbacks(self.unregistrationAnswer, self.unregistrationFailure, callbackArgs=[profile], errbackArgs=[profile])
-        else:
-            deferred.addCallbacks(self.registrationAnswer, self.registrationFailure, callbackArgs=[profile], errbackArgs=[profile])
-        return id
+    def _inBandRegister(self, to_jid_s, profile_key='@NONE@'):
+        return self.inBandRegister, jid.JID(to_jid_s, profile_key)
 
-    def in_band_register(self, target, profile_key='@DEFAULT@'):
+    def inBandRegister(self, to_jid, post_treat_cb=None, profile_key='@NONE@'):
         """register to a target JID"""
         client = self.host.getClient(profile_key)
         if not client:
-            error(_('Asking for an non-existant or not connected profile'))
-            return ""
-        to_jid = jid.JID(target)
+            raise exceptions.ProfileUnknownError
         debug(_("Asking registration for [%s]") % to_jid.full())
         reg_request = IQ(client.xmlstream, 'get')
         reg_request["from"] = client.jid.full()
         reg_request["to"] = to_jid.full()
         reg_request.addElement('query', NS_REG)
-        reg_request.send(to_jid.full()).addCallbacks(self.reg_ok, self.reg_err, callbackArgs=[client.profile], errbackArgs=[client.profile])
-        return reg_request["id"]
+        d = reg_request.send(to_jid.full()).addCallbacks(self._regOk, self._regErr, callbackArgs=[client, post_treat_cb], errbackArgs=[client])
+        return d
--- a/src/plugins/plugin_xep_0100.py	Tue Feb 04 18:24:27 2014 +0100
+++ b/src/plugins/plugin_xep_0100.py	Tue Feb 04 18:26:03 2014 +0100
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from sat.core.i18n import _
-from logging import debug, info, error
+from sat.core.i18n import _, D_
+from sat.core import exceptions
+from sat.tools import xml_tools
+from logging import debug, info, warning, error
 from twisted.words.protocols.jabber import client as jabber_client, jid
 from twisted.words.protocols.jabber import error as jab_error
-import twisted.internet.error
+from twisted.internet import reactor, defer
 
 PLUGIN_INFO = {
     "name": "Gateways Plugin",
@@ -33,6 +35,22 @@
     "description": _("""Implementation of Gateways protocol""")
 }
 
+WARNING_MSG = D_(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
+But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""")
+
+GATEWAY_TIMEOUT = 10 # time to wait before cancelling a gateway disco info, in seconds
+
+TYPE_DESCRIPTIONS = { 'irc': D_("Internet Relay Chat"),
+                      'xmpp': D_("XMPP"),
+                      'qq': D_("Tencent QQ"),
+                      'simple': D_("SIP/SIMPLE"),
+                      'icq': D_("ICQ"),
+                      'yahoo': D_("Yahoo! Messenger"),
+                      'gadu-gadu': D_("Gadu-Gadu"),
+                      'aim': D_("AOL Instant Messenger"),
+                      'msn': D_("Windows Live Messenger"),
+                    }
+
 
 class XEP_0100(object):
 
@@ -40,82 +58,155 @@
         info(_("Gateways plugin initialization"))
         self.host = host
         self.__gateways = {}  # dict used to construct the answer to findGateways. Key = target jid
-        host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self.findGateways)
-        host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ssa(ss)s', out_sign='s', method=self.gatewayRegister)
+        host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways)
+        host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister)
+        self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True)
+        self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True)
+        host.importMenu((D_("Service"), D_("gateways")), self._gatewaysMenu, help_string=D_("Find gateways"))
+
+
+    def _gatewaysMenu(self, data, profile):
+        """ XMLUI activated by menu: return Gateways UI
+        @param profile: %(doc_profile)s
 
-    def __inc_handled_items(self, request_id, profile):
-        self.__gateways[request_id]['__handled_items'] += 1
+        """
+        client = self.host.getClient(profile)
+        try:
+            jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host))
+        except RuntimeError:
+            raise exceptions.DataError(_("Invalid JID"))
+        d = self.findGateways(jid_, profile)
+        d.addCallback(self._gatewaysResult2XMLUI, jid_)
+        d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
+        return d
 
-        if self.__gateways[request_id]['__total_items'] == self.__gateways[request_id]['__handled_items']:
-            debug(_("All items checked for id [%s]") % str(request_id))
+    def _gatewaysResult2XMLUI(self, result, entity):
+        xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full())
+        xmlui.addText(_(WARNING_MSG))
+        xmlui.addDivider('dash')
+        adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id)
+        for success, gateway_data in result:
+            if not success:
+                fail_cond, disco_item = gateway_data
+                xmlui.addJid(disco_item.entity)
+                xmlui.addText(_('Failed (%s)') % fail_cond)
+                xmlui.addEmpty()
+            else:
+                jid_, data = gateway_data
+                for datum in data:
+                    identity, name = datum
+                    adv_list.setRowIndex(jid_.full())
+                    xmlui.addJid(jid_)
+                    xmlui.addText(name)
+                    xmlui.addText(self._getIdentityDesc(identity))
+        adv_list.end()
+        xmlui.addDivider('blank')
+        xmlui.changeContainer('advanced_list', columns=3)
+        xmlui.addLabel(_('Use external XMPP server'))
+        xmlui.addString('external_jid')
+        xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',))
+        return xmlui
+
+    def _gatewaySelectedCb(self, data, profile):
+        try:
+            target_jid = jid.JID(data['index'])
+        except (KeyError, RuntimeError):
+            warning(_("No gateway index selected"))
+            return {}
 
-            del self.__gateways[request_id]['__total_items']
-            del self.__gateways[request_id]['__handled_items']
-            self.host.actionResultExt(request_id, "DICT_DICT", self.__gateways[request_id], profile)
+        d = self.gatewayRegister(target_jid, profile)
+        d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
+        return d
+
+    def _getIdentityDesc(self, identity):
+        """ Return a human readable description of identity
+        @param identity: tuple as returned by Disco identities (category, type)
+
+        """
+        category, type_ = identity
+        if category != 'gateway':
+            error(_('INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category)
+        try:
+            return _(TYPE_DESCRIPTIONS[type_])
+        except KeyError:
+            return _("Unknown IM")
 
-    def discoInfo(self, disco, entity, request_id, profile):
+    def _registrationSuccessful(self, jid_, profile):
+        """Called when in_band registration is ok, we must now follow the rest of procedure"""
+        debug(_("Registration successful, doing the rest"))
+        self.host.addContact(jid_, profile_key=profile)
+        self.host.setPresence(jid_, profile_key=profile)
+
+    def _gatewayRegister(self, target_jid_s, profile_key='@NONE@'):
+        d = self.gatewayRegister(jid.JID(target_jid_s), profile_key)
+        d.addCallback(lambda xmlui: xmlui.toXml())
+        return d
+
+    def gatewayRegister(self, target_jid, profile_key='@NONE@'):
+        """Register gateway using in-band registration, then log-in to gateway"""
+        profile = self.host.memory.getProfileName(profile_key)
+        assert(profile)
+        d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile)
+        return d
+
+    def _infosReceived(self, dl_result, items, target, client):
         """Find disco infos about entity, to check if it is a gateway"""
 
-        for identity in disco.identities:
-            if identity[0] == 'gateway':
-                print (_("Found gateway (%(jid)s): %(identity)s") % {'jid': entity.full(), 'identity': disco.identities[identity]})
-                self.__gateways[request_id][entity.full()] = {
-                    'name': disco.identities[identity],
-                    'type': identity[1]
-                }
-
-        self.__inc_handled_items(request_id, profile)
+        ret = []
+        for idx, (success, result) in enumerate(dl_result):
+            if not success:
+                if isinstance(result.value, defer.CancelledError):
+                    msg = _("Timeout")
+                else:
+                    try:
+                        msg = result.value.condition
+                    except AttibuteError:
+                        msg = str(result)
+                ret.append((success, (msg, items[idx])))
+            else:
+                entity = items[idx].entity
+                gateways = [(identity, result.identities[identity]) for identity in result.identities if identity[0] == 'gateway']
+                if gateways:
+                    info (_("Found gateway [%(jid)s]: %(identity_name)s") % {'jid': entity.full(), 'identity_name': ' - '.join([gateway[1] for gateway in gateways])})
+                    ret.append((success, (entity, gateways)))
+                else:
+                    info(_("Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()})
+        return ret
 
-    def discoInfoErr(self, failure, entity, request_id, profile):
-        """Something is going wrong with disco"""
-        failure.trap(jab_error.StanzaError, twisted.internet.error.ConnectionLost)
-        error(_("Error when discovering [%(jid)s]: %(error)s") % {'jid': entity.full(), 'error': failure.getErrorMessage()})
-        self.__inc_handled_items(request_id, profile)
+    def _itemsReceived(self, disco, target, client):
+        """Look for items with disco protocol, and ask infos for each one"""
 
-    def discoItems(self, disco, request_id, target, client):
-        """Look for items with disco protocol, and ask infos for each one"""
-        #FIXME: target is used as we can't find the original iq node (parent is None)
-        #       an other way would avoid this useless parameter (is there a way with wokkel ?)
         if len(disco._items) == 0:
             debug(_("No gateway found"))
-            self.host.actionResultExt(request_id, "DICT_DICT", {})
-            return
-
-        self.__gateways[request_id] = {'__total_items': len(disco._items), '__handled_items': 0, '__private__': {'target': target.full()}}
-        for item in disco._items:
-            #TODO: need to set a timeout for theses requests
-            debug(_("item found: %s"), item.name)
-            client.disco.requestInfo(item.entity).addCallback(self.discoInfo, entity=item.entity, request_id=request_id, profile=client.profile).addErrback(self.discoInfoErr, entity=item.entity, request_id=request_id, profile=client.profile)
+            return []
 
-    def discoItemsErr(self, failure, request_id, target, client):
-        """Something is going wrong with disco"""
-        error(_("Error when discovering [%(target)s]: %(condition)s") % {'target': target.full(), 'condition': unicode(failure.value)})
-        message_data = {"reason": "connection error", "message": _(u"Error while trying to discover %(target)s gateways: %(error_mess)s") % {'target': target.full(), 'error_mess': unicode(failure.value)}}
-        self.host.bridge.actionResult("ERROR", request_id, message_data)
+        _defers = []
+        for item in disco._items:
+            debug(_("item found: %s"), item.entity)
+            _defers.append(client.disco.requestInfo(item.entity))
+        dl = defer.DeferredList(_defers)
+        dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client)
+        reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
+        return dl
 
-    def registrationSuccessful(self, target, profile):
-        """Called when in_band registration is ok, we must now follow the rest of procedure"""
-        debug(_("Registration successful, doing the rest"))
-        self.host.addContact(target, profile)
-        self.host.setPresence(target, profile)
 
-    def gatewayRegister(self, action, target, fields, profile_key='@DEFAULT@'):
-        """Register gateway using in-band registration, then log-in to gateway"""
+    def _findGateways(self, target_jid_s, profile_key):
+        target_jid = jid.JID(target_jid_s)
         profile = self.host.memory.getProfileName(profile_key)
-        assert(profile)  # FIXME: return an error here
-        if action == 'SUBMIT':
-            self.host.plugins["XEP-0077"].addTrigger(target, self.registrationSuccessful, profile)
-        return self.host.plugins["XEP-0077"].in_band_submit(action, target, fields, profile)
+        if not profile:
+            raise exceptions.ProfileUnknownError
+        d = self.findGateways(target_jid, profile)
+        d.addCallback(self._gatewaysResult2XMLUI, target_jid)
+        d.addCallback(lambda xmlui: xmlui.toXml())
+        return d
 
-    def findGateways(self, target, profile_key):
+
+    def findGateways(self, target, profile):
         """Find gateways in the target JID, using discovery protocol
-        Return an id used for retrieving the list of gateways
         """
-        profile = self.host.memory.getProfileName(profile_key)
-        client = self.host.getClient(profile_key)
+        client = self.host.getClient(profile)
         assert(client)
-        to_jid = jid.JID(target)
-        debug(_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target': to_jid.full(), 'profile': profile})
-        request_id = self.host.get_next_id()
-        client.disco.requestItems(to_jid).addCallback(self.discoItems, request_id=request_id, target=to_jid, client=client).addErrback(self.discoItemsErr, request_id=request_id, target=to_jid, client=client)
-        return request_id
+        debug(_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile})
+        d = client.disco.requestItems(target)
+        d.addCallback(self._itemsReceived , target=target, client=client)
+        return d