In-band-registration: data form 2 xml conversion
This commit is contained in:
parent
e92710fb2f
commit
a5c34eeec9
6 changed files with 63 additions and 437 deletions
|
|
@ -1,387 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
sortilege: a SAT frontend
|
||||
Copyright (C) 2009 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import curses
|
||||
import pdb
|
||||
from window import Window
|
||||
from editbox import EditBox
|
||||
from statusbar import StatusBar
|
||||
from chat import Chat
|
||||
from tools.jid import JID
|
||||
import logging
|
||||
from logging import debug, info, error
|
||||
import locale
|
||||
import sys, os
|
||||
import gobject
|
||||
import time
|
||||
from curses import ascii
|
||||
import locale
|
||||
from signal import signal, SIGWINCH
|
||||
import fcntl
|
||||
import struct
|
||||
import termios
|
||||
from boxsizer import BoxSizer
|
||||
from quick_frontend.quick_chat_list import QuickChatList
|
||||
from quick_frontend.quick_contact_list import QuickContactList
|
||||
from quick_frontend.quick_app import QuickApp
|
||||
|
||||
### logging configuration FIXME: put this elsewhere ###
|
||||
logging.basicConfig(level=logging.CRITICAL, #TODO: configure it top put messages in a log file
|
||||
format='%(message)s')
|
||||
###
|
||||
|
||||
const_APP_NAME = "Sortilège"
|
||||
const_CONTACT_WIDTH = 30
|
||||
|
||||
def ttysize():
|
||||
"""This function return term size.
|
||||
Comes from Donn Cave from python list mailing list"""
|
||||
buf = 'abcdefgh'
|
||||
buf = fcntl.ioctl(0, termios.TIOCGWINSZ, buf)
|
||||
row, col, rpx, cpx = struct.unpack('hhhh', buf)
|
||||
return row, col
|
||||
|
||||
def C(k):
|
||||
"""return the value of Ctrl+key"""
|
||||
return ord(ascii.ctrl(k))
|
||||
|
||||
class ChatList(QuickChatList):
|
||||
"""This class manage the list of chat windows"""
|
||||
|
||||
def __init__(self, host):
|
||||
QuickChatList.__init__(self, host)
|
||||
self.sizer=host.sizer
|
||||
|
||||
def createChat(self, name):
|
||||
chat = Chat(name, self.host)
|
||||
self.sizer.appendColum(0,chat)
|
||||
self.sizer.update()
|
||||
return chat
|
||||
|
||||
|
||||
class ContactList(Window, QuickContactList):
|
||||
|
||||
def __init__(self):
|
||||
QuickContactList.__init__(self)
|
||||
self.__index=0 #indicate which contact is selected (ie: where we are)
|
||||
Window.__init__(self, stdscr, stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0, True, "Contact List", code=code)
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
Window.resize(self, height, width, y, x)
|
||||
self.update()
|
||||
|
||||
def resizeAdapt(self):
|
||||
"""Adapt window size to stdscr size.
|
||||
Must be called when stdscr is resized."""
|
||||
self.resize(stdscr.getmaxyx()[0]-2,const_CONTACT_WIDTH,0,0)
|
||||
self.update()
|
||||
|
||||
def registerEnterCB(self, CB):
|
||||
self.__enterCB=CB
|
||||
|
||||
def replace(self, jid, name="", show="", status="", group=""):
|
||||
"""add a contact to the list"""
|
||||
self.jid_ids[jid] = name or jid
|
||||
self.update()
|
||||
|
||||
def indexUp(self):
|
||||
"""increment select contact index"""
|
||||
if self.__index < len(self.jid_ids)-1: #we dont want to select a missing contact
|
||||
self.__index = self.__index + 1
|
||||
self.update()
|
||||
|
||||
def indexDown(self):
|
||||
"""decrement select contact index"""
|
||||
if self.__index > 0:
|
||||
self.__index = self.__index - 1
|
||||
self.update()
|
||||
|
||||
def remove(self, jid):
|
||||
"""remove a contact from the list"""
|
||||
del self.jid_ids[jid]
|
||||
if self.__index >= len(self.jid_ids) and self.__index > 0: #if select index is out of border, we put it on the last contact
|
||||
self.__index = len(self.jid_ids)-1
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
"""redraw all the window"""
|
||||
if self.isHidden():
|
||||
return
|
||||
Window.update(self)
|
||||
viewList=[]
|
||||
for jid in self.jid_ids:
|
||||
viewList.append(self.jid_ids[jid])
|
||||
viewList.sort()
|
||||
begin=0 if self.__index<self.rHeight else self.__index-self.rHeight+1
|
||||
idx=0
|
||||
for item in viewList[begin:self.rHeight+begin]:
|
||||
attr = curses.A_REVERSE if ( self.isActive() and (idx+begin) == self.__index ) else 0
|
||||
centered = item.center(self.rWidth) ## it's nicer in the center :)
|
||||
self.addYXStr(idx, 0, centered, attr)
|
||||
idx = idx + 1
|
||||
|
||||
self.noutrefresh()
|
||||
|
||||
def handleKey(self, k):
|
||||
if k == curses.KEY_UP:
|
||||
self.indexDown()
|
||||
elif k == curses.KEY_DOWN:
|
||||
self.indexUp()
|
||||
elif k == ascii.NL:
|
||||
if not self.jid_ids:
|
||||
return
|
||||
try:
|
||||
self.__enterCB(self.jid_ids.keys()[self.__index])
|
||||
except NameError:
|
||||
pass # TODO: thrown an error here
|
||||
|
||||
class SortilegeApp(QuickApp):
|
||||
|
||||
def __init__(self):
|
||||
#debug(const_APP_NAME+" init...")
|
||||
|
||||
## unicode support ##
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
global code
|
||||
code = locale.getpreferredencoding()
|
||||
self.code=code
|
||||
|
||||
## main loop setup ##
|
||||
self.loop=gobject.MainLoop()
|
||||
gobject.io_add_watch(0, gobject.IO_IN, self.loopCB)
|
||||
|
||||
## misc init stuff ##
|
||||
self.listWins=[]
|
||||
self.chatParams={'timestamp':True,
|
||||
'color':True,
|
||||
'short_nick':False}
|
||||
|
||||
def start(self):
|
||||
curses.wrapper(self.start_curses)
|
||||
|
||||
def start_curses(self, win):
|
||||
global stdscr
|
||||
stdscr = win
|
||||
self.stdscr = stdscr
|
||||
curses.raw() #we handle everything ourself
|
||||
curses.curs_set(False)
|
||||
stdscr.nodelay(True)
|
||||
|
||||
## colours ##
|
||||
self.color(True)
|
||||
|
||||
## windows ##
|
||||
self.contactList = ContactList()
|
||||
self.editBar = EditBox(stdscr, "> ", self.code)
|
||||
self.editBar.activate(False)
|
||||
self.statusBar = StatusBar(stdscr, self.code)
|
||||
self.statusBar.hide(True)
|
||||
self.addWin(self.contactList)
|
||||
self.addWin(self.editBar)
|
||||
self.addWin(self.statusBar)
|
||||
self.sizer=BoxSizer(stdscr)
|
||||
self.sizer.appendRow(self.contactList)
|
||||
self.sizer.appendRow(self.statusBar)
|
||||
self.sizer.appendRow(self.editBar)
|
||||
self.currentChat=None
|
||||
|
||||
self.contactList.registerEnterCB(self.onContactChoosed)
|
||||
self.editBar.registerEnterCB(self.onTextEntered)
|
||||
|
||||
self.chat_wins=ChatList(self)
|
||||
|
||||
QuickApp.__init__(self) #XXX: yes it's an unusual place for the constructor of a parent class, but the init order is important
|
||||
|
||||
signal (SIGWINCH, self.onResize) #we manage SIGWINCH ourselves, because the loop is not called otherwise
|
||||
|
||||
#last but not least, we adapt windows' sizes
|
||||
self.sizer.update()
|
||||
self.editBar.replace_cur()
|
||||
curses.doupdate()
|
||||
|
||||
self.loop.run()
|
||||
|
||||
def addWin(self, win):
|
||||
self.listWins.append(win)
|
||||
|
||||
def color(self, activate=True):
|
||||
if activate:
|
||||
debug ("Activation des couleurs")
|
||||
curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK)
|
||||
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
|
||||
else:
|
||||
debug ("Desactivation des couleurs")
|
||||
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||
|
||||
|
||||
def showChat(self, chat):
|
||||
debug ("show chat")
|
||||
if self.currentChat:
|
||||
debug ("hide de %s", self.currentChat)
|
||||
self.chat_wins[self.currentChat].hide()
|
||||
self.currentChat=chat
|
||||
debug ("show de %s", self.currentChat)
|
||||
self.chat_wins[self.currentChat].show()
|
||||
self.chat_wins[self.currentChat].update()
|
||||
|
||||
|
||||
### EVENTS ###
|
||||
|
||||
def onContactChoosed(self, jid_txt):
|
||||
"""Called when a contact is selected in contact list."""
|
||||
jid=JID(jid_txt)
|
||||
debug ("contact choose: %s", jid)
|
||||
self.showChat(jid.short)
|
||||
self.statusBar.remove_item(jid.short)
|
||||
if len(self.statusBar)==0:
|
||||
self.statusBar.hide()
|
||||
self.sizer.update()
|
||||
|
||||
|
||||
def onTextEntered(self, text):
|
||||
jid=JID(self.whoami)
|
||||
self.bridge.sendMessage(self.currentChat, text)
|
||||
|
||||
def showDialog(self, message, title, type="info"):
|
||||
if type==question:
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
|
||||
def presenceUpdate(self, jabber_id, type, show, status, priority):
|
||||
QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority)
|
||||
self.editBar.replace_cur()
|
||||
curses.doupdate()
|
||||
|
||||
def askConfirmation(self, type, id, data):
|
||||
#FIXME
|
||||
info ("FIXME: askConfirmation not implemented")
|
||||
|
||||
def actionResult(self, type, id, data):
|
||||
#FIXME
|
||||
info ("FIXME: actionResult not implemented")
|
||||
|
||||
def newMessage(self, from_jid, msg, type, to_jid):
|
||||
QuickApp.newMessage(self, from_jid, msg, type, to_jid)
|
||||
sender=JID(from_jid)
|
||||
addr=JID(to_jid)
|
||||
win = addr if sender.short == self.whoami.short else sender #FIXME: duplicate code with QuickApp
|
||||
if (self.currentChat==None):
|
||||
self.currentChat=win.short
|
||||
self.showChat(win.short)
|
||||
|
||||
# we show the window in the status bar
|
||||
if not self.currentChat == win.short:
|
||||
self.statusBar.add_item(win.short)
|
||||
self.statusBar.show()
|
||||
self.sizer.update()
|
||||
self.statusBar.update()
|
||||
|
||||
self.editBar.replace_cur()
|
||||
curses.doupdate()
|
||||
|
||||
def onResize(self, sig, stack):
|
||||
"""Called on SIGWINCH.
|
||||
resize the screen and launch the loop"""
|
||||
height, width = ttysize()
|
||||
curses.resizeterm(height, width)
|
||||
gobject.idle_add(self.callOnceLoop)
|
||||
|
||||
def callOnceLoop(self):
|
||||
"""Call the loop and return false (for not being called again by gobject mainloop).
|
||||
Usefull for calling loop when there is no input in stdin"""
|
||||
self.loopCB()
|
||||
return False
|
||||
|
||||
def __key_handling(self, k):
|
||||
"""Handle key and transmit to active window."""
|
||||
|
||||
### General keys, handled _everytime_ ###
|
||||
if k == C('x'):
|
||||
if os.getenv('TERM')=='screen':
|
||||
os.system('screen -X remove')
|
||||
else:
|
||||
self.loop.quit()
|
||||
|
||||
## windows navigation
|
||||
elif k == C('l') and not self.contactList.isHidden():
|
||||
"""We go to the contact list"""
|
||||
self.contactList.activate(not self.contactList.isActive())
|
||||
if self.currentChat:
|
||||
self.editBar.activate(not self.contactList.isActive())
|
||||
|
||||
elif k == curses.KEY_F2:
|
||||
self.contactList.hide(not self.contactList.isHidden())
|
||||
if self.contactList.isHidden():
|
||||
self.contactList.activate(False) #TODO: auto deactivation when hiding ?
|
||||
if self.currentChat:
|
||||
self.editBar.activate(True)
|
||||
self.sizer.update()
|
||||
|
||||
## Chat Params ##
|
||||
elif k == C('c'):
|
||||
self.chatParams["color"] = not self.chatParams["color"]
|
||||
self.color(self.chatParams["color"])
|
||||
elif k == C('t'):
|
||||
self.chatParams["timestamp"] = not self.chatParams["timestamp"]
|
||||
self.chat_wins[self.currentChat].update()
|
||||
elif k == C('s'):
|
||||
self.chatParams["short_nick"] = not self.chatParams["short_nick"]
|
||||
self.chat_wins[self.currentChat].update()
|
||||
|
||||
## misc ##
|
||||
elif k == curses.KEY_RESIZE:
|
||||
stdscr.erase()
|
||||
height, width = stdscr.getmaxyx()
|
||||
if height<5 and width<35:
|
||||
stdscr.addstr("Pleeeeasse, I can't even breathe !")
|
||||
else:
|
||||
for win in self.listWins:
|
||||
win.resizeAdapt()
|
||||
for win in self.chat_wins.keys():
|
||||
self.chat_wins[win].resizeAdapt()
|
||||
self.sizer.update() # FIXME: everything need to be managed by the sizer
|
||||
|
||||
## we now throw the key to win handlers ##
|
||||
else:
|
||||
for win in self.listWins:
|
||||
if win.isActive():
|
||||
win.handleKey(k)
|
||||
if self.currentChat:
|
||||
self.chat_wins[self.currentChat].handleKey(k)
|
||||
|
||||
def loopCB(self, source="", cb_condition=""):
|
||||
"""This callback is called by the main loop"""
|
||||
#pressed = self.contactList.window.getch()
|
||||
pressed = stdscr.getch()
|
||||
if pressed != curses.ERR:
|
||||
self.__key_handling(pressed)
|
||||
self.editBar.replace_cur()
|
||||
curses.doupdate()
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
sat = SortilegeApp()
|
||||
sat.start()
|
||||
|
|
@ -327,6 +327,9 @@ class MainWindow(wx.Frame, QuickApp):
|
|||
)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
elif type == "FORM":
|
||||
self.current_action_ids.remove(id)
|
||||
info ("Form received !")
|
||||
elif type == "DICT_DICT":
|
||||
self.current_action_ids.remove(id)
|
||||
if self.current_action_ids_cb.has_key(id):
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
wix: a SAT frontend
|
||||
Copyright (C) 2009 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import wx
|
||||
from sat_bridge_frontend.DBus import DBusBridgeFrontend
|
||||
import pdb
|
||||
import logging
|
||||
from logging import debug, info, error
|
||||
from main_window import MainWindow
|
||||
|
||||
### logging configuration FIXME: put this elsewhere ###
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(message)s')
|
||||
###
|
||||
|
||||
|
||||
class SATApp(wx.App):
|
||||
def __init__(self, redirect=False, filename=None, useBestVisual=False, clearSigInt=True):
|
||||
super(SATApp,self).__init__(redirect, filename, useBestVisual, clearSigInt)
|
||||
|
||||
def OnInit(self):
|
||||
self.main = MainWindow()
|
||||
self.main.Show(True)
|
||||
self.SetTopWindow(self.main)
|
||||
return True
|
||||
|
||||
|
||||
sat = SATApp()
|
||||
sat.MainLoop()
|
||||
|
|
@ -23,8 +23,11 @@ from logging import debug, info, error
|
|||
from twisted.words.protocols.jabber import client, jid, xmlstream, error
|
||||
from twisted.words.protocols.jabber.xmlstream import IQ
|
||||
from twisted.internet import reactor
|
||||
from tools.xml_tools import XMLTools
|
||||
import pdb
|
||||
|
||||
from wokkel import data_form
|
||||
|
||||
NS_REG = 'jabber:iq:register'
|
||||
|
||||
PLUGIN_INFO = {
|
||||
|
|
@ -46,6 +49,9 @@ class XEP_0077():
|
|||
def reg_ok(self, answer):
|
||||
"""Called after the first get IQ"""
|
||||
print "answer:",answer
|
||||
form = data_form.Form.fromElement(answer.firstChildElement().firstChildElement())
|
||||
xml_data = XMLTools.dataForm2xml(form)
|
||||
self.host.bridge.actionResult("FORM", answer['id'], {"type":"registration", "xml":xml_data})
|
||||
|
||||
def reg_err(self, failure):
|
||||
"""Called when something is wrong with registration"""
|
||||
|
|
|
|||
4
sat.tac
4
sat.tac
|
|
@ -524,7 +524,7 @@ class SAT(service.Service):
|
|||
def actionResult(self, id, type, data):
|
||||
"""Send the result of an action
|
||||
@param id: same id used with action
|
||||
@type: result type ("PARAM", "SUCCESS", "ERROR")
|
||||
@type: result type ("PARAM", "SUCCESS", "ERROR", "FORM")
|
||||
@data: dictionary
|
||||
"""
|
||||
self.bridge.actionResult(type, id, data)
|
||||
|
|
@ -532,7 +532,7 @@ class SAT(service.Service):
|
|||
def actionResultExt(self, id, type, data):
|
||||
"""Send the result of an action, extended version
|
||||
@param id: same id used with action
|
||||
@type: result type ("PARAM", "SUCCESS", "ERROR")
|
||||
@type: result type /!\ only "DICT_DICT" for this method
|
||||
@data: dictionary of dictionaries
|
||||
"""
|
||||
if type != "DICT_DICT":
|
||||
|
|
|
|||
52
tools/xml_tools.py
Normal file
52
tools/xml_tools.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT: a jabber client
|
||||
Copyright (C) 2009 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from logging import debug, info, error
|
||||
from xml.dom import minidom
|
||||
import pdb
|
||||
|
||||
class XMLTools:
|
||||
"""This class help manage XML used in SàT (parameters, registration, etc) """
|
||||
|
||||
|
||||
@staticmethod
|
||||
def dataForm2xml(form):
|
||||
"""Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml"""
|
||||
result_xml = ["<form>", "</form>"]
|
||||
if form.instructions:
|
||||
result_xml.insert(1,"<elem name='instructions' value='%s' type='text' />" % '\n'.join(form.instructions))
|
||||
for field in form.fieldList:
|
||||
if field.fieldType == 'text-single':
|
||||
__field_type = "string"
|
||||
elif field.fieldType == 'text-private':
|
||||
__field_type = "password"
|
||||
else:
|
||||
error (u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType)
|
||||
__field_type = "string_field"
|
||||
|
||||
result_xml.insert(-1,"<elem name='%s' type='%s' label='%s'>" % (field.var, __field_type, field.label))
|
||||
|
||||
return '\n'.join(result_xml)
|
||||
|
||||
|
||||
|
||||
|
||||
pdb.set_trace()
|
||||
Loading…
Reference in a new issue