diff --git a/frontends/sortilege/sortilege b/frontends/sortilege/sortilege
new file mode 100755
index 00000000..7257760c
--- /dev/null
+++ b/frontends/sortilege/sortilege
@@ -0,0 +1,387 @@
+#!/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 .
+"""
+
+
+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.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()
diff --git a/frontends/wix/wix b/frontends/wix/wix
new file mode 100755
index 00000000..4c289965
--- /dev/null
+++ b/frontends/wix/wix
@@ -0,0 +1,48 @@
+#!/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 .
+"""
+
+
+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()