diff --git a/CHANGELOG b/CHANGELOG
index e033a32c..c0eee2b5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,15 +3,20 @@ For more detailed changelog, see the roadmap:
http://codingteam.net/project/poezio/roadmap
* Poezio 0.7 - dev
-"Complete the MUC support"
-- All poezio colors can be changed with a theme file
-- /say command
+"Roster"
+- Introduce the roster
+- One to one conversations
+- Roster search
+- Resizable mini-buffer displaying various informations
+- All colors can be changed with a theme file
+- /say and // commands
- Warn user about publicly logged rooms
- Possibility to limit the number of history messages received from MUC
- auto-rejoin when kicked from a MUC
- The number of lines available to scroll down is displayed
- Possibility to use a modified nickname automatically when a nick is reserved
-- A line indicates the new messages in the window
+- A line separates the alread-read messages
+ from the new messages in a conversation
- Information messages are more colored
- bugfixes
diff --git a/README b/README
index 834c14ec..89667b19 100644
--- a/README
+++ b/README
@@ -10,24 +10,27 @@
Homepage: http://poezio.eu
Forge Page: http://codingteam.net/projet/poezio
-Poezio is a console Jabber/XMPP client. Its goal is to use anonymous
+Poezio is a console Jabber/XMPP client. Its goal is to use anonymous
connections to simply let the user join MultiUserChats. This way, the user
doesn't have to create a Jabber account, exactly like people are using
-IRC. Poezio's commands are designed to be (if possible) like IRC
+IRC. Poezio's commands are designed to be (if possible) like IRC
clients (weechat, irssi, etc).
Since version 0.7, poezio can handle real Jabber accounts along with
-roster and one to one conversation, making it a full-featured console
+roster and one to one conversations, making it a full-featured console
Jabber client, but still MultiUserChats-centered.
+In the futur, poezio should implement at a 100% level all XEP related to
+MUCs, especially XEP 0045.
+All other IM-related XEP (wherever possible) should be implemented through
+plugins or directly in poezio's core.
=======================
Install
=======================
You need python 3.0 or higher, and the SleekXMPP python library.
-You can find my patched version at http://github.com/louiz/SleekXMPP until
-my changes (required to properly run poezio) are merged upstream.
+SleekXMPP can be found at http://github.com/fritzy/SleekXMPP.
you can launch poezio with
-sh launch.sh
+./launch.sh
or you can install it with (as root or with sudo)
make install
@@ -36,7 +39,7 @@ you can now simply launch `poezio'
You can edit the config file (~/.config/poezio/poezio.cfg by default)
or data/default_config.cfg (if you want to edit the config before the
-first launch)
+first launch). The default config file is fully commented.
Please, see the online documentation for more information on installing,
configuring or using poezio:
@@ -66,10 +69,10 @@ Report a bug: http://codingteam.net/project/poezio/bugs/add
Poezio is Free Software.
(learn more: http://www.gnu.org/philosophy/free-sw.html)
-Poezio is released under the Gnu GPLv3 license
-Please read the COPYING file for details
+Poezio is released under the Gnu GPLv3 license.
+Please read the COPYING file for details.
-The artwork logo is made by Gaëtan Ribémont and released under
+The artwork logo was made by Gaëtan Ribémont and released under
the Creative Commons BY license (http://creativecommons.org/licenses/by/2.0/)
=======================
@@ -85,9 +88,3 @@ the Creative Commons BY license (http://creativecommons.org/licenses/by/2.0/)
FlashCode (weechat dev) - Useful advices on how to use ncurses efficiently
= Project =
Gajim - send_vcard method and common.py
-
-=======================
- Donate
-=======================
-If you're willing to thank me, or ask for an on-demand feature, please
-see the page http://louiz.org/donate.html and contact me.
diff --git a/data/default_config.cfg b/data/default_config.cfg
index 5d1ddd5e..aaa607b2 100644
--- a/data/default_config.cfg
+++ b/data/default_config.cfg
@@ -17,7 +17,7 @@ port = 5222
resource =
# the nick you will use when joining a room with no associated nick
-# If this is empty, the $USER variable will be used
+# If this is empty, the $USER environn.
+"""
+Define all the buffers.
+A buffer is a little part of the screen, for example the input buffer,
+the text bufferr, the roster buffer, etc.
+A Tab (see tab.py) is composed of multiple Buffers
+A buffer can also be called Window, even if it's not prefered.
+"""
+
from gettext import (bindtextdomain, textdomain, bind_textdomain_codeset,
gettext as _)
from os.path import isfile
diff --git a/src/contact.py b/src/contact.py
index 1eb41f72..8341d4b8 100644
--- a/src/contact.py
+++ b/src/contact.py
@@ -15,12 +15,15 @@
# along with Poezio. If not, see .
"""
-Defines the Resource and Contact classes
+Defines the Resource and Contact classes, which are used in
+the roster
"""
-from sleekxmpp.xmlstream.stanzabase import JID
+
import logging
log = logging.getLogger(__name__)
+from sleekxmpp.xmlstream.stanzabase import JID
+
class Resource(object):
"""
Defines a roster item.
@@ -80,8 +83,7 @@ class Contact(object):
def get_highest_priority_resource(self):
"""
- There must be, at any time, at least ONE resource.
- And they always should be ordered by priority.
+ Return the resource with the highest priority
"""
ret = None
for resource in self._resources:
@@ -94,8 +96,10 @@ class Contact(object):
Called, for example, when a new resource get offline
(the first, or any subsequent one)
"""
- # TODO sort by priority
+ def f(o):
+ return o.get_priority()
self._resources.append(resource)
+ self._resources = sorted(self._resources, key=f, reverse=True)
def remove_resource(self, resource):
"""
diff --git a/src/core.py b/src/core.py
index 40d727b4..ce271613 100644
--- a/src/core.py
+++ b/src/core.py
@@ -38,9 +38,9 @@ log = logging.getLogger(__name__)
import multiuserchat as muc
from connection import connection
-from handler import Handler
from config import config
from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab
+from logger import logger
from user import User
from room import Room
from roster import Roster, RosterGroup, roster
@@ -822,6 +822,8 @@ class Core(object):
body = message['body']
if body:
date = date if delayed == True else None
+ if not delayed:
+ logger.groupchat(room_from, nick_from, body)
self.add_message_to_text_buffer(room, body, date, nick_from)
self.refresh_window()
self.doupdate()
diff --git a/src/handler.py b/src/handler.py
deleted file mode 100644
index 350ea0ed..00000000
--- a/src/handler.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright 2009, 2010 Erwan Briand
-# Copyright 2010, Florent Le Coz
-
-# 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 version 3 of the License.
-
-# 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 .
-
-from singleton import Singleton
-
-#Todo, it's not a singleton. Oh, also, remove-me
-
-class Handler(Singleton):
- """
- This class is the global handler for the software's signals.
- """
- __is_first_instance = True
-
- def __init__(self):
- if Handler.__is_first_instance:
- Handler.__is_first_instance = False
-
- self.__signals__ = {
-
- 'on-connected': list(),
- # At the end of a successful connection process.
- # emitted when presence confirmation is received
- # Args: jid
-
- 'join-room': list(),
- # Join a room.
- # Args: room, nick
-
- 'room-presence': list(),
- # A presence is received
- # Args: the stanza object
-
- 'room-message': list(),
- # A message is received
- # Args: the stanza object
-
- 'private-message': list(),
- # A message is received
- # Args: the stanza object
-
- 'room-delayed-message': list(),
- # A message is received
- # Args: the stanza object
-
- 'send-version': list(),
- # We send our version
- # Args: the stanza we reply to
-
- 'send-time': list(),
- # We send our time
- # Args: the stanza we reply to
-
- 'error-message': list(),
- # We send our time
- # Args: the stanza we reply to
-
- 'error': list()
- # We send our time
- # Args: the stanza we reply to
- }
-
- def connect(self, signal, func):
- """Connect a function to a signal."""
- if func not in self.__signals__[signal]:
- self.__signals__[signal].append(func)
-
- def emit(self, signal, **kwargs):
- """Emit a signal."""
- if signal in self.__signals__:
- for func in self.__signals__[signal]:
- func(**kwargs)
diff --git a/src/logger.py b/src/logger.py
index 4cfb7793..a1479f9c 100644
--- a/src/logger.py
+++ b/src/logger.py
@@ -29,32 +29,10 @@ class Logger(object):
"""
def __init__(self):# , logfile, loglevel):
self.logfile = config.get('logfile', 'logs')
- self.loglevel = config.get('loglevel', 3)
- # self.logfile = logfile
- # self.loglevel = loglevel
- def info(self, msg):
- if self.logfile and self.loglevel >= 3:
- fd = open(self.logfile, 'a')
- fd.write(datetime.now().strftime("%H:%M:%S") + ' Info [' + msg + ']\n')
- fd.close()
-
- def warning(self, msg):
- if self.logfile and self.loglevel >= 2:
- fd = open(self.logfile, 'a')
- fd.write(datetime.now().strftime("%H:%M:%S") + ' Warning [' + msg + ']\n')
- fd.close()
-
- def error(self, msg):
- if self.logfile and self.loglevel >= 1:
- fd = open(self.logfile, 'a')
- fd.write(datetime.now().strftime("%H:%M:%S") + ' Error [' + msg + ']\n')
- fd.close()
- sys.exit(-1)
-
- def message(self, room, nick, msg):
+ def groupchat(self, room, nick, msg):
"""
- log the message in the appropriate room
+ log the message in the appropriate room's file
"""
if config.get('use_log', 'false') == 'false':
return
diff --git a/src/message.py b/src/message.py
index 07526819..6cf7a3d3 100644
--- a/src/message.py
+++ b/src/message.py
@@ -14,6 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see .
+"""
+Define the Message class
+"""
+
from datetime import datetime
class Message(object):
diff --git a/src/multiuserchat.py b/src/multiuserchat.py
index 3927fc38..0fc5cb3c 100644
--- a/src/multiuserchat.py
+++ b/src/multiuserchat.py
@@ -46,7 +46,7 @@ def change_show(xmpp, jid, own_nick, show, status):
Change our 'Show'
"""
pres = xmpp.makePresence(pto='%s/%s' % (jid, own_nick))
- if show: # if show is None, don't put a tag. It means "online"
+ if show: # if show is None, don't put a tag. It means "available"
pres['type'] = show
if status:
pres['status'] = status
diff --git a/src/singleton.py b/src/singleton.py
deleted file mode 100644
index 688d20f0..00000000
--- a/src/singleton.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2009, 2010 Erwan Briand
-# Copyright 2010, Florent Le Coz
-
-# 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 version 3 of the License.
-#
-# 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 .
-
-class Singleton(object):
- """ Canonic Singleton implementation.
- """
- _instance = None
- def __new__(cls, *args, **kwargs):
- if cls._instance is None:
- cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
- return cls._instance
diff --git a/src/tab.py b/src/tab.py
index 5086735b..bef42be8 100644
--- a/src/tab.py
+++ b/src/tab.py
@@ -15,11 +15,11 @@
# along with Poezio. If not, see .
"""
-a Tab object is a way to organize various Window (see window.py)
+a Tab object is a way to organize various Buffers (see buffers.py)
around the screen at once.
-A tab is then composed of multiple Window.
-Each Tab object has different refresh() and resize() methods, defining of its
-Window are displayed, etc
+A tab is then composed of multiple Buffer.
+Each Tab object has different refresh() and resize() methods, defining how its
+Buffer are displayed, resized, etc
"""
MIN_WIDTH = 50
@@ -28,7 +28,7 @@ MIN_HEIGHT = 16
import logging
log = logging.getLogger(__name__)
-import window
+import buffers
import theme
import curses
import difflib
@@ -145,9 +145,9 @@ class InfoTab(Tab):
"""
def __init__(self, stdscr, core, name):
Tab.__init__(self, stdscr, core)
- self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
- self.text_win = window.TextWin(self.height-2, self.width, 0, 0, stdscr, self.visible)
- self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible)
+ self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
+ self.text_win = buffers.TextWin(self.height-2, self.width, 0, 0, stdscr, self.visible)
+ self.input = buffers.Input(1, self.width, self.height-1, 0, stdscr, self.visible)
self.name = name
self.color_state = theme.COLOR_TAB_NORMAL
@@ -209,14 +209,14 @@ class MucTab(Tab):
"""
Tab.__init__(self, stdscr, core)
self._room = room
- self.topic_win = window.Topic(1, self.width, 0, 0, stdscr, self.visible)
- self.text_win = window.TextWin(self.height-4-self.core.information_win_size, (self.width//10)*9, 1, 0, stdscr, self.visible)
- self.v_separator = window.VerticalSeparator(self.height-3, 1, 1, 9*(self.width//10), stdscr, self.visible)
- self.user_win = window.UserList(self.height-3, (self.width//10), 1, 9*(self.width//10)+1, stdscr, self.visible)
- self.info_header = window.MucInfoWin(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
- self.info_win = window.TextWin(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
- self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
- self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
+ self.topic_win = buffers.Topic(1, self.width, 0, 0, stdscr, self.visible)
+ self.text_win = buffers.TextWin(self.height-4-self.core.information_win_size, (self.width//10)*9, 1, 0, stdscr, self.visible)
+ self.v_separator = buffers.VerticalSeparator(self.height-3, 1, 1, 9*(self.width//10), stdscr, self.visible)
+ self.user_win = buffers.UserList(self.height-3, (self.width//10), 1, 9*(self.width//10)+1, stdscr, self.visible)
+ self.info_header = buffers.MucInfoWin(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
+ self.info_win = buffers.TextWin(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
+ self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
+ self.input = buffers.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
"""
@@ -333,11 +333,11 @@ class PrivateTab(Tab):
def __init__(self, stdscr, core, room):
Tab.__init__(self, stdscr, core)
self._room = room
- self.text_win = window.TextWin(self.height-3-self.core.information_win_size, self.width, 0, 0, stdscr, self.visible)
- self.info_header = window.PrivateInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
- self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
- self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
- self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
+ self.text_win = buffers.TextWin(self.height-3-self.core.information_win_size, self.width, 0, 0, stdscr, self.visible)
+ self.info_header = buffers.PrivateInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
+ self.info_win = buffers.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
+ self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
+ self.input = buffers.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
Tab.resize(self, stdscr)
@@ -420,12 +420,12 @@ class RosterInfoTab(Tab):
self.name = "Roster"
roster_width = self.width//2
info_width = self.width-roster_width-1
- self.v_separator = window.VerticalSeparator(self.height-2, 1, 0, roster_width, stdscr, self.visible)
- self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
- self.info_win = window.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible)
- self.roster_win = window.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible)
- self.contact_info_win = window.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible)
- self.default_help_message = window.HelpText(1, self.width, self.height-1, 0, stdscr, self.visible, "Enter commands with “/”. “o”: toggle offline show")
+ self.v_separator = buffers.VerticalSeparator(self.height-2, 1, 0, roster_width, stdscr, self.visible)
+ self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
+ self.info_win = buffers.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible)
+ self.roster_win = buffers.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible)
+ self.contact_info_win = buffers.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible)
+ self.default_help_message = buffers.HelpText(1, self.width, self.height-1, 0, stdscr, self.visible, "Enter commands with “/”. “o”: toggle offline show")
self.input = self.default_help_message
self.set_color_state(theme.COLOR_TAB_NORMAL)
@@ -491,7 +491,7 @@ class RosterInfoTab(Tab):
'/' is pressed, we enter "input mode"
"""
curses.curs_set(1)
- self.input = window.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "", self.reset_help_message, self.execute_slash_command)
+ self.input = buffers.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "", self.reset_help_message, self.execute_slash_command)
self.input.do_command("/") # we add the slash
def reset_help_message(self, _=None):
@@ -551,7 +551,7 @@ class RosterInfoTab(Tab):
in it.
"""
curses.curs_set(1)
- self.input = window.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
+ self.input = buffers.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
return True
def set_roster_filter(self, txt):
@@ -578,12 +578,12 @@ class ConversationTab(Tab):
self._text_buffer = text_buffer
self.color_state = theme.COLOR_TAB_NORMAL
self._name = jid # a conversation tab is linked to one specific full jid OR bare jid
- self.text_win = window.TextWin(self.height-4-self.core.information_win_size, self.width, 1, 0, stdscr, self.visible)
- self.upper_bar = window.ConversationStatusMessageWin(1, self.width, 0, 0, stdscr, self.visible)
- self.info_header = window.ConversationInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
- self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
- self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
- self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
+ self.text_win = buffers.TextWin(self.height-4-self.core.information_win_size, self.width, 1, 0, stdscr, self.visible)
+ self.upper_bar = buffers.ConversationStatusMessageWin(1, self.width, 0, 0, stdscr, self.visible)
+ self.info_header = buffers.ConversationInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
+ self.info_win = buffers.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
+ self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
+ self.input = buffers.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
Tab.resize(self, stdscr)
diff --git a/src/text_buffer.py b/src/text_buffer.py
index 203a966e..43bf008f 100644
--- a/src/text_buffer.py
+++ b/src/text_buffer.py
@@ -14,6 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see .
+"""
+Define the TextBuffer class
+"""
+
from message import Message
from datetime import datetime
import theme
diff --git a/src/user.py b/src/user.py
index eb8587f8..ba39a8fb 100644
--- a/src/user.py
+++ b/src/user.py
@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see .
+"""
+Define the user class.
+An user is a MUC participant, not a roster contact (see contact.py)
+"""
+
from random import randrange, choice
from config import config
from datetime import timedelta, datetime
@@ -26,6 +31,7 @@ ROLE_DICT = {
'participant':2,
'moderator':3
}
+
class User(object):
"""
keep trace of an user in a Room