summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/day_change.py4
-rw-r--r--plugins/gpg/__init__.py2
-rw-r--r--plugins/iq_show.py4
-rw-r--r--plugins/link.py39
-rw-r--r--plugins/pipe_cmd.py41
-rw-r--r--plugins/replace.py2
-rw-r--r--plugins/uptime.py4
-rw-r--r--requirements.txt4
-rw-r--r--src/bookmark.py124
-rw-r--r--src/common.py6
-rw-r--r--src/config.py2
-rw-r--r--src/connection.py38
-rw-r--r--src/contact.py2
-rw-r--r--src/core/commands.py78
-rw-r--r--src/core/completions.py17
-rw-r--r--src/core/core.py322
-rw-r--r--src/core/handlers.py195
-rw-r--r--src/fixes.py43
-rwxr-xr-xsrc/keyboard.py85
-rw-r--r--src/multiuserchat.py22
-rw-r--r--src/plugin.py10
-rw-r--r--src/plugin_manager.py2
-rw-r--r--src/poezio.py23
-rw-r--r--src/roster.py8
-rw-r--r--src/size_manager.py14
-rw-r--r--src/tabs/adhoc_commands_list.py4
-rw-r--r--src/tabs/basetabs.py35
-rw-r--r--src/tabs/conversationtab.py2
-rw-r--r--src/tabs/muclisttab.py2
-rw-r--r--src/tabs/muctab.py12
-rw-r--r--src/tabs/rostertab.py44
-rw-r--r--src/tabs/xmltab.py4
-rw-r--r--src/timed_events.py82
-rw-r--r--src/windows/__init__.py3
-rw-r--r--src/windows/base_wins.py5
-rw-r--r--src/windows/data_forms.py169
-rw-r--r--src/windows/info_bar.py148
-rw-r--r--src/windows/info_wins.py108
-rw-r--r--src/windows/input_placeholders.py33
-rw-r--r--src/windows/inputs.py51
-rw-r--r--src/windows/list.py77
-rw-r--r--src/windows/misc.py17
-rw-r--r--src/windows/muc.py107
-rw-r--r--src/windows/roster_win.py159
-rw-r--r--src/windows/text_win.py167
-rw-r--r--src/xhtml.py2
-rwxr-xr-xupdate.sh30
47 files changed, 1170 insertions, 1182 deletions
diff --git a/plugins/day_change.py b/plugins/day_change.py
index cedb4b35..6e0c3e1f 100644
--- a/plugins/day_change.py
+++ b/plugins/day_change.py
@@ -27,9 +27,7 @@ class Plugin(BasePlugin):
msg = _("Day changed to %s") % (datetime.date.today().isoformat())
for tab in self.core.tabs:
- if (isinstance(tab, tabs.MucTab) or
- isinstance(tab, tabs.PrivateTab) or
- isinstance(tab, tabs.ConversationTab)):
+ if isinstance(tab, tabs.ChatTab):
tab.add_message(msg)
self.core.refresh_window()
diff --git a/plugins/gpg/__init__.py b/plugins/gpg/__init__.py
index 67720a5e..bc8a96c1 100644
--- a/plugins/gpg/__init__.py
+++ b/plugins/gpg/__init__.py
@@ -106,7 +106,7 @@ to upload you public key on a public server.
"""
from gpg import gnupg
-from sleekxmpp.xmlstream.stanzabase import JID
+from slixmpp.xmlstream.stanzabase import JID
from xml.etree import cElementTree as ET
import xml.sax.saxutils
diff --git a/plugins/iq_show.py b/plugins/iq_show.py
index 8f82ca72..387878f4 100644
--- a/plugins/iq_show.py
+++ b/plugins/iq_show.py
@@ -3,8 +3,8 @@ Show the exchanged IQs (useful for debugging).
"""
from plugin import BasePlugin
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.matcher import StanzaPath
+from slixmpp.xmlstream.handler import Callback
class Plugin(BasePlugin):
def init(self):
diff --git a/plugins/link.py b/plugins/link.py
index b2c4470d..d39d01b9 100644
--- a/plugins/link.py
+++ b/plugins/link.py
@@ -43,11 +43,14 @@ Usage
.. glossary::
/link
- **Usage:** ``/link [range]``
+ **Usage:** ``/link [range] [command]``
- This plugin adds a :term:`/link` command that will open the links in ``firefox``. If
- you want to use another browser, you can use the :term:`/set` command to change the
- :term:`browser` option.
+ This plugin adds a :term:`/link` command that will open the links in
+ ``firefox``. If you want to use another browser, or any other
+ command, you can use the :term:`/set` command to change the
+ :term:`browser` option. You can also specify the command to execute
+ directly in the arguments. For example `/link "mpv %s"` will open
+ the first link found using mpv, instead of the configured browser.
:term:`/link` without argument will open the last link found
@@ -94,8 +97,10 @@ class Plugin(BasePlugin):
def init(self):
for _class in (tabs.MucTab, tabs.PrivateTab, tabs.ConversationTab):
self.api.add_tab_command(_class, 'link', self.command_link,
- usage='[num]',
- help='Opens the last link from the conversation into a browser.\nIf [num] is given, then it will open the num-th link displayed.',
+ usage='[num] [command]',
+ help='Opens the last link from the conversation into a browser.\n\
+ If [num] is given, then it will\open the num-th link displayed. \
+ Use a [command] argument to override the configured browser value.',
short='Open links into a browser')
def find_link(self, nb):
@@ -114,7 +119,13 @@ class Plugin(BasePlugin):
def command_link(self, args):
args = common.shell_split(args)
- if len(args) == 1:
+ start = 1
+ end = 1
+ # With two arguments, the first is the range, the second is the command
+ # With only one argument, it is a range if it starts with a number
+ # or :, otherwise it is a command
+ if len(args) == 2 or\
+ len(args) == 1 and (args[0][0].isnumeric() or args[0][0] == ":"):
if args[0].find(':') == -1:
try:
start = int(args[0])
@@ -130,15 +141,19 @@ class Plugin(BasePlugin):
end = int(end)
except ValueError:
return self.api.information('Invalid range: %s' % (args[0]), 'Error')
- else:
- start = 1
- end = 1
+ command = None
+ if len(args) == 2:
+ command = args[1]
+ if len(args) == 1 and (not args[0][0].isnumeric() and args[0][0] != ":"):
+ command = args[0]
for nb in range(start, end+1):
link = self.find_link(nb)
if not link:
return self.api.information('No URL found.', 'Warning')
default = app_mapping.get(platform.system(), 'firefox')
- self.core.exec_command([self.config.get('browser', default), link])
-
+ if command is None:
+ self.core.exec_command([self.config.get('browser', default), link])
+ else:
+ self.core.exec_command([command, link])
def cleanup(self):
del self.config
diff --git a/plugins/pipe_cmd.py b/plugins/pipe_cmd.py
index f554a71d..762501ae 100644
--- a/plugins/pipe_cmd.py
+++ b/plugins/pipe_cmd.py
@@ -6,10 +6,10 @@ This plugins allows commands to be sent to poezio via a named pipe.
from plugin import BasePlugin
-import threading
import os
import stat
import logging
+import asyncio
log = logging.getLogger(__name__)
@@ -25,19 +25,30 @@ class Plugin(BasePlugin):
os.mkfifo(self.pipename)
if not stat.S_ISFIFO(os.stat(self.pipename).st_mode):
- log.error("File %s is not a fifo file" % self.pipename)
- raise TypeError
-
- thread = threading.Thread(target=self.main_loop)
- thread.setDaemon(True)
- thread.start()
-
- def main_loop(self):
- while not self.stop:
- fd = open(self.pipename, 'r')
- line = fd.read().strip()
- self.api.run_command(line)
- fd.close()
+ raise TypeError("File %s is not a fifo file" % self.pipename)
+
+ self.fd = os.open(self.pipename, os.O_RDONLY|os.O_NONBLOCK)
+
+ self.data = b""
+ asyncio.get_event_loop().add_reader(self.fd, self.read_from_fifo)
+
+ def read_from_fifo(self):
+ data = os.read(self.fd, 512)
+ if not data:
+ # EOF, close the fifo. And reopen it
+ asyncio.get_event_loop().remove_reader(self.fd)
+ os.close(self.fd)
+ self.fd = os.open(self.pipename, os.O_RDONLY|os.O_NONBLOCK)
+ asyncio.get_event_loop().add_reader(self.fd, self.read_from_fifo)
+ self.data = b''
+ else:
+ self.data += data
+ l = self.data.split(b'\n', 1)
+ if len(l) == 2:
+ line, self.data = l
+ log.debug("run: %s" % (line.decode().strip()))
+ self.api.run_command(line.decode().strip())
def cleanup(self):
- self.stop = True
+ asyncio.get_event_loop().remove_reader(self.fd)
+ os.close(self.fd)
diff --git a/plugins/replace.py b/plugins/replace.py
index c9143ef8..21f2c9af 100644
--- a/plugins/replace.py
+++ b/plugins/replace.py
@@ -58,7 +58,7 @@ import tabs
import datetime
import random
import re
-from sleekxmpp.xmlstream.stanzabase import JID
+from slixmpp.xmlstream.stanzabase import JID
class Plugin(BasePlugin):
def init(self):
diff --git a/plugins/uptime.py b/plugins/uptime.py
index 2b171703..dbeb6a63 100644
--- a/plugins/uptime.py
+++ b/plugins/uptime.py
@@ -13,7 +13,7 @@ Command
"""
from plugin import BasePlugin
from common import parse_secs_to_str, safeJID
-from sleekxmpp.xmlstream import ET
+from slixmpp.xmlstream import ET
class Plugin(BasePlugin):
def init(self):
@@ -33,4 +33,4 @@ class Plugin(BasePlugin):
return
iq = self.core.xmpp.makeIqGet(ito=jid.server)
iq.append(ET.Element('{jabber:iq:last}query'))
- iq.send(block=False, callback=callback)
+ iq.send(callback=callback)
diff --git a/requirements.txt b/requirements.txt
index 79d2a470..19070299 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
git+git://github.com/afflux/pure-python-otr.git#egg=potr
-sleekxmpp==1.2
-dnspython3==1.11.1
+git+git://git.louiz.org/slixmpp#egg=slixmpp
+aiodns
sphinx==1.2.1
setuptools
argparse
diff --git a/src/bookmark.py b/src/bookmark.py
index 672fb4a5..15a28c9d 100644
--- a/src/bookmark.py
+++ b/src/bookmark.py
@@ -8,10 +8,12 @@ bookmark storage. It can also parse xml Elements.
This module also defines several functions for retrieving and updating
bookmarks, both local and remote.
"""
+
+import functools
import logging
from sys import version_info
-from sleekxmpp.plugins.xep_0048 import Bookmarks, Conference
+from slixmpp.plugins.xep_0048 import Bookmarks, Conference
from common import safeJID
from config import config
@@ -26,7 +28,7 @@ def xml_iter(xml, tag=''):
preferred = config.get('use_bookmarks_method').lower()
if preferred not in ('pep', 'privatexml'):
preferred = 'privatexml'
-not_preferred = 'privatexml' if preferred == 'pep' else 'privatexml'
+not_preferred = 'privatexml' if preferred == 'pep' else 'pep'
methods = ('local', preferred, not_preferred)
@@ -131,21 +133,18 @@ def save_privatexml(xmpp):
xmpp.plugin['xep_0048'].set_bookmarks(stanza_storage('privatexml'),
method='xep_0049')
-def save_remote(xmpp, method=preferred):
+def save_remote(xmpp, callback, method=preferred):
"""Save the remote bookmarks."""
method = 'privatexml' if method != 'pep' else 'pep'
- try:
- if method is 'privatexml':
- xmpp.plugin['xep_0048'].set_bookmarks(stanza_storage('privatexml'),
- method='xep_0049')
- else:
- xmpp.plugin['xep_0048'].set_bookmarks(stanza_storage('pep'),
- method='xep_0223')
- except Exception:
- log.error("Could not save the bookmarks:", exc_info=True)
- return False
- return True
+ if method is 'privatexml':
+ xmpp.plugin['xep_0048'].set_bookmarks(stanza_storage('privatexml'),
+ method='xep_0049',
+ callback=callback)
+ else:
+ xmpp.plugin['xep_0048'].set_bookmarks(stanza_storage('pep'),
+ method='xep_0223',
+ callback=callback)
def save_local():
"""Save the local bookmarks."""
@@ -155,62 +154,81 @@ def save_local():
def save(xmpp, core=None):
"""Save all the bookmarks."""
save_local()
- if config.get('use_remote_bookmarks'):
- preferred = config.get('use_bookmarks_method')
- if not save_remote(xmpp, method=preferred) and core:
+ def _cb(core, iq):
+ if iq["type"] == "error":
core.information('Could not save bookmarks.', 'Error')
- return False
elif core:
core.information('Bookmarks saved', 'Info')
- return True
+ if config.get('use_remote_bookmarks'):
+ preferred = config.get('use_bookmarks_method')
+ cb = functools.partial(_cb, core)
+ save_remote(xmpp, cb, method=preferred)
-def get_pep(xmpp):
+def get_pep(xmpp, available_methods, callback):
"""Add the remotely stored bookmarks via pep to the list."""
- try:
- iq = xmpp.plugin['xep_0048'].get_bookmarks(method='xep_0223', block=True)
- except:
- return False
- for conf in xml_iter(iq.xml, '{storage:bookmarks}conference'):
- b = Bookmark.parse_from_element(conf, method='pep')
- if not get_by_jid(b.jid):
- bookmarks.append(b)
- return True
-
-def get_privatexml(xmpp):
- """Add the remotely stored bookmarks via privatexml to the list."""
- try:
- iq = xmpp.plugin['xep_0048'].get_bookmarks(method='xep_0049', block=True)
- except:
- return False
- for conf in xml_iter(iq.xml, '{storage:bookmarks}conference'):
- b = Bookmark.parse_from_element(conf, method='privatexml')
- if not get_by_jid(b.jid):
- bookmarks.append(b)
- return True
+ def _cb(iq):
+ if iq["type"] == "error":
+ available_methods["pep"] = False
+ else:
+ available_methods["pep"] = True
+ for conf in xml_iter(iq.xml, '{storage:bookmarks}conference'):
+ b = Bookmark.parse_from_element(conf, method='pep')
+ if not get_by_jid(b.jid):
+ bookmarks.append(b)
+ if callback:
+ callback()
+
+ xmpp.plugin['xep_0048'].get_bookmarks(method='xep_0223', callback=_cb)
+
+def get_privatexml(xmpp, available_methods, callback):
+ """Add the remotely stored bookmarks via privatexml to the list.
+ If both is True, we want to have the result of both methods (privatexml and pep) before calling pep"""
+ def _cb(iq):
+ if iq["type"] == "error":
+ available_methods["privatexml"] = False
+ else:
+ available_methods["privatexml"] = True
+ for conf in xml_iter(iq.xml, '{storage:bookmarks}conference'):
+ b = Bookmark.parse_from_element(conf, method='privatexml')
+ if not get_by_jid(b.jid):
+ bookmarks.append(b)
+ if callback:
+ callback()
+
+ xmpp.plugin['xep_0048'].get_bookmarks(method='xep_0049', callback=_cb)
-def get_remote(xmpp):
+def get_remote(xmpp, callback):
"""Add the remotely stored bookmarks to the list."""
if xmpp.anon:
return
method = config.get('use_bookmarks_method')
if not method:
- pep, privatexml = True, True
+ available_methods = {}
+ def _save_and_call_callback():
+ # If both methods returned a result, we can now call the given callback
+ if callback and "privatexml" in available_methods and "pep" in available_methods:
+ save_bookmarks_method(available_methods)
+ if callback:
+ callback()
for method in methods[1:]:
if method == 'pep':
- pep = get_pep(xmpp)
+ get_pep(xmpp, available_methods, _save_and_call_callback)
else:
- privatexml = get_privatexml(xmpp)
- if pep and not privatexml:
- config.set_and_save('use_bookmarks_method', 'pep')
- elif privatexml and not pep:
- config.set_and_save('use_bookmarks_method', 'privatexml')
- elif not pep and not privatexml:
- config.set_and_save('use_bookmarks_method', '')
+ get_privatexml(xmpp, available_methods, _save_and_call_callback)
else:
if method == 'pep':
- get_pep(xmpp)
+ get_pep(xmpp, {}, callback)
else:
- get_privatexml(xmpp)
+ get_privatexml(xmpp, {}, callback)
+
+def save_bookmarks_method(available_methods):
+ pep, privatexml = available_methods["pep"], available_methods["privatexml"]
+ if pep and not privatexml:
+ config.set_and_save('use_bookmarks_method', 'pep')
+ elif privatexml and not pep:
+ config.set_and_save('use_bookmarks_method', 'privatexml')
+ elif not pep and not privatexml:
+ config.set_and_save('use_bookmarks_method', '')
def get_local():
"""Add the locally stored bookmarks to the list."""
diff --git a/src/common.py b/src/common.py
index 8cb02d4c..a62c83f1 100644
--- a/src/common.py
+++ b/src/common.py
@@ -11,7 +11,7 @@ Various useful functions.
from sys import version_info
from datetime import datetime, timedelta
-from sleekxmpp import JID, InvalidJID
+from slixmpp import JID, InvalidJID
import base64
import os
@@ -261,7 +261,7 @@ def find_delayed_tag(message):
"""
Check if a message is delayed or not.
- :param sleekxmpp.Message message: The message to check.
+ :param slixmpp.Message message: The message to check.
:return: A tuple containing (True, the datetime) or (False, None)
:rtype: :py:class:`tuple`
"""
@@ -471,7 +471,7 @@ def format_gaming_string(infos):
def safeJID(*args, **kwargs):
"""
- Construct a :py:class:`sleekxmpp.JID` object from a string.
+ Construct a :py:class:`slixmpp.JID` object from a string.
Used to avoid tracebacks during is stringprep fails
(fall back to a JID with an empty string).
diff --git a/src/config.py b/src/config.py
index 1c191eb1..1f0771ca 100644
--- a/src/config.py
+++ b/src/config.py
@@ -591,7 +591,7 @@ def setup_logging():
log = logging.getLogger(__name__)
def post_logging_setup():
- # common imports sleekxmpp, which creates then its loggers, so
+ # common imports slixmpp, which creates then its loggers, so
# it needs to be after logger configuration
from common import safeJID as JID
global safeJID
diff --git a/src/connection.py b/src/connection.py
index dc35dd94..1bbe632d 100644
--- a/src/connection.py
+++ b/src/connection.py
@@ -14,15 +14,15 @@ log = logging.getLogger(__name__)
import getpass
-import sleekxmpp
-from sleekxmpp.plugins.xep_0184 import XEP_0184
+import slixmpp
+from slixmpp.plugins.xep_0184 import XEP_0184
import common
import fixes
from common import safeJID
from config import config, options
-class Connection(sleekxmpp.ClientXMPP):
+class Connection(slixmpp.ClientXMPP):
"""
Receives everything from Jabber and emits the
appropriate signals
@@ -47,7 +47,7 @@ class Connection(sleekxmpp.ClientXMPP):
password = None
jid = safeJID(jid)
# TODO: use the system language
- sleekxmpp.ClientXMPP.__init__(self, jid, password,
+ slixmpp.ClientXMPP.__init__(self, jid, password,
lang=config.get('lang'))
force_encryption = config.get('force_encryption')
@@ -95,7 +95,6 @@ class Connection(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0191')
self.register_plugin('xep_0199')
- self.set_keepalive_values()
if config.get('enable_user_tune'):
self.register_plugin('xep_0118')
@@ -131,14 +130,18 @@ class Connection(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0280')
self.register_plugin('xep_0297')
self.register_plugin('xep_0308')
+ self.init_plugins()
def set_keepalive_values(self, option=None, value=None):
"""
- Called at startup, or triggered when one of
+ Called after the XMPP session has been started, or triggered when one of
"connection_timeout_delay" and "connection_check_interval" options
- is changed.
- Unload and reload the ping plugin, with the new values.
+ is changed. Unload and reload the ping plugin, with the new values.
"""
+ if not self.is_connected():
+ # Happens when we change the value with /set while we are not
+ # connected. Do nothing in that case
+ return
ping_interval = config.get('connection_check_interval')
timeout_delay = config.get('connection_timeout_delay')
if timeout_delay <= 0:
@@ -156,34 +159,27 @@ class Connection(sleekxmpp.ClientXMPP):
def start(self):
"""
Connect and process events.
-
- TODO: try multiple servers with anon auth.
"""
custom_host = config.get('custom_host')
custom_port = config.get('custom_port', 5222)
if custom_port == -1:
custom_port = 5222
if custom_host:
- res = self.connect((custom_host, custom_port), reattempt=True)
+ self.connect((custom_host, custom_port))
elif custom_port != 5222 and custom_port != -1:
- res = self.connect((self.boundjid.host, custom_port),
- reattempt=True)
+ self.connect((self.boundjid.host, custom_port))
else:
- res = self.connect(reattempt=True)
- if not res:
- return False
- self.process(threaded=True)
- return True
+ self.connect()
- def send_raw(self, data, now=False, reconnect=None):
+ def send_raw(self, data):
"""
Overrides XMLStream.send_raw, with an event added
"""
if self.core:
self.core.outgoing_stanza(data)
- sleekxmpp.ClientXMPP.send_raw(self, data, now, reconnect)
+ slixmpp.ClientXMPP.send_raw(self, data)
-class MatchAll(sleekxmpp.xmlstream.matcher.base.MatcherBase):
+class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase):
"""
Callback to retrieve all the stanzas for the XML tab
"""
diff --git a/src/contact.py b/src/contact.py
index 908b609e..c670e5bc 100644
--- a/src/contact.py
+++ b/src/contact.py
@@ -62,7 +62,7 @@ class Contact(object):
"""
def __init__(self, item):
"""
- item: a SleekXMPP RosterItem pointing to that contact
+ item: a slixmpp RosterItem pointing to that contact
"""
self.__item = item
self.folded_states = defaultdict(lambda: True)
diff --git a/src/core/commands.py b/src/core/commands.py
index d212de9b..4a8f7f19 100644
--- a/src/core/commands.py
+++ b/src/core/commands.py
@@ -6,15 +6,16 @@ import logging
log = logging.getLogger(__name__)
+import functools
import os
import sys
from datetime import datetime
from gettext import gettext as _
from xml.etree import cElementTree as ET
-from sleekxmpp.xmlstream.stanzabase import StanzaBase
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
+from slixmpp.xmlstream.stanzabase import StanzaBase
+from slixmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.matcher import StanzaPath
import bookmark
import common
@@ -276,7 +277,6 @@ def command_list(self, arg):
self.add_tab(list_tab, True)
cb = list_tab.on_muc_list_item_received
self.xmpp.plugin['xep_0030'].get_items(jid=server,
- block=False,
callback=cb)
def command_version(self, arg):
@@ -439,7 +439,7 @@ def command_bookmark_local(self, arg=''):
new_bookmarks.extend(bookmark.bookmarks)
bookmark.bookmarks = new_bookmarks
bookmark.save_local()
- bookmark.save_remote(self.xmpp)
+ bookmark.save_remote(self.xmpp, None)
self.information('Bookmarks added and saved.', 'Info')
return
else:
@@ -507,12 +507,13 @@ def command_bookmark(self, arg=''):
new_bookmarks.append(b)
new_bookmarks.extend(bookmark.bookmarks)
bookmark.bookmarks = new_bookmarks
-
- if bookmark.save_remote(self.xmpp):
- bookmark.save_local()
- self.information("Bookmarks added.", "Info")
- else:
- self.information("Could not add the bookmarks.", "Info")
+ def _cb(self, iq):
+ if iq["type"] != "error":
+ bookmark.save_local()
+ self.information("Bookmarks added.", "Info")
+ else:
+ self.information("Could not add the bookmarks.", "Info")
+ bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
return
else:
info = safeJID(args[0])
@@ -541,14 +542,16 @@ def command_bookmark(self, arg=''):
if password:
bm.password = password
bm.autojoin = autojoin
- if bookmark.save_remote(self.xmpp):
- self.information('Bookmark added.', 'Info')
+ def _cb(self, iq):
+ if iq["type"] != "error":
+ self.information('Bookmark added.', 'Info')
+ else:
+ self.information("Could not add the bookmarks.", "Info")
+ bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
remote = []
for each in bookmark.bookmarks:
if each.method in ('pep', 'privatexml'):
remote.append(each)
- self.information(_('Your remote bookmarks are now: %s') % remote,
- _('Info'))
def command_bookmarks(self, arg=''):
"""/bookmarks"""
@@ -718,7 +721,6 @@ def command_last_activity(self, arg):
if jid == '':
return self.command_help('last_activity')
self.xmpp.plugin['xep_0012'].get_last_activity(jid,
- block=False,
callback=callback)
def command_mood(self, arg):
@@ -727,7 +729,8 @@ def command_mood(self, arg):
"""
args = common.shell_split(arg)
if not args:
- return self.xmpp.plugin['xep_0107'].stop(block=False)
+ self.xmpp.plugin['xep_0107'].stop()
+ return
mood = args[0]
if mood not in pep.MOODS:
return self.information(_('%s is not a correct value for a mood.')
@@ -737,10 +740,8 @@ def command_mood(self, arg):
text = args[1]
else:
text = None
- self.xmpp.plugin['xep_0107'].publish_mood(mood,
- text,
- callback=dumb_callback,
- block=False)
+ self.xmpp.plugin['xep_0107'].publish_mood(mood, text,
+ callback=dumb_callback)
def command_activity(self, arg):
"""
@@ -749,7 +750,8 @@ def command_activity(self, arg):
args = common.shell_split(arg)
length = len(args)
if not length:
- return self.xmpp.plugin['xep_0108'].stop(block=False)
+ self.xmpp.plugin['xep_0108'].stop()
+ return
general = args[0]
if general not in pep.ACTIVITIES:
return self.information(_('%s is not a correct value for an activity')
@@ -769,11 +771,8 @@ def command_activity(self, arg):
return self.information(_('%s is not a correct value '
'for an activity') % specific,
_('Error'))
- self.xmpp.plugin['xep_0108'].publish_activity(general,
- specific,
- text,
- callback=dumb_callback,
- block=False)
+ self.xmpp.plugin['xep_0108'].publish_activity(general, specific, text,
+ callback=dumb_callback)
def command_gaming(self, arg):
"""
@@ -781,7 +780,8 @@ def command_gaming(self, arg):
"""
args = common.shell_split(arg)
if not args:
- return self.xmpp.plugin['xep_0196'].stop(block=False)
+ self.xmpp.plugin['xep_0196'].stop()
+ return
name = args[0]
if len(args) > 1:
address = args[1]
@@ -789,8 +789,7 @@ def command_gaming(self, arg):
address = None
return self.xmpp.plugin['xep_0196'].publish_gaming(name=name,
server_address=address,
- callback=dumb_callback,
- block=False)
+ callback=dumb_callback)
def command_invite(self, arg):
"""/invite <to> <room> [reason]"""
@@ -834,22 +833,23 @@ def command_quit(self, arg=''):
"""
/quit
"""
+ if not self.xmpp.is_connected():
+ self.exit()
+ return
if len(arg.strip()) != 0:
msg = arg
else:
msg = None
if config.get('enable_user_mood'):
- self.xmpp.plugin['xep_0107'].stop(block=False)
+ self.xmpp.plugin['xep_0107'].stop()
if config.get('enable_user_activity'):
- self.xmpp.plugin['xep_0108'].stop(block=False)
+ self.xmpp.plugin['xep_0108'].stop()
if config.get('enable_user_gaming'):
- self.xmpp.plugin['xep_0196'].stop(block=False)
+ self.xmpp.plugin['xep_0196'].stop()
self.save_config()
self.plugin_manager.disable_plugins()
self.disconnect(msg)
- self.running = False
- self.reset_curses()
- sys.exit()
+ self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
def command_destroy_room(self, arg=''):
"""
@@ -972,15 +972,13 @@ def command_adhoc(self, arg):
if len(arg) > 1:
return self.command_help('ad-hoc')
elif arg:
- jid = safeJID(arg[0]).server
+ jid = safeJID(arg[0])
else:
return self.information('Please provide a jid', 'Error')
list_tab = tabs.AdhocCommandsListTab(jid)
self.add_tab(list_tab, True)
cb = list_tab.on_list_received
- self.xmpp.plugin['xep_0050'].get_commands(jid=jid,
- local=False,
- block=False,
+ self.xmpp.plugin['xep_0050'].get_commands(jid=jid, local=False,
callback=cb)
def command_self(self, arg=None):
diff --git a/src/core/completions.py b/src/core/completions.py
index 9549c13f..7d95321b 100644
--- a/src/core/completions.py
+++ b/src/core/completions.py
@@ -106,22 +106,7 @@ def completion_join(self, the_input):
if the_input.last_completion:
return the_input.new_completion([], 1, quotify=True)
- if jid.server and not jid.user:
- # no room was given: complete the node
- try:
- response = self.xmpp.plugin['xep_0030'].get_items(jid=jid.server, block=True, timeout=1)
- except:
- log.error('/join completion: Unable to get the list of rooms for %s',
- jid.server,
- exc_info=True)
- response = None
- if response:
- items = response['disco_items'].get_items()
- else:
- return True
- items = sorted('%s/%s' % (tup[0], jid.resource) for tup in items)
- return the_input.new_completion(items, 1, quotify=True, override=True)
- elif jid.user:
+ if jid.user:
# we are writing the server: complete the server
serv_list = []
for tab in self.get_tabs(tabs.MucTab):
diff --git a/src/core/core.py b/src/core/core.py
index 52199206..4daeed6c 100644
--- a/src/core/core.py
+++ b/src/core/core.py
@@ -9,7 +9,9 @@ import logging
log = logging.getLogger(__name__)
+import asyncio
import collections
+import shutil
import curses
import os
import pipes
@@ -19,7 +21,7 @@ from threading import Event
from datetime import datetime
from gettext import gettext as _
-from sleekxmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.handler import Callback
import bookmark
import connection
@@ -37,14 +39,13 @@ from config import config, firstrun
from contact import Contact, Resource
from daemon import Executor
from fifo import Fifo
-from keyboard import Keyboard
from logger import logger
from plugin_manager import PluginManager
from roster import roster
from size_manager import SizeManager
from text_buffer import TextBuffer
from theming import get_theme
-from windows import g_lock
+import keyboard
from . import completions
from . import commands
@@ -71,7 +72,7 @@ class Core(object):
self.running = True
self.xmpp = singleton.Singleton(connection.Connection)
self.xmpp.core = self
- self.keyboard = Keyboard()
+ self.keyboard = keyboard.Keyboard()
roster.set_node(self.xmpp.client_roster)
decorators.refresh_wrapper.core = self
self.paused = False
@@ -108,6 +109,13 @@ class Core(object):
self.size = SizeManager(self, windows.Win)
+ # Set to True whenever we consider that we have been disconnected
+ # from the server because of a legitimate reason (bad credentials,
+ # or explicit disconnect from the user for example), in that case we
+ # should not try to auto-reconnect, even if auto_reconnect is true
+ # in the user config.
+ self.legitimate_disconnect = False
+
# global commands, available from all tabs
# a command is tuple of the form:
# (the function executing the command. Takes a string as argument,
@@ -123,6 +131,11 @@ class Core(object):
del self.commands['status']
del self.commands['show']
+ # A list of integers. For example if the user presses Alt+j, 2, 1,
+ # we will insert 2, then 1 in that list, and we will finally build
+ # the number 21 and use it with command_win, before clearing the
+ # list.
+ self.room_number_jump = []
self.key_func = KeyDict()
# Key bindings associated with handlers
# and pseudo-keys used to map actions below.
@@ -188,9 +201,12 @@ class Core(object):
self.key_func.update(key_func)
# Add handlers
+ self.xmpp.add_event_handler('connecting', self.on_connecting)
self.xmpp.add_event_handler('connected', self.on_connected)
+ self.xmpp.add_event_handler('connection_failed', self.on_failed_connection)
self.xmpp.add_event_handler('disconnected', self.on_disconnected)
- self.xmpp.add_event_handler('failed_auth', self.on_failed_auth)
+ self.xmpp.add_event_handler('stream_error', self.on_stream_error)
+ self.xmpp.add_event_handler('failed_all_auth', self.on_failed_all_auth)
self.xmpp.add_event_handler('no_auth', self.on_no_auth)
self.xmpp.add_event_handler("session_start", self.on_session_start)
self.xmpp.add_event_handler("session_start",
@@ -259,8 +275,6 @@ class Core(object):
self.initial_joins = []
- self.timed_events = set()
-
self.connected_events = {}
self.pending_invites = {}
@@ -296,6 +310,8 @@ class Core(object):
theming.update_themes_dir)
self.add_configuration_handler("theme",
self.on_theme_config_change)
+ self.add_configuration_handler("password",
+ self.on_password_change)
self.add_configuration_handler("", self.on_any_config_change)
@@ -374,6 +390,12 @@ class Core(object):
self.information(error_msg, 'Warning')
self.refresh_window()
+ def on_password_change(self, option, value):
+ """
+ Set the new password in the slixmpp.ClientXMPP object
+ """
+ self.xmpp.password = value
+
def sigusr_handler(self, num, stack):
"""
Handle SIGUSR1 (10)
@@ -422,19 +444,14 @@ class Core(object):
log.error("%s received. Exiting…", signals[sig])
if config.get('enable_user_mood'):
- self.xmpp.plugin['xep_0107'].stop(block=False)
+ self.xmpp.plugin['xep_0107'].stop()
if config.get('enable_user_activity'):
- self.xmpp.plugin['xep_0108'].stop(block=False)
+ self.xmpp.plugin['xep_0108'].stop()
if config.get('enable_user_gaming'):
- self.xmpp.plugin['xep_0196'].stop(block=False)
+ self.xmpp.plugin['xep_0196'].stop()
self.plugin_manager.disable_plugins()
- self.disconnect('')
- self.running = False
- try:
- self.reset_curses()
- except: # too bad
- pass
- sys.exit()
+ self.disconnect('%s received' % signals.get(sig))
+ self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
def autoload_plugins(self):
"""
@@ -469,6 +486,11 @@ class Core(object):
' ask for help or tell us how great it is.'),
_('Help'))
self.refresh_window()
+ self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
+
+ def exit(self, event=None):
+ log.debug("exit(%s)" % (event,))
+ asyncio.get_event_loop().stop()
def on_exception(self, typ, value, trace):
"""
@@ -481,7 +503,28 @@ class Core(object):
pass
sys.__excepthook__(typ, value, trace)
- def main_loop(self):
+ def sigwinch_handler(self):
+ """A work-around for ncurses resize stuff, which sucks. Normally, ncurses
+ catches SIGWINCH itself. In its signal handler, it updates the
+ windows structures (for example the size, etc) and it
+ ungetch(KEY_RESIZE). That way, the next time we call getch() we know
+ that a resize occured and we can act on it. BUT poezio doesn’t call
+ getch() until it knows it will return something. The problem is we
+ can’t know that, because stdin is not affected by this KEY_RESIZE
+ value (it is only inserted in a ncurses internal fifo that we can’t
+ access).
+
+ The (ugly) solution is to handle SIGWINCH ourself, trigger the
+ change of the internal windows sizes stored in ncurses module, using
+ sizes that we get using shutil, ungetch the KEY_RESIZE value and
+ then call getch to handle the resize on poezio’s side properly.
+ """
+ size = shutil.get_terminal_size()
+ curses.resizeterm(size.lines, size.columns)
+ curses.ungetch(curses.KEY_RESIZE)
+ self.on_input_readable()
+
+ def on_input_readable(self):
"""
main loop waiting for the user to press a key
"""
@@ -528,39 +571,42 @@ class Core(object):
res.append(current)
return res
- while self.running:
- self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
- big_char_list = [replace_key_with_bound(key)\
- for key in self.read_keyboard()]
- # whether to refresh after ALL keys have been handled
- for char_list in separate_chars_from_bindings(big_char_list):
- if self.paused:
- self.current_tab().input.do_command(char_list[0])
- self.current_tab().input.prompt()
- self.event.set()
- continue
- # Special case for M-x where x is a number
- if len(char_list) == 1:
- char = char_list[0]
- if char.startswith('M-') and len(char) == 3:
- try:
- nb = int(char[2])
- except ValueError:
- pass
- else:
- if self.current_tab().nb == nb:
- self.go_to_previous_tab()
- else:
- self.command_win('%d' % nb)
- # search for keyboard shortcut
- func = self.key_func.get(char, None)
- if func:
- func()
+ log.debug("Input is readable.")
+ big_char_list = [replace_key_with_bound(key)\
+ for key in self.read_keyboard()]
+ log.debug("Got from keyboard: %s", (big_char_list,))
+
+ # whether to refresh after ALL keys have been handled
+ for char_list in separate_chars_from_bindings(big_char_list):
+ if self.paused:
+ self.current_tab().input.do_command(char_list[0])
+ self.current_tab().input.prompt()
+ self.event.set()
+ continue
+ # Special case for M-x where x is a number
+ if len(char_list) == 1:
+ char = char_list[0]
+ if char.startswith('M-') and len(char) == 3:
+ try:
+ nb = int(char[2])
+ except ValueError:
+ pass
else:
- self.do_command(replace_line_breaks(char), False)
+ if self.current_tab().nb == nb:
+ self.go_to_previous_tab()
+ else:
+ self.command_win('%d' % nb)
+ # search for keyboard shortcut
+ func = self.key_func.get(char, None)
+ if func:
+ func()
else:
- self.do_command(''.join(char_list), True)
- self.doupdate()
+ self.do_command(replace_line_breaks(char), False)
+ else:
+ self.do_command(''.join(char_list), True)
+ if self.status.show not in ('xa', 'away'):
+ self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
+ self.doupdate()
def save_config(self):
"""
@@ -703,10 +749,21 @@ class Core(object):
def do_command(self, key, raw):
"""
Execute the action associated with a key
+
+ Or if keyboard.continuation_keys_callback is set, call it instead. See
+ the comment of this variable.
"""
if not key:
return
- return self.current_tab().on_input(key, raw)
+ if keyboard.continuation_keys_callback is not None:
+ # Reset the callback to None BEFORE calling it, because this
+ # callback MAY set a new callback itself, and we don’t want to
+ # erase it in that case
+ cb = keyboard.continuation_keys_callback
+ keyboard.continuation_keys_callback = None
+ cb(key)
+ else:
+ self.current_tab().on_input(key, raw)
def try_execute(self, line):
@@ -724,22 +781,13 @@ class Core(object):
def remove_timed_event(self, event):
"""Remove an existing timed event"""
- if event and event in self.timed_events:
- self.timed_events.remove(event)
+ event.handler.cancel()
def add_timed_event(self, event):
"""Add a new timed event"""
- self.timed_events.add(event)
-
- def check_timed_events(self):
- """Check for the execution of timed events"""
- now = datetime.now()
- for event in self.timed_events:
- if event.has_timed_out(now):
- res = event()
- if not res:
- self.timed_events.remove(event)
- break
+ event.handler = asyncio.get_event_loop().call_later(event.delay,
+ event.callback,
+ *event.args)
####################### XMPP-related actions ##################################
@@ -779,12 +827,15 @@ class Core(object):
Disconnect from remote server and correctly set the states of all
parts of the client (for example, set the MucTabs as not joined, etc)
"""
+ self.legitimate_disconnect = True
msg = msg or ''
for tab in self.get_tabs(tabs.MucTab):
tab.command_part(msg)
self.xmpp.disconnect()
if reconnect:
- self.xmpp.start()
+ # Add a one-time event to reconnect as soon as we are
+ # effectively disconnected
+ self.xmpp.add_event_handler('disconnected', lambda event: self.xmpp.connect(), disposable=True)
def send_message(self, msg):
"""
@@ -815,8 +866,8 @@ class Core(object):
self.xmpp.plugin['xep_0045'].invite(room, jid,
reason=reason or '')
- self.xmpp.plugin['xep_0030'].get_info(jid=jid, block=False,
- timeout=5, callback=callback)
+ self.xmpp.plugin['xep_0030'].get_info(jid=jid, timeout=5,
+ callback=callback)
def get_error_message(self, stanza, deprecated=False):
"""
@@ -1027,17 +1078,24 @@ class Core(object):
Read 2 more chars and go to the tab
with the given number
"""
- char = self.read_keyboard()[0]
- try:
- nb1 = int(char)
- except ValueError:
- return
- char = self.read_keyboard()[0]
- try:
- nb2 = int(char)
- except ValueError:
- return
- self.command_win('%s%s' % (nb1, nb2))
+ def read_next_digit(digit):
+ try:
+ nb = int(digit)
+ except ValueError:
+ # If it is not a number, we do nothing. If it was the first
+ # one, we do not wait for a second one by re-setting the
+ # callback
+ self.room_number_jump.clear()
+ else:
+ self.room_number_jump.append(digit)
+ if len(self.room_number_jump) == 2:
+ arg = "".join(self.room_number_jump)
+ self.room_number_jump.clear()
+ self.command_win(arg)
+ else:
+ # We need to read more digits
+ keyboard.continuation_keys_callback = read_next_digit
+ keyboard.continuation_keys_callback = read_next_digit
def go_to_roster(self):
"Select the roster as the current tab"
@@ -1505,41 +1563,39 @@ class Core(object):
"""
Resize the global_information_win only once at each resize.
"""
- with g_lock:
- if self.information_win_size > tabs.Tab.height - 6:
- self.information_win_size = tabs.Tab.height - 6
- if tabs.Tab.height < 6:
- self.information_win_size = 0
- height = (tabs.Tab.height - 1 - self.information_win_size
- - tabs.Tab.tab_win_height())
- self.information_win.resize(self.information_win_size,
- tabs.Tab.width,
- height,
- 0)
+ if self.information_win_size > tabs.Tab.height - 6:
+ self.information_win_size = tabs.Tab.height - 6
+ if tabs.Tab.height < 6:
+ self.information_win_size = 0
+ height = (tabs.Tab.height - 1 - self.information_win_size
+ - tabs.Tab.tab_win_height())
+ self.information_win.resize(self.information_win_size,
+ tabs.Tab.width,
+ height,
+ 0)
def resize_global_info_bar(self):
"""
Resize the GlobalInfoBar only once at each resize
"""
- with g_lock:
- height, width = self.stdscr.getmaxyx()
- if config.get('enable_vertical_tab_list'):
+ height, width = self.stdscr.getmaxyx()
+ if config.get('enable_vertical_tab_list'):
- if self.size.core_degrade_x:
- return
- try:
- height, _ = self.stdscr.getmaxyx()
- truncated_win = self.stdscr.subwin(height,
- config.get('vertical_tab_list_size'),
- 0, 0)
- except:
- log.error('Curses error on infobar resize', exc_info=True)
- return
- self.left_tab_win = windows.VerticalGlobalInfoBar(truncated_win)
- elif not self.size.core_degrade_y:
- self.tab_win.resize(1, tabs.Tab.width,
- tabs.Tab.height - 2, 0)
- self.left_tab_win = None
+ if self.size.core_degrade_x:
+ return
+ try:
+ height, _ = self.stdscr.getmaxyx()
+ truncated_win = self.stdscr.subwin(height,
+ config.get('vertical_tab_list_size'),
+ 0, 0)
+ except:
+ log.error('Curses error on infobar resize', exc_info=True)
+ return
+ self.left_tab_win = windows.VerticalGlobalInfoBar(truncated_win)
+ elif not self.size.core_degrade_y:
+ self.tab_win.resize(1, tabs.Tab.width,
+ tabs.Tab.height - 2, 0)
+ self.left_tab_win = None
def add_message_to_text_buffer(self, buff, txt,
time=None, nickname=None, history=None):
@@ -1564,46 +1620,38 @@ class Core(object):
Called when we want to resize the screen
"""
# If we have the tabs list on the left, we just give a truncated
- # window to each Tab class, so the draw themself in the portion
- # of the screen that the can occupy, and we draw the tab list
- # on the left remaining space
- with g_lock:
- height, width = self.stdscr.getmaxyx()
+ # window to each Tab class, so they draw themself in the portion of
+ # the screen that they can occupy, and we draw the tab list on the
+ # remaining space, on the left
+ height, width = self.stdscr.getmaxyx()
if (config.get('enable_vertical_tab_list') and
not self.size.core_degrade_x):
- with g_lock:
- try:
- scr = self.stdscr.subwin(0,
- config.get('vertical_tab_list_size'))
- except:
- log.error('Curses error on resize', exc_info=True)
- return
+ try:
+ scr = self.stdscr.subwin(0,
+ config.get('vertical_tab_list_size'))
+ except:
+ log.error('Curses error on resize', exc_info=True)
+ return
else:
scr = self.stdscr
tabs.Tab.resize(scr)
self.resize_global_info_bar()
self.resize_global_information_win()
- with g_lock:
- for tab in self.tabs:
- if config.get('lazy_resize'):
- tab.need_resize = True
- else:
- tab.resize()
- if self.tabs:
- self.full_screen_redraw()
+ for tab in self.tabs:
+ if config.get('lazy_resize'):
+ tab.need_resize = True
+ else:
+ tab.resize()
+ if self.tabs:
+ self.full_screen_redraw()
def read_keyboard(self):
"""
- Get the next keyboard key pressed and returns it.
- get_user_input() has a timeout: it returns None when the timeout
- occurs. In that case we do not return (we loop until we get
- a non-None value), but we check for timed events instead.
+ Get the next keyboard key pressed and returns it. It blocks until
+ something can be read on stdin, this function must be called only if
+ there is something to read. No timeout ever occurs.
"""
- res = self.keyboard.get_user_input(self.stdscr)
- while res is None:
- self.check_timed_events()
- res = self.keyboard.get_user_input(self.stdscr)
- return res
+ return self.keyboard.get_user_input(self.stdscr)
def escape_next_key(self):
"""
@@ -1883,9 +1931,11 @@ class Core(object):
on_groupchat_presence = handlers.on_groupchat_presence
on_failed_connection = handlers.on_failed_connection
on_disconnected = handlers.on_disconnected
- on_failed_auth = handlers.on_failed_auth
+ on_stream_error = handlers.on_stream_error
+ on_failed_all_auth = handlers.on_failed_all_auth
on_no_auth = handlers.on_no_auth
on_connected = handlers.on_connected
+ on_connecting = handlers.on_connecting
on_session_start = handlers.on_session_start
on_status_codes = handlers.on_status_codes
on_groupchat_subject = handlers.on_groupchat_subject
diff --git a/src/core/handlers.py b/src/core/handlers.py
index dfcb3223..50dca216 100644
--- a/src/core/handlers.py
+++ b/src/core/handlers.py
@@ -5,16 +5,19 @@ XMPP-related handlers for the Core class
import logging
log = logging.getLogger(__name__)
+import asyncio
import curses
+import functools
import ssl
+import sys
import time
from hashlib import sha1, sha512
from gettext import gettext as _
from os import path
-from sleekxmpp import InvalidJID
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream.stanzabase import StanzaBase
+from slixmpp import InvalidJID
+from slixmpp.stanza import Message
+from slixmpp.xmlstream.stanzabase import StanzaBase
import bookmark
import common
@@ -49,47 +52,54 @@ def on_session_start_features(self, _):
self.xmpp.plugin['xep_0280'].enable()
self.xmpp.add_event_handler('carbon_received', self.on_carbon_received)
self.xmpp.add_event_handler('carbon_sent', self.on_carbon_sent)
- features = self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain, callback=callback, block=False)
+
+ self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain,
+ callback=callback)
def on_carbon_received(self, message):
"""
Carbon <received/> received
"""
+ def ignore_message(recv):
+ log.debug('%s has category conference, ignoring carbon',
+ recv['from'].server)
+ def receive_message(recv):
+ recv['to'] = self.xmpp.boundjid.full
+ if recv['receipt']:
+ return self.on_receipt(recv)
+ self.on_normal_message(recv)
+
recv = message['carbon_received']
if (recv['from'].bare not in roster or
- roster[recv['from'].bare].subscription == 'none'):
- try:
- if fixes.has_identity(self.xmpp, recv['from'].server,
- identity='conference'):
- log.debug('%s has category conference, ignoring carbon',
- recv['from'].server)
- return
- except:
- log.debug('Traceback when getting the identity of a server:',
- exc_info=True)
- recv['to'] = self.xmpp.boundjid.full
- if recv['receipt']:
- return self.on_receipt(recv)
- self.on_normal_message(recv)
+ roster[recv['from'].bare].subscription == 'none'):
+ fixes.has_identity(self.xmpp, recv['from'].server,
+ identity='conference',
+ on_true=functools.partial(ignore_message, recv),
+ on_false=functools.partial(receive_message, recv))
+ return
+ else:
+ receive_message(recv)
def on_carbon_sent(self, message):
"""
Carbon <sent/> received
"""
+ def ignore_message(sent):
+ log.debug('%s has category conference, ignoring carbon',
+ sent['to'].server)
+ def send_message(sent):
+ sent['from'] = self.xmpp.boundjid.full
+ self.on_normal_message(sent)
+
sent = message['carbon_sent']
if (sent['to'].bare not in roster or
roster[sent['to'].bare].subscription == 'none'):
- try:
- if fixes.has_identity(self.xmpp, sent['to'].server,
- identity='conference'):
- log.debug('%s has category conference, ignoring carbon',
- sent['to'].server)
- return
- except:
- log.debug('Traceback when getting the identity of a server:',
- exc_info=True)
- sent['from'] = self.xmpp.boundjid.full
- self.on_normal_message(sent)
+ fixes.has_identity(self.xmpp, sent['to'].server,
+ identity='conference',
+ on_true=functools.partial(ignore_message, sent),
+ on_false=functools.partial(send_message, sent))
+ else:
+ send_message(sent)
### Invites ###
@@ -171,7 +181,8 @@ def on_message(self, message):
def on_normal_message(self, message):
"""
- When receiving "normal" messages (from someone in our roster)
+ When receiving "normal" messages (not a private message from a
+ muc participant)
"""
if message['type'] == 'error':
return self.information(self.get_error_message(message, deprecated=True), 'Error')
@@ -630,7 +641,7 @@ def on_chatstate_groupchat_conversation(self, message, state):
Chatstate received in a MUC
"""
nick = message['mucnick']
- room_from = message.getMucroom()
+ room_from = message.get_mucroom()
tab = self.get_tab_by_name(room_from, tabs.MucTab)
if tab and tab.get_user_by_name(nick):
self.events.trigger('muc_chatstate', message, tab)
@@ -828,27 +839,40 @@ def on_groupchat_presence(self, presence):
### Connection-related handlers ###
-def on_failed_connection(self):
+def on_failed_connection(self, error):
"""
We cannot contact the remote server
"""
- self.information(_("Connection to remote server failed"), _('Error'))
+ self.information(_("Connection to remote server failed: %s" % (error,)), _('Error'))
def on_disconnected(self, event):
"""
When we are disconnected from remote server
"""
+ # Stop the ping plugin. It would try to send stanza on regular basis
+ self.xmpp.plugin['xep_0199'].disable_keepalive()
roster.modified()
for tab in self.get_tabs(tabs.MucTab):
tab.disconnect()
self.information(_("Disconnected from server."), _('Error'))
+ if not self.legitimate_disconnect and config.get('auto_reconnect', False):
+ self.information(_("Auto-reconnecting."), _('Info'))
+ self.xmpp.connect()
-def on_failed_auth(self, event):
+def on_stream_error(self, event):
+ """
+ When we receive a stream error
+ """
+ if event and event['text']:
+ self.information(_('Stream error: %s') % event['text'], _('Error'))
+
+def on_failed_all_auth(self, event):
"""
Authentication failed
"""
self.information(_("Authentication failed (bad credentials?)."),
_('Error'))
+ self.legitimate_disconnect = True
def on_no_auth(self, event):
"""
@@ -856,6 +880,7 @@ def on_no_auth(self, event):
"""
self.information(_("Authentication failed, no login method available."),
_('Error'))
+ self.legitimate_disconnect = True
def on_connected(self, event):
"""
@@ -863,6 +888,12 @@ def on_connected(self, event):
"""
self.information(_("Connected to server."), 'Info')
+def on_connecting(self, event):
+ """
+ Just before we try to connect to the server
+ """
+ self.legitimate_disconnect = False
+
def on_session_start(self, event):
"""
Called when we are connected and authenticated
@@ -883,32 +914,42 @@ def on_session_start(self, event):
self.events.trigger('send_normal_presence', pres)
pres.send()
bookmark.get_local()
+ def _join_initial_rooms(bookmarks):
+ """Join all rooms given in the iterator `bookmarks`"""
+ for bm in bookmarks:
+ if bm.autojoin or config.get('open_all_bookmarks'):
+ tab = self.get_tab_by_name(bm.jid, tabs.MucTab)
+ nick = bm.nick if bm.nick else self.own_nick
+ if not tab:
+ self.open_new_room(bm.jid, nick, False)
+ self.initial_joins.append(bm.jid)
+ histo_length = config.get('muc_history_length')
+ if histo_length == -1:
+ histo_length = None
+ if histo_length is not None:
+ histo_length = str(histo_length)
+ # do not join rooms that do not have autojoin
+ # but display them anyway
+ if bm.autojoin:
+ muc.join_groupchat(self, bm.jid, nick,
+ passwd=bm.password,
+ maxhistory=histo_length,
+ status=self.status.message,
+ show=self.status.show)
+ def _join_remote_only():
+ remote_bookmarks = (bm for bm in bookmark.bookmarks if (bm.method in ("pep", "privatexml")))
+ _join_initial_rooms(remote_bookmarks)
if not self.xmpp.anon and config.get('use_remote_bookmarks'):
- bookmark.get_remote(self.xmpp)
- for bm in bookmark.bookmarks:
- if bm.autojoin or config.get('open_all_bookmarks'):
- tab = self.get_tab_by_name(bm.jid, tabs.MucTab)
- nick = bm.nick if bm.nick else self.own_nick
- if not tab:
- self.open_new_room(bm.jid, nick, False)
- self.initial_joins.append(bm.jid)
- histo_length = config.get('muc_history_length')
- if histo_length == -1:
- histo_length = None
- if histo_length is not None:
- histo_length = str(histo_length)
- # do not join rooms that do not have autojoin
- # but display them anyway
- if bm.autojoin:
- muc.join_groupchat(self, bm.jid, nick,
- passwd=bm.password,
- maxhistory=histo_length,
- status=self.status.message,
- show=self.status.show)
+ bookmark.get_remote(self.xmpp, _join_remote_only)
+ # join all the available bookmarks. As of yet, this is just the local
+ # ones
+ _join_initial_rooms(bookmark.bookmarks)
if config.get('enable_user_nick'):
- self.xmpp.plugin['xep_0172'].publish_nick(nick=self.own_nick, callback=dumb_callback, block=False)
+ self.xmpp.plugin['xep_0172'].publish_nick(nick=self.own_nick, callback=dumb_callback)
self.xmpp.plugin['xep_0115'].update_caps()
+ # Start the ping's plugin regular event
+ self.xmpp.set_keepalive_values()
### Other handlers ###
@@ -974,7 +1015,7 @@ def on_groupchat_subject(self, message):
Triggered when the topic is changed.
"""
nick_from = message['mucnick']
- room_from = message.getMucroom()
+ room_from = message.get_mucroom()
tab = self.get_tab_by_name(room_from, tabs.MucTab)
subject = message['subject']
if subject is None or not tab:
@@ -1079,7 +1120,7 @@ def incoming_stanza(self, stanza):
def validate_ssl(self, pem):
"""
- Check the server certificate using the sleekxmpp ssl_cert event
+ Check the server certificate using the slixmpp ssl_cert event
"""
if config.get('ignore_certificate'):
return
@@ -1115,19 +1156,31 @@ def validate_ssl(self, pem):
input.resize(1, self.current_tab().width, self.current_tab().height-1, 0)
input.refresh()
self.doupdate()
- self.paused = True
- while input.value is None:
- self.event.wait()
- self.current_tab().input = saved_input
- self.paused = False
- if input.value:
- self.information('Setting new certificate: old: %s, new: %s' % (cert, sha2_found_cert), 'Info')
- log.debug('Setting certificate to %s', sha2_found_cert)
- if not config.silent_set('certificate', sha2_found_cert):
- self.information(_('Unable to write in the config file'), 'Error')
- else:
- self.information('You refused to validate the certificate. You are now disconnected', 'Info')
- self.xmpp.disconnect()
+ old_loop = asyncio.get_event_loop()
+ new_loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(new_loop)
+ new_loop.add_reader(sys.stdin, self.on_input_readable)
+ future = asyncio.Future()
+ @asyncio.coroutine
+ def check_input(future):
+ while input.value is None:
+ yield from asyncio.sleep(0.01)
+ self.current_tab().input = saved_input
+ self.paused = False
+ if input.value:
+ self.information('Setting new certificate: old: %s, new: %s' % (cert, sha2_found_cert), 'Info')
+ log.debug('Setting certificate to %s', sha2_found_cert)
+ if not config.silent_set('certificate', sha2_found_cert):
+ self.information(_('Unable to write in the config file'), 'Error')
+ else:
+ self.information('You refused to validate the certificate. You are now disconnected', 'Info')
+ self.xmpp.disconnect()
+ new_loop.stop()
+ asyncio.set_event_loop(old_loop)
+ asyncio.async(check_input(future))
+ new_loop.run_forever()
+
+
else:
log.debug('First time. Setting certificate to %s', sha2_found_cert)
if not config.silent_set('certificate', sha2_found_cert):
diff --git a/src/fixes.py b/src/fixes.py
index 18c117d8..1c5da7c8 100644
--- a/src/fixes.py
+++ b/src/fixes.py
@@ -1,25 +1,26 @@
"""
-Module used to provide fixes for sleekxmpp functions not yet fixed
+Module used to provide fixes for slixmpp functions not yet fixed
upstream.
TODO: Check that they are fixed and remove those hacks
"""
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream import ET
+from slixmpp.stanza import Message
+from slixmpp.xmlstream import ET
import logging
log = logging.getLogger(__name__)
-def has_identity(xmpp, jid, identity):
- try:
- iq = xmpp.plugin['xep_0030'].get_info(jid=jid, block=True, timeout=1)
+def has_identity(xmpp, jid, identity, on_true=None, on_false=None):
+ def _cb(iq):
ident = lambda x: x[0]
- return identity in map(ident, iq['disco_info']['identities'])
- except:
- log.debug('Traceback while retrieving identity', exc_info=True)
- return False
+ res = identity in map(ident, iq['disco_info']['identities'])
+ if res and on_true is not None:
+ on_true()
+ if not res and on_false is not None:
+ on_false()
+ xmpp.plugin['xep_0030'].get_info(jid=jid, callback=_cb)
def get_version(xmpp, jid, callback=None, **kwargs):
def handle_result(res):
@@ -37,20 +38,20 @@ def get_version(xmpp, jid, callback=None, **kwargs):
return handle_result(result)
-def get_room_form(xmpp, room):
+def get_room_form(xmpp, room, callback):
+ def _cb(result):
+ if result["type"] == "error":
+ callback(None)
+ xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
+ if xform is None:
+ callback(None)
+ form = xmpp.plugin['xep_0004'].buildForm(xform)
+ callback(form)
+
iq = xmpp.make_iq_get(ito=room)
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
iq.append(query)
- try:
- result = iq.send()
- except:
- return False
- xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
- if xform is None:
- return False
- form = xmpp.plugin['xep_0004'].buildForm(xform)
- return form
-
+ iq.send(callback=_cb)
def _filter_add_receipt_request(self, stanza):
"""
diff --git a/src/keyboard.py b/src/keyboard.py
index 0a1391ea..ec1e7d0a 100755
--- a/src/keyboard.py
+++ b/src/keyboard.py
@@ -18,6 +18,15 @@ import curses.ascii
import logging
log = logging.getLogger(__name__)
+# A callback that will handle the next key entered by the user. For
+# example if the user presses Ctrl+j, we set a callbacks, and the
+# next key pressed by the user will be passed to this callback
+# instead of the normal process of executing global keybard
+# shortcuts or inserting text in the current output. The callback
+# is always reset to None afterwards (to resume the normal
+# processing of keys)
+continuation_keys_callback = None
+
def get_next_byte(s):
"""
Read the next byte of the utf-8 char
@@ -33,59 +42,7 @@ def get_next_byte(s):
return (None, c)
return (ord(c), c.encode('latin-1')) # returns a number and a bytes object
-def get_char_list_old(s):
- """
- Kept for compatibility for python versions without get_wchar()
- (introduced in 3.3) Read one or more bytes, concatenate them to create a
- unicode char. Also treat special bytes to create special chars (like
- control, alt, etc), returns one or more utf-8 chars
-
- see http://en.wikipedia.org/wiki/UTF-8#Description
- """
- ret_list = []
- # The list of all chars. For example if you paste a text, the list the chars pasted
- # so that they can be handled at once.
- (first, char) = get_next_byte(s)
- while first is not None or char is not None:
- if not isinstance(first, int): # Keyboard special, like KEY_HOME etc
- return [char]
- if first == 127 or first == 8:
- ret_list.append("KEY_BACKSPACE")
- break
- s.timeout(0) # we are now getting the missing utf-8 bytes to get a whole char
- if first < 127: # ASCII char on one byte
- if first <= 26: # transform Ctrl+* keys
- char = chr(first + 64)
- ret_list.append("^"+char)
- (first, char) = get_next_byte(s)
- continue
- if first == 27:
- second = get_char_list_old(s)
- if not second: # if escape was pressed, a second char
- # has to be read. But it timed out.
- return []
- res = 'M-%s' % (second[0],)
- ret_list.append(res)
- (first, char) = get_next_byte(s)
- continue
- if 194 <= first:
- (code, c) = get_next_byte(s) # 2 bytes char
- char += c
- if 224 <= first:
- (code, c) = get_next_byte(s) # 3 bytes char
- char += c
- if 240 <= first:
- (code, c) = get_next_byte(s) # 4 bytes char
- char += c
- try:
- ret_list.append(char.decode('utf-8')) # return all the concatened byte objets, decoded
- except UnicodeDecodeError:
- return None
- # s.timeout(1) # timeout to detect a paste of many chars
- (first, char) = get_next_byte(s)
- return ret_list
-
-def get_char_list_new(s):
+def get_char_list(s):
ret_list = []
while True:
try:
@@ -96,6 +53,9 @@ def get_char_list_new(s):
except ValueError: # invalid input
log.debug('Invalid character entered.')
return ret_list
+ # Set to non-blocking. We try to read more bytes. If there are no
+ # more data to read, it will immediately timeout and return with the
+ # data we have so far
s.timeout(0)
if isinstance(key, int):
ret_list.append(curses.keyname(key).decode())
@@ -132,7 +92,6 @@ def get_char_list_new(s):
class Keyboard(object):
def __init__(self):
- self.get_char_list = get_char_list_new
self.escape = False
def escape_next_key(self):
@@ -144,7 +103,7 @@ class Keyboard(object):
"""
self.escape = True
- def get_user_input(self, s, timeout=1000):
+ def get_user_input(self, s):
"""
Returns a list of all the available characters to read (for example it
may contain a whole text if there’s some lag, or the user pasted text,
@@ -153,19 +112,11 @@ class Keyboard(object):
blocking, we need to get out of it every now and then even if nothing
was entered).
"""
- s.timeout(timeout) # The timeout for timed events to be checked every second
- try:
- ret_list = self.get_char_list(s)
- except AttributeError:
- # caught if screen.get_wch() does not exist. In that case we use the
- # old version, so this exception is caught only once. No efficiency
- # issue here.
- log.debug("get_wch() missing, switching to old keyboard method")
- self.get_char_list = get_char_list_old
- ret_list = self.get_char_list(s)
+ # Disable the timeout
+ s.timeout(-1)
+ ret_list = get_char_list(s)
if not ret_list:
- # nothing at all was read, that’s a timed event timeout
- return None
+ return ret_list
if len(ret_list) != 1:
if ret_list[-1] == '^M':
ret_list.pop(-1)
diff --git a/src/multiuserchat.py b/src/multiuserchat.py
index ae8acb77..92d09a60 100644
--- a/src/multiuserchat.py
+++ b/src/multiuserchat.py
@@ -8,7 +8,7 @@
"""
Implementation of the XEP-0045: Multi-User Chat.
Add some facilities that are not available on the XEP_0045
-sleek plugin
+slix plugin
"""
from gettext import gettext as _
@@ -47,7 +47,7 @@ def destroy_room(xmpp, room, reason='', altroom=''):
_('Info'))
else:
xmpp.core.information(_('Room %s destroyed') % room, _('Info'))
- iq.send(block=False, callback=callback)
+ iq.send(callback=callback)
return True
def send_private_message(xmpp, jid, line):
@@ -97,7 +97,7 @@ def change_nick(core, jid, nick, status=None, show=None):
def join_groupchat(core, jid, nick, passwd='', maxhistory=None, status=None, show=None, seconds=None):
xmpp = core.xmpp
- stanza = xmpp.makePresence(pto='%s/%s' % (jid, nick), pstatus=status, pshow=show)
+ stanza = xmpp.make_presence(pto='%s/%s' % (jid, nick), pstatus=status, pshow=show)
x = ET.Element('{http://jabber.org/protocol/muc}x')
if passwd:
passelement = ET.Element('password')
@@ -131,7 +131,7 @@ def set_user_role(xmpp, jid, nick, reason, role, callback=None):
(role = 'none': eject user)
"""
jid = safeJID(jid)
- iq = xmpp.makeIqSet()
+ iq = xmpp.make_iq_set()
query = ET.Element('{%s}query' % NS_MUC_ADMIN)
item = ET.Element('{%s}item' % NS_MUC_ADMIN, {'nick':nick, 'role':role})
if reason:
@@ -142,7 +142,7 @@ def set_user_role(xmpp, jid, nick, reason, role, callback=None):
iq.append(query)
iq['to'] = jid
if callback:
- return iq.send(block=False, callback=callback)
+ return iq.send(callback=callback)
try:
return iq.send()
except Exception as e:
@@ -165,10 +165,10 @@ def set_user_affiliation(xmpp, muc_jid, affiliation, nick=None, jid=None, reason
item.append(reason_item)
query.append(item)
- iq = xmpp.makeIqSet(query)
+ iq = xmpp.make_iq_set(query)
iq['to'] = muc_jid
if callback:
- return iq.send(block=False, callback=callback)
+ return iq.send(callback=callback)
try:
return xmpp.plugin['xep_0045'].setAffiliation(str(muc_jid), str(jid) if jid else None, nick, affiliation)
except:
@@ -180,18 +180,18 @@ def cancel_config(xmpp, room):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x)
- iq = xmpp.makeIqSet(query)
+ iq = xmpp.make_iq_set(query)
iq['to'] = room
- iq.send(block=False)
+ iq.send()
def configure_room(xmpp, room, form):
if form is None:
return
- iq = xmpp.makeIqSet()
+ iq = xmpp.make_iq_set()
iq['to'] = room
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
form = form.getXML('submit')
query.append(form)
iq.append(query)
- iq.send(block=False)
+ iq.send()
diff --git a/src/plugin.py b/src/plugin.py
index 22a17eee..eb2a89e3 100644
--- a/src/plugin.py
+++ b/src/plugin.py
@@ -338,21 +338,21 @@ class PluginAPI(object):
"""
return self.plugin_manager.del_event_handler(module, *args, **kwargs)
- def add_sleek_event_handler(self, module, event_name, handler):
+ def add_slix_event_handler(self, module, event_name, handler):
"""
- Add an event handler for a sleekxmpp event.
+ Add an event handler for a slixmpp event.
:param str event_name: The event name.
:param function handler: The handler function.
- A list of the SleekXMPP events can be found here
+ A list of the slixmpp events can be found here
http://sleekxmpp.com/event_index.html
"""
self.core.xmpp.add_event_handler(event_name, handler)
- def del_sleek_event_handler(self, module, event_name, handler):
+ def del_slix_event_handler(self, module, event_name, handler):
"""
- Remove a handler for a SleekXMPP event
+ Remove a handler for a slixmpp event
:param str event_name: The name of the targeted event.
:param function handler: The function to remove from the handlers.
diff --git a/src/plugin_manager.py b/src/plugin_manager.py
index d5cb9bc1..d4cc7384 100644
--- a/src/plugin_manager.py
+++ b/src/plugin_manager.py
@@ -261,7 +261,7 @@ class PluginManager(object):
def add_event_handler(self, module_name, event_name, handler, position=0):
"""
Add an event handler. If event_name isn’t in the event list, assume
- it is a sleekxmpp event.
+ it is a slixmpp event.
"""
eh = self.event_handlers[module_name]
eh.append((event_name, handler))
diff --git a/src/poezio.py b/src/poezio.py
index f82f103f..9a26e135 100644
--- a/src/poezio.py
+++ b/src/poezio.py
@@ -57,17 +57,22 @@ def main():
if options.debug:
cocore.debug = True
cocore.start()
+
+ # Warning: asyncio must always be imported after the config. Otherwise
+ # the asyncio logger will not follow our configuration and won't write
+ # the tracebacks in the correct file, etc
+ import asyncio
+ loop = asyncio.get_event_loop()
+
+ loop.add_reader(sys.stdin, cocore.on_input_readable)
+ loop.add_signal_handler(signal.SIGWINCH, cocore.sigwinch_handler)
+ cocore.xmpp.start()
+ loop.run_forever()
+ # We reach this point only when loop.stop() is called
try:
- if not cocore.xmpp.start(): # Connect to remote server
- cocore.on_failed_connection()
- except:
- cocore.running = False
cocore.reset_curses()
- print("Poezio could not start, maybe you tried aborting it while it was starting?\n"
- "If you think it is abnormal, please run it with the -d option and report the bug.")
- else:
- log.error('------------------------ new poezio start ------------------------')
- cocore.main_loop() # Refresh the screen, wait for user events etc
+ except:
+ pass
if __name__ == '__main__':
main()
diff --git a/src/roster.py b/src/roster.py
index d18a41c4..d2b99cef 100644
--- a/src/roster.py
+++ b/src/roster.py
@@ -19,18 +19,18 @@ from roster_sorting import SORTING_METHODS, GROUP_SORTING_METHODS
from os import path as p
from datetime import datetime
from common import safeJID
-from sleekxmpp.exceptions import IqError, IqTimeout
+from slixmpp.exceptions import IqError, IqTimeout
class Roster(object):
"""
- The proxy class to get the roster from SleekXMPP.
+ The proxy class to get the roster from slixmpp.
Caches Contact and RosterGroup objects.
"""
def __init__(self):
"""
- node: the RosterSingle from SleekXMPP
+ node: the RosterSingle from slixmpp
"""
self.__node = None
self.contact_filter = None # A tuple(function, *args)
@@ -111,7 +111,7 @@ class Roster(object):
return self.contacts[jid]
def set_node(self, value):
- """Set the SleekXMPP RosterSingle for our roster"""
+ """Set the slixmpp RosterSingle for our roster"""
self.__node = value
def get_groups(self, sort=''):
diff --git a/src/size_manager.py b/src/size_manager.py
index 7e01d5d0..1cad83fd 100644
--- a/src/size_manager.py
+++ b/src/size_manager.py
@@ -3,8 +3,6 @@ Size Manager:
used to check size boundaries of the whole window and
specific tabs
"""
-from windows import g_lock
-
THRESHOLD_WIDTH_DEGRADE = 45
THRESHOLD_HEIGHT_DEGRADE = 10
@@ -27,26 +25,22 @@ class SizeManager(object):
@property
def tab_degrade_x(self):
- with g_lock:
- _, x = self.tab_scr.getmaxyx()
+ _, x = self.tab_scr.getmaxyx()
return x < THRESHOLD_WIDTH_DEGRADE
@property
def tab_degrade_y(self):
- with g_lock:
- y, x = self.tab_scr.getmaxyx()
+ y, x = self.tab_scr.getmaxyx()
return y < THRESHOLD_HEIGHT_DEGRADE
@property
def core_degrade_x(self):
- with g_lock:
- y, x = self.core_scr.getmaxyx()
+ y, x = self.core_scr.getmaxyx()
return x < FULL_WIDTH_DEGRADE
@property
def core_degrade_y(self):
- with g_lock:
- y, x = self.core_scr.getmaxyx()
+ y, x = self.core_scr.getmaxyx()
return y < FULL_HEIGHT_DEGRADE
diff --git a/src/tabs/adhoc_commands_list.py b/src/tabs/adhoc_commands_list.py
index 87ee0c52..7f5abf6a 100644
--- a/src/tabs/adhoc_commands_list.py
+++ b/src/tabs/adhoc_commands_list.py
@@ -11,14 +11,14 @@ log = logging.getLogger(__name__)
from . import ListTab
-from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItem
+from slixmpp.plugins.xep_0030.stanza.items import DiscoItem
class AdhocCommandsListTab(ListTab):
plugin_commands = {}
plugin_keys = {}
def __init__(self, jid):
- ListTab.__init__(self, jid,
+ ListTab.__init__(self, jid.full,
"“Enter”: execute selected command.",
_('Ad-hoc commands of JID %s (Loading)') % jid,
(('Node', 0), ('Description', 1)))
diff --git a/src/tabs/basetabs.py b/src/tabs/basetabs.py
index 1b6e0d4c..645a297f 100644
--- a/src/tabs/basetabs.py
+++ b/src/tabs/basetabs.py
@@ -35,7 +35,6 @@ from decorators import refresh_wrapper
from logger import logger
from text_buffer import TextBuffer
from theming import get_theme, dump_tuple
-from windows import g_lock
# getters for tab colors (lambdas, so that they are dynamic)
@@ -187,9 +186,8 @@ class Tab(object):
@staticmethod
def resize(scr):
- with g_lock:
- Tab.height, Tab.width = scr.getmaxyx()
- windows.Win._tab_win = scr
+ Tab.height, Tab.width = scr.getmaxyx()
+ windows.Win._tab_win = scr
def missing_command_callback(self, command_name):
"""
@@ -447,12 +445,9 @@ class ChatTab(Tab):
self.text_win = None
self._text_buffer = TextBuffer()
self.chatstate = None # can be "active", "composing", "paused", "gone", "inactive"
- # We keep a weakref of the event that will set our chatstate to "paused", so that
+ # We keep a reference of the event that will set our chatstate to "paused", so that
# we can delete it or change it if we need to
self.timed_event_paused = None
- # if that’s None, then no paused chatstate was sent recently
- # if that’s a weakref returning None, then a paused chatstate was sent
- # since the last input
# Keeps the last sent message to complete it easily in completion_correct, and to replace it.
self.last_sent_message = None
self.key_func['M-v'] = self.move_separator
@@ -625,17 +620,12 @@ class ChatTab(Tab):
"""
if not config.get_by_tabname('send_chat_states', self.general_jid):
return
- if self.timed_event_paused:
- # check the weakref
- event = self.timed_event_paused()
- if event:
- # the event already exists: we just update
- # its date
- event.change_date(datetime.now() + timedelta(seconds=4))
- return
+ # First, cancel the delay if it already exists, before rescheduling
+ # it at a new date
+ self.cancel_paused_delay()
new_event = timed_events.DelayedEvent(4, self.send_chat_state, 'paused')
self.core.add_timed_event(new_event)
- self.timed_event_paused = weakref.ref(new_event)
+ self.timed_event_paused = new_event
def cancel_paused_delay(self):
"""
@@ -643,12 +633,9 @@ class ChatTab(Tab):
Called for example when the input is emptied, or when the message
is sent
"""
- if self.timed_event_paused:
- event = self.timed_event_paused()
- if event:
- self.core.remove_timed_event(event)
- del event
- self.timed_event_paused = None
+ if self.timed_event_paused is not None:
+ self.core.remove_timed_event(self.timed_event_paused)
+ self.timed_event_paused = None
def command_correct(self, line):
"""
@@ -738,7 +725,7 @@ class OneToOneTab(ChatTab):
"check the features supported by the other party"
if safeJID(self.get_dest_jid()).resource:
self.core.xmpp.plugin['xep_0030'].get_info(
- jid=self.get_dest_jid(), block=False, timeout=5,
+ jid=self.get_dest_jid(), timeout=5,
callback=self.features_checked)
def command_attention(self, message=''):
diff --git a/src/tabs/conversationtab.py b/src/tabs/conversationtab.py
index b2d526de..3d5769f7 100644
--- a/src/tabs/conversationtab.py
+++ b/src/tabs/conversationtab.py
@@ -188,7 +188,7 @@ class ConversationTab(OneToOneTab):
self.add_message(msg)
self.core.refresh_window()
- self.core.xmpp.plugin['xep_0012'].get_last_activity(self.general_jid, block=False, callback=callback)
+ self.core.xmpp.plugin['xep_0012'].get_last_activity(self.general_jid, callback=callback)
@refresh_wrapper.conditional
def command_info(self, arg):
diff --git a/src/tabs/muclisttab.py b/src/tabs/muclisttab.py
index d7c68588..55d5c2bd 100644
--- a/src/tabs/muclisttab.py
+++ b/src/tabs/muclisttab.py
@@ -11,7 +11,7 @@ log = logging.getLogger(__name__)
from . import ListTab
-from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItem
+from slixmpp.plugins.xep_0030.stanza.items import DiscoItem
class MucListTab(ListTab):
"""
diff --git a/src/tabs/muctab.py b/src/tabs/muctab.py
index fb89b0fa..547830cb 100644
--- a/src/tabs/muctab.py
+++ b/src/tabs/muctab.py
@@ -362,13 +362,15 @@ class MucTab(ChatTab):
"""
/configure
"""
- form = fixes.get_room_form(self.core.xmpp, self.name)
- if not form:
- self.core.information(
+ def on_form_received(form):
+ if not form:
+ self.core.information(
_('Could not retrieve the configuration form'),
_('Error'))
- return
- self.core.open_new_form(form, self.cancel_config, self.send_config)
+ return
+ self.core.open_new_form(form, self.cancel_config, self.send_config)
+
+ form = fixes.get_room_form(self.core.xmpp, self.name, on_form_received)
def cancel_config(self, form):
"""
diff --git a/src/tabs/rostertab.py b/src/tabs/rostertab.py
index 26f429d0..878e89ed 100644
--- a/src/tabs/rostertab.py
+++ b/src/tabs/rostertab.py
@@ -175,7 +175,7 @@ class RosterInfoTab(Tab):
jid = item.bare_jid
elif isinstance(item, Resource):
jid = item.jid.bare
- self.core.xmpp.plugin['xep_0191'].block(jid, block=False, callback=callback)
+ self.core.xmpp.plugin['xep_0191'].block(jid, callback=callback)
def completion_block(self, the_input):
"""
@@ -202,22 +202,21 @@ class RosterInfoTab(Tab):
jid = item.bare_jid
elif isinstance(item, Resource):
jid = item.jid.bare
- self.core.xmpp.plugin['xep_0191'].unblock(jid, block=False, callback=callback)
+ self.core.xmpp.plugin['xep_0191'].unblock(jid, callback=callback)
def completion_unblock(self, the_input):
"""
Completion for /unblock
"""
+ def on_result(iq):
+ if iq['type'] == 'error':
+ return
+ l = sorted(str(item) for item in iq['blocklist']['items'])
+ return the_input.new_completion(l, 1, quotify=False)
+
if the_input.get_argument_position():
- try:
- iq = self.core.xmpp.plugin['xep_0191'].get_blocked(block=True)
- except Exception as e:
- iq = e.iq
- finally:
- if iq['type'] == 'error':
- return
- l = sorted(str(item) for item in iq['blocklist']['items'])
- return the_input.new_completion(l, 1, quotify=False)
+ self.core.xmpp.plugin['xep_0191'].get_blocked(callback=on_result)
+ return True
def command_list_blocks(self, arg=None):
"""
@@ -235,13 +234,16 @@ class RosterInfoTab(Tab):
s = 'No blocked JIDs.'
self.core.information(s, 'Info')
- self.core.xmpp.plugin['xep_0191'].get_blocked(block=False, callback=callback)
+ self.core.xmpp.plugin['xep_0191'].get_blocked(callback=callback)
def command_reconnect(self, args=None):
"""
/reconnect
"""
- self.core.disconnect(reconnect=True)
+ if self.core.xmpp.is_connected():
+ self.core.disconnect(reconnect=True)
+ else:
+ self.core.xmpp.connect()
def command_disconnect(self, args=None):
"""
@@ -419,8 +421,8 @@ class RosterInfoTab(Tab):
if 'none' in groups:
groups.remove('none')
subscription = contact.subscription
- self.core.xmpp.update_roster(jid, name=name, groups=groups, subscription=subscription,
- callback=callback, block=False)
+ self.core.xmpp.update_roster(jid, name=name, groups=groups,
+ subscription=subscription, callback=callback)
def command_groupadd(self, args):
"""
@@ -459,8 +461,8 @@ class RosterInfoTab(Tab):
self.core.information('The group could not be set.', 'Error')
log.debug('Error in groupadd:\n%s', iq)
- self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription,
- callback=callback, block=False)
+ self.core.xmpp.update_roster(jid, name=name, groups=new_groups,
+ subscription=subscription, callback=callback)
def command_groupmove(self, arg):
"""
@@ -514,8 +516,8 @@ class RosterInfoTab(Tab):
self.core.information('The group could not be set')
log.debug('Error in groupmove:\n%s', iq)
- self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription,
- callback=callback, block=False)
+ self.core.xmpp.update_roster(jid, name=name, groups=new_groups,
+ subscription=subscription, callback=callback)
def command_groupremove(self, args):
"""
@@ -554,8 +556,8 @@ class RosterInfoTab(Tab):
self.core.information('The group could not be set')
log.debug('Error in groupremove:\n%s', iq)
- self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription,
- callback=callback, block=False)
+ self.core.xmpp.update_roster(jid, name=name, groups=new_groups,
+ subscription=subscription, callback=callback)
def command_remove(self, args):
"""
diff --git a/src/tabs/xmltab.py b/src/tabs/xmltab.py
index d33f4d48..083e97c5 100644
--- a/src/tabs/xmltab.py
+++ b/src/tabs/xmltab.py
@@ -12,8 +12,8 @@ log = logging.getLogger(__name__)
import curses
import os
-from sleekxmpp.xmlstream import matcher
-from sleekxmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream import matcher
+from slixmpp.xmlstream.handler import Callback
from . import Tab
diff --git a/src/timed_events.py b/src/timed_events.py
index a922ee03..6160645b 100644
--- a/src/timed_events.py
+++ b/src/timed_events.py
@@ -13,87 +13,47 @@ Once created, they must be added to the list of checked events with
:py:func:`.PluginAPI.add_timed_event` (within a plugin).
"""
+import asyncio
import logging
log = logging.getLogger(__name__)
import datetime
-class TimedEvent(object):
+class DelayedEvent(object):
"""
- An event with a callback that is called when the specified time is passed.
-
- Note that these events can NOT be used for very small delay or a very
- precise date, since the check for events is done once per second, as
- a maximum.
-
- The callback and its arguments should be passed as the lasts arguments.
+ A TimedEvent, but with the date calculated from now + a delay in seconds.
+ Use it if you want an event to happen in, e.g. 6 seconds.
"""
- def __init__(self, date, callback, *args):
+ def __init__(self, delay, callback, *args):
"""
- Create a new timed event.
+ Create a new DelayedEvent.
- :param datetime.datetime date: Time at which the callback must be run.
+ :param int delay: The number of seconds.
:param function callback: The handler that will be executed.
:param \*args: Optional arguments passed to the handler.
"""
- self._callback = callback
+ self.callback = callback
self.args = args
self.repetive = False
- self.next_call_date = date
-
- def __call__(self):
- """
- the call should return False if this event should be remove from
- the events list.
- If it’s true, the date should be updated beforehand to a later date,
- or else it will be called every second
- """
- self._callback(*self.args)
- return self.repetive
+ self.delay = delay
+ # An asyncio handler, as returned by call_later() or call_at()
+ self.handler = None
- def has_timed_out(self, current_date):
- """
- Check if the event has timed out.
-
- :param datetime.datetime current_date: The current date.
- :returns: True if the callback should be called
- :rtype: bool
- """
- if self.next_call_date < current_date:
- return True
- else:
- return False
-
- def change_date(self, date):
- """
- Simply change the date of the event.
-
- :param datetime.datetime date: Next date.
- """
- self.next_call_date = date
-
- def add_delay(self, delay):
- """
- Add a delay (in seconds) to the date.
-
- :param int delay: The delay to add.
- """
- self.next_call_date += datetime.timedelta(seconds=delay)
-
-class DelayedEvent(TimedEvent):
+class TimedEvent(DelayedEvent):
"""
- A TimedEvent, but with the date calculated from now + a delay in seconds.
- Use it if you want an event to happen in, e.g. 6 seconds.
+ An event with a callback that is called when the specified time is passed.
+
+ The callback and its arguments should be passed as the lasts arguments.
"""
- def __init__(self, delay, callback, *args):
+ def __init__(self, date, callback, *args):
"""
- Create a new DelayedEvent.
+ Create a new timed event.
- :param int delay: The number of seconds.
+ :param datetime.datetime date: Time at which the callback must be run.
:param function callback: The handler that will be executed.
:param \*args: Optional arguments passed to the handler.
"""
- date = datetime.datetime.now() + datetime.timedelta(seconds=delay)
- TimedEvent.__init__(self, date, callback, *args)
-
+ delta = date - datetime.datetime.now()
+ delay = delta.total_seconds()
+ DelayedEvent.__init__(self, delay, callback, *args)
diff --git a/src/windows/__init__.py b/src/windows/__init__.py
index adb07cbe..9e165201 100644
--- a/src/windows/__init__.py
+++ b/src/windows/__init__.py
@@ -2,7 +2,8 @@
Module exporting all the Windows, which are wrappers around curses wins
used to display information on the screen
"""
-from . base_wins import Win, g_lock
+
+from . base_wins import Win
from . data_forms import FormWin
from . info_bar import GlobalInfoBar, VerticalGlobalInfoBar
from . info_wins import InfoWin, XMLInfoWin, PrivateInfoWin, MucListInfoWin, \
diff --git a/src/windows/base_wins.py b/src/windows/base_wins.py
index 44c62e91..574eee89 100644
--- a/src/windows/base_wins.py
+++ b/src/windows/base_wins.py
@@ -32,8 +32,6 @@ allowed_color_digits = ('0', '1', '2', '3', '4', '5', '6', '7')
# text_end are the position delimiting the text in this line.
Line = collections.namedtuple('Line', 'msg start_pos end_pos prepend')
-g_lock = RLock()
-
LINES_NB_LIMIT = 4096
class DummyWin(object):
@@ -69,8 +67,7 @@ class Win(object):
"""
Override if something has to be done on resize
"""
- with g_lock:
- self._resize(height, width, y, x)
+ self._resize(height, width, y, x)
def _refresh(self):
self._win.noutrefresh()
diff --git a/src/windows/data_forms.py b/src/windows/data_forms.py
index 0b27291c..d6e2cc66 100644
--- a/src/windows/data_forms.py
+++ b/src/windows/data_forms.py
@@ -6,7 +6,7 @@ does not inherit from the Win base class), as it will create the
others when needed.
"""
-from . import g_lock, Win
+from . import Win
from . inputs import Input
from theming import to_curses_attr, get_theme
@@ -61,12 +61,11 @@ class ColoredLabel(Win):
self.refresh()
def refresh(self):
- with g_lock:
- self._win.erase()
- self._win.attron(to_curses_attr(self.color))
- self.addstr(0, 0, self.text)
- self._win.attroff(to_curses_attr(self.color))
- self._refresh()
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ self.addstr(0, 0, self.text)
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
class DummyInput(FieldInput, Win):
@@ -100,19 +99,18 @@ class BooleanWin(FieldInput, Win):
self.refresh()
def refresh(self):
- with g_lock:
- self._win.erase()
- self._win.attron(to_curses_attr(self.color))
- self.addnstr(0, 0, ' '*(8), self.width)
- self.addstr(0, 2, "%s"%self.value)
- self.addstr(0, 8, '→')
- self.addstr(0, 0, '←')
- if self.last_key == 'KEY_RIGHT':
- self.addstr(0, 8, '')
- else:
- self.addstr(0, 0, '')
- self._win.attroff(to_curses_attr(self.color))
- self._refresh()
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ self.addnstr(0, 0, ' '*(8), self.width)
+ self.addstr(0, 2, "%s"%self.value)
+ self.addstr(0, 8, '→')
+ self.addstr(0, 0, '←')
+ if self.last_key == 'KEY_RIGHT':
+ self.addstr(0, 8, '')
+ else:
+ self.addstr(0, 0, '')
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
def reply(self):
self._field['label'] = ''
@@ -166,18 +164,17 @@ class TextMultiWin(FieldInput, Win):
def refresh(self):
if not self.edition_input:
- with g_lock:
- self._win.erase()
- self._win.attron(to_curses_attr(self.color))
- self.addnstr(0, 0, ' '*self.width, self.width)
- option = self.options[self.val_pos]
- self.addstr(0, self.width//2-len(option)//2, option)
- if self.val_pos > 0:
- self.addstr(0, 0, '←')
- if self.val_pos < len(self.options)-1:
- self.addstr(0, self.width-1, '→')
- self._win.attroff(to_curses_attr(self.color))
- self._refresh()
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ self.addnstr(0, 0, ' '*self.width, self.width)
+ option = self.options[self.val_pos]
+ self.addstr(0, self.width//2-len(option)//2, option)
+ if self.val_pos > 0:
+ self.addstr(0, 0, '←')
+ if self.val_pos < len(self.options)-1:
+ self.addstr(0, self.width-1, '→')
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
else:
self.edition_input.refresh()
@@ -219,20 +216,19 @@ class ListMultiWin(FieldInput, Win):
self.refresh()
def refresh(self):
- with g_lock:
- self._win.erase()
- self._win.attron(to_curses_attr(self.color))
- self.addnstr(0, 0, ' '*self.width, self.width)
- if self.val_pos > 0:
- self.addstr(0, 0, '←')
- if self.val_pos < len(self.options)-1:
- self.addstr(0, self.width-1, '→')
- if self.options:
- option = self.options[self.val_pos]
- self.addstr(0, self.width//2-len(option)//2, option[0]['label'])
- self.addstr(0, 2, '✔' if option[1] else '☐')
- self._win.attroff(to_curses_attr(self.color))
- self._refresh()
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ self.addnstr(0, 0, ' '*self.width, self.width)
+ if self.val_pos > 0:
+ self.addstr(0, 0, '←')
+ if self.val_pos < len(self.options)-1:
+ self.addstr(0, self.width-1, '→')
+ if self.options:
+ option = self.options[self.val_pos]
+ self.addstr(0, self.width//2-len(option)//2, option[0]['label'])
+ self.addstr(0, 2, '✔' if option[1] else '☐')
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
def reply(self):
self._field['label'] = ''
@@ -267,19 +263,18 @@ class ListSingleWin(FieldInput, Win):
self.refresh()
def refresh(self):
- with g_lock:
- self._win.erase()
- self._win.attron(to_curses_attr(self.color))
- self.addnstr(0, 0, ' '*self.width, self.width)
- if self.val_pos > 0:
- self.addstr(0, 0, '←')
- if self.val_pos < len(self.options)-1:
- self.addstr(0, self.width-1, '→')
- if self.options:
- option = self.options[self.val_pos]['label']
- self.addstr(0, self.width//2-len(option)//2, option)
- self._win.attroff(to_curses_attr(self.color))
- self._refresh()
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ self.addnstr(0, 0, ' '*self.width, self.width)
+ if self.val_pos > 0:
+ self.addstr(0, 0, '←')
+ if self.val_pos < len(self.options)-1:
+ self.addstr(0, self.width-1, '→')
+ if self.options:
+ option = self.options[self.val_pos]['label']
+ self.addstr(0, self.width//2-len(option)//2, option)
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
def reply(self):
self._field['label'] = ''
@@ -310,19 +305,18 @@ class TextPrivateWin(TextSingleWin):
TextSingleWin.__init__(self, field)
def rewrite_text(self):
- with g_lock:
- self._win.erase()
- if self.color:
- self._win.attron(to_curses_attr(self.color))
- self.addstr('*'*len(self.text[self.view_pos:self.view_pos+self.width-1]))
- if self.color:
- (y, x) = self._win.getyx()
- size = self.width-x
- self.addnstr(' '*size, size, to_curses_attr(self.color))
- self.addstr(0, self.pos, '')
- if self.color:
- self._win.attroff(to_curses_attr(self.color))
- self._refresh()
+ self._win.erase()
+ if self.color:
+ self._win.attron(to_curses_attr(self.color))
+ self.addstr('*'*len(self.text[self.view_pos:self.view_pos+self.width-1]))
+ if self.color:
+ (y, x) = self._win.getyx()
+ size = self.width-x
+ self.addnstr(' '*size, size, to_curses_attr(self.color))
+ self.addstr(0, self.pos, '')
+ if self.color:
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
def get_help_message(self):
return 'Edit the secret text'
@@ -346,8 +340,7 @@ class FormWin(object):
}
def __init__(self, form, height, width, y, x):
self._form = form
- with g_lock:
- self._win = Win._tab_win.derwin(height, width, y, x)
+ self._win = Win._tab_win.derwin(height, width, y, x)
self.scroll_pos = 0
self.current_input = 0
self.inputs = [] # dict list
@@ -370,8 +363,7 @@ class FormWin(object):
def resize(self, height, width, y, x):
self.height = height
self.width = width
- with g_lock:
- self._win = Win._tab_win.derwin(height, width, y, x)
+ self._win = Win._tab_win.derwin(height, width, y, x)
# Adjust the scroll position, if resizing made the window too small
# for the cursor to be visible
while self.current_input - self.scroll_pos > self.height-1:
@@ -443,19 +435,18 @@ class FormWin(object):
self.inputs[self.current_input]['input'].do_command(key)
def refresh(self):
- with g_lock:
- self._win.erase()
- y = -self.scroll_pos
- i = 0
- for name, field in self._form.getFields().items():
- if field['type'] == 'hidden':
- continue
- self.inputs[i]['label'].resize(1, self.width//2, y + 1, 0)
- self.inputs[i]['input'].resize(1, self.width//2, y+1, self.width//2)
- # TODO: display the field description
- y += 1
- i += 1
- self._win.refresh()
+ self._win.erase()
+ y = -self.scroll_pos
+ i = 0
+ for name, field in self._form.getFields().items():
+ if field['type'] == 'hidden':
+ continue
+ self.inputs[i]['label'].resize(1, self.width//2, y + 1, 0)
+ self.inputs[i]['input'].resize(1, self.width//2, y+1, self.width//2)
+ # TODO: display the field description
+ y += 1
+ i += 1
+ self._win.refresh()
for i, inp in enumerate(self.inputs):
if i < self.scroll_pos:
continue
diff --git a/src/windows/info_bar.py b/src/windows/info_bar.py
index cea4702f..e66343c5 100644
--- a/src/windows/info_bar.py
+++ b/src/windows/info_bar.py
@@ -12,7 +12,7 @@ import curses
from config import config
-from . import Win, g_lock
+from . import Win
from theming import get_theme, to_curses_attr
class GlobalInfoBar(Win):
@@ -21,46 +21,45 @@ class GlobalInfoBar(Win):
def refresh(self):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- self.addstr(0, 0, "[", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self._win.erase()
+ self.addstr(0, 0, "[", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- create_gaps = config.get('create_gaps')
- show_names = config.get('show_tab_names')
- show_nums = config.get('show_tab_numbers')
- use_nicks = config.get('use_tab_nicks')
- # ignore any remaining gap tabs if the feature is not enabled
- if create_gaps:
- sorted_tabs = self.core.tabs[:]
- else:
- sorted_tabs = [tab for tab in self.core.tabs if tab]
+ create_gaps = config.get('create_gaps')
+ show_names = config.get('show_tab_names')
+ show_nums = config.get('show_tab_numbers')
+ use_nicks = config.get('use_tab_nicks')
+ # ignore any remaining gap tabs if the feature is not enabled
+ if create_gaps:
+ sorted_tabs = self.core.tabs[:]
+ else:
+ sorted_tabs = [tab for tab in self.core.tabs if tab]
- for nb, tab in enumerate(sorted_tabs):
- if not tab: continue
- color = tab.color
- if not config.get('show_inactive_tabs') and\
- color is get_theme().COLOR_TAB_NORMAL:
- continue
- try:
- if show_nums or not show_names:
- self.addstr("%s" % str(nb), to_curses_attr(color))
- if show_names:
- self.addstr(' ', to_curses_attr(color))
+ for nb, tab in enumerate(sorted_tabs):
+ if not tab: continue
+ color = tab.color
+ if not config.get('show_inactive_tabs') and\
+ color is get_theme().COLOR_TAB_NORMAL:
+ continue
+ try:
+ if show_nums or not show_names:
+ self.addstr("%s" % str(nb), to_curses_attr(color))
if show_names:
- if use_nicks:
- self.addstr("%s" % str(tab.get_nick()), to_curses_attr(color))
- else:
- self.addstr("%s" % tab.name, to_curses_attr(color))
- self.addstr("|", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- except: # end of line
- break
- (y, x) = self._win.getyx()
- self.addstr(y, x-1, '] ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- (y, x) = self._win.getyx()
- remaining_size = self.width - x
- self.addnstr(' '*remaining_size, remaining_size,
- to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- self._refresh()
+ self.addstr(' ', to_curses_attr(color))
+ if show_names:
+ if use_nicks:
+ self.addstr("%s" % str(tab.get_nick()), to_curses_attr(color))
+ else:
+ self.addstr("%s" % tab.name, to_curses_attr(color))
+ self.addstr("|", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ except: # end of line
+ break
+ (y, x) = self._win.getyx()
+ self.addstr(y, x-1, '] ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ (y, x) = self._win.getyx()
+ remaining_size = self.width - x
+ self.addnstr(' '*remaining_size, remaining_size,
+ to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self._refresh()
class VerticalGlobalInfoBar(Win):
def __init__(self, scr):
@@ -68,42 +67,39 @@ class VerticalGlobalInfoBar(Win):
self._win = scr
def refresh(self):
- with g_lock:
- height, width = self._win.getmaxyx()
- self._win.erase()
- sorted_tabs = [tab for tab in self.core.tabs if tab]
- if not config.get('show_inactive_tabs'):
- sorted_tabs = [tab for tab in sorted_tabs if\
- tab.vertical_color != get_theme().COLOR_VERTICAL_TAB_NORMAL]
- nb_tabs = len(sorted_tabs)
- use_nicks = config.get('use_tab_nicks')
- if nb_tabs >= height:
- for y, tab in enumerate(sorted_tabs):
- if tab.vertical_color == get_theme().COLOR_VERTICAL_TAB_CURRENT:
- pos = y
- break
- # center the current tab as much as possible
- if pos < height//2:
- sorted_tabs = sorted_tabs[:height]
- elif nb_tabs - pos <= height//2:
- sorted_tabs = sorted_tabs[-height:]
- else:
- sorted_tabs = sorted_tabs[pos-height//2 : pos+height//2]
+ height, width = self._win.getmaxyx()
+ self._win.erase()
+ sorted_tabs = [tab for tab in self.core.tabs if tab]
+ if not config.get('show_inactive_tabs'):
+ sorted_tabs = [tab for tab in sorted_tabs if\
+ tab.vertical_color != get_theme().COLOR_VERTICAL_TAB_NORMAL]
+ nb_tabs = len(sorted_tabs)
+ use_nicks = config.get('use_tab_nicks')
+ if nb_tabs >= height:
for y, tab in enumerate(sorted_tabs):
- color = tab.vertical_color
-
- if not config.get('vertical_tab_list_sort') != 'asc':
- y = height - y - 1
- self.addstr(y, 0, "%2d" % tab.nb,
- to_curses_attr(get_theme().COLOR_VERTICAL_TAB_NUMBER))
- self.addstr('.')
- if use_nicks:
- self.addnstr("%s" % tab.get_nick(), width - 4, to_curses_attr(color))
- else:
- self.addnstr("%s" % tab.name, width - 4, to_curses_attr(color))
- separator = to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)
- self._win.attron(separator)
- self._win.vline(0, width-1, curses.ACS_VLINE, height)
- self._win.attroff(separator)
- self._refresh()
-
+ if tab.vertical_color == get_theme().COLOR_VERTICAL_TAB_CURRENT:
+ pos = y
+ break
+ # center the current tab as much as possible
+ if pos < height//2:
+ sorted_tabs = sorted_tabs[:height]
+ elif nb_tabs - pos <= height//2:
+ sorted_tabs = sorted_tabs[-height:]
+ else:
+ sorted_tabs = sorted_tabs[pos-height//2 : pos+height//2]
+ for y, tab in enumerate(sorted_tabs):
+ color = tab.vertical_color
+ if not config.get('vertical_tab_list_sort') != 'asc':
+ y = height - y - 1
+ self.addstr(y, 0, "%2d" % tab.nb,
+ to_curses_attr(get_theme().COLOR_VERTICAL_TAB_NUMBER))
+ self.addstr('.')
+ if use_nicks:
+ self.addnstr("%s" % tab.get_nick(), width - 4, to_curses_attr(color))
+ else:
+ self.addnstr("%s" % tab.name, width - 4, to_curses_attr(color))
+ separator = to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)
+ self._win.attron(separator)
+ self._win.vline(0, width-1, curses.ACS_VLINE, height)
+ self._win.attroff(separator)
+ self._refresh()
diff --git a/src/windows/info_wins.py b/src/windows/info_wins.py
index 7c659a6c..766afb75 100644
--- a/src/windows/info_wins.py
+++ b/src/windows/info_wins.py
@@ -8,7 +8,7 @@ log = logging.getLogger(__name__)
from common import safeJID
-from . import Win, g_lock
+from . import Win
from . funcs import truncate_nick
from theming import get_theme, to_curses_attr
@@ -39,17 +39,16 @@ class XMLInfoWin(InfoWin):
def refresh(self, filter_t='', filter='', window=None):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- bar = to_curses_attr(get_theme().COLOR_INFORMATION_BAR)
- if not filter_t:
- self.addstr('[No filter]', bar)
- else:
- info = '[%s] %s' % (filter_t, filter)
- self.addstr(info, bar)
- self.print_scroll_position(window)
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ bar = to_curses_attr(get_theme().COLOR_INFORMATION_BAR)
+ if not filter_t:
+ self.addstr('[No filter]', bar)
+ else:
+ info = '[%s] %s' % (filter_t, filter)
+ self.addstr(info, bar)
+ self.print_scroll_position(window)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
class PrivateInfoWin(InfoWin):
"""
@@ -61,14 +60,13 @@ class PrivateInfoWin(InfoWin):
def refresh(self, name, window, chatstate, informations):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- self.write_room_name(name)
- self.print_scroll_position(window)
- self.write_chatstate(chatstate)
- self.write_additional_informations(informations, name)
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ self.write_room_name(name)
+ self.print_scroll_position(window)
+ self.write_chatstate(chatstate)
+ self.write_additional_informations(informations, name)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
def write_additional_informations(self, informations, jid):
"""
@@ -100,16 +98,15 @@ class MucListInfoWin(InfoWin):
def refresh(self, name=None, window=None):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- if name:
- self.addstr(name, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- else:
- self.addstr(self.message, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- if window:
- self.print_scroll_position(window)
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ if name:
+ self.addstr(name, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ else:
+ self.addstr(self.message, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ if window:
+ self.print_scroll_position(window)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
class ConversationInfoWin(InfoWin):
"""
@@ -138,16 +135,15 @@ class ConversationInfoWin(InfoWin):
# If contact is a Contact, then
# resource can now be a Resource: user is in the roster and online
# or resource is None: user is in the roster but offline
- with g_lock:
- self._win.erase()
- self.write_contact_jid(jid)
- self.write_contact_informations(contact)
- self.write_resource_information(resource)
- self.print_scroll_position(window)
- self.write_chatstate(chatstate)
- self.write_additional_informations(informations, jid)
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ self.write_contact_jid(jid)
+ self.write_contact_informations(contact)
+ self.write_resource_information(resource)
+ self.print_scroll_position(window)
+ self.write_chatstate(chatstate)
+ self.write_additional_informations(informations, jid)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
def write_additional_informations(self, informations, jid):
"""
@@ -217,17 +213,16 @@ class MucInfoWin(InfoWin):
def refresh(self, room, window=None):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- self.write_room_name(room)
- self.write_participants_number(room)
- self.write_own_nick(room)
- self.write_disconnected(room)
- self.write_role(room)
- if window:
- self.print_scroll_position(window)
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ self.write_room_name(room)
+ self.write_participants_number(room)
+ self.write_own_nick(room)
+ self.write_disconnected(room)
+ self.write_role(room)
+ if window:
+ self.print_scroll_position(window)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
def write_room_name(self, room):
self.addstr('[', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
@@ -289,12 +284,11 @@ class ConversationStatusMessageWin(InfoWin):
resource = contact.get_highest_priority_resource()
else:
resource = None
- with g_lock:
- self._win.erase()
- if resource:
- self.write_status_message(resource)
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ if resource:
+ self.write_status_message(resource)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
def write_status_message(self, resource):
self.addstr(resource.status, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
diff --git a/src/windows/input_placeholders.py b/src/windows/input_placeholders.py
index 6ede6b32..8bcf1524 100644
--- a/src/windows/input_placeholders.py
+++ b/src/windows/input_placeholders.py
@@ -7,7 +7,7 @@ import logging
log = logging.getLogger(__name__)
-from . import Win, g_lock
+from . import Win
from theming import get_theme, to_curses_attr
@@ -25,11 +25,10 @@ class HelpText(Win):
log.debug('Refresh: %s', self.__class__.__name__)
if txt:
self.txt = txt
- with g_lock:
- self._win.erase()
- self.addstr(0, 0, self.txt[:self.width-1], to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- self.finish_line(get_theme().COLOR_INFORMATION_BAR)
- self._refresh()
+ self._win.erase()
+ self.addstr(0, 0, self.txt[:self.width-1], to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
def do_command(self, key, raw=False):
return False
@@ -61,11 +60,10 @@ class YesNoInput(Win):
log.debug('Refresh: %s', self.__class__.__name__)
if txt:
self.txt = txt
- with g_lock:
- self._win.erase()
- self.addstr(0, 0, self.txt[:self.width-1], to_curses_attr(get_theme().COLOR_WARNING_PROMPT))
- self.finish_line(get_theme().COLOR_WARNING_PROMPT)
- self._refresh()
+ self._win.erase()
+ self.addstr(0, 0, self.txt[:self.width-1], to_curses_attr(get_theme().COLOR_WARNING_PROMPT))
+ self.finish_line(get_theme().COLOR_WARNING_PROMPT)
+ self._refresh()
def do_command(self, key, raw=False):
if key.lower() in self.key_func:
@@ -73,11 +71,14 @@ class YesNoInput(Win):
def prompt(self):
"""Monopolizes the input while waiting for a recognized keypress"""
- cl = []
- while self.value is None:
- if len(cl) == 1 and cl[0] in self.key_func:
- self.key_func[cl[0]]()
- cl = self.core.read_keyboard()
+ def cb(key):
+ if key in self.key_func:
+ self.key_func[key]()
+ if self.value is None:
+ # We didn’t finish with this prompt, continue monopolizing
+ # it again until value is set
+ keyboard.continuation_keys_callback = cb
+ keyboard.continuation_keys_callback = cb
def on_delete(self):
return
diff --git a/src/windows/inputs.py b/src/windows/inputs.py
index 8e1673e1..d345443b 100644
--- a/src/windows/inputs.py
+++ b/src/windows/inputs.py
@@ -8,9 +8,10 @@ log = logging.getLogger(__name__)
import curses
import string
+import keyboard
import common
import poopt
-from . import Win, g_lock
+from . import Win
from . base_wins import format_chars
from . funcs import find_first_format_char
from config import config
@@ -494,25 +495,24 @@ class Input(Win):
length of text to display, and the position of the cursor.
"""
self.adjust_view_pos()
- with g_lock:
- text = self.text
- self._win.erase()
- if self.color:
- self._win.attron(to_curses_attr(self.color))
- displayed_text = text[self.view_pos:self.view_pos+self.width-1].replace('\t', '\x18')
- self._win.attrset(0)
- self.addstr_colored_lite(displayed_text)
- # Fill the rest of the line with the input color
- if self.color:
- (_, x) = self._win.getyx()
- size = self.width - x
- self.addnstr(' ' * size, size, to_curses_attr(self.color))
- self.addstr(0,
- poopt.wcswidth(displayed_text[:self.pos-self.view_pos]), '')
- if self.color:
- self._win.attroff(to_curses_attr(self.color))
- curses.curs_set(1)
- self._refresh()
+ text = self.text
+ self._win.erase()
+ if self.color:
+ self._win.attron(to_curses_attr(self.color))
+ displayed_text = text[self.view_pos:self.view_pos+self.width-1].replace('\t', '\x18')
+ self._win.attrset(0)
+ self.addstr_colored_lite(displayed_text)
+ # Fill the rest of the line with the input color
+ if self.color:
+ (_, x) = self._win.getyx()
+ size = self.width - x
+ self.addnstr(' ' * size, size, to_curses_attr(self.color))
+ self.addstr(0,
+ poopt.wcswidth(displayed_text[:self.pos-self.view_pos]), '')
+ if self.color:
+ self._win.attroff(to_curses_attr(self.color))
+ curses.curs_set(1)
+ self._refresh()
def adjust_view_pos(self):
"""
@@ -656,11 +656,12 @@ class MessageInput(HistoryInput):
"""
Read one more char (c), add the corresponding char from formats_char to the text string
"""
- attr_char = self.core.read_keyboard()[0]
- if attr_char in self.text_attributes:
- char = format_chars[self.text_attributes.index(attr_char)]
- self.do_command(char, False)
- self.rewrite_text()
+ def cb(attr_char):
+ if attr_char in self.text_attributes:
+ char = format_chars[self.text_attributes.index(attr_char)]
+ self.do_command(char, False)
+ self.rewrite_text()
+ keyboard.continuation_keys_callback = cb
def key_enter(self):
if self.history_enter():
diff --git a/src/windows/list.py b/src/windows/list.py
index 3cfb8af5..677df6ff 100644
--- a/src/windows/list.py
+++ b/src/windows/list.py
@@ -7,7 +7,7 @@ log = logging.getLogger(__name__)
import curses
-from . import Win, g_lock
+from . import Win
from theming import to_curses_attr, get_theme
@@ -86,26 +86,26 @@ class ListWin(Win):
def refresh(self):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- lines = self.lines[self._starting_pos:self._starting_pos+self.height]
- for y, line in enumerate(lines):
- x = 0
- for col in self._columns.items():
- try:
- txt = line[col[1]] or ''
- except KeyError:
- txt = ''
- size = self._columns_sizes[col[0]]
- txt += ' ' * (size-len(txt))
- if not txt:
- continue
- if line is self.lines[self._selected_row]:
- self.addstr(y, x, txt[:size], to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- else:
- self.addstr(y, x, txt[:size])
- x += size
- self._refresh()
+ self._win.erase()
+ lines = self.lines[self._starting_pos:self._starting_pos+self.height]
+ for y, line in enumerate(lines):
+ x = 0
+ for col in self._columns.items():
+ try:
+ txt = line[col[1]] or ''
+ except KeyError:
+ txt = ''
+ size = self._columns_sizes[col[0]]
+ txt += ' ' * (size-len(txt))
+ if not txt:
+ continue
+ if line is self.lines[self._selected_row]:
+ self.addstr(y, x, txt[:size],
+ to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ else:
+ self.addstr(y, x, txt[:size])
+ x += size
+ self._refresh()
def move_cursor_down(self):
"""
@@ -175,25 +175,24 @@ class ColumnHeaderWin(Win):
def refresh(self):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- x = 0
- for col in self._columns:
- txt = col
- if col in self._column_order:
- if self._column_order_asc:
- txt += get_theme().CHAR_COLUMN_ASC
- else:
- txt += get_theme().CHAR_COLUMN_DESC
- #⇓⇑↑↓⇧⇩▲▼
- size = self._columns_sizes[col]
- txt += ' ' * (size-len(txt))
- if col in self._column_sel:
- self.addstr(0, x, txt, to_curses_attr(get_theme().COLOR_COLUMN_HEADER_SEL))
+ self._win.erase()
+ x = 0
+ for col in self._columns:
+ txt = col
+ if col in self._column_order:
+ if self._column_order_asc:
+ txt += get_theme().CHAR_COLUMN_ASC
else:
- self.addstr(0, x, txt, to_curses_attr(get_theme().COLOR_COLUMN_HEADER))
- x += size
- self._refresh()
+ txt += get_theme().CHAR_COLUMN_DESC
+ #⇓⇑↑↓⇧⇩▲▼
+ size = self._columns_sizes[col]
+ txt += ' ' * (size-len(txt))
+ if col in self._column_sel:
+ self.addstr(0, x, txt, to_curses_attr(get_theme().COLOR_COLUMN_HEADER_SEL))
+ else:
+ self.addstr(0, x, txt, to_curses_attr(get_theme().COLOR_COLUMN_HEADER))
+ x += size
+ self._refresh()
def sel_column(self, dic):
self._column_sel = dic
diff --git a/src/windows/misc.py b/src/windows/misc.py
index 0f6bce59..07c91bbd 100644
--- a/src/windows/misc.py
+++ b/src/windows/misc.py
@@ -7,7 +7,7 @@ log = logging.getLogger(__name__)
import curses
-from . import Win, g_lock
+from . import Win
from theming import get_theme, to_curses_attr
class VerticalSeparator(Win):
@@ -19,9 +19,9 @@ class VerticalSeparator(Win):
Win.__init__(self)
def rewrite_line(self):
- with g_lock:
- self._win.vline(0, 0, curses.ACS_VLINE, self.height, to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
- self._refresh()
+ self._win.vline(0, 0, curses.ACS_VLINE, self.height,
+ to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
+ self._refresh()
def refresh(self):
log.debug('Refresh: %s', self.__class__.__name__)
@@ -53,9 +53,8 @@ class SimpleTextWin(Win):
def refresh(self):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- for y, line in enumerate(self.built_lines):
- self.addstr_colored(line, y, 0)
- self._refresh()
+ self._win.erase()
+ for y, line in enumerate(self.built_lines):
+ self.addstr_colored(line, y, 0)
+ self._refresh()
diff --git a/src/windows/muc.py b/src/windows/muc.py
index cd594c4c..7e3541ba 100644
--- a/src/windows/muc.py
+++ b/src/windows/muc.py
@@ -7,7 +7,7 @@ log = logging.getLogger(__name__)
import curses
-from . import Win, g_lock
+from . import Win
import poopt
from config import config
@@ -36,44 +36,43 @@ class UserList(Win):
log.debug('Refresh: %s', self.__class__.__name__)
if config.get('hide_user_list'):
return # do not refresh if this win is hidden.
- with g_lock:
- self._win.erase()
+ self._win.erase()
+ if config.get('user_list_sort').lower() == 'asc':
+ y, x = self._win.getmaxyx()
+ y -= 1
+ users = sorted(users)
+ else:
+ y = 0
+ users = sorted(users)
+
+ if len(users) < self.height:
+ self.pos = 0
+ elif self.pos >= len(users) - self.height and self.pos != 0:
+ self.pos = len(users) - self.height
+ for user in users[self.pos:]:
+ self.draw_role_affiliation(y, user)
+ self.draw_status_chatstate(y, user)
+ self.addstr(y, 2,
+ poopt.cut_by_columns(user.nick, self.width - 2),
+ to_curses_attr(user.color))
if config.get('user_list_sort').lower() == 'asc':
- y, x = self._win.getmaxyx()
y -= 1
- users = sorted(users)
else:
- y = 0
- users = sorted(users)
-
- if len(users) < self.height:
- self.pos = 0
- elif self.pos >= len(users) - self.height and self.pos != 0:
- self.pos = len(users) - self.height
- for user in users[self.pos:]:
- self.draw_role_affiliation(y, user)
- self.draw_status_chatstate(y, user)
- self.addstr(y, 2,
- poopt.cut_by_columns(user.nick, self.width - 2),
- to_curses_attr(user.color))
- if config.get('user_list_sort').lower() == 'asc':
- y -= 1
- else:
- y += 1
- if y == self.height:
- break
- # draw indicators of position in the list
- if self.pos > 0:
- if config.get('user_list_sort').lower() == 'asc':
- self.draw_plus(self.height-1)
- else:
- self.draw_plus(0)
- if self.pos + self.height < len(users):
- if config.get('user_list_sort').lower() == 'asc':
- self.draw_plus(0)
- else:
- self.draw_plus(self.height-1)
- self._refresh()
+ y += 1
+ if y == self.height:
+ break
+ # draw indicators of position in the list
+ if self.pos > 0:
+ if config.get('user_list_sort').lower() == 'asc':
+ self.draw_plus(self.height-1)
+ else:
+ self.draw_plus(0)
+ if self.pos + self.height < len(users):
+ if config.get('user_list_sort').lower() == 'asc':
+ self.draw_plus(0)
+ else:
+ self.draw_plus(self.height-1)
+ self._refresh()
def draw_role_affiliation(self, y, user):
theme = get_theme()
@@ -94,12 +93,11 @@ class UserList(Win):
self.addstr(y, 0, char, to_curses_attr(show_col))
def resize(self, height, width, y, x):
- with g_lock:
- separator = to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)
- self._resize(height, width, y, x)
- self._win.attron(separator)
- self._win.vline(0, 0, curses.ACS_VLINE, self.height)
- self._win.attroff(separator)
+ separator = to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)
+ self._resize(height, width, y, x)
+ self._win.attron(separator)
+ self._win.vline(0, 0, curses.ACS_VLINE, self.height)
+ self._win.attroff(separator)
class Topic(Win):
def __init__(self):
@@ -108,19 +106,18 @@ class Topic(Win):
def refresh(self, topic=None):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- if topic:
- msg = topic[:self.width-1]
- else:
- msg = self._message[:self.width-1]
- self.addstr(0, 0, msg, to_curses_attr(get_theme().COLOR_TOPIC_BAR))
- (y, x) = self._win.getyx()
- remaining_size = self.width - x
- if remaining_size:
- self.addnstr(' '*remaining_size, remaining_size,
- to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- self._refresh()
+ self._win.erase()
+ if topic:
+ msg = topic[:self.width-1]
+ else:
+ msg = self._message[:self.width-1]
+ self.addstr(0, 0, msg, to_curses_attr(get_theme().COLOR_TOPIC_BAR))
+ (y, x) = self._win.getyx()
+ remaining_size = self.width - x
+ if remaining_size:
+ self.addnstr(' '*remaining_size, remaining_size,
+ to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self._refresh()
def set_message(self, message):
self._message = message
diff --git a/src/windows/roster_win.py b/src/windows/roster_win.py
index d98f27ce..6ecb6128 100644
--- a/src/windows/roster_win.py
+++ b/src/windows/roster_win.py
@@ -7,7 +7,7 @@ log = logging.getLogger(__name__)
from datetime import datetime
-from . import Win, g_lock
+from . import Win
import common
from config import config
@@ -89,39 +89,39 @@ class RosterWin(Win):
"""
Regenerates the roster cache if needed
"""
- with g_lock:
- if roster.needs_rebuild:
- log.debug('The roster has changed, rebuilding the cache…')
- # This is a search
- if roster.contact_filter:
- self.roster_cache = []
- sort = config.get('roster_sort') or 'jid:show'
- for contact in roster.get_contacts_sorted_filtered(sort):
- self.roster_cache.append(contact)
- else:
- show_offline = config.get('roster_show_offline') or roster.contact_filter
- sort = config.get('roster_sort') or 'jid:show'
- group_sort = config.get('roster_group_sort') or 'name'
- self.roster_cache = []
- # build the cache
- for group in roster.get_groups(group_sort):
- contacts_filtered = group.get_contacts(roster.contact_filter)
- if (not show_offline and group.get_nb_connected_contacts() == 0) or not contacts_filtered:
- continue # Ignore empty groups
- self.roster_cache.append(group)
- if group.folded:
- continue # ignore folded groups
- for contact in group.get_contacts(roster.contact_filter, sort):
- if not show_offline and len(contact) == 0:
- continue # ignore offline contacts
- self.roster_cache.append(contact)
- if not contact.folded(group.name):
- for resource in contact.get_resources():
- self.roster_cache.append(resource)
- roster.last_built = datetime.now()
- if self.selected_row in self.roster_cache:
- if self.pos < self.roster_len and self.roster_cache[self.pos] != self.selected_row:
- self.pos = self.roster_cache.index(self.selected_row)
+ if not roster.needs_rebuild:
+ return
+ log.debug('The roster has changed, rebuilding the cache…')
+ # This is a search
+ if roster.contact_filter:
+ self.roster_cache = []
+ sort = config.get('roster_sort', 'jid:show') or 'jid:show'
+ for contact in roster.get_contacts_sorted_filtered(sort):
+ self.roster_cache.append(contact)
+ else:
+ show_offline = config.get('roster_show_offline') or roster.contact_filter
+ sort = config.get('roster_sort') or 'jid:show'
+ group_sort = config.get('roster_group_sort') or 'name'
+ self.roster_cache = []
+ # build the cache
+ for group in roster.get_groups(group_sort):
+ contacts_filtered = group.get_contacts(roster.contact_filter)
+ if (not show_offline and group.get_nb_connected_contacts() == 0) or not contacts_filtered:
+ continue # Ignore empty groups
+ self.roster_cache.append(group)
+ if group.folded:
+ continue # ignore folded groups
+ for contact in group.get_contacts(roster.contact_filter, sort):
+ if not show_offline and len(contact) == 0:
+ continue # ignore offline contacts
+ self.roster_cache.append(contact)
+ if not contact.folded(group.name):
+ for resource in contact.get_resources():
+ self.roster_cache.append(resource)
+ roster.last_built = datetime.now()
+ if self.selected_row in self.roster_cache:
+ if self.pos < self.roster_len and self.roster_cache[self.pos] != self.selected_row:
+ self.pos = self.roster_cache.index(self.selected_row)
def refresh(self, roster):
"""
@@ -130,43 +130,42 @@ class RosterWin(Win):
"""
log.debug('Refresh: %s', self.__class__.__name__)
self.build_roster_cache(roster)
- with g_lock:
- # make sure we are within bounds
- self.move_cursor_up((self.roster_len + self.pos) if self.pos >= self.roster_len else 0)
- if not self.roster_cache:
- self.selected_row = None
- self._win.erase()
- self._win.move(0, 0)
- self.draw_roster_information(roster)
- y = 1
- group = "none"
- # scroll down if needed
- if self.start_pos+self.height <= self.pos+2:
- self.scroll_down(self.pos - self.start_pos - self.height + (self.height//2))
- # draw the roster from the cache
- roster_view = self.roster_cache[self.start_pos-1:self.start_pos+self.height]
-
- for item in roster_view:
- draw_selected = False
- if y -2 + self.start_pos == self.pos:
- draw_selected = True
- self.selected_row = item
-
- if isinstance(item, RosterGroup):
- self.draw_group(y, item, draw_selected)
- group = item.name
- elif isinstance(item, Contact):
- self.draw_contact_line(y, item, draw_selected, group)
- elif isinstance(item, Resource):
- self.draw_resource_line(y, item, draw_selected)
-
- y += 1
-
- if self.start_pos > 1:
- self.draw_plus(1)
- if self.start_pos + self.height-2 < self.roster_len:
- self.draw_plus(self.height-1)
- self._refresh()
+ # make sure we are within bounds
+ self.move_cursor_up((self.roster_len + self.pos) if self.pos >= self.roster_len else 0)
+ if not self.roster_cache:
+ self.selected_row = None
+ self._win.erase()
+ self._win.move(0, 0)
+ self.draw_roster_information(roster)
+ y = 1
+ group = "none"
+ # scroll down if needed
+ if self.start_pos+self.height <= self.pos+2:
+ self.scroll_down(self.pos - self.start_pos - self.height + (self.height//2))
+ # draw the roster from the cache
+ roster_view = self.roster_cache[self.start_pos-1:self.start_pos+self.height]
+
+ for item in roster_view:
+ draw_selected = False
+ if y -2 + self.start_pos == self.pos:
+ draw_selected = True
+ self.selected_row = item
+
+ if isinstance(item, RosterGroup):
+ self.draw_group(y, item, draw_selected)
+ group = item.name
+ elif isinstance(item, Contact):
+ self.draw_contact_line(y, item, draw_selected, group)
+ elif isinstance(item, Resource):
+ self.draw_resource_line(y, item, draw_selected)
+
+ y += 1
+
+ if self.start_pos > 1:
+ self.draw_plus(1)
+ if self.start_pos + self.height-2 < self.roster_len:
+ self.draw_plus(self.height-1)
+ self._refresh()
def draw_plus(self, y):
@@ -373,13 +372,11 @@ class ContactInfoWin(Win):
def refresh(self, selected_row):
log.debug('Refresh: %s', self.__class__.__name__)
- with g_lock:
- self._win.erase()
- if isinstance(selected_row, RosterGroup):
- self.draw_group_info(selected_row)
- elif isinstance(selected_row, Contact):
- self.draw_contact_info(selected_row)
- # elif isinstance(selected_row, Resource):
- # self.draw_contact_info(None, selected_row)
- self._refresh()
-
+ self._win.erase()
+ if isinstance(selected_row, RosterGroup):
+ self.draw_group_info(selected_row)
+ elif isinstance(selected_row, Contact):
+ self.draw_contact_info(selected_row)
+ # elif isinstance(selected_row, Resource):
+ # self.draw_contact_info(None, selected_row)
+ self._refresh()
diff --git a/src/windows/text_win.py b/src/windows/text_win.py
index f634f5a6..8a8f75ae 100644
--- a/src/windows/text_win.py
+++ b/src/windows/text_win.py
@@ -9,7 +9,7 @@ log = logging.getLogger(__name__)
import curses
from math import ceil, log10
-from . import Win, g_lock
+from . import Win
from . base_wins import FORMAT_CHAR, Line
from . funcs import truncate_nick, parse_attrs
@@ -268,74 +268,73 @@ class TextWin(Win):
else:
lines = self.built_lines[-self.height-self.pos:-self.pos]
with_timestamps = config.get("show_timestamps")
- with g_lock:
- self._win.move(0, 0)
- self._win.erase()
- for y, line in enumerate(lines):
- if line:
- msg = line.msg
- if line.start_pos == 0:
- if msg.nick_color:
- color = msg.nick_color
- elif msg.user:
- color = msg.user.color
- else:
- color = None
- if with_timestamps:
- self.write_time(msg.str_time)
- if msg.ack:
- self.write_ack()
- if msg.me:
- self._win.attron(to_curses_attr(get_theme().COLOR_ME_MESSAGE))
- self.addstr('* ')
- self.write_nickname(msg.nickname, color, msg.highlight)
- if msg.revisions:
- self._win.attron(to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
- self.addstr('%d' % msg.revisions)
- self._win.attrset(0)
- self.addstr(' ')
- else:
- self.write_nickname(msg.nickname, color, msg.highlight)
- if msg.revisions:
- self._win.attron(to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
- self.addstr('%d' % msg.revisions)
- self._win.attrset(0)
- self.addstr('> ')
- if y != self.height-1:
- self.addstr('\n')
- self._win.attrset(0)
- for y, line in enumerate(lines):
- if not line:
- self.write_line_separator(y)
- else:
- offset = 0
- # Offset for the timestamp (if any) plus a space after it
+ self._win.move(0, 0)
+ self._win.erase()
+ for y, line in enumerate(lines):
+ if line:
+ msg = line.msg
+ if line.start_pos == 0:
+ if msg.nick_color:
+ color = msg.nick_color
+ elif msg.user:
+ color = msg.user.color
+ else:
+ color = None
if with_timestamps:
- offset += len(line.msg.str_time)
- if offset:
- offset += 1
-
- # Offset for the nickname (if any)
- # plus a space and a > after it
- if line.msg.nickname:
- offset += poopt.wcswidth(
- truncate_nick(line.msg.nickname))
- if line.msg.me:
- offset += 3
- else:
- offset += 2
- offset += ceil(log10(line.msg.revisions + 1))
-
- if line.msg.ack:
- offset += 1 + poopt.wcswidth(
- get_theme().CHAR_ACK_RECEIVED)
-
- self.write_text(y, offset,
- line.prepend+line.msg.txt[line.start_pos:line.end_pos])
- if y != self.height-1:
- self.addstr('\n')
- self._win.attrset(0)
- self._refresh()
+ self.write_time(msg.str_time)
+ if msg.ack:
+ self.write_ack()
+ if msg.me:
+ self._win.attron(to_curses_attr(get_theme().COLOR_ME_MESSAGE))
+ self.addstr('* ')
+ self.write_nickname(msg.nickname, color, msg.highlight)
+ if msg.revisions:
+ self._win.attron(to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
+ self.addstr('%d' % msg.revisions)
+ self._win.attrset(0)
+ self.addstr(' ')
+ else:
+ self.write_nickname(msg.nickname, color, msg.highlight)
+ if msg.revisions:
+ self._win.attron(to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
+ self.addstr('%d' % msg.revisions)
+ self._win.attrset(0)
+ self.addstr('> ')
+ if y != self.height-1:
+ self.addstr('\n')
+ self._win.attrset(0)
+ for y, line in enumerate(lines):
+ if not line:
+ self.write_line_separator(y)
+ else:
+ offset = 0
+ # Offset for the timestamp (if any) plus a space after it
+ if with_timestamps:
+ offset += len(line.msg.str_time)
+ if offset:
+ offset += 1
+
+ # Offset for the nickname (if any)
+ # plus a space and a > after it
+ if line.msg.nickname:
+ offset += poopt.wcswidth(
+ truncate_nick(line.msg.nickname))
+ if line.msg.me:
+ offset += 3
+ else:
+ offset += 2
+ offset += ceil(log10(line.msg.revisions + 1))
+
+ if line.msg.ack:
+ offset += 1 + poopt.wcswidth(
+ get_theme().CHAR_ACK_RECEIVED)
+
+ self.write_text(y, offset,
+ line.prepend+line.msg.txt[line.start_pos:line.end_pos])
+ if y != self.height-1:
+ self.addstr('\n')
+ self._win.attrset(0)
+ self._refresh()
def write_line_separator(self, y):
char = get_theme().CHAR_NEW_TEXT_SEPARATOR
@@ -387,23 +386,21 @@ class TextWin(Win):
self.addstr(' ')
def resize(self, height, width, y, x, room=None):
- with g_lock:
- if hasattr(self, 'width'):
- old_width = self.width
- else:
- old_width = None
- self._resize(height, width, y, x)
- if room and self.width != old_width:
- self.rebuild_everything(room)
-
- # reposition the scrolling after resize
- # (see #2450)
- buf_size = len(self.built_lines)
- if buf_size - self.pos < self.height:
- self.pos = buf_size - self.height
- if self.pos < 0:
- self.pos = 0
-
+ if hasattr(self, 'width'):
+ old_width = self.width
+ else:
+ old_width = None
+ self._resize(height, width, y, x)
+ if room and self.width != old_width:
+ self.rebuild_everything(room)
+
+ # reposition the scrolling after resize
+ # (see #2450)
+ buf_size = len(self.built_lines)
+ if buf_size - self.pos < self.height:
+ self.pos = buf_size - self.height
+ if self.pos < 0:
+ self.pos = 0
def rebuild_everything(self, room):
self.built_lines = []
diff --git a/src/xhtml.py b/src/xhtml.py
index 69519f8d..01e2dfcd 100644
--- a/src/xhtml.py
+++ b/src/xhtml.py
@@ -17,7 +17,7 @@ import curses
import hashlib
import re
from os import path
-from sleekxmpp.xmlstream import ET
+from slixmpp.xmlstream import ET
from urllib.parse import unquote
from io import BytesIO
diff --git a/update.sh b/update.sh
index 9eed6d17..ae333108 100755
--- a/update.sh
+++ b/update.sh
@@ -13,7 +13,7 @@ error() {
}
echo 'Updating poezio'
-git pull origin master || error poezio
+git pull origin slix || error poezio
make
if [ $? -ne 0 ]
@@ -24,23 +24,15 @@ then
exit -1
fi
-if [ -e "SleekXMPP" ]
+if [ -e "slixmpp" ]
then
- echo "Removing the old SleekXMPP"
- rm -rf SleekXMPP
- rm src/sleekxmpp
- git clone https://github.com/fritzy/SleekXMPP.git Sleek || error SleekXMPP
-fi
-
-if [ -e "Sleek" ]
-then
- echo "Updating SleekXMPP"
- cd Sleek
- git pull || error SleekXMPP
+ echo "Updating slixmpp"
+ cd slixmpp
+ git pull || error slixmpp
cd ..
else
- echo "Downloading SleekXMPP"
- git clone https://github.com/fritzy/SleekXMPP.git Sleek || error SleekXMPP
+ echo "Downloading slixmpp"
+ git clone git://git.louiz.org/slixmpp || error slixmpp
fi
if [ -e ".dnspython.tgz" ]
@@ -69,10 +61,10 @@ else
echo "Creating link src/dns"
ln -s ../dnspython/dns dns
fi
-if [ -h "sleekxmpp" ]
+if [ -h "slixmpp" ]
then
- echo 'Link src/sleekxmpp already exists'
+ echo 'Link src/slixmpp already exists'
else
- echo "Creating link src/sleekxmpp"
- ln -s ../Sleek/sleekxmpp sleekxmpp
+ echo "Creating link src/slixmpp"
+ ln -s ../slixmpp/slixmpp slixmpp
fi