diff options
-rw-r--r-- | src/client.py | 25 | ||||
-rw-r--r-- | src/connection.py | 38 | ||||
-rw-r--r-- | src/gui.py | 351 | ||||
-rw-r--r-- | src/handler.py | 140 | ||||
-rw-r--r-- | src/multiuserchat.py | 25 |
5 files changed, 242 insertions, 337 deletions
diff --git a/src/client.py b/src/client.py index e51c278e..ecbfb1de 100644 --- a/src/client.py +++ b/src/client.py @@ -17,17 +17,21 @@ # You should have received a copy of the GNU General Public License # along with Poezio. If not, see <http://www.gnu.org/licenses/>. +import sys +#sys.stderr = open('errors', 'w') # never print anyerror +#sys.stdout = open('salut', 'w') + from connection import Connection from multiuserchat import MultiUserChat from config import config from handler import Handler from gui import Gui +from curses import wrapper, initscr class Client(object): """ Main class - Do what should be done automatically by the Client: - join the rooms at startup, for example + Just read some configuration and instantiate the classes """ def __init__(self): self.handler = Handler() @@ -35,22 +39,15 @@ class Client(object): self.resource = config.get('resource') self.server = config.get('server') self.connection = Connection(self.server, self.resource) - self.connection.start() - - self.muc = MultiUserChat(self.connection.client) - self.gui = Gui() - self.rooms = config.get('rooms').split(':') - - import time - time.sleep(1) # remove - for room in self.rooms: - self.handler.emit('join-room', room = room.split('/')[0], nick=room.split('/')[1]) - while 1: - self.connection.process() + self.stdscr = initscr() + self.connection.start() + self.gui = Gui(self.stdscr, MultiUserChat(self.connection.client)) def main(): client = Client() + client.gui.main_loop(client.stdscr) + sys.exit() if __name__ == '__main__': main() diff --git a/src/connection.py b/src/connection.py index 385dfe00..ec1f2552 100644 --- a/src/connection.py +++ b/src/connection.py @@ -28,7 +28,8 @@ from handler import Handler class Connection(Thread): """ - Handles all network transactions + Receives everything from Jabber and emits the + appropriate signals """ def __init__(self, server, resource): Thread.__init__(self) @@ -47,6 +48,7 @@ class Connection(Thread): def run(self): """ + run in a thread connect to server """ self.client = xmpp.Client(self.server, debug=[]) @@ -59,7 +61,6 @@ class Connection(Thread): self.client.sendInitPresence() self.online = 1 # 2 when confirmation of auth is received self.register_handlers() - self.muc = MultiUserChat(self.client) while 1: self.process() @@ -75,38 +76,25 @@ class Connection(Thread): return None def register_handlers(self): + """ + register handlers from xmpppy signals + """ self.client.RegisterHandler('message', self.handler_message) self.client.RegisterHandler('presence', self.handler_presence) - self.client.RegisterHandler('iq', self.handler_iq) - - def handler_message(self, connection, message): - # body = message.getTag('body').getData() - # fro = str(message.getAttr('from')) - # room, nick = fro.split('/') - # print 'Message from %s in %s :' % (nick, room), body - self.handler.emit('xmpp-message-handler', message=message) + self.client.RegisterHandler('iq', self.handler_iq) def handler_presence(self, connection, presence): - affil, role = u'', u'' - fro = presence.getFrom()#presence.getAttr('from') - room_from, nick_from = fro.getStripped().encode('utf-8'), fro.getResource().encode('utf-8') + fro = presence.getFrom() to = presence.getAttr('to') - room_to, nick_to = to.getStripped().encode('utf-8'), to.getResource().encode('utf-8') if fro == to: # own presence self.online = 2 self.jid = to - print 'Authentification confirmation received!' + self.handler.emit('on-connected') return - for x in presence.getTags('x'): - if x.getTag('item'): - affil = x.getTagAttr('item', 'affiliation').encode('utf-8') - role = x.getTagAttr('item', 'role').encode('utf-8') - break -# print '[%s] in room {%s}. (%s - %s)'% (nick_from, room_from, affil, role) - self.handler.emit('xmpp-presence-handler', presence=presence) + self.handler.emit('room-presence', stanza=presence) - def send_join_room(self, room, nick): - self.handler.emit('join-room', room=room, nick=nick) + def handler_message(self, connection, message): + self.handler.emit('room-message', stanza=message) def handler_iq(self, connection, iq): pass @@ -116,7 +104,7 @@ class Connection(Thread): self.client.Process(timeout) else: log.warning('disconnecting...') - + sys.exit() if __name__ == '__main__': resource = config.get('resource') @@ -22,222 +22,162 @@ import curses from curses import textpad import locale +from datetime import datetime + locale.setlocale(locale.LC_ALL, '') code = locale.getpreferredencoding() import sys from connection import * +from window import Window -class Win(object): - def __init__(self, height, width, y, x, parent_win): - self._resize(height, width, y, x, parent_win) - - def _resize(self, height, width, y, x, parent_win): - self.height, self.width, self.x, self.y = height, width, x, y - try: - self.win = parent_win.subwin(height, width, y, x) - except: - pass - -class UserList(Win): - def __init__(self, height, width, y, x, parent_win): - Win.__init__(self, height, width, y, x, parent_win) - self.win.attron(curses.color_pair(2)) - self.win.vline(0, 0, curses.ACS_VLINE, self.height) - self.win.attroff(curses.color_pair(2)) - self.list = [] - - def add_user(self, name): - """ - add an user to the list - """ - self.list.append(name) - - def refresh(self): - self.win.clear() - self.win.attron(curses.color_pair(2)) - self.win.vline(0, 0, curses.ACS_VLINE, self.height) - self.win.attroff(curses.color_pair(2)) - y = 0 - for name in self.list: - self.win.addstr(y, 1, name) - y += 1 - self.win.refresh() - - def resize(self, height, width, y, x, stdscr): - self._resize(height, width, y, x, stdscr) - self.refresh() - -class Info(Win): - def __init__(self, height, width, y, x, parent_win): - Win.__init__(self, height, width, y, x, parent_win) - self.txt = "" -# self.win.bkgd(ord('p'), curses.COLOR_BLUE) - - def set_info(self, text): - self.txt = text - self.refresh() - - def resize(self, height, width, y, x, stdscr): - self._resize(height, width, y, x, stdscr) - self.refresh() - - def refresh(self): - self.win.clear() - try: - self.win.addstr(0, 0, self.txt + " "*(self.width-len(self.txt)-1) - , curses.color_pair(1)) - except: - pass - -class TextWin(Win): - def __init__(self, height, width, y, x, parent_win): - Win.__init__(self, height, width, y, x, parent_win) - self.lines = [] - - def add_line(self, time, nick, text): - self.lines.append((time, nick, text)) - self.refresh() - - def refresh(self): - self.win.clear() - y = 0 - for line in self.lines[-self.height:]: - self.win.addstr(y, 0, line[0] + " : " + line[1] + ": " + line[2]) - y += 1 - self.win.refresh() - - def resize(self, height, width, y, x, stdscr): - self._resize(height, width, y, x, stdscr) - self.refresh() - -class Input(Win): +class User(object): """ + keep trace of an user in a Room """ - def __init__(self, height, width, y, x, stdscr): - Win.__init__(self, height, width, y, x, stdscr) - self.input = curses.textpad.Textbox(self.win) - self.input.stripspaces = False - self.input.insert_mode = True - self.txt = '' - - def resize(self, height, width, y, x, stdscr): - self._resize(height, width, y, x, stdscr) - self.input = curses.textpad.Textbox(self.win) - self.input.insert_mode = True - self.input.stripspaces = False - self.win.clear() - self.win.addstr(self.txt) - - def do_command(self, key): - self.input.do_command(key) -# self.win.refresh() -# self.text = self.input.gather() + def __init__(self, nick, affiliation, show, status, role): + self.update(affiliation, show, status, role) + self.change_nick(nick) - # def insert_char(self, key): - # if self.insert: - # self.text = self.text[:self.pos]+key+self.text[self.pos:] - # else: - # self.text = self.text[:self.pos]+key+self.text[self.pos+1:] - # self.pos += 1 - # pass + def update(self, affiliation, show, status, role): + self.affiliation = None + self.show = None + self.status = status + self.role = role - def get_text(self): - return self.input.gather() + def change_nick(self, nick): + self.nick = nick - def save_text(self): - self.txt = self.input.gather() -# self.win.clear() -# self.win.addstr(self.txt) - - def refresh(self): -# self.win.clear() -# self.win.addstr(self.text) -# self.win.move(0, len(self.text)-1) - self.win.refresh() - - def clear_text(self): - self.win.clear() - self.txt = '' - self.pos = 0 - self.refresh() - -class Tab(object): +class Room(object): """ - The whole "screen" that can be seen at once in the terminal. - It contains an userlist, an input zone and a chat zone, all - related to one single chat room. """ - def __init__(self, stdscr, name='info'): - """ - name is the name of the Tab, and it's also - the JID of the chatroom. - A particular tab is the "Info" tab which has no - name (None). This info tab should be unique. - The stdscr should be passed to know the size of the - terminal - """ + def __init__(self, name, nick): self.name = name - self.size = (self.height, self.width) = stdscr.getmaxyx() + self.own_nick = nick + self.joined = False # false until self presence is received + self.users = [] + self.lines = [] # (time, nick, msg) or (time, info) + self.topic = '' - self.user_win = UserList(self.height-3, self.width/7, 1, 6*(self.width/7), stdscr) - self.topic_win = Info(1, self.width, 0, 0, stdscr) - self.info_win = Info(1, self.width, self.height-2, 0, stdscr) - self.text_win = TextWin(self.height-3, (self.width/7)*6, 1, 0, stdscr) - self.input = Input(1, self.width, self.height-1, 0, stdscr) + def add_message(self, nick, msg): + self.lines.append((datetime.now(), nick, msg.encode('utf-8'))) - self.info_win.set_info(name) - # debug - self.refresh() + def add_info(self, info): + """ info, like join/quit/status messages""" + self.lines.append((datetime.now(), info.encode('utf-8'))) - def resize(self, stdscr): + def on_presence(self, stanza, nick): """ - Resize the whole tabe. i.e. all its sub-windows """ - self.size = (self.height, self.width) = stdscr.getmaxyx() - self.user_win.resize(self.height-3, self.width/7, 1, 6*(self.width/7), stdscr) - self.topic_win.resize(1, self.width, 0, 0, stdscr) - self.info_win.resize(1, self.width, self.height-2, 0, stdscr) - self.text_win.resize(self.height-3, (self.width/7)*6, 1, 0, stdscr) - self.input.resize(1, self.width, self.height-1, 0, stdscr) - self.refresh() - - def refresh(self): - self.text_win.add_line("fion", "fion", "refresh") - self.text_win.refresh() - self.user_win.refresh() - self.topic_win.refresh() - self.info_win.refresh() - self.input.refresh() - - def do_command(self, key): - self.input.do_command(key) -# self.input.save_text() - self.input.refresh() + affiliation = stanza.getAffiliation() + show = stanza.getShow() + status = stanza.getStatus() + role = stanza.getRole() + if not self.joined: + self.users.append(User(nick, affiliation, show, status, role)) + if nick == self.own_nick: + self.joined = True + self.add_info("%s is in the room" % (nick)) + return + change_nick = stanza.getStatusCode() == '303' + for user in self.users: + if user.nick == nick: + if change_nick: + user.change_nick(stanza.getNick()) + self.add_info('%s is now known as %s' % (nick, stanza.getNick())) + return + if status == 'offline': + self.users.remove(user) + self.add_info('%s has left the room' % (nick)) + return + user.update(affiliation, show, status, role) + self.add_info('%s, status : %s, %s, %s, %s' % (nick, affiliation, role, show, status)) + return + self.users.append(User(nick, affiliation, show, status, role)) + self.add_info('%s joined the room %s' % (nick, self.name)) class Gui(object): """ Graphical user interface using ncurses """ - def __init__(self, stdscr): - self.handler = Handler() + def __init__(self, stdscr=None, muc=None): + + self.init_curses(stdscr) + self.stdscr = stdscr + self.stdscr.leaveok(True) + self.rooms = [Room('Info', '')] # current_room is self.rooms[0] + self.window = Window(stdscr) + self.window.refresh(self.rooms[0]) + + self.muc = muc self.commands = { 'join': self.command_join, 'quit': self.command_quit, + 'next': self.rotate_rooms_left, + 'prev': self.rotate_rooms_right, } - self.handler.connect('on-muc-message-received', self.on_message) - self.handler.connect('gui-join-room', self.on_join_room) - self.handler.connect('on-muc-presence-changed', self.on_presence) - self.init_curses(stdscr) - self.stdscr = stdscr + self.handler = Handler() + self.handler.connect('on-connected', self.on_connected) + self.handler.connect('join-room', self.join_room) + self.handler.connect('room-presence', self.room_presence) + self.handler.connect('room-message', self.room_message) + + def init_curses(self, stdscr): + curses.start_color() + curses.noecho() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) + curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK) + + def on_connected(self): + pass + + def join_room(self, room, nick): + self.rooms.insert(0, Room(room, nick)) + self.window.refresh(self.rooms[0]) + + def rotate_rooms_left(self, args): + self.rooms.append(self.rooms.pop(0)) + self.window.refresh(self.rooms[0]) + + def rotate_rooms_right(self, args): + self.rooms.insert(0, self.rooms.pop()) + self.window.refresh(self.rooms[0]) + + def room_message(self, stanza): + if stanza.getType() != 'groupchat': + return # ignore all messages not comming from a MUC + room_from = stanza.getFrom().getStripped() + nick_from = stanza.getFrom().getResource() + for room in self.rooms: + if room_from == room.name: + room.add_message(nick_from, stanza.getBody()) + if room == self.rooms[0]: + # self.window.text_win.refresh(room.lines) + # self.window.user_win.refresh(room.users) + # self.window.input.refresh() + self.window.refresh(self.rooms[0]) + break + + def room_presence(self, stanza): + from_nick = stanza.getFrom().getResource() + from_room = stanza.getFrom().getStripped() + for room in self.rooms: + if from_room == room.name: + room.on_presence(stanza, from_nick) + if room == self.rooms[0]: + self.window.text_win.refresh(room.lines) + self.window.user_win.refresh(room.users) + break def execute(self): - line = self.current_tab.input.get_text() - self.current_tab.input.clear_text() + line = self.window.input.get_text() + self.window.input.clear_text() + if line == "": + return if line.strip().startswith('/'): command = line.strip()[:].split()[0][1:] args = line.strip()[:].split()[1:] @@ -245,58 +185,25 @@ class Gui(object): func = self.commands[command] func(args) return - self.current_tab.text_win.add_line("NOW", "louiz'", line) - # TODO, send message to jabber + if self.rooms[0].name != 'Info': + self.muc.send_message(self.rooms[0].name, line) def command_join(self, args): room = args[0] - self.on_join_room(room, "poezio") + self.muc.join_room(room, "poezio") + self.join_room(room, 'poezio') def command_quit(self, args): sys.exit() - def init_curses(self, stdscr): - stdscr.leaveok(True) - curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) - curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK) - self.current_tab = Tab(stdscr) - self.tabs = [self.current_tab] - def main_loop(self, stdscr): while 1: stdscr.refresh() + # self.window.input.refresh() key = stdscr.getch() if key == curses.KEY_RESIZE: - self.current_tab.resize(stdscr) + self.window.resize(stdscr) elif key == 10: self.execute() else: - self.current_tab.do_command(key) - - def on_message(self, jid, msg, subject, typ, stanza): - print "on_message", jid, msg, subject, typ - - def on_join_room(self, room, nick): - sys.stderr.write(room) - self.current_tab = Tab(self.stdscr, room) - self.tabs.append(self.current_tab) -# self.current_tab.resize() - self.current_tab.refresh() - print "on_join_room", room, nick - - def on_presence(self, jid, priority, show, status, stanza): - print "on presence", jid, priority, show, status - -def main(stdscr): - gui = Gui(stdscr) - gui.main_loop(stdscr) - -if __name__ == '__main__': - resource = config.get('resource') - server = config.get('server') - connection = Connection(server, resource) - connection.start() - curses.wrapper(main) - # rooms = config.get('rooms').split(':') - # for room in rooms: - # connection.send_join_room(room.split('/')[0], room.split('/')[1]) + self.window.do_command(key) diff --git a/src/handler.py b/src/handler.py index b08280a0..5d7831e7 100644 --- a/src/handler.py +++ b/src/handler.py @@ -18,7 +18,9 @@ from singleton import Singleton class Handler(Singleton): - """This class is the global handler for the software's signals.""" + """ + This class is the global handler for the software's signals. + """ __is_first_instance = True def __init__(self): @@ -26,96 +28,104 @@ class Handler(Singleton): Handler.__is_first_instance = False self.__signals__ = { - # - XMPP's core handlers (don't use outside of src/jabber/*.py) - 'xmpp-presence-handler': list(), - # A presence is received - # Args: the stanza object + 'on-connected': list(), + # At the end of a successful connection process. + # emitted when presence confirmation is received - 'xmpp-iq-handler': list(), - # An iq is received + 'join-room': list(), + # Join a room. + # Args: room, nick + + 'room-presence': list(), + # A presence is received # Args: the stanza object - 'xmpp-message-handler': list(), + 'room-message': list(), # A message is received # Args: the stanza object - # - GUI event + # 'xmpp-presence-handler': list(), + # # A presence is received + # # Args: the stanza object - 'on-quit': list(), - # When the user wants to quit. + # 'xmpp-iq-handler': list(), + # # An iq is received + # # Args: the stanza object - # - Roster and presence + # 'xmpp-message-handler': list(), + # # A message is received + # # Args: the stanza object - 'on-connected': list(), - # At the end of a successful connection process. + # # - GUI event - 'on-disconnected': list(), - # When the user is disconnected from the server. + # 'on-quit': list(), + # # When the user wants to quit. - 'on-message-received': list(), - # When a message is received. - # Args: jid, msg, subject, typ + # # - Roster and presence - 'send-message': list(), - # Send a message to someone. - # Args: jid, msg, subj, typ + # 'on-disconnected': list(), + # # When the user is disconnected from the server. - # - vCard (XEP-0054) + # 'on-message-received': list(), + # # When a message is received. + # # Args: jid, msg, subject, typ - 'vcard-request': list(), - # Request a vcard. - # Args: jid + # 'send-message': list(), + # # Send a message to someone. + # # Args: jid, msg, subj, typ - 'on-vcard-received': list(), - # When a vcard is received. - # Args: jid, vcard + # # - vCard (XEP-0054) - # - Multi-User Chat (XEP-0045) + # 'vcard-request': list(), + # # Request a vcard. + # # Args: jid - 'gui-join-room': list(), - # Join a room inside the GUI (call `join-room`). - # Args: room, nickname + # 'on-vcard-received': list(), + # # When a vcard is received. + # # Args: jid, vcard - 'join-room': list(), - # Join a room. - # Args: room, nick + # # - Multi-User Chat (XEP-0045) - 'quit-room': list(), - # Quit a room. - # Args: room, nick + # 'gui-join-room': list(), + # # Join a room inside the GUI (call `join-room`). + # # Args: room, nickname - 'on-muc-message-received': list(), - # When a message is received. - # Args: jid, msg, subject, typ, stanza + # 'quit-room': list(), + # # Quit a room. + # # Args: room, nick - 'on-muc-presence-changed': list(), - # When someone in the roster changes his presence. - # Args: jid, priority, show, status, stanza + # 'on-muc-message-received': list(), + # # When a message is received. + # # Args: jid, msg, subject, typ, stanza - 'on-muc-error': list(), - # When the MUC composant sends an error - # Args: room, code, msg + # 'on-muc-presence-changed': list(), + # # When someone in the roster changes his presence. + # # Args: jid, priority, show, status, stanza - 'eject-user': list(), - # When the user try to eject another one. - # Args: room, action, nick, reason + # 'on-muc-error': list(), + # # When the MUC composant sends an error + # # Args: room, code, msg - 'change-user-role': list(), - # When the user try to change the role of someone. - # Args: room, nick, role + # 'eject-user': list(), + # # When the user try to eject another one. + # # Args: room, action, nick, reason - 'change-user-affiliation': list(), - # When the user try to change the affiliation of someone. - # Args: room, jid, aff + # 'change-user-role': list(), + # # When the user try to change the role of someone. + # # Args: room, nick, role - 'change-subject': list(), - # When the user try to change the topic. - # Args: room, subject + # 'change-user-affiliation': list(), + # # When the user try to change the affiliation of someone. + # # Args: room, jid, aff - 'change-nick': list() - # When the user try to change his nick. - # Args: room, nick + # 'change-subject': list(), + # # When the user try to change the topic. + # # Args: room, subject + + # 'change-nick': list() + # # When the user try to change his nick. + # # Args: room, nick } def connect(self, signal, func): @@ -123,7 +133,7 @@ class Handler(Singleton): if func not in self.__signals__[signal]: self.__signals__[signal].append(func) else: - print "%s doesn't exist." % signal + print "signal %s doesn't exist." % signal def emit(self, signal, **kwargs): """Emit a signal.""" @@ -131,4 +141,4 @@ class Handler(Singleton): for func in self.__signals__[signal]: func(**kwargs) else: - print "%s doesn't exist." % signal + print "signal %s doesn't exist." % signal diff --git a/src/multiuserchat.py b/src/multiuserchat.py index 0c392064..415b7f42 100644 --- a/src/multiuserchat.py +++ b/src/multiuserchat.py @@ -21,6 +21,7 @@ from xmpp import NS_MUC_ADMIN from xmpp.protocol import Presence, Iq, Message, JID from handler import Handler +from config import config def get_stripped_jid(jid): """Return the stripped JID (bare representation)""" @@ -42,20 +43,22 @@ class MultiUserChat(object): self.handler = Handler() self.handler.connect('join-room', self.join_room) - self.handler.connect('quit-room', self.quit_room) - self.handler.connect('on-disconnected', self.on_disconnect) - self.handler.connect('xmpp-iq-handler', self.on_iq) - self.handler.connect('xmpp-presence-handler', self.on_presence) - self.handler.connect('xmpp-message-handler', self.on_message) - self.handler.connect('eject-user', self.eject_user) - self.handler.connect('change-user-role', self.change_role) - self.handler.connect('change-user-affiliation', self.change_aff) - self.handler.connect('change-subject', self.change_subject) - self.handler.connect('change-nick', self.change_nick) + self.handler.connect('on-connected', self.on_connected) + + def on_connected(self): + rooms = config.get('rooms').split(':') + for room in rooms: + [roomname, nick] = room.split('/') + self.handler.emit('join-room', room=roomname, nick=nick) + + def send_message(self, room, message): + mes = Message(to=room) + mes.setBody(message) + mes.setType('groupchat') + self.connection.send(mes) def join_room(self, room, nick): """Join a new room""" - print "banane" self.rooms.append(room) self.rn[room] = nick |