summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/rainbow.py19
-rw-r--r--src/core.py20
-rw-r--r--src/tabs.py66
-rw-r--r--src/windows.py26
-rw-r--r--src/xhtml.py92
5 files changed, 138 insertions, 85 deletions
diff --git a/plugins/rainbow.py b/plugins/rainbow.py
new file mode 100644
index 00000000..838c91f4
--- /dev/null
+++ b/plugins/rainbow.py
@@ -0,0 +1,19 @@
+from plugin import BasePlugin
+import random
+
+possible_colors = list(range(256))
+# remove the colors that are almost white or almost black
+for col in [16, 232, 233, 234, 235, 236, 237, 15, 231, 255, 254, 253, 252, 251]:
+ possible_colors.remove(col)
+
+def rand_color():
+ return '\x19%s}' % (random.choice(possible_colors),)
+
+class Plugin(BasePlugin):
+ def init(self):
+ self.add_poezio_event_handler('muc_say', self.rainbowize)
+ self.add_poezio_event_handler('private_say', self.rainbowize)
+ self.add_poezio_event_handler('conversation_say', self.rainbowize)
+
+ def rainbowize(self, msg):
+ msg['body'] = ''.join(['%s%s' % (rand_color(),char,) for char in msg['body']])
diff --git a/src/core.py b/src/core.py
index 27f5ca03..f92e2730 100644
--- a/src/core.py
+++ b/src/core.py
@@ -51,6 +51,7 @@ from text_buffer import TextBuffer
from keyboard import read_char
from theming import get_theme
from fifo import Fifo
+from windows import g_lock
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
@@ -73,8 +74,6 @@ possible_show = {'available':None,
'xa':'xa'
}
-resize_lock = threading.Lock()
-
Status = collections.namedtuple('Status', 'show message')
class Core(object):
@@ -177,6 +176,8 @@ class Core(object):
self.timed_events = set()
+ self.connected_events = {}
+
self.autoload_plugins()
def autoload_plugins(self):
@@ -209,18 +210,21 @@ class Core(object):
'Just press Ctrl-n.' \
))
self.refresh_window()
+
def resize_global_information_win(self):
"""
Resize the global_information_win only once at each resize.
"""
- self.information_win.resize(self.information_win_size, tabs.Tab.width,
- tabs.Tab.height - 2 - self.information_win_size, 0)
+ with g_lock:
+ self.information_win.resize(self.information_win_size, tabs.Tab.width,
+ tabs.Tab.height - 2 - self.information_win_size, 0)
def resize_global_info_bar(self):
"""
Resize the GlobalInfoBar only once at each resize
"""
- self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0)
+ with g_lock:
+ self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0)
def on_exception(self, typ, value, trace):
"""
@@ -561,7 +565,7 @@ class Core(object):
if not body:
return
tab.add_message(body, time=None, nickname=nick_from,
- forced_user=self.get_tab_by_name(room_from, tabs.MucTab).get_user_by_name(nick_from))
+ forced_user=self.get_tab_by_name(room_from, tabs.MucTab).get_user_by_name(nick_from))
conversation = self.get_tab_by_name(jid.full, tabs.PrivateTab)
if conversation and conversation.remote_wants_chatstates is None:
if message['chat_state']:
@@ -709,7 +713,7 @@ class Core(object):
tabs.Tab.resize(self.stdscr)
self.resize_global_information_win()
self.resize_global_info_bar()
- with resize_lock:
+ with g_lock:
for tab in self.tabs:
if config.get('lazy_resize', 'true') == 'true':
tab.need_resize = True
@@ -1047,7 +1051,7 @@ class Core(object):
if tab.get_user_by_name(nick_from) and\
tab.get_user_by_name(nick_from) in tab.ignores:
return
- self.events.trigger('muc_mg', message)
+ self.events.trigger('muc_msg', message)
body = xhtml.get_body_from_message_stanza(message)
if body:
date = date if delayed == True else None
diff --git a/src/tabs.py b/src/tabs.py
index 79be7d58..02d3c90d 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -305,7 +305,7 @@ class ChatTab(Tab):
# build the list of the recent words
char_we_dont_want = string.punctuation+' '
words = list()
- for msg in self.messages[:-40:-1]:
+ for msg in self._text_buffer.messages[:-40:-1]:
if not msg:
continue
txt = xhtml.clean_text(msg.txt)
@@ -323,7 +323,7 @@ class ChatTab(Tab):
if not self.execute_command(clean_text):
if txt.startswith('//'):
txt = txt[1:]
- self.command_say(txt)
+ self.command_say(xhtml.convert_simple_to_full_colors(txt))
self.cancel_paused_delay()
def send_chat_state(self, state, always_send=False):
@@ -497,7 +497,7 @@ class MucTab(ChatTab):
"""
/clear
"""
- self.messages = []
+ self._text_buffer.messages = []
self.text_win.rebuild_everything(self._text_buffer)
self.refresh()
self.core.doupdate()
@@ -601,7 +601,7 @@ class MucTab(ChatTab):
r = self.core.open_private_window(self.name, user.nick)
if r and len(args) > 1:
msg = arg[len(nick)+1:]
- self.core.current_tab().command_say(msg)
+ self.core.current_tab().command_say(xhtml.convert_simple_to_full_colors(msg))
if not r:
self.core.information(_("Cannot find user: %s" % nick), 'Error')
@@ -715,14 +715,16 @@ class MucTab(ChatTab):
needed = 'inactive' if self.core.status.show in ('xa', 'away') else 'active'
msg = self.core.xmpp.make_message(self.get_name())
msg['type'] = 'groupchat'
- if line.find('\x19') == -1:
- msg['body'] = line
- else:
- msg['body'] = xhtml.clean_text_simple(line)
- msg['xhtml_im'] = xhtml.poezio_colors_to_html(line)
+ msg['body'] = line
+ # trigger the event BEFORE looking for colors.
+ # This lets a plugin insert \x19xxx} colors, that will
+ # be converted in xhtml.
+ self.core.events.trigger('muc_say', msg)
+ if msg['body'].find('\x19') != -1:
+ msg['xhtml_im'] = xhtml.poezio_colors_to_html(msg['body'])
+ msg['body'] = xhtml.clean_text(msg['body'])
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates is not False:
msg['chat_state'] = needed
- self.core.events.trigger('muc_say', msg)
self.cancel_paused_delay()
msg.send()
self.chat_state = needed
@@ -1208,17 +1210,20 @@ class PrivateTab(ChatTab):
return
msg = self.core.xmpp.make_message(self.get_name())
msg['type'] = 'chat'
- if line.find('\x19') == -1:
- msg['body'] = line
- else:
- msg['body'] = xhtml.clean_text_simple(line)
- msg['xhtml_im'] = xhtml.poezio_colors_to_html(line)
+ msg['body'] = line
+ # trigger the event BEFORE looking for colors.
+ # This lets a plugin insert \x19xxx} colors, that will
+ # be converted in xhtml.
+ self.core.events.trigger('private_say', msg)
+ if msg['body'].find('\x19') != -1:
+ msg['body'] = xhtml.clean_text(msg['body'])
+ msg['xhtml_im'] = xhtml.poezio_colors_to_html(msg['body'])
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates is not False:
needed = 'inactive' if self.core.status.show in ('xa', 'away') else 'active'
msg['chat_state'] = needed
self.core.events.trigger('private_say', msg)
msg.send()
- self.core.add_message_to_text_buffer(self._text_buffer, line, None, self.core.own_nick or self.own_nick)
+ self.core.add_message_to_text_buffer(self._text_buffer, xhtml.convert_simple_to_full_colors(line), None, self.core.own_nick or self.own_nick)
self.cancel_paused_delay()
self.text_win.refresh()
self.input.refresh()
@@ -1580,7 +1585,6 @@ class RosterInfoTab(Tab):
self.command_add(jid.lstrip('\n'))
self.core.information('Contacts imported from %s' % filepath, 'Info')
-
def command_export(self, arg):
"""
Export the contacts
@@ -1864,6 +1868,7 @@ class ConversationTab(ChatTab):
self.commands['unquery'] = (self.command_unquery, _("Usage: /unquery\nUnquery: close the tab"), None)
self.commands['close'] = (self.command_unquery, _("Usage: /close\Close: close the tab"), None)
self.commands['version'] = (self.command_version, _('Usage: /version\nVersion: get the software version of the current interlocutor (usually its XMPP client and Operating System)'), None)
+ self.commands['info'] = (self.command_info, _('Usage: /info\nInfo: get the status of the contact.'), None)
self.resize()
def completion(self):
@@ -1872,22 +1877,37 @@ class ConversationTab(ChatTab):
def command_say(self, line):
msg = self.core.xmpp.make_message(self.get_name())
msg['type'] = 'chat'
- if line.find('\x19') == -1:
- msg['body'] = line
- else:
- msg['body'] = xhtml.clean_text_simple(line)
- msg['xhtml_im'] = xhtml.poezio_colors_to_html(line)
+ msg['body'] = line
+ # trigger the event BEFORE looking for colors.
+ # This lets a plugin insert \x19xxx} colors, that will
+ # be converted in xhtml.
+ self.core.events.trigger('conversation_say', msg)
+ if msg['body'].find('\x19') != -1:
+ msg['body'] = xhtml.clean_text(msg['body'])
+ msg['xhtml_im'] = xhtml.poezio_colors_to_html(msg['body'])
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates is not False:
needed = 'inactive' if self.core.status.show in ('xa', 'away') else 'active'
msg['chat_state'] = needed
self.core.events.trigger('conversation_say', msg)
msg.send()
- self.core.add_message_to_text_buffer(self._text_buffer, line, None, self.core.own_nick)
+ self.core.add_message_to_text_buffer(self._text_buffer, xhtml.convert_simple_to_full_colors(line), None, self.core.own_nick)
logger.log_message(JID(self.get_name()).bare, self.core.own_nick, line)
self.cancel_paused_delay()
self.text_win.refresh()
self.input.refresh()
+ def command_info(self, arg):
+ contact = roster.get_contact_by_jid(self.get_name())
+ jid = JID(self.get_name())
+ if jid.resource:
+ resource = contact.get_resource_by_fulljid(jid.full)
+ else:
+ resource = contact.get_highest_priority_resource()
+ if resource:
+ self._text_buffer.add_message("\x195}Status: %s\x193}" %resource.get_status(), None, None, None, None, None)
+ self.refresh()
+ self.core.doupdate()
+
def command_unquery(self, arg):
self.core.close_tab()
diff --git a/src/windows.py b/src/windows.py
index 42c9bfa0..1ff8f858 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -24,7 +24,7 @@ import curses
import string
from config import config
-from threading import Lock
+from threading import RLock
from contact import Contact, Resource
from roster import RosterGroup, roster
@@ -46,7 +46,7 @@ allowed_color_digits = ('0', '1', '2', '3', '4', '5', '6', '7')
# first is a bool telling if this is the first line of the message.
Line = collections.namedtuple('Line', 'msg start_pos end_pos')
-g_lock = Lock()
+g_lock = RLock()
LINES_NB_LIMIT = 4096
@@ -79,7 +79,8 @@ class Win(object):
"""
Override if something has to be done on resize
"""
- self._resize(height, width, y, x)
+ with g_lock:
+ self._resize(height, width, y, x)
def _refresh(self):
self._win.noutrefresh()
@@ -137,7 +138,8 @@ class Win(object):
self._win.attron(curses.A_BOLD)
if attr_char in string.digits and attr_char != '':
color_str = text[next_attr_char+1:text.find('}', next_attr_char)]
- self._win.attron(to_curses_attr((int(color_str), -1)))
+ if color_str:
+ self._win.attron(to_curses_attr((int(color_str), -1)))
text = text[next_attr_char+len(color_str)+2:]
else:
text = text[next_attr_char+2:]
@@ -255,10 +257,11 @@ class UserList(Win):
self._refresh()
def resize(self, height, width, y, x):
- self._resize(height, width, y, x)
- self._win.attron(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
- self._win.vline(0, 0, curses.ACS_VLINE, self.height)
- self._win.attroff(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
+ with g_lock:
+ self._resize(height, width, y, x)
+ self._win.attron(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
+ self._win.vline(0, 0, curses.ACS_VLINE, self.height)
+ self._win.attroff(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
class Topic(Win):
def __init__(self):
@@ -674,9 +677,10 @@ class TextWin(Win):
self.addstr(' ')
def resize(self, height, width, y, x, room=None):
- self._resize(height, width, y, x)
- if room:
- self.rebuild_everything(room)
+ with g_lock:
+ self._resize(height, width, y, x)
+ if room:
+ self.rebuild_everything(room)
def rebuild_everything(self, room):
self.built_lines = []
diff --git a/src/xhtml.py b/src/xhtml.py
index 38239d18..973a4d18 100644
--- a/src/xhtml.py
+++ b/src/xhtml.py
@@ -14,6 +14,7 @@ poezio colors to xhtml code
import re
import subprocess
+import curses
from sleekxmpp.xmlstream import ET
from xml.etree.ElementTree import ElementTree
from sys import version_info
@@ -178,6 +179,8 @@ whitespace_re = re.compile(r'\s+')
xhtml_attr_re = re.compile(r'\x19\d{0,3}\}|\x19[buaio]')
+xhtml_simple_attr_re = re.compile(r'\x19\d')
+
def get_body_from_message_stanza(message):
"""
Returns a string with xhtml markups converted to
@@ -190,6 +193,29 @@ def get_body_from_message_stanza(message):
return xhtml_to_poezio_colors(xhtml_body)
return message['body']
+def ncurses_color_to_html(color):
+ """
+ Takes an int between 0 and 256 and returns
+ a string of the form #XXXXXX representing an
+ html color.
+ """
+ if color <= 15:
+ (r, g, b) = curses.color_content(color)
+ r = r / 1000 * 6 - 0.01
+ g = g / 1000 * 6 - 0.01
+ b = b / 1000 * 6 - 0.01
+ elif color <= 231:
+ color = color - 16
+ r = color % 6
+ color = color / 6
+ g = color % 6
+ color = color / 6
+ b = color % 6
+ else:
+ color -= 232
+ r = g = b = color / 24 * 6
+ return '#%02X%02X%02X' % (r*256/6, g*256/6, b*256/6)
+
def xhtml_to_poezio_colors(text):
def parse_css(css):
def get_color(value):
@@ -225,7 +251,6 @@ def xhtml_to_poezio_colors(text):
key, value = rule.split(':', 1)
key = key.strip()
value = value.strip()
- log.debug(value)
if key == 'background-color':
pass#shell += '\x191'
elif key == 'color':
@@ -248,7 +273,6 @@ def xhtml_to_poezio_colors(text):
def trim(string):
return re.sub(whitespace_re, ' ', string)
- log.debug(text)
xml = ET.fromstring(text)
message = ''
if version_info[1] == 2:
@@ -322,7 +346,6 @@ def xhtml_to_poezio_colors(text):
message += trim(elem.tail)
return message
-
def clean_text(s):
"""
Remove all xhtml-im attributes (\x19etc) from the string with the
@@ -342,6 +365,15 @@ def clean_text_simple(string):
pos = string.find('\x19')
return string
+def convert_simple_to_full_colors(text):
+ """
+ takes a \x19n formatted string and returns
+ a \x19n} formatted one.
+ """
+ def add_curly_bracket(match):
+ return match.group(0) + '}'
+ return re.sub(xhtml_simple_attr_re, add_curly_bracket, text)
+
number_to_color_names = {
1: 'red',
2: 'green',
@@ -368,7 +400,6 @@ def poezio_colors_to_html(string):
attr_char = string[next_attr_char+1].lower()
if next_attr_char != 0:
res += string[:next_attr_char]
- string = string[next_attr_char+2:]
if attr_char == 'o':
for elem in opened_elements[::-1]:
res += '</%s>' % (elem,)
@@ -377,48 +408,23 @@ def poezio_colors_to_html(string):
if 'strong' not in opened_elements:
opened_elements.append('strong')
res += '<strong>'
- elif attr_char in digits:
- number = int(attr_char)
- if number in number_to_color_names:
- if 'strong' in opened_elements:
- res += '</strong>'
- opened_elements.remove('strong')
- if 'span' in opened_elements:
- res += '</span>'
- else:
- opened_elements.append('span')
- res += "<span style='color: %s'>" % (number_to_color_names[number])
+ if attr_char in digits:
+ number_str = string[next_attr_char+1:string.find('}', next_attr_char)]
+ number = int(number_str)
+ if 'strong' in opened_elements:
+ res += '</strong>'
+ opened_elements.remove('strong')
+ if 'span' in opened_elements:
+ res += '</span>'
+ else:
+ opened_elements.append('span')
+ res += "<span style='color: %s'>" % (ncurses_color_to_html(number),)
+ string = string[next_attr_char+len(number_str)+2:]
+ else:
+ string = string[next_attr_char+2:]
next_attr_char = string.find('\x19')
res += string
for elem in opened_elements[::-1]:
res += '</%s>' % (elem,)
res += "</p></body>"
return res.replace('\n', '<br />')
-
-def poezio_colors_to_xhtml(string):
- """
- Generate a valid xhtml string from
- the poezio colors in the given string
- """
- res = "<body xmlns='http://www.w3.org/1999/xhtml'>"
- next_attr_char = string.find('\x19')
- open_elements = []
- while next_attr_char != -1:
- attr_char = string[next_attr_char+1].lower()
- if next_attr_char != 0:
- res += string[:next_attr_char]
- string = string[next_attr_char+2:]
- if attr_char == 'o':
- # close all opened elements
- for elem in open_elements:
- res += '</%s>'
- open_elements = []
- elif attr_char == 'b':
- if 'strong' not in open_elements:
- res += '<strong>'
- open_elements.append('strong')
- elif attr_char in digits:
- self._win.attron(common.curses_color_pair(int(attr_char)))
- next_attr_char = string.find('\x19')
-
-