summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormathieui <mathieui@mathieui.net>2014-10-31 19:15:57 +0100
committermathieui <mathieui@mathieui.net>2014-10-31 19:16:44 +0100
commit1c1ab3cb839e5509db52770e10c7190f844eb2e5 (patch)
treeb331060b3dc42651e61e4ccb2dfe6af5e2e97752
parentcedc5a6ec80a46437f42464415fd1806049c593d (diff)
parentea2b703bfd07d293ba9fdd85ac657275d43da2a7 (diff)
downloadpoezio-1c1ab3cb839e5509db52770e10c7190f844eb2e5.tar.gz
poezio-1c1ab3cb839e5509db52770e10c7190f844eb2e5.tar.bz2
poezio-1c1ab3cb839e5509db52770e10c7190f844eb2e5.tar.xz
poezio-1c1ab3cb839e5509db52770e10c7190f844eb2e5.zip
Merge branch 'master' of git.poez.io:poezio into slix
Conflicts: src/bookmark.py src/config.py src/connection.py src/core/commands.py src/core/core.py src/core/handlers.py src/windows/info_bar.py src/windows/muc.py src/windows/roster_win.py src/windows/text_win.py src/xhtml.py
-rw-r--r--.travis.yml7
-rw-r--r--Makefile6
-rw-r--r--data/default_config.cfg8
-rw-r--r--doc/source/configuration.rst27
-rw-r--r--doc/source/install.rst5
-rw-r--r--doc/source/misc/correct.rst2
-rw-r--r--plugins/otr.py12
-rw-r--r--plugins/screen_detach.py7
-rw-r--r--requirements.txt3
-rwxr-xr-xscripts/poezio4
-rwxr-xr-xsetup.py13
-rw-r--r--src/bookmark.py12
-rw-r--r--src/common.py64
-rw-r--r--src/config.py157
-rw-r--r--src/connection.py51
-rw-r--r--src/core/commands.py69
-rw-r--r--src/core/completions.py16
-rw-r--r--src/core/core.py72
-rw-r--r--src/core/handlers.py124
-rw-r--r--src/logger.py12
-rw-r--r--src/plugin.py3
-rw-r--r--src/plugin_manager.py4
-rw-r--r--src/poezio.py1
-rw-r--r--src/roster.py6
-rw-r--r--src/tabs/basetabs.py52
-rw-r--r--src/tabs/conversationtab.py15
-rw-r--r--src/tabs/muctab.py99
-rw-r--r--src/tabs/privatetab.py20
-rw-r--r--src/tabs/rostertab.py4
-rw-r--r--src/text_buffer.py4
-rwxr-xr-xsrc/theming.py11
-rw-r--r--src/windows/funcs.py2
-rw-r--r--src/windows/info_bar.py17
-rw-r--r--src/windows/inputs.py2
-rw-r--r--src/windows/muc.py10
-rw-r--r--src/windows/roster_win.py14
-rw-r--r--src/windows/text_win.py10
-rw-r--r--src/xhtml.py40
-rw-r--r--test/test_common.py79
-rw-r--r--test/test_config.py114
-rw-r--r--test/test_poopt.py14
-rw-r--r--test/test_theming.py26
-rw-r--r--test/test_windows.py76
-rw-r--r--test/test_xhtml.py50
44 files changed, 952 insertions, 392 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..28bf3dd3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: python
+python:
+ - "3.4"
+install:
+ - pip install -r requirements.txt
+ - python setup.py build_ext --inplace
+script: make test
diff --git a/Makefile b/Makefile
index 1782fc9c..7f5eb224 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,10 @@ uninstall:
doc:
make -C doc/ html
+
+test:
+ py.test -v test/
+
pot:
xgettext src/*.py --from-code=utf-8 --keyword=_ -o locale/poezio.pot
@@ -45,4 +49,4 @@ release:
tar cJf poezio-$(version).tar.xz poezio-$(version) && \
tar czf poezio-$(version).tar.gz poezio-$(version)
-.PHONY : doc
+.PHONY : doc test
diff --git a/data/default_config.cfg b/data/default_config.cfg
index c1f766b0..35bc498b 100644
--- a/data/default_config.cfg
+++ b/data/default_config.cfg
@@ -379,6 +379,14 @@ ack_message_receipts = true
# Ask for message delivery receipts (XEP-0184)
request_message_receipts = true
+# Extract base64 images received in XHTML-IM messages
+# if true.
+extract_inline_images = true
+
+# The directory where the images will be saved; if unset,
+# defaults to $XDG_CACHE_HOME/poezio/images.
+tmp_image_dir =
+
# Receive the tune notifications or not (in order to display informations
# in the roster).
# If this is set to false, then the display_tune_notifications
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 32d82f7a..d0ff5bbe 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -316,6 +316,14 @@ to understand what is :ref:`carbons <carbons-details>` or
If this is set to ``false``, you will no longer be subscribed to tune events,
and the :term:`display_tune_notifications` option will be ignored.
+ group_corrections
+
+ **Default value:** ``true``
+
+ Enable a message to “correct” (replace) another message in the display if the
+ sender intended it as such. See :ref:`Message Correction <correct-feature>` for
+ more information.
+
use_bookmark_method
**Default value:** ``[empty]``
@@ -851,6 +859,25 @@ Other
The lang some automated entities will use when replying to you.
+ extract_inline_images
+
+ **Default value:** ``true``
+
+ Some clients send inline images in base64 inside some messages, which results in
+ an useless wall of text. If this option is ``true``, then that base64 text will
+ be replaced with a :file:`file://` link to the image file extracted in
+ :term:`tmp_image_dir` or :file:`$XDG_CACHE_HOME/poezio/images` by default, which
+ is usually :file:`~/.cache/poezio/images`
+
+ tmp_image_dir
+
+ **Default value:** ``[empty]``
+
+ The directory where poezio will save the images received, if
+ :term:`extract_inline_images` is set to true. If unset, poezio
+ will default to :file:`$XDG_CACHE_HOME/poezio/images` which is
+ usually :file:`~/.cache/poezio/images`.
+
muc_history_length
**Default value:** ``50``
diff --git a/doc/source/install.rst b/doc/source/install.rst
index b655894b..dbe5ddfa 100644
--- a/doc/source/install.rst
+++ b/doc/source/install.rst
@@ -80,6 +80,8 @@ Poezio depends on two libraries:
- DNSPython_ (the python3 version, often called dnspython3)
- SleekXMPP_
+Additionally, it needs *python3-setuptools* to install an executable file.
+
If you do not want to install those libraries, you can skip directly to
the :ref:`installation part <poezio-install-label>`
@@ -139,7 +141,8 @@ If you have git installed, it will download and update locally the
libraries for you. (and if you don’t have git installed, install it)
-If you really want to install it, run as root (or sudo in ubuntu or whatever):
+If you really want to install it, first install the *python3-setuptools* package
+in your distribution, then run as root (or sudo in ubuntu or whatever):
.. code-block:: bash
diff --git a/doc/source/misc/correct.rst b/doc/source/misc/correct.rst
index 61100634..fda4abcb 100644
--- a/doc/source/misc/correct.rst
+++ b/doc/source/misc/correct.rst
@@ -1,3 +1,5 @@
+.. _correct-feature:
+
Message Correction
==================
diff --git a/plugins/otr.py b/plugins/otr.py
index c2e5a663..44fdb323 100644
--- a/plugins/otr.py
+++ b/plugins/otr.py
@@ -210,7 +210,7 @@ def hl(tab):
conv_jid = safeJID(tab.name)
if 'private' in config.get('beep_on', 'highlight private').split():
- if not config.get_by_tabname('disable_beep', False, conv_jid.bare, False):
+ if not config.get_by_tabname('disable_beep', conv_jid.bare, default=False):
curses.beep()
class PoezioContext(Context):
@@ -430,11 +430,11 @@ class Plugin(BasePlugin):
jid = safeJID(jid).full
if not jid in self.contexts:
flags = POLICY_FLAGS.copy()
- policy = self.config.get_by_tabname('encryption_policy', 'ondemand', jid).lower()
- logging_policy = self.config.get_by_tabname('log', 'false', jid).lower()
- allow_v2 = self.config.get_by_tabname('allow_v2', 'true', jid).lower()
+ policy = self.config.get_by_tabname('encryption_policy', jid, default='ondemand').lower()
+ logging_policy = self.config.get_by_tabname('log', jid, default='false').lower()
+ allow_v2 = self.config.get_by_tabname('allow_v2', jid, default='true').lower()
flags['ALLOW_V2'] = (allow_v2 != 'false')
- allow_v1 = self.config.get_by_tabname('allow_v1', 'false', jid).lower()
+ allow_v1 = self.config.get_by_tabname('allow_v1', jid, default='false').lower()
flags['ALLOW_V1'] = (allow_v1 == 'true')
self.contexts[jid] = PoezioContext(self.account, jid, self.core.xmpp, self.core)
self.contexts[jid].log = 1 if logging_policy != 'false' else 0
@@ -544,7 +544,7 @@ class Plugin(BasePlugin):
nick_color = get_theme().COLOR_REMOTE_USER
body = txt.decode()
- if self.config.get_by_tabname('decode_xhtml', True, msg['from'].bare):
+ if self.config.get_by_tabname('decode_xhtml', msg['from'].bare, default=True):
try:
body = xhtml.xhtml_to_poezio_colors(body, force=True)
except:
diff --git a/plugins/screen_detach.py b/plugins/screen_detach.py
index 3552a179..53827c11 100644
--- a/plugins/screen_detach.py
+++ b/plugins/screen_detach.py
@@ -7,15 +7,14 @@ import os
import stat
import pyinotify
-SCREEN_DIR = '/var/run/screen/S-%s' % (os.getlogin(),)
-
class Plugin(BasePlugin):
def init(self):
+ screen_dir = '/var/run/screen/S-%s' % (os.getlogin(),)
self.timed_event = None
sock_path = None
self.thread = None
- for f in os.listdir(SCREEN_DIR):
- path = os.path.join(SCREEN_DIR, f)
+ for f in os.listdir(screen_dir):
+ path = os.path.join(screen_dir, f)
if screen_attached(path):
sock_path = path
self.attached = True
diff --git a/requirements.txt b/requirements.txt
index 462dc735..79d2a470 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,8 @@
--e git://github.com/afflux/pure-python-otr.git#egg=potr
+git+git://github.com/afflux/pure-python-otr.git#egg=potr
sleekxmpp==1.2
dnspython3==1.11.1
sphinx==1.2.1
+setuptools
argparse
pyinotify
python-mpd2
diff --git a/scripts/poezio b/scripts/poezio
deleted file mode 100755
index 665edcaa..00000000
--- a/scripts/poezio
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/python3
-
-from poezio import main
-main()
diff --git a/setup.py b/setup.py
index aeac3737..e9e8e4a7 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,13 @@
#!/usr/bin/env python3
+
try:
from setuptools import setup, Extension
except ImportError:
+ print('Setuptools was not found.\n'
+ 'This script will use distutils instead, which will NOT'
+ ' be able to install a `poezio` executable.\nIf you are '
+ 'using it to build a package or install poezio, please '
+ 'install setuptools.\n\nYou will also see a few warnings.\n')
from distutils.core import setup, Extension
import os
@@ -53,11 +59,12 @@ setup(name="poezio",
'poezio_plugins', 'poezio_plugins.gpg', 'poezio_themes'],
package_dir = {'poezio': 'src', 'poezio_plugins': 'plugins', 'poezio_themes': 'data/themes'},
package_data = {'poezio': ['default_config.cfg']},
- scripts = ['scripts/poezio', 'scripts/poezio_gpg_export'],
+ scripts = ['scripts/poezio_gpg_export'],
+ entry_points={ 'console_scripts': [ 'poezio = poezio:main' ] },
data_files = [('share/man/man1/', ['data/poezio.1'])],
- install_requires = ['sleekxmpp==1.2.4',
- 'dnspython3>=1.11.1'],
+ install_requires = ['sleekxmpp>=1.2.4',
+ 'dnspython3>=1.10.0'],
extras_require = {'OTR plugin': 'python-potr>=1.0',
'Screen autoaway plugin': 'pyinotify==0.9.4'}
)
diff --git a/src/bookmark.py b/src/bookmark.py
index 6d271652..15a28c9d 100644
--- a/src/bookmark.py
+++ b/src/bookmark.py
@@ -25,7 +25,7 @@ def xml_iter(xml, tag=''):
else:
return xml.getiterator(tag)
-preferred = config.get('use_bookmarks_method', 'pep').lower()
+preferred = config.get('use_bookmarks_method').lower()
if preferred not in ('pep', 'privatexml'):
preferred = 'privatexml'
not_preferred = 'privatexml' if preferred == 'pep' else 'pep'
@@ -159,8 +159,8 @@ def save(xmpp, core=None):
core.information('Could not save bookmarks.', 'Error')
elif core:
core.information('Bookmarks saved', 'Info')
- if config.get('use_remote_bookmarks', True):
- preferred = config.get('use_bookmarks_method', 'privatexml')
+ if config.get('use_remote_bookmarks'):
+ preferred = config.get('use_bookmarks_method')
cb = functools.partial(_cb, core)
save_remote(xmpp, cb, method=preferred)
@@ -201,7 +201,7 @@ def get_remote(xmpp, callback):
"""Add the remotely stored bookmarks to the list."""
if xmpp.anon:
return
- method = config.get('use_bookmarks_method', '')
+ method = config.get('use_bookmarks_method')
if not method:
available_methods = {}
def _save_and_call_callback():
@@ -232,7 +232,7 @@ def save_bookmarks_method(available_methods):
def get_local():
"""Add the locally stored bookmarks to the list."""
- rooms = config.get('rooms', '')
+ rooms = config.get('rooms')
if not rooms:
return
rooms = rooms.split(':')
@@ -244,7 +244,7 @@ def get_local():
nick = jid.resource
else:
nick = None
- passwd = config.get_by_tabname('password', '', jid.bare, fallback=False) or None
+ passwd = config.get_by_tabname('password', jid.bare, fallback=False) or None
b = Bookmark(jid.bare, autojoin=True, nick=nick, password=passwd, method='local')
if not get_by_jid(b.jid):
bookmarks.append(b)
diff --git a/src/common.py b/src/common.py
index d50e7027..a62c83f1 100644
--- a/src/common.py
+++ b/src/common.py
@@ -188,17 +188,6 @@ def datetime_tuple(timestamp):
:param str timestamp: The string containing the formatted date.
:return: The date.
:rtype: :py:class:`datetime.datetime`
-
- >>> time.timezone = 0; time.altzone = 0
- >>> datetime_tuple('20130226T06:23:12')
- datetime.datetime(2013, 2, 26, 6, 23, 12)
- >>> datetime_tuple('2013-02-26T06:23:12+02:00')
- datetime.datetime(2013, 2, 26, 4, 23, 12)
- >>> time.timezone = -3600; time.altzone = -3600
- >>> datetime_tuple('20130226T07:23:12')
- datetime.datetime(2013, 2, 26, 8, 23, 12)
- >>> datetime_tuple('2013-02-26T07:23:12+02:00')
- datetime.datetime(2013, 2, 26, 6, 23, 12)
"""
timestamp = timestamp.replace('-', '', 2).replace(':', '')
date = timestamp[:15]
@@ -227,15 +216,10 @@ def datetime_tuple(timestamp):
def get_utc_time(local_time=None):
"""
- Get the current time in UTC
+ Get the current UTC time
:param datetime local_time: The current local time
:return: The current UTC time
- >>> delta = timedelta(seconds=-3600)
- >>> d = datetime.now()
- >>> time.timezone = -3600; time.altzone = -3600
- >>> get_utc_time(local_time=d) == d + delta
- True
"""
if local_time is None:
local_time = datetime.now()
@@ -258,12 +242,6 @@ def get_utc_time(local_time=None):
def get_local_time(utc_time):
"""
Get the local time from an UTC time
-
- >>> delta = timedelta(seconds=-3600)
- >>> d = datetime.now()
- >>> time.timezone = -3600; time.altzone = -3600
- >>> get_local_time(d) == d - delta
- True
"""
if OLD_PYTHON:
isdst = time.localtime(int(utc_time.strftime("%s"))).tm_isdst
@@ -315,16 +293,6 @@ def shell_split(st):
>>> shell_split('"sdf 1" "toto 2"')
['sdf 1', 'toto 2']
- >>> shell_split('toto "titi"')
- ['toto', 'titi']
- >>> shell_split('toto ""')
- ['toto', '']
- >>> shell_split('to"to titi "a" b')
- ['to"to', 'titi', 'a', 'b']
- >>> shell_split('"toto titi" toto ""')
- ['toto titi', 'toto', '']
- >>> shell_split('toto "titi')
- ['toto', 'titi']
"""
sh = shlex.shlex(st)
ret = []
@@ -358,18 +326,8 @@ def find_argument(pos, text, quoted=True):
def find_argument_quoted(pos, text):
"""
- >>> find_argument_quoted(4, 'toto titi tata')
- 3
- >>> find_argument_quoted(4, '"toto titi" tata')
- 0
- >>> find_argument_quoted(8, '"toto" "titi tata"')
- 1
- >>> find_argument_quoted(8, '"toto" "titi tata')
- 1
- >>> find_argument_quoted(3, '"toto" "titi tata')
- 0
- >>> find_argument_quoted(18, '"toto" "titi tata" ')
- 2
+ Get the number of the argument at position pos in
+ a string with possibly quoted text.
"""
sh = shlex.shlex(text)
count = -1
@@ -384,16 +342,8 @@ def find_argument_quoted(pos, text):
def find_argument_unquoted(pos, text):
"""
- >>> find_argument_unquoted(2, 'toto titi tata')
- 0
- >>> find_argument_unquoted(3, 'toto titi tata')
- 0
- >>> find_argument_unquoted(6, 'toto titi tata')
- 1
- >>> find_argument_unquoted(4, 'toto titi tata')
- 3
- >>> find_argument_unquoted(25, 'toto titi tata')
- 3
+ Get the number of the argument at position pos in
+ a string without interpreting quotes.
"""
ret = text.split()
search = 0
@@ -531,7 +481,3 @@ def safeJID(*args, **kwargs):
except InvalidJID:
return JID('')
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/src/config.py b/src/config.py
index f40f8742..1f0771ca 100644
--- a/src/config.py
+++ b/src/config.py
@@ -22,16 +22,129 @@ from os import environ, makedirs, path, remove
from shutil import copy2
from args import parse_args
+DEFAULT_CONFIG = {
+ 'Poezio': {
+ 'ack_message_receipts': True,
+ 'add_space_after_completion': True,
+ 'after_completion': ',',
+ 'alternative_nickname': '',
+ 'auto_reconnect': False,
+ 'autorejoin_delay': '5',
+ 'autorejoin': False,
+ 'beep_on': 'highlight private invite',
+ 'ca_cert_path': '',
+ 'certificate': '',
+ 'ciphers': 'HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL',
+ 'connection_check_interval': 60,
+ 'connection_timeout_delay': 10,
+ 'create_gaps': False,
+ 'custom_host': '',
+ 'custom_port': '',
+ 'default_nick': '',
+ 'display_activity_notifications': False,
+ 'display_gaming_notifications': False,
+ 'display_mood_notifications': False,
+ 'display_tune_notifications': False,
+ 'display_user_color_in_join_part': True,
+ 'enable_carbons': False,
+ 'enable_user_activity': True,
+ 'enable_user_gaming': True,
+ 'enable_user_mood': True,
+ 'enable_user_nick': True,
+ 'enable_user_tune': True,
+ 'enable_vertical_tab_list': False,
+ 'enable_xhtml_im': True,
+ 'exec_remote': False,
+ 'extract_inline_images': True,
+ 'filter_info_messages': '',
+ 'force_encryption': True,
+ 'group_corrections': True,
+ 'hide_exit_join': -1,
+ 'hide_status_change': 120,
+ 'hide_user_list': False,
+ 'highlight_on': '',
+ 'ignore_certificate': False,
+ 'ignore_private': False,
+ 'information_buffer_popup_on': 'error roster warning help info',
+ 'jid': '',
+ 'lang': 'en',
+ 'lazy_resize': True,
+ 'load_log': 10,
+ 'log_dir': '',
+ 'logfile': 'logs',
+ 'log_errors': True,
+ 'max_lines_in_memory': 2048,
+ 'max_messages_in_memory': 2048,
+ 'max_nick_length': 25,
+ 'muc_history_length': 50,
+ 'notify_messages': True,
+ 'open_all_bookmarks': False,
+ 'password': '',
+ 'plugins_autoload': '',
+ 'plugins_conf_dir': '',
+ 'plugins_dir': '',
+ 'popup_time': 4,
+ 'private_auto_response': '',
+ 'remote_fifo_path': './',
+ 'request_message_receipts': True,
+ 'resource': '',
+ 'rooms': '',
+ 'roster_group_sort': 'name',
+ 'roster_show_offline': False,
+ 'roster_sort': 'jid:show',
+ 'save_status': True,
+ 'send_chat_states': True,
+ 'send_initial_presence': True,
+ 'send_os_info': True,
+ 'send_poezio_info': True,
+ 'send_time': True,
+ 'separate_history': False,
+ 'server': 'anon.jeproteste.info',
+ 'show_composing_tabs': 'direct',
+ 'show_inactive_tabs': True,
+ 'show_muc_jid': True,
+ 'show_roster_jids': True,
+ 'show_roster_subscriptions': '',
+ 'show_s2s_errors': True,
+ 'show_tab_names': False,
+ 'show_tab_numbers': True,
+ 'show_timestamps': True,
+ 'show_useless_separator': False,
+ 'status': '',
+ 'status_message': '',
+ 'theme': 'default',
+ 'themes_dir': '',
+ 'tmp_image_dir': '',
+ 'use_bookmarks_method': '',
+ 'use_log': False,
+ 'use_remote_bookmarks': True,
+ 'user_list_sort': 'desc',
+ 'use_tab_nicks': True,
+ 'vertical_tab_list_size': 20,
+ 'vertical_tab_list_sort': 'desc',
+ 'whitespace_interval': 300,
+ 'words': ''
+ },
+ 'bindings': {
+ 'M-i': '^I'
+ },
+ 'var': {
+ 'folded_roster_groups': '',
+ 'info_win_height': 2
+ }
+}
+
class Config(RawConfigParser):
"""
load/save the config to a file
"""
- def __init__(self, file_name):
+ def __init__(self, file_name, default=None):
RawConfigParser.__init__(self, None)
# make the options case sensitive
self.optionxform = str
self.file_name = file_name
self.read_file()
+ self.default = default
def read_file(self):
try:
@@ -43,13 +156,19 @@ class Config(RawConfigParser):
if not self.has_section(section):
self.add_section(section)
- def get(self, option, default, section=DEFSECTION):
+ def get(self, option, default=None, section=DEFSECTION):
"""
get a value from the config but return
a default value if it is not found
The type of default defines the type
returned
"""
+ if default is None:
+ if self.default:
+ default = self.default.get(section, {}).get(option)
+ else:
+ default = ''
+
try:
if type(default) == int:
res = self.getint(option, section)
@@ -61,18 +180,21 @@ class Config(RawConfigParser):
res = self.getstr(option, section)
except (NoOptionError, NoSectionError, ValueError, AttributeError):
return default
+
if res is None:
return default
return res
- def get_by_tabname(self, option, default, tabname, fallback=True,
- fallback_server=True):
+ def get_by_tabname(self, option, tabname,
+ fallback=True, fallback_server=True, default=''):
"""
Try to get the value for the option. First we look in
a section named `tabname`, if the option is not present
in the section, we search for the global option if fallback is
True. And we return `default` as a fallback as a last resort.
"""
+ if self.default and (not default) and fallback:
+ default = self.default.get(DEFSECTION, {}).get(option, '')
if tabname in self.sections():
if option in self.options(tabname):
# We go the tab-specific option
@@ -360,7 +482,6 @@ def file_ok(filepath):
def check_create_config_dir():
"""
create the configuration directory if it doesn't exist
- and copy the default config in it
"""
CONFIG_HOME = environ.get("XDG_CONFIG_HOME")
if not CONFIG_HOME:
@@ -373,6 +494,23 @@ def check_create_config_dir():
pass
return CONFIG_PATH
+def check_create_cache_dir():
+ """
+ create the cache directory if it doesn't exist
+ also create the subdirectories
+ """
+ global CACHE_DIR
+ CACHE_HOME = environ.get("XDG_CACHE_HOME")
+ if not CACHE_HOME:
+ CACHE_HOME = path.join(environ.get('HOME'), '.cache')
+ CACHE_DIR = path.join(CACHE_HOME, 'poezio')
+
+ try:
+ makedirs(CACHE_DIR)
+ makedirs(path.join(CACHE_DIR, 'images'))
+ except OSError:
+ pass
+
def run_cmdline_args(CONFIG_PATH):
"Parse the command line arguments"
global options
@@ -394,7 +532,7 @@ def create_global_config():
"Create the global config object, or crash"
try:
global config
- config = Config(options.filename)
+ config = Config(options.filename, DEFAULT_CONFIG)
except:
import traceback
sys.stderr.write('Poezio was unable to read or'
@@ -405,7 +543,7 @@ def create_global_config():
def check_create_log_dir():
"Create the poezio logging directory if it doesn’t exist"
global LOG_DIR
- LOG_DIR = config.get('log_dir', '')
+ LOG_DIR = config.get('log_dir')
if not LOG_DIR:
@@ -425,7 +563,7 @@ def check_create_log_dir():
def setup_logging():
"Change the logging config according to the cmdline options and config"
- if config.get('log_errors', True):
+ if config.get('log_errors'):
LOGGING_CONFIG['root']['handlers'].append('error')
LOGGING_CONFIG['handlers']['error'] = {
'level': 'ERROR',
@@ -494,3 +632,6 @@ safeJID = None
# the global log dir
LOG_DIR = ''
+
+# the global cache dir
+CACHE_DIR = ''
diff --git a/src/connection.py b/src/connection.py
index 214194f9..1bbe632d 100644
--- a/src/connection.py
+++ b/src/connection.py
@@ -29,28 +29,28 @@ class Connection(slixmpp.ClientXMPP):
"""
__init = False
def __init__(self):
- resource = config.get('resource', '')
- if config.get('jid', ''):
+ resource = config.get('resource')
+ if config.get('jid'):
# Field used to know if we are anonymous or not.
# many features will be handled differently
# depending on this setting
self.anon = False
- jid = '%s' % config.get('jid', '')
+ jid = '%s' % config.get('jid')
if resource:
jid = '%s/%s'% (jid, resource)
- password = config.get('password', '') or getpass.getpass()
+ password = config.get('password') or getpass.getpass()
else: # anonymous auth
self.anon = True
- jid = config.get('server', 'anon.jeproteste.info')
+ jid = config.get('server')
if resource:
jid = '%s/%s' % (jid, resource)
password = None
jid = safeJID(jid)
# TODO: use the system language
slixmpp.ClientXMPP.__init__(self, jid, password,
- lang=config.get('lang', 'en'))
+ lang=config.get('lang'))
- force_encryption = config.get('force_encryption', True)
+ force_encryption = config.get('force_encryption')
if force_encryption:
self['feature_mechanisms'].unencrypted_plain = False
self['feature_mechanisms'].unencrypted_digest = False
@@ -58,6 +58,7 @@ class Connection(slixmpp.ClientXMPP):
self['feature_mechanisms'].unencrypted_scram = False
self.core = None
+ self.auto_reconnect = config.get('auto_reconnect')
self.reconnect_max_attempts = 0
self.auto_authorize = None
# prosody defaults, lowest is AES128-SHA, it should be a minimum
@@ -65,12 +66,12 @@ class Connection(slixmpp.ClientXMPP):
self.ciphers = config.get('ciphers',
'HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK'
':!SRP:!3DES:!aNULL')
- self.ca_certs = config.get('ca_cert_path', '') or None
- interval = config.get('whitespace_interval', '300')
- if interval.isdecimal() and int(interval) > 0:
+ self.ca_certs = config.get('ca_cert_path') or None
+ interval = config.get('whitespace_interval')
+ if int(interval) > 0:
self.whitespace_keepalive_interval = int(interval)
else:
- self.whitespace_keepalive_interval = 300
+ self.whitespace_keepalive = False
self.register_plugin('xep_0004')
self.register_plugin('xep_0012')
self.register_plugin('xep_0030')
@@ -89,33 +90,31 @@ class Connection(slixmpp.ClientXMPP):
# without a body
XEP_0184._filter_add_receipt_request = fixes._filter_add_receipt_request
self.register_plugin('xep_0184')
- self.plugin['xep_0184'].auto_ack = config.get('ack_message_receipts',
- True)
- self.plugin['xep_0184'].auto_request = config.get(
- 'request_message_receipts', True)
+ self.plugin['xep_0184'].auto_ack = config.get('ack_message_receipts')
+ self.plugin['xep_0184'].auto_request = config.get('request_message_receipts')
self.register_plugin('xep_0191')
self.register_plugin('xep_0199')
- if config.get('enable_user_tune', True):
+ if config.get('enable_user_tune'):
self.register_plugin('xep_0118')
- if config.get('enable_user_nick', True):
+ if config.get('enable_user_nick'):
self.register_plugin('xep_0172')
- if config.get('enable_user_mood', True):
+ if config.get('enable_user_mood'):
self.register_plugin('xep_0107')
- if config.get('enable_user_activity', True):
+ if config.get('enable_user_activity'):
self.register_plugin('xep_0108')
- if config.get('enable_user_gaming', True):
+ if config.get('enable_user_gaming'):
self.register_plugin('xep_0196')
- if config.get('send_poezio_info', True):
+ if config.get('send_poezio_info'):
info = {'name':'poezio',
'version': options.version}
- if config.get('send_os_info', True):
+ if config.get('send_os_info'):
info['os'] = common.get_os_info()
self.plugin['xep_0030'].set_identities(
identities=set([('client', 'pc', None, 'Poezio')]))
@@ -124,7 +123,7 @@ class Connection(slixmpp.ClientXMPP):
self.plugin['xep_0030'].set_identities(
identities=set([('client', 'pc', None, '')]))
self.register_plugin('xep_0092', pconfig=info)
- if config.get('send_time', True):
+ if config.get('send_time'):
self.register_plugin('xep_0202')
self.register_plugin('xep_0224')
self.register_plugin('xep_0249')
@@ -143,8 +142,8 @@ class Connection(slixmpp.ClientXMPP):
# 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', 60)
- timeout_delay = config.get('connection_timeout_delay', 10)
+ ping_interval = config.get('connection_check_interval')
+ timeout_delay = config.get('connection_timeout_delay')
if timeout_delay <= 0:
# We help the stupid user (with a delay of 0, poezio will try to
# reconnect immediately because the timeout is immediately
@@ -161,7 +160,7 @@ class Connection(slixmpp.ClientXMPP):
"""
Connect and process events.
"""
- custom_host = config.get('custom_host', '')
+ custom_host = config.get('custom_host')
custom_port = config.get('custom_port', 5222)
if custom_port == -1:
custom_port = 5222
diff --git a/src/core/commands.py b/src/core/commands.py
index c27263e2..4a8f7f19 100644
--- a/src/core/commands.py
+++ b/src/core/commands.py
@@ -7,6 +7,7 @@ import logging
log = logging.getLogger(__name__)
import functools
+import os
import sys
from datetime import datetime
from gettext import gettext as _
@@ -24,6 +25,7 @@ import tabs
from common import safeJID
from config import config, options as config_opts
import multiuserchat as muc
+from plugin import PluginConfig
from roster import roster
from theming import dump_tuple, get_theme
@@ -369,16 +371,13 @@ def command_join(self, arg, histo_length=None):
room = room[1:]
current_status = self.get_status()
if not histo_length:
- histo_length = config.get('muc_history_length', 20)
+ 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)
if password is None: # try to use a saved password
- password = config.get_by_tabname('password',
- None,
- room,
- fallback=False)
+ password = config.get_by_tabname('password', room, fallback=False)
if tab and not tab.joined:
if tab.last_connection:
if tab.last_connection is not None:
@@ -476,7 +475,7 @@ def command_bookmark(self, arg=''):
/bookmark [room][/nick] [autojoin] [password]
"""
- if not config.get('use_remote_bookmarks', True):
+ if not config.get('use_remote_bookmarks'):
self.command_bookmark_local(arg)
return
args = common.shell_split(arg)
@@ -537,7 +536,7 @@ def command_bookmark(self, arg=''):
if not bm:
bm = bookmark.Bookmark(roomname)
bookmark.bookmarks.append(bm)
- bm.method = config.get('use_bookmarks_method', 'pep')
+ bm.method = config.get('use_bookmarks_method')
if nick:
bm.nick = nick
if password:
@@ -592,17 +591,39 @@ def command_remove_bookmark(self, arg=''):
def command_set(self, arg):
"""
- /set [module|][section] <option> <value>
+ /set [module|][section] <option> [value]
"""
args = common.shell_split(arg)
- if len(args) != 2 and len(args) != 3:
- self.command_help('set')
- return
- if len(args) == 2:
+ if len(args) == 1:
option = args[0]
- value = args[1]
- info = config.set_and_save(option, value)
- self.trigger_configuration_change(option, value)
+ value = config.get(option)
+ info = ('%s=%s' % (option, value), 'Info')
+ elif len(args) == 2:
+ if '|' in args[0]:
+ plugin_name, section = args[0].split('|')[:2]
+ if not section:
+ section = plugin_name
+ option = args[1]
+ if not plugin_name in self.plugin_manager.plugins:
+ file_name = self.plugin_manager.plugins_conf_dir
+ file_name = os.path.join(file_name, plugin_name + '.cfg')
+ plugin_config = PluginConfig(file_name, plugin_name)
+ else:
+ plugin_config = self.plugin_manager.plugins[plugin_name].config
+ value = plugin_config.get(option, default='', section=section)
+ info = ('%s=%s' % (option, value), 'Info')
+ else:
+ possible_section = args[0]
+ if config.has_section(possible_section):
+ section = possible_section
+ option = args[1]
+ value = config.get(option, section=section)
+ info = ('%s=%s' % (option, value), 'Info')
+ else:
+ option = args[0]
+ value = args[1]
+ info = config.set_and_save(option, value)
+ self.trigger_configuration_change(option, value)
elif len(args) == 3:
if '|' in args[0]:
plugin_name, section = args[0].split('|')[:2]
@@ -611,15 +632,21 @@ def command_set(self, arg):
option = args[1]
value = args[2]
if not plugin_name in self.plugin_manager.plugins:
- return
- plugin = self.plugin_manager.plugins[plugin_name]
- info = plugin.config.set_and_save(option, value, section)
+ file_name = self.plugin_manager.plugins_conf_dir
+ file_name = os.path.join(file_name, plugin_name + '.cfg')
+ plugin_config = PluginConfig(file_name, plugin_name)
+ else:
+ plugin_config = self.plugin_manager.plugins[plugin_name].config
+ info = plugin_config.set_and_save(option, value, section)
else:
section = args[0]
option = args[1]
value = args[2]
info = config.set_and_save(option, value, section)
self.trigger_configuration_change(option, value)
+ else:
+ self.command_help('set')
+ return
self.call_for_resize()
self.information(*info)
@@ -813,11 +840,11 @@ def command_quit(self, arg=''):
msg = arg
else:
msg = None
- if config.get('enable_user_mood', True):
+ if config.get('enable_user_mood'):
self.xmpp.plugin['xep_0107'].stop()
- if config.get('enable_user_activity', True):
+ if config.get('enable_user_activity'):
self.xmpp.plugin['xep_0108'].stop()
- if config.get('enable_user_gaming', True):
+ if config.get('enable_user_gaming'):
self.xmpp.plugin['xep_0196'].stop()
self.save_config()
self.plugin_manager.disable_plugins()
diff --git a/src/core/completions.py b/src/core/completions.py
index 7acddef9..7d95321b 100644
--- a/src/core/completions.py
+++ b/src/core/completions.py
@@ -46,7 +46,7 @@ def completion_presence(self, the_input):
def completion_theme(self, the_input):
""" Completion for /theme"""
- themes_dir = config.get('themes_dir', '')
+ themes_dir = config.get('themes_dir')
themes_dir = themes_dir or\
os.path.join(os.environ.get('XDG_DATA_HOME') or\
os.path.join(os.environ.get('HOME'), '.local', 'share'),
@@ -175,7 +175,7 @@ def completion_bookmark(self, the_input):
tab = self.get_tab_by_name(jid.bare, tabs.MucTab)
nicks = [tab.own_nick] if tab else []
default = os.environ.get('USER') if os.environ.get('USER') else 'poezio'
- nick = config.get('default_nick', '')
+ nick = config.get('default_nick')
if not nick:
if not default in nicks:
nicks.append(default)
@@ -309,7 +309,7 @@ def completion_set(self, the_input):
if '|' in args[1]:
plugin_name, section = args[1].split('|')[:2]
if not plugin_name in self.plugin_manager.plugins:
- return the_input.auto_completion([''], n, quotify=True)
+ return the_input.new_completion([''], n, quotify=True)
plugin = self.plugin_manager.plugins[plugin_name]
end_list = plugin.config.options(section or plugin_name)
elif not config.has_option('Poezio', args[1]):
@@ -319,19 +319,19 @@ def completion_set(self, the_input):
else:
end_list = []
else:
- end_list = [config.get(args[1], ''), '']
+ end_list = [str(config.get(args[1], '')), '']
elif n == 3:
if '|' in args[1]:
plugin_name, section = args[1].split('|')[:2]
if not plugin_name in self.plugin_manager.plugins:
- return the_input.auto_completion([''], n, quotify=True)
+ return the_input.new_completion([''], n, quotify=True)
plugin = self.plugin_manager.plugins[plugin_name]
- end_list = [plugin.config.get(args[2], '', section or plugin_name), '']
+ end_list = [str(plugin.config.get(args[2], '', section or plugin_name)), '']
else:
if not config.has_section(args[1]):
end_list = ['']
else:
- end_list = [config.get(args[2], '', args[1]), '']
+ end_list = [str(config.get(args[2], '', args[1])), '']
else:
return
return the_input.new_completion(end_list, n, quotify=True)
@@ -356,7 +356,7 @@ def completion_bookmark_local(self, the_input):
tab = self.get_tab_by_name(jid.bare, tabs.MucTab)
nicks = [tab.own_nick] if tab else []
default = os.environ.get('USER') if os.environ.get('USER') else 'poezio'
- nick = config.get('default_nick', '')
+ nick = config.get('default_nick')
if not nick:
if not default in nicks:
nicks.append(default)
diff --git a/src/core/core.py b/src/core/core.py
index eeb25c83..70136250 100644
--- a/src/core/core.py
+++ b/src/core/core.py
@@ -65,10 +65,10 @@ class Core(object):
sys.excepthook = self.on_exception
self.connection_time = time.time()
self.stdscr = None
- status = config.get('status', None)
+ status = config.get('status')
status = possible_show.get(status, None)
self.status = Status(show=status,
- message=config.get('status_message', ''))
+ message=config.get('status_message'))
self.running = True
self.xmpp = singleton.Singleton(connection.Connection)
self.xmpp.core = self
@@ -83,7 +83,7 @@ class Core(object):
# that are displayed in almost all tabs, in an
# information window.
self.information_buffer = TextBuffer()
- self.information_win_size = config.get('info_win_height', 2, 'var')
+ self.information_win_size = config.get('info_win_height', section='var')
self.information_win = windows.TextWin(300)
self.information_buffer.add_window(self.information_win)
self.left_tab_win = None
@@ -97,7 +97,7 @@ class Core(object):
self._current_tab_nb = 0
self.previous_tab_nb = 0
- own_nick = config.get('default_nick', '')
+ own_nick = config.get('default_nick')
own_nick = own_nick or self.xmpp.boundjid.user
own_nick = own_nick or os.environ.get('USER')
own_nick = own_nick or 'poezio'
@@ -127,7 +127,7 @@ class Core(object):
self.register_initial_commands()
# We are invisible
- if not config.get('send_initial_presence', True):
+ if not config.get('send_initial_presence'):
del self.commands['status']
del self.commands['show']
@@ -256,19 +256,19 @@ class Core(object):
connection.MatchAll(None),
self.incoming_stanza)
self.xmpp.register_handler(self.all_stanzas)
- if config.get('enable_user_tune', True):
+ if config.get('enable_user_tune'):
self.xmpp.add_event_handler("user_tune_publish",
self.on_tune_event)
- if config.get('enable_user_nick', True):
+ if config.get('enable_user_nick'):
self.xmpp.add_event_handler("user_nick_publish",
self.on_nick_received)
- if config.get('enable_user_mood', True):
+ if config.get('enable_user_mood'):
self.xmpp.add_event_handler("user_mood_publish",
self.on_mood_event)
- if config.get('enable_user_activity', True):
+ if config.get('enable_user_activity'):
self.xmpp.add_event_handler("user_activity_publish",
self.on_activity_event)
- if config.get('enable_user_gaming', True):
+ if config.get('enable_user_gaming'):
self.xmpp.add_event_handler("user_gaming_publish",
self.on_gaming_event)
@@ -357,13 +357,14 @@ class Core(object):
"""
Called when the request_message_receipts option changes
"""
- self.xmpp.plugin['xep_0184'].auto_request = config.get(option, True)
+ self.xmpp.plugin['xep_0184'].auto_request = config.get(option,
+ default=True)
def on_ack_receipts_config_change(self, option, value):
"""
Called when the ack_message_receipts option changes
"""
- self.xmpp.plugin['xep_0184'].auto_ack = config.get(option, True)
+ self.xmpp.plugin['xep_0184'].auto_ack = config.get(option, default=True)
def on_plugins_dir_config_change(self, option, value):
"""
@@ -419,7 +420,7 @@ class Core(object):
old_section = old_config.get(section, {})
for option in config.options(section):
old_value = old_section.get(option)
- new_value = config.get(option, "", section)
+ new_value = config.get(option, default="", section=section)
if new_value != old_value:
self.trigger_configuration_change(option, new_value)
log.debug("Config reloaded.")
@@ -441,11 +442,11 @@ class Core(object):
}
log.error("%s received. Exiting…", signals[sig])
- if config.get('enable_user_mood', True):
+ if config.get('enable_user_mood'):
self.xmpp.plugin['xep_0107'].stop()
- if config.get('enable_user_activity', True):
+ if config.get('enable_user_activity'):
self.xmpp.plugin['xep_0108'].stop()
- if config.get('enable_user_gaming', True):
+ if config.get('enable_user_gaming'):
self.xmpp.plugin['xep_0196'].stop()
self.plugin_manager.disable_plugins()
self.disconnect('%s received' % signals.get(sig))
@@ -455,7 +456,7 @@ class Core(object):
"""
Load the plugins on startup.
"""
- plugins = config.get('plugins_autoload', '')
+ plugins = config.get('plugins_autoload')
if ':' in plugins:
for plugin in plugins.split(':'):
self.plugin_manager.load(plugin)
@@ -704,9 +705,9 @@ class Core(object):
work. If you try to do anything else, your |, [, <<, etc will be
interpreted as normal command arguments, not shell special tokens.
"""
- if config.get('exec_remote', False):
+ if config.get('exec_remote'):
# We just write the command in the fifo
- fifo_path = config.get('remote_fifo_path', './')
+ fifo_path = config.get('remote_fifo_path')
if not self.remote_fifo:
try:
self.remote_fifo = Fifo(os.path.join(fifo_path,
@@ -802,7 +803,7 @@ class Core(object):
or to use it when joining a new muc)
"""
self.status = Status(show=pres, message=msg)
- if config.get('save_status', True):
+ if config.get('save_status'):
ok = config.silent_set('status', pres if pres else '')
msg = msg.replace('\n', '|') if msg else ''
ok = ok and config.silent_set('status_message', msg)
@@ -1043,7 +1044,7 @@ class Core(object):
return False
elif not self.tabs[old_pos]:
return False
- if config.get('create_gaps', False):
+ if config.get('create_gaps'):
return self.insert_tab_gaps(old_pos, new_pos)
return self.insert_tab_nogaps(old_pos, new_pos)
@@ -1294,7 +1295,7 @@ class Core(object):
if self.previous_tab_nb != nb:
self.current_tab_nb = self.previous_tab_nb
self.previous_tab_nb = 0
- if config.get('create_gaps', False):
+ if config.get('create_gaps'):
if nb >= len(self.tabs) - 1:
self.tabs.remove(tab)
nb -= 1
@@ -1345,7 +1346,7 @@ class Core(object):
"""
Displays an informational message in the "Info" buffer
"""
- filter_messages = config.get('filter_info_messages', '').split(':')
+ filter_messages = config.get('filter_info_messages').split(':')
for words in filter_messages:
if words and words in msg:
log.debug('Did not show the message:\n\t%s> %s', typ, msg)
@@ -1355,12 +1356,11 @@ class Core(object):
nb_lines = self.information_buffer.add_message(msg,
nickname=typ,
nick_color=color)
- popup_on = config.get('information_buffer_popup_on',
- 'error roster warning help info').split()
+ popup_on = config.get('information_buffer_popup_on').split()
if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window()
elif typ != '' and typ.lower() in popup_on:
- popup_time = config.get('popup_time', 4) + (nb_lines - 1) * 2
+ popup_time = config.get('popup_time') + (nb_lines - 1) * 2
self.pop_information_win_up(nb_lines, popup_time)
else:
if self.information_win_size != 0:
@@ -1553,7 +1553,7 @@ class Core(object):
"""
Enable/disable the left panel.
"""
- enabled = config.get('enable_vertical_tab_list', False)
+ enabled = config.get('enable_vertical_tab_list')
if not config.silent_set('enable_vertical_tab_list', str(not enabled)):
self.information(_('Unable to write in the config file'), 'Error')
self.call_for_resize()
@@ -1578,14 +1578,14 @@ class Core(object):
Resize the GlobalInfoBar only once at each resize
"""
height, width = self.stdscr.getmaxyx()
- if config.get('enable_vertical_tab_list', False):
+ 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', 20),
+ config.get('vertical_tab_list_size'),
0, 0)
except:
log.error('Curses error on infobar resize', exc_info=True)
@@ -1623,11 +1623,11 @@ class Core(object):
# 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', False) and
+ if (config.get('enable_vertical_tab_list') and
not self.size.core_degrade_x):
try:
scr = self.stdscr.subwin(0,
- config.get('vertical_tab_list_size', 20))
+ config.get('vertical_tab_list_size'))
except:
log.error('Curses error on resize', exc_info=True)
return
@@ -1637,7 +1637,7 @@ class Core(object):
self.resize_global_info_bar()
self.resize_global_information_win()
for tab in self.tabs:
- if config.get('lazy_resize', True):
+ if config.get('lazy_resize'):
tab.need_resize = True
else:
tab.resize()
@@ -1865,7 +1865,7 @@ class Core(object):
usage='<jid>',
shortdesc=_('List available ad-hoc commands on the given jid'))
- if config.get('enable_user_activity', True):
+ if config.get('enable_user_activity'):
self.register_command('activity', self.command_activity,
usage='[<general> [specific] [text]]',
desc=_('Send your current activity to your contacts '
@@ -1873,7 +1873,7 @@ class Core(object):
'"stop broadcasting an activity".'),
shortdesc=_('Send your activity.'),
completion=self.completion_activity)
- if config.get('enable_user_mood', True):
+ if config.get('enable_user_mood'):
self.register_command('mood', self.command_mood,
usage='[<mood> [text]]',
desc=_('Send your current mood to your contacts '
@@ -1881,7 +1881,7 @@ class Core(object):
'"stop broadcasting a mood".'),
shortdesc=_('Send your mood.'),
completion=self.completion_mood)
- if config.get('enable_user_gaming', True):
+ if config.get('enable_user_gaming'):
self.register_command('gaming', self.command_gaming,
usage='[<game name> [server address]]',
desc=_('Send your current gaming activity to '
@@ -2025,7 +2025,7 @@ def replace_key_with_bound(key):
Replace an inputted key with the one defined as its replacement
in the config
"""
- bind = config.get(key, key, 'bindings')
+ bind = config.get(key, default=key, section='bindings')
if not bind:
bind = key
return bind
diff --git a/src/core/handlers.py b/src/core/handlers.py
index 4e2fcfd3..648c3e4d 100644
--- a/src/core/handlers.py
+++ b/src/core/handlers.py
@@ -13,6 +13,7 @@ import sys
import time
from hashlib import sha1, sha512
from gettext import gettext as _
+from os import path
from slixmpp import InvalidJID
from slixmpp.stanza import Message
@@ -27,7 +28,7 @@ import windows
import xhtml
import multiuserchat as muc
from common import safeJID
-from config import config
+from config import config, CACHE_DIR
from contact import Resource
from logger import logger
from roster import roster
@@ -46,7 +47,7 @@ def on_session_start_features(self, _):
features = iq['disco_info']['features']
rostertab = self.get_tab_by_name('Roster', tabs.RosterInfoTab)
rostertab.check_blocking(features)
- if (config.get('enable_carbons', True) and
+ if (config.get('enable_carbons') and
'urn:xmpp:carbons:2' in features):
self.xmpp.plugin['xep_0280'].enable()
self.xmpp.add_event_handler('carbon_received', self.on_carbon_received)
@@ -120,7 +121,7 @@ def on_groupchat_invitation(self, message):
if password:
msg += ". The password is \"%s\"." % password
self.information(msg, 'Info')
- if 'invite' in config.get('beep_on', 'invite').split():
+ if 'invite' in config.get('beep_on').split():
curses.beep()
logger.log_roster_change(inviter.full, 'invited you to %s' % jid.full)
self.pending_invites[jid.bare] = inviter.full
@@ -151,7 +152,7 @@ def on_groupchat_direct_invitation(self, message):
msg += "\nreason: %s" % reason
self.information(msg, 'Info')
- if 'invite' in config.get('beep_on', 'invite').split():
+ if 'invite' in config.get('beep_on').split():
curses.beep()
self.pending_invites[room.bare] = inviter.full
@@ -188,8 +189,12 @@ def on_normal_message(self, message):
elif message['type'] == 'headline' and message['body']:
return self.information('%s says: %s' % (message['from'], message['body']), 'Headline')
- use_xhtml = config.get('enable_xhtml_im', True)
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml)
+ use_xhtml = config.get('enable_xhtml_im')
+ tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images')
+ extract_images = config.get('extract_inline_images')
+ body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
if not body:
return
@@ -203,7 +208,7 @@ def on_normal_message(self, message):
if conv_jid.bare in roster:
remote_nick = roster[conv_jid.bare].name
# check for a received nick
- if not remote_nick and config.get('enable_user_nick', True):
+ if not remote_nick and config.get('enable_user_nick'):
if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None:
remote_nick = message['nick']['nick']
if not remote_nick:
@@ -234,13 +239,15 @@ def on_normal_message(self, message):
self.events.trigger('conversation_msg', message, conversation)
if not message['body']:
return
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml)
+ body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
delayed, date = common.find_delayed_tag(message)
def try_modify():
replaced_id = message['replace']['id']
- if replaced_id and (config.get_by_tabname('group_corrections',
- True, conv_jid.bare)):
+ if replaced_id and config.get_by_tabname('group_corrections',
+ conv_jid.bare):
try:
conversation.modify_message(body, replaced_id, message['id'], jid=jid,
nickname=remote_nick)
@@ -263,8 +270,8 @@ def on_normal_message(self, message):
conversation.remote_wants_chatstates = True
else:
conversation.remote_wants_chatstates = False
- if 'private' in config.get('beep_on', 'highlight private').split():
- if not config.get_by_tabname('disable_beep', False, conv_jid.bare, False):
+ if 'private' in config.get('beep_on').split():
+ if not config.get_by_tabname('disable_beep', conv_jid.bare):
curses.beep()
if self.current_tab() is not conversation:
conversation.state = 'private'
@@ -314,7 +321,7 @@ def on_gaming_event(self, message):
if contact.gaming:
logger.log_roster_change(contact.bare_jid, 'is playing %s' % (common.format_gaming_string(contact.gaming)))
- if old_gaming != contact.gaming and config.get_by_tabname('display_gaming_notifications', False, contact.bare_jid):
+ if old_gaming != contact.gaming and config.get_by_tabname('display_gaming_notifications', contact.bare_jid):
if contact.gaming:
self.information('%s is playing %s' % (contact.bare_jid, common.format_gaming_string(contact.gaming)), 'Gaming')
else:
@@ -347,7 +354,7 @@ def on_mood_event(self, message):
if contact.mood:
logger.log_roster_change(contact.bare_jid, 'has now the mood: %s' % contact.mood)
- if old_mood != contact.mood and config.get_by_tabname('display_mood_notifications', False, contact.bare_jid):
+ if old_mood != contact.mood and config.get_by_tabname('display_mood_notifications', contact.bare_jid):
if contact.mood:
self.information('Mood from '+ contact.bare_jid + ': ' + contact.mood, 'Mood')
else:
@@ -386,7 +393,7 @@ def on_activity_event(self, message):
if contact.activity:
logger.log_roster_change(contact.bare_jid, 'has now the activity %s' % contact.activity)
- if old_activity != contact.activity and config.get_by_tabname('display_activity_notifications', False, contact.bare_jid):
+ if old_activity != contact.activity and config.get_by_tabname('display_activity_notifications', contact.bare_jid):
if contact.activity:
self.information('Activity from '+ contact.bare_jid + ': ' + contact.activity, 'Activity')
else:
@@ -420,7 +427,7 @@ def on_tune_event(self, message):
if contact.tune:
logger.log_roster_change(message['from'].bare, 'is now listening to %s' % common.format_tune_string(contact.tune))
- if old_tune != contact.tune and config.get_by_tabname('display_tune_notifications', False, contact.bare_jid):
+ if old_tune != contact.tune and config.get_by_tabname('display_tune_notifications', contact.bare_jid):
if contact.tune:
self.information(
'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune),
@@ -451,8 +458,12 @@ def on_groupchat_message(self, message):
return
self.events.trigger('muc_msg', message, tab)
- use_xhtml = config.get('enable_xhtml_im', True)
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml)
+ use_xhtml = config.get('enable_xhtml_im')
+ tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images')
+ extract_images = config.get('extract_inline_images')
+ body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
if not body:
return
@@ -460,8 +471,8 @@ def on_groupchat_message(self, message):
delayed, date = common.find_delayed_tag(message)
replaced_id = message['replace']['id']
replaced = False
- if replaced_id is not '' and (config.get_by_tabname(
- 'group_corrections', True, message['from'].bare)):
+ if replaced_id is not '' and config.get_by_tabname('group_corrections',
+ message['from'].bare):
try:
if tab.modify_message(body, replaced_id, message['id'], time=date,
nickname=nick_from, user=user):
@@ -487,8 +498,8 @@ def on_groupchat_message(self, message):
current.input.refresh()
self.doupdate()
- if 'message' in config.get('beep_on', 'highlight private').split():
- if (not config.get_by_tabname('disable_beep', False, room_from, False)
+ if 'message' in config.get('beep_on').split():
+ if (not config.get_by_tabname('disable_beep', room_from)
and self.own_nick != message['from'].resource):
curses.beep()
@@ -508,28 +519,34 @@ def on_groupchat_private_message(self, message):
return self.on_groupchat_message(message)
room_from = jid.bare
- use_xhtml = config.get('enable_xhtml_im', True)
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml)
+ use_xhtml = config.get('enable_xhtml_im')
+ tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images')
+ extract_images = config.get('extract_inline_images')
+ body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
tab = self.get_tab_by_name(jid.full, tabs.PrivateTab) # get the tab with the private conversation
- ignore = config.get_by_tabname('ignore_private', False, room_from)
+ ignore = config.get_by_tabname('ignore_private', room_from)
if not tab: # It's the first message we receive: create the tab
if body and not ignore:
tab = self.open_private_window(room_from, nick_from, False)
if ignore:
self.events.trigger('ignored_private', message, tab)
- msg = config.get_by_tabname('private_auto_response', None, room_from)
+ msg = config.get_by_tabname('private_auto_response', room_from)
if msg and body:
self.xmpp.send_message(mto=jid.full, mbody=msg, mtype='chat')
return
self.events.trigger('private_msg', message, tab)
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml)
+ body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
if not body or not tab:
return
replaced_id = message['replace']['id']
replaced = False
user = tab.parent_muc.get_user_by_name(nick_from)
- if replaced_id is not '' and (config.get_by_tabname(
- 'group_corrections', True, room_from)):
+ if replaced_id is not '' and config.get_by_tabname('group_corrections',
+ room_from):
try:
tab.modify_message(body, replaced_id, message['id'], user=user, jid=message['from'],
nickname=nick_from)
@@ -548,8 +565,8 @@ def on_groupchat_private_message(self, message):
tab.remote_wants_chatstates = True
else:
tab.remote_wants_chatstates = False
- if 'private' in config.get('beep_on', 'highlight private').split():
- if not config.get_by_tabname('disable_beep', False, jid.full, False):
+ if 'private' in config.get('beep_on').split():
+ if not config.get_by_tabname('disable_beep', jid.full):
curses.beep()
if tab is self.current_tab():
self.refresh_window()
@@ -883,7 +900,7 @@ def on_session_start(self, event):
# request the roster
self.xmpp.get_roster()
# send initial presence
- if config.get('send_initial_presence', True):
+ if config.get('send_initial_presence'):
pres = self.xmpp.make_presence()
pres['show'] = self.status.show
pres['status'] = self.status.message
@@ -893,13 +910,13 @@ def on_session_start(self, event):
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', False):
+ 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', 20)
+ histo_length = config.get('muc_history_length')
if histo_length == -1:
histo_length = None
if histo_length is not None:
@@ -915,13 +932,13 @@ def on_session_start(self, event):
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', True):
+ if not self.xmpp.anon and config.get('use_remote_bookmarks'):
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', True):
+ if config.get('enable_user_nick'):
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
@@ -994,18 +1011,21 @@ def on_groupchat_subject(self, message):
room_from = message.get_mucroom()
tab = self.get_tab_by_name(room_from, tabs.MucTab)
subject = message['subject']
- if not subject or not tab:
+ if subject is None or not tab:
return
- if nick_from:
- tab.add_message(_("\x19%(info_col)s}%(nick)s set the subject to: %(subject)s") %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'nick':nick_from, 'subject':subject},
- time=None,
- typ=2)
- else:
- tab.add_message(_("\x19%(info_col)s}The subject is: %(subject)s") %
- {'subject':subject, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- time=None,
- typ=2)
+ if subject != tab.topic:
+ # Do not display the message if the subject did not change or if we
+ # receive an empty topic when joining the room.
+ if nick_from:
+ tab.add_message(_("\x19%(info_col)s}%(nick)s set the subject to: %(subject)s") %
+ {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'nick':nick_from, 'subject':subject},
+ time=None,
+ typ=2)
+ else:
+ tab.add_message(_("\x19%(info_col)s}The subject is: %(subject)s") %
+ {'subject':subject, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
+ time=None,
+ typ=2)
tab.topic = subject
tab.topic_from = nick_from
if self.get_tab_by_name(room_from, tabs.MucTab) is self.current_tab():
@@ -1064,8 +1084,8 @@ def room_error(self, error, room_name):
msg = _('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)')
tab.add_message(msg, typ=2)
if code == '409':
- if config.get('alternative_nickname', '') != '':
- self.command_join('%s/%s'% (tab.name, tab.own_nick+config.get('alternative_nickname', '')))
+ if config.get('alternative_nickname') != '':
+ self.command_join('%s/%s'% (tab.name, tab.own_nick+config.get('alternative_nickname')))
else:
if not tab.joined:
tab.add_message(_('You can join the room with an other nick, by typing "/join /other_nick"'), typ=2)
@@ -1095,9 +1115,9 @@ def validate_ssl(self, pem):
"""
Check the server certificate using the slixmpp ssl_cert event
"""
- if config.get('ignore_certificate', False):
+ if config.get('ignore_certificate'):
return
- cert = config.get('certificate', '')
+ cert = config.get('certificate')
# update the cert representation when it uses the old one
if cert and not ':' in cert:
cert = ':'.join(i + j for i, j in zip(cert[::2], cert[1::2])).upper()
@@ -1173,7 +1193,7 @@ def _composing_tab_state(tab, state):
else:
return # should not happen
- show = config.get('show_composing_tabs', 'direct')
+ show = config.get('show_composing_tabs')
show = show in values
if tab.state != 'composing' and state == 'composing':
diff --git a/src/logger.py b/src/logger.py
index 7ed0692f..85c7a746 100644
--- a/src/logger.py
+++ b/src/logger.py
@@ -50,7 +50,7 @@ class Logger(object):
and also log the conversations to logfiles
"""
def __init__(self):
- self.logfile = config.get('logfile', 'logs')
+ self.logfile = config.get('logfile')
self.roster_logfile = None
# a dict of 'groupchatname': file-object (opened)
self.fds = dict()
@@ -78,7 +78,7 @@ class Logger(object):
Check that the directory where we want to log the messages
exists. if not, create it
"""
- if not config.get_by_tabname('use_log', True, room):
+ if not config.get_by_tabname('use_log', room):
return
try:
makedirs(log_dir)
@@ -106,10 +106,10 @@ class Logger(object):
this function is a little bit more complicated than “read the last
nb lines”.
"""
- if config.get_by_tabname('load_log', 10, jid) <= 0:
+ if config.get_by_tabname('load_log', jid) <= 0:
return
- if not config.get_by_tabname('use_log', True, jid):
+ if not config.get_by_tabname('use_log', jid):
return
if nb <= 0:
@@ -197,7 +197,7 @@ class Logger(object):
return True
jid = str(jid).replace('/', '\\')
- if not config.get_by_tabname('use_log', False, jid):
+ if not config.get_by_tabname('use_log', jid):
return True
if jid in self.fds.keys():
fd = self.fds[jid]
@@ -245,7 +245,7 @@ class Logger(object):
"""
Log a roster change
"""
- if not config.get_by_tabname('use_log', False, jid):
+ if not config.get_by_tabname('use_log', jid):
return True
self.check_and_create_log_dir('', open_fd=False)
if not self.roster_logfile:
diff --git a/src/plugin.py b/src/plugin.py
index 48ff3bd3..eb2a89e3 100644
--- a/src/plugin.py
+++ b/src/plugin.py
@@ -20,9 +20,8 @@ class PluginConfig(config.Config):
and behave like the core Config object.
"""
def __init__(self, filename, module_name):
- self.file_name = filename
+ config.Config.__init__(self, filename)
self.module_name = module_name
- RawConfigParser.__init__(self, None)
self.read()
def get(self, option, default, section=None):
diff --git a/src/plugin_manager.py b/src/plugin_manager.py
index e449442d..d4cc7384 100644
--- a/src/plugin_manager.py
+++ b/src/plugin_manager.py
@@ -325,7 +325,7 @@ class PluginManager(object):
"""
Create the plugins_conf_dir
"""
- plugins_conf_dir = config.get('plugins_conf_dir', '')
+ plugins_conf_dir = config.get('plugins_conf_dir')
if not plugins_conf_dir:
config_home = os.environ.get('XDG_CONFIG_HOME')
if not config_home:
@@ -352,7 +352,7 @@ class PluginManager(object):
"""
Set the plugins_dir on start
"""
- plugins_dir = config.get('plugins_dir', '')
+ plugins_dir = config.get('plugins_dir')
plugins_dir = plugins_dir or\
os.path.join(os.environ.get('XDG_DATA_HOME') or\
os.path.join(os.environ.get('HOME'),
diff --git a/src/poezio.py b/src/poezio.py
index 6a4a0b77..9a26e135 100644
--- a/src/poezio.py
+++ b/src/poezio.py
@@ -30,6 +30,7 @@ def main():
config.run_cmdline_args(config_path)
config.create_global_config()
config.check_create_log_dir()
+ config.check_create_cache_dir()
config.setup_logging()
config.post_logging_setup()
diff --git a/src/roster.py b/src/roster.py
index ef556021..d2b99cef 100644
--- a/src/roster.py
+++ b/src/roster.py
@@ -36,10 +36,8 @@ class Roster(object):
self.contact_filter = None # A tuple(function, *args)
# function to filter contacts,
# on search, for example
- self.folded_groups = set(config.get(
- 'folded_roster_groups',
- '',
- section='var').split(':'))
+ self.folded_groups = set(config.get('folded_roster_groups',
+ section='var').split(':'))
self.groups = {}
self.contacts = {}
diff --git a/src/tabs/basetabs.py b/src/tabs/basetabs.py
index 9212278d..645a297f 100644
--- a/src/tabs/basetabs.py
+++ b/src/tabs/basetabs.py
@@ -138,7 +138,7 @@ class Tab(object):
Returns 1 or 0, depending on if we are using the vertical tab list
or not.
"""
- if config.get('enable_vertical_tab_list', False):
+ if config.get('enable_vertical_tab_list'):
return 0
return 1
@@ -296,7 +296,7 @@ class Tab(object):
return False
def refresh_tab_win(self):
- if config.get('enable_vertical_tab_list', False):
+ if config.get('enable_vertical_tab_list'):
if self.left_tab_win and not self.size.core_degrade_x:
self.left_tab_win.refresh()
elif not self.size.core_degrade_y:
@@ -471,7 +471,7 @@ class ChatTab(Tab):
self.update_keys()
# Get the logs
- log_nb = config.get('load_log', 10)
+ log_nb = config.get('load_log')
logs = self.load_logs(log_nb)
if logs:
@@ -532,7 +532,7 @@ class ChatTab(Tab):
for word in txt.split():
if len(word) >= 4 and word not in words:
words.append(word)
- words.extend([word for word in config.get('words', '').split(':') if word])
+ words.extend([word for word in config.get('words').split(':') if word])
self.input.auto_completion(words, ' ', quotify=False)
def on_enter(self):
@@ -587,8 +587,8 @@ class ChatTab(Tab):
if not self.is_muc or self.joined:
if state in ('active', 'inactive', 'gone') and self.inactive and not always_send:
return
- if config.get_by_tabname('send_chat_states', True, self.general_jid, True) and \
- self.remote_wants_chatstates is not False:
+ if (config.get_by_tabname('send_chat_states', self.general_jid)
+ and self.remote_wants_chatstates is not False):
msg = self.core.xmpp.make_message(self.get_dest_jid())
msg['type'] = self.message_type
msg['chat_state'] = state
@@ -602,7 +602,8 @@ class ChatTab(Tab):
on the the current status of the input
"""
name = self.general_jid
- if config.get_by_tabname('send_chat_states', True, name, True) and self.remote_wants_chatstates:
+ if (config.get_by_tabname('send_chat_states', name)
+ and self.remote_wants_chatstates):
needed = 'inactive' if self.inactive else 'active'
self.cancel_paused_delay()
if not empty_after:
@@ -617,7 +618,7 @@ class ChatTab(Tab):
we create a timed event that will put us to paused
in a few seconds
"""
- if not config.get_by_tabname('send_chat_states', True, self.general_jid, True):
+ if not config.get_by_tabname('send_chat_states', self.general_jid):
return
# First, cancel the delay if it already exists, before rescheduling
# it at a new date
@@ -803,32 +804,17 @@ class OneToOneTab(ChatTab):
else:
self.__initial_disco = True
- empty = not any((correct, attention, receipts))
-
- features = []
- if correct or empty:
- features.append(_('message correction (/correct)'))
- if attention or empty:
- features.append(_('attention requests (/attention)'))
- if (receipts or empty) \
- and config.get('request_message_receipts', True):
- features.append(_('message delivery receipts'))
- if len(features) > 1:
- tail = features.pop()
- else:
- tail = None
- features_str = ', '.join(features)
- if tail and empty:
- features_str += _(', or %s') % tail
- elif tail:
- features_str += _(' and %s') % tail
-
- if empty:
- msg = _('\x19%s}This contact does not support %s.')
- else:
- msg = _('\x19%s}This contact supports %s.')
+ ok = get_theme().CHAR_OK
+ nope = get_theme().CHAR_EMPTY
+
+ correct = ok if correct else nope
+ attention = ok if attention else nope
+ receipts = ok if receipts else nope
+
+ msg = _('\x19%s}Contact supports: correction [%s], '
+ 'attention [%s], receipts [%s].')
color = dump_tuple(get_theme().COLOR_INFORMATION_TEXT)
- msg = msg % (color, features_str)
+ msg = msg % (color, correct, attention, receipts)
self.add_message(msg, typ=0)
self.core.refresh_window()
diff --git a/src/tabs/conversationtab.py b/src/tabs/conversationtab.py
index cc9e6b2e..3d5769f7 100644
--- a/src/tabs/conversationtab.py
+++ b/src/tabs/conversationtab.py
@@ -108,7 +108,7 @@ class ConversationTab(OneToOneTab):
replaced = False
if correct or msg['replace']['id']:
msg['replace']['id'] = self.last_sent_message['id']
- if config.get_by_tabname('group_corrections', True, self.name):
+ if config.get_by_tabname('group_corrections', self.name):
try:
self.modify_message(msg['body'], self.last_sent_message['id'], msg['id'], jid=self.core.xmpp.boundjid,
nickname=self.core.own_nick)
@@ -121,7 +121,8 @@ class ConversationTab(OneToOneTab):
msg.enable('html')
msg['html']['body'] = xhtml.poezio_colors_to_html(msg['body'])
msg['body'] = xhtml.clean_text(msg['body'])
- if config.get_by_tabname('send_chat_states', True, self.general_jid, True) and self.remote_wants_chatstates is not False:
+ if (config.get_by_tabname('send_chat_states', self.general_jid) and
+ self.remote_wants_chatstates is not False):
needed = 'inactive' if self.inactive else 'active'
msg['chat_state'] = needed
if attention and self.remote_supports_attention:
@@ -316,7 +317,9 @@ class ConversationTab(OneToOneTab):
self.state = 'normal'
self.text_win.remove_line_separator()
self.text_win.add_line_separator(self._text_buffer)
- if config.get_by_tabname('send_chat_states', True, self.general_jid, True) and (not self.input.get_text() or not self.input.get_text().startswith('//')):
+ if (config.get_by_tabname('send_chat_states', self.general_jid)
+ and (not self.input.get_text()
+ or not self.input.get_text().startswith('//'))):
if resource:
self.send_chat_state('inactive')
self.check_scrolled()
@@ -334,7 +337,9 @@ class ConversationTab(OneToOneTab):
self.state = 'current'
curses.curs_set(1)
- if config.get_by_tabname('send_chat_states', True, self.general_jid, True) and (not self.input.get_text() or not self.input.get_text().startswith('//')):
+ if (config.get_by_tabname('send_chat_states', self.general_jid)
+ and (not self.input.get_text()
+ or not self.input.get_text().startswith('//'))):
if resource:
self.send_chat_state('active')
@@ -349,7 +354,7 @@ class ConversationTab(OneToOneTab):
def on_close(self):
Tab.on_close(self)
- if config.get_by_tabname('send_chat_states', True, self.general_jid, True):
+ if config.get_by_tabname('send_chat_states', self.general_jid):
self.send_chat_state('gone')
def matching_names(self):
diff --git a/src/tabs/muctab.py b/src/tabs/muctab.py
index f526ec80..547830cb 100644
--- a/src/tabs/muctab.py
+++ b/src/tabs/muctab.py
@@ -251,7 +251,7 @@ class MucTab(ChatTab):
def completion_nick(self, the_input):
"""Completion for /nick"""
nicks = [os.environ.get('USER'),
- config.get('default_nick', ''),
+ config.get('default_nick'),
self.core.get_bookmark_nickname(self.name)]
nicks = [i for i in nicks if i]
return the_input.auto_completion(nicks, '', quotify=False)
@@ -471,8 +471,8 @@ class MucTab(ChatTab):
char_quit = get_theme().CHAR_QUIT
spec_col = dump_tuple(get_theme().COLOR_QUIT_CHAR)
- if config.get_by_tabname('display_user_color_in_join_part', True,
- self.general_jid, True):
+ if config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
color = dump_tuple(get_theme().COLOR_OWN_NICK)
else:
color = 3
@@ -737,8 +737,8 @@ class MucTab(ChatTab):
msg.enable('html')
msg['html']['body'] = xhtml.poezio_colors_to_html(msg['body'])
msg['body'] = xhtml.clean_text(msg['body'])
- if (config.get_by_tabname('send_chat_states', True, self.general_jid,
- True) and self.remote_wants_chatstates is not False):
+ if (config.get_by_tabname('send_chat_states', self.general_jid)
+ and self.remote_wants_chatstates is not False):
msg['chat_state'] = needed
if correct:
msg['replace']['id'] = self.last_sent_message['id']
@@ -803,7 +803,7 @@ class MucTab(ChatTab):
Resize the whole window. i.e. all its sub-windows
"""
self.need_resize = False
- if config.get("hide_user_list", False) or self.size.tab_degrade_x:
+ if config.get('hide_user_list') or self.size.tab_degrade_x:
display_user_list = False
text_width = self.width
else:
@@ -844,7 +844,7 @@ class MucTab(ChatTab):
if self.need_resize:
self.resize()
log.debug(' TAB Refresh: %s', self.__class__.__name__)
- if config.get("hide_user_list", False) or self.size.tab_degrade_x:
+ if config.get('hide_user_list') or self.size.tab_degrade_x:
display_user_list = False
else:
display_user_list = True
@@ -887,7 +887,7 @@ class MucTab(ChatTab):
for user in sorted(self.users, key=compare_users, reverse=True):
if user.nick != self.own_nick:
word_list.append(user.nick)
- after = config.get('after_completion', ',') + ' '
+ after = config.get('after_completion') + ' '
input_pos = self.input.pos
if ' ' not in self.input.get_text()[:input_pos] or (
self.input.last_completion and
@@ -895,7 +895,7 @@ class MucTab(ChatTab):
self.input.last_completion + after):
add_after = after
else:
- if not config.get('add_space_after_completion', True):
+ if not config.get('add_space_after_completion'):
add_after = ''
else:
add_after = ' '
@@ -907,7 +907,7 @@ class MucTab(ChatTab):
self.send_composing_chat_state(empty_after)
def get_nick(self):
- if not config.get('show_muc_jid', True):
+ if not config.get('show_muc_jid'):
return safeJID(self.name).user
return self.name
@@ -924,25 +924,25 @@ class MucTab(ChatTab):
self.state = 'disconnected'
self.text_win.remove_line_separator()
self.text_win.add_line_separator(self._text_buffer)
- if config.get_by_tabname('send_chat_states', True,
- self.general_jid, True) and not self.input.get_text():
+ if (config.get_by_tabname('send_chat_states', self.general_jid) and
+ not self.input.get_text()):
self.send_chat_state('inactive')
self.check_scrolled()
def on_gain_focus(self):
self.state = 'current'
if (self.text_win.built_lines and self.text_win.built_lines[-1] is None
- and not config.get('show_useless_separator', False)):
+ and not config.get('show_useless_separator')):
self.text_win.remove_line_separator()
curses.curs_set(1)
- if self.joined and config.get_by_tabname('send_chat_states', True,
- self.general_jid, True) and not self.input.get_text():
+ if self.joined and config.get_by_tabname('send_chat_states',
+ self.general_jid) and not self.input.get_text():
self.send_chat_state('active')
def on_info_win_size_changed(self):
if self.core.information_win_size >= self.height-3:
return
- if config.get("hide_user_list", False):
+ if config.get("hide_user_list"):
text_width = self.width
else:
text_width = (self.width//10)*9
@@ -1003,7 +1003,7 @@ class MucTab(ChatTab):
new_user.color = get_theme().COLOR_OWN_NICK
if config.get_by_tabname('display_user_color_in_join_part',
- True, self.general_jid, True):
+ self.general_jid):
color = dump_tuple(new_user.color)
else:
color = 3
@@ -1122,11 +1122,11 @@ class MucTab(ChatTab):
user = User(from_nick, affiliation,
show, status, role, jid)
self.users.append(user)
- hide_exit_join = config.get_by_tabname('hide_exit_join', -1,
- self.general_jid, True)
+ hide_exit_join = config.get_by_tabname('hide_exit_join',
+ self.general_jid)
if hide_exit_join != 0:
- if config.get_by_tabname('display_user_color_in_join_part', True,
- self.general_jid, True):
+ if config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
color = dump_tuple(user.color)
else:
color = 3
@@ -1163,8 +1163,8 @@ class MucTab(ChatTab):
self.core.on_muc_own_nickchange(self)
user.change_nick(new_nick)
- if config.get_by_tabname('display_user_color_in_join_part', True,
- self.general_jid, True):
+ if config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
color = dump_tuple(user.color)
else:
color = 3
@@ -1206,10 +1206,9 @@ class MucTab(ChatTab):
self.refresh_tab_win()
self.core.current_tab().input.refresh()
self.core.doupdate()
- if config.get_by_tabname('autorejoin', False,
- self.general_jid, True):
- delay = config.get_by_tabname('autorejoin_delay', '5',
- self.general_jid, True)
+ if config.get_by_tabname('autorejoin', self.general_jid):
+ delay = config.get_by_tabname('autorejoin_delay',
+ self.general_jid)
delay = common.parse_str_to_secs(delay)
if delay <= 0:
muc.join_groupchat(self.core, self.name, self.own_nick)
@@ -1223,7 +1222,7 @@ class MucTab(ChatTab):
else:
if config.get_by_tabname('display_user_color_in_join_part',
- True, self.general_jid, True):
+ self.general_jid):
color = dump_tuple(user.color)
else:
color = 3
@@ -1278,10 +1277,9 @@ class MucTab(ChatTab):
self.core.current_tab().input.refresh()
self.core.doupdate()
# try to auto-rejoin
- if config.get_by_tabname('autorejoin', False,
- self.general_jid, True):
- delay = config.get_by_tabname('autorejoin_delay', "5",
- self.general_jid, True)
+ if config.get_by_tabname('autorejoin', self.general_jid):
+ delay = config.get_by_tabname('autorejoin_delay',
+ self.general_jid)
delay = common.parse_str_to_secs(delay)
if delay <= 0:
muc.join_groupchat(self.core, self.name, self.own_nick)
@@ -1293,8 +1291,8 @@ class MucTab(ChatTab):
self.name,
self.own_nick))
else:
- if config.get_by_tabname('display_user_color_in_join_part', True,
- self.general_jid, True):
+ if config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
color = dump_tuple(user.color)
else:
color = 3
@@ -1327,13 +1325,12 @@ class MucTab(ChatTab):
self.core.disable_private_tabs(from_room)
self.refresh_tab_win()
- hide_exit_join = max(config.get_by_tabname('hide_exit_join', -1,
- self.general_jid, True),
- -1)
+ hide_exit_join = config.get_by_tabname('hide_exit_join',
+ self.general_jid)
- if hide_exit_join == -1 or user.has_talked_since(hide_exit_join):
- if config.get_by_tabname('display_user_color_in_join_part', True,
- self.general_jid, True):
+ if hide_exit_join <= -1 or user.has_talked_since(hide_exit_join):
+ if config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
color = dump_tuple(user.color)
else:
color = 3
@@ -1373,8 +1370,8 @@ class MucTab(ChatTab):
# build the message
display_message = False # flag to know if something significant enough
# to be displayed has changed
- if config.get_by_tabname('display_user_color_in_join_part', True,
- self.general_jid, True):
+ if config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
color = dump_tuple(user.color)
else:
color = 3
@@ -1410,8 +1407,8 @@ class MucTab(ChatTab):
if not display_message:
return
msg = msg[:-2] # remove the last ", "
- hide_status_change = config.get_by_tabname('hide_status_change', -1,
- self.general_jid, True)
+ hide_status_change = config.get_by_tabname('hide_status_change',
+ self.general_jid)
if hide_status_change < -1:
hide_status_change = -1
if ((hide_status_change == -1 or \
@@ -1471,9 +1468,9 @@ class MucTab(ChatTab):
self.state = 'highlight'
highlighted = True
else:
- highlight_words = config.get_by_tabname('highlight_on', '',
- self.general_jid,
- True).split(':')
+ highlight_words = config.get_by_tabname('highlight_on',
+ self.general_jid)
+ highlight_words = highlight_words.split(':')
for word in highlight_words:
if word and word.lower() in txt.lower():
if self.state != 'current':
@@ -1481,10 +1478,9 @@ class MucTab(ChatTab):
highlighted = True
break
if highlighted:
- beep_on = config.get('beep_on', 'highlight private').split()
+ beep_on = config.get('beep_on').split()
if 'highlight' in beep_on and 'message' not in beep_on:
- if not config.get_by_tabname('disable_beep', False,
- self.name, False):
+ if not config.get_by_tabname('disable_beep', self.name):
curses.beep()
return highlighted
@@ -1523,8 +1519,7 @@ class MucTab(ChatTab):
if (not time and nickname and nickname != self.own_nick
and self.state != 'current'):
if (self.state != 'highlight' and
- config.get_by_tabname('notify_messages',
- True, self.name)):
+ config.get_by_tabname('notify_messages', self.name)):
self.state = 'message'
if time and not txt.startswith('/me'):
txt = '\x19%(info_col)s}%(txt)s' % {
diff --git a/src/tabs/privatetab.py b/src/tabs/privatetab.py
index c1e8c8e5..4c01cd70 100644
--- a/src/tabs/privatetab.py
+++ b/src/tabs/privatetab.py
@@ -109,7 +109,7 @@ class PrivateTab(OneToOneTab):
compare_users = lambda x: x.last_talked
word_list = [user.nick for user in sorted(self.parent_muc.users, key=compare_users, reverse=True)\
if user.nick != self.own_nick]
- after = config.get('after_completion', ',')+" "
+ after = config.get('after_completion') + ' '
input_pos = self.input.pos
if ' ' not in self.input.get_text()[:input_pos] or (self.input.last_completion and\
self.input.get_text()[:input_pos] == self.input.last_completion + after):
@@ -139,7 +139,7 @@ class PrivateTab(OneToOneTab):
replaced = False
if correct or msg['replace']['id']:
msg['replace']['id'] = self.last_sent_message['id']
- if config.get_by_tabname('group_corrections', True, self.name):
+ if config.get_by_tabname('group_corrections', self.name):
try:
self.modify_message(msg['body'], self.last_sent_message['id'], msg['id'],
user=user, jid=self.core.xmpp.boundjid, nickname=self.own_nick)
@@ -153,7 +153,8 @@ class PrivateTab(OneToOneTab):
msg.enable('html')
msg['html']['body'] = xhtml.poezio_colors_to_html(msg['body'])
msg['body'] = xhtml.clean_text(msg['body'])
- if config.get_by_tabname('send_chat_states', True, self.general_jid, True) and self.remote_wants_chatstates is not False:
+ if (config.get_by_tabname('send_chat_states', self.general_jid) and
+ self.remote_wants_chatstates is not False):
needed = 'inactive' if self.inactive else 'active'
msg['chat_state'] = needed
if attention and self.remote_supports_attention:
@@ -278,9 +279,8 @@ class PrivateTab(OneToOneTab):
self.text_win.remove_line_separator()
self.text_win.add_line_separator(self._text_buffer)
tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab)
- if tab and tab.joined and config.get_by_tabname(
- 'send_chat_states', True, self.general_jid, True) and\
- not self.input.get_text() and self.on:
+ if tab and tab.joined and config.get_by_tabname('send_chat_states',
+ self.general_jid) and not self.input.get_text() and self.on:
self.send_chat_state('inactive')
self.check_scrolled()
@@ -288,9 +288,8 @@ class PrivateTab(OneToOneTab):
self.state = 'current'
curses.curs_set(1)
tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab)
- if tab and tab.joined and config.get_by_tabname(
- 'send_chat_states', True, self.general_jid, True) and\
- not self.input.get_text() and self.on:
+ if tab and tab.joined and config.get_by_tabname('send_chat_states',
+ self.general_jid,) and not self.input.get_text() and self.on:
self.send_chat_state('active')
def on_info_win_size_changed(self):
@@ -334,7 +333,8 @@ class PrivateTab(OneToOneTab):
self.check_features()
tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab)
color = 3
- if tab and config.get_by_tabname('display_user_color_in_join_part', '', self.general_jid, True):
+ if tab and config.get_by_tabname('display_user_color_in_join_part',
+ self.general_jid):
user = tab.get_user_by_name(nick)
if user:
color = dump_tuple(user.color)
diff --git a/src/tabs/rostertab.py b/src/tabs/rostertab.py
index 1ee98dd8..878e89ed 100644
--- a/src/tabs/rostertab.py
+++ b/src/tabs/rostertab.py
@@ -351,7 +351,7 @@ class RosterInfoTab(Tab):
def callback(iq):
if iq['type'] == 'result':
self.core.information('Password updated', 'Account')
- if config.get('password', ''):
+ if config.get('password'):
config.silent_set('password', arg)
else:
self.core.information('Unable to change the password', 'Account')
@@ -765,7 +765,7 @@ class RosterInfoTab(Tab):
Show or hide offline contacts
"""
option = 'roster_show_offline'
- value = config.get(option, False)
+ value = config.get(option)
success = config.silent_set(option, str(not value))
roster.modified()
if not success:
diff --git a/src/text_buffer.py b/src/text_buffer.py
index 4a41fd97..59aa96e1 100644
--- a/src/text_buffer.py
+++ b/src/text_buffer.py
@@ -64,7 +64,7 @@ class TextBuffer(object):
def __init__(self, messages_nb_limit=None):
if messages_nb_limit is None:
- messages_nb_limit = config.get('max_messages_in_memory', 2048)
+ messages_nb_limit = config.get('max_messages_in_memory')
self.messages_nb_limit = messages_nb_limit
# Message objects
self.messages = []
@@ -138,7 +138,7 @@ class TextBuffer(object):
self.messages.pop(0)
ret_val = None
- show_timestamps = config.get('show_timestamps', True)
+ show_timestamps = config.get('show_timestamps')
for window in self.windows: # make the associated windows
# build the lines from the new message
nb = window.build_new_message(msg, history=history,
diff --git a/src/theming.py b/src/theming.py
index 9820addf..1e9d6c40 100755
--- a/src/theming.py
+++ b/src/theming.py
@@ -301,10 +301,13 @@ class Theme(object):
CHAR_QUIT = '<---'
CHAR_KICK = '-!-'
CHAR_NEW_TEXT_SEPARATOR = '- '
- CHAR_ACK_RECEIVED = '✔'
+ CHAR_OK = '✔'
+ CHAR_ERROR = '✖'
+ CHAR_EMPTY = ' '
+ CHAR_ACK_RECEIVED = CHAR_OK
CHAR_COLUMN_ASC = ' ▲'
CHAR_COLUMN_DESC = ' ▼'
- CHAR_ROSTER_ERROR = '✖'
+ CHAR_ROSTER_ERROR = CHAR_ERROR
CHAR_ROSTER_TUNE = '♪'
CHAR_ROSTER_ASKED = '?'
CHAR_ROSTER_ACTIVITY = 'A'
@@ -455,7 +458,7 @@ def update_themes_dir(option=None, value=None):
# import from the user-defined prefs
themes_dir = path.expanduser(
value or
- config.get('themes_dir', '') or
+ config.get('themes_dir') or
path.join(os.environ.get('XDG_DATA_HOME') or
path.join(os.environ.get('HOME'), '.local', 'share'),
'poezio', 'themes')
@@ -482,7 +485,7 @@ def update_themes_dir(option=None, value=None):
log.debug('Theme load path: %s', load_path)
def reload_theme():
- theme_name = config.get('theme', 'default')
+ theme_name = config.get('theme')
global theme
if theme_name == 'default' or not theme_name.strip():
theme = Theme()
diff --git a/src/windows/funcs.py b/src/windows/funcs.py
index 47011faf..d58d4683 100644
--- a/src/windows/funcs.py
+++ b/src/windows/funcs.py
@@ -20,7 +20,7 @@ def find_first_format_char(text, chars=None):
return pos
def truncate_nick(nick, size=None):
- size = size or config.get('max_nick_length', 25)
+ size = size or config.get('max_nick_length')
if size < 1:
size = 1
if nick and len(nick) > size:
diff --git a/src/windows/info_bar.py b/src/windows/info_bar.py
index 10225d5d..e66343c5 100644
--- a/src/windows/info_bar.py
+++ b/src/windows/info_bar.py
@@ -24,10 +24,10 @@ class GlobalInfoBar(Win):
self._win.erase()
self.addstr(0, 0, "[", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
- create_gaps = config.get('create_gaps', False)
- show_names = config.get('show_tab_names', False)
- show_nums = config.get('show_tab_numbers', True)
- use_nicks = config.get('use_tab_nicks', True)
+ 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[:]
@@ -37,7 +37,7 @@ class GlobalInfoBar(Win):
for nb, tab in enumerate(sorted_tabs):
if not tab: continue
color = tab.color
- if not config.get('show_inactive_tabs', True) and\
+ if not config.get('show_inactive_tabs') and\
color is get_theme().COLOR_TAB_NORMAL:
continue
try:
@@ -70,11 +70,11 @@ class VerticalGlobalInfoBar(Win):
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', True):
+ 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', True)
+ 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:
@@ -89,8 +89,7 @@ class VerticalGlobalInfoBar(Win):
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', 'desc') != 'asc':
+ 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))
diff --git a/src/windows/inputs.py b/src/windows/inputs.py
index afce3dd8..d345443b 100644
--- a/src/windows/inputs.py
+++ b/src/windows/inputs.py
@@ -564,7 +564,7 @@ class HistoryInput(Input):
self.current_completed = ''
self.key_func['^R'] = self.toggle_search
self.search = False
- if config.get('separate_history', False):
+ if config.get('separate_history'):
self.history = list()
def toggle_search(self):
diff --git a/src/windows/muc.py b/src/windows/muc.py
index 02bc58ef..7e3541ba 100644
--- a/src/windows/muc.py
+++ b/src/windows/muc.py
@@ -34,10 +34,10 @@ class UserList(Win):
def refresh(self, users):
log.debug('Refresh: %s', self.__class__.__name__)
- if config.get("hide_user_list", False):
+ if config.get('hide_user_list'):
return # do not refresh if this win is hidden.
self._win.erase()
- if config.get('user_list_sort', 'desc').lower() == 'asc':
+ if config.get('user_list_sort').lower() == 'asc':
y, x = self._win.getmaxyx()
y -= 1
users = sorted(users)
@@ -55,7 +55,7 @@ class UserList(Win):
self.addstr(y, 2,
poopt.cut_by_columns(user.nick, self.width - 2),
to_curses_attr(user.color))
- if config.get('user_list_sort', 'desc').lower() == 'asc':
+ if config.get('user_list_sort').lower() == 'asc':
y -= 1
else:
y += 1
@@ -63,12 +63,12 @@ class UserList(Win):
break
# draw indicators of position in the list
if self.pos > 0:
- if config.get('user_list_sort', 'desc').lower() == 'asc':
+ 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', 'desc').lower() == 'asc':
+ if config.get('user_list_sort').lower() == 'asc':
self.draw_plus(0)
else:
self.draw_plus(self.height-1)
diff --git a/src/windows/roster_win.py b/src/windows/roster_win.py
index f9858a3a..6ecb6128 100644
--- a/src/windows/roster_win.py
+++ b/src/windows/roster_win.py
@@ -99,9 +99,9 @@ class RosterWin(Win):
for contact in roster.get_contacts_sorted_filtered(sort):
self.roster_cache.append(contact)
else:
- show_offline = config.get('roster_show_offline', False) or roster.contact_filter
- sort = config.get('roster_sort', 'jid:show') or 'jid:show'
- group_sort = config.get('roster_group_sort', 'name') or 'name'
+ 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):
@@ -229,7 +229,7 @@ class RosterWin(Win):
self.addstr(y, 0, ' ')
self.addstr(theme.CHAR_STATUS, to_curses_attr(color))
- show_roster_sub = config.get('show_roster_subscriptions', '')
+ show_roster_sub = config.get('show_roster_subscriptions')
self.addstr(' ')
if resource:
@@ -237,7 +237,7 @@ class RosterWin(Win):
added += 4
if contact.ask:
added += len(get_theme().CHAR_ROSTER_ASKED)
- if config.get('show_s2s_errors', True) and contact.error:
+ if config.get('show_s2s_errors') and contact.error:
added += len(get_theme().CHAR_ROSTER_ERROR)
if contact.tune:
added += len(get_theme().CHAR_ROSTER_TUNE)
@@ -250,7 +250,7 @@ class RosterWin(Win):
if show_roster_sub in ('all', 'incomplete', 'to', 'from', 'both', 'none'):
added += len(theme.char_subscription(contact.subscription, keep=show_roster_sub))
- if not config.get('show_roster_jids', True) and contact.name:
+ if not config.get('show_roster_jids') and contact.name:
display_name = '%s' % contact.name
elif contact.name and contact.name != contact.bare_jid:
display_name = '%s (%s)' % (contact.name, contact.bare_jid)
@@ -268,7 +268,7 @@ class RosterWin(Win):
self.addstr(theme.char_subscription(contact.subscription, keep=show_roster_sub), to_curses_attr(theme.COLOR_ROSTER_SUBSCRIPTION))
if contact.ask:
self.addstr(get_theme().CHAR_ROSTER_ASKED, to_curses_attr(get_theme().COLOR_IMPORTANT_TEXT))
- if config.get('show_s2s_errors', True) and contact.error:
+ if config.get('show_s2s_errors') and contact.error:
self.addstr(get_theme().CHAR_ROSTER_ERROR, to_curses_attr(get_theme().COLOR_ROSTER_ERROR))
if contact.tune:
self.addstr(get_theme().CHAR_ROSTER_TUNE, to_curses_attr(get_theme().COLOR_ROSTER_TUNE))
diff --git a/src/windows/text_win.py b/src/windows/text_win.py
index 86aea1ef..8a8f75ae 100644
--- a/src/windows/text_win.py
+++ b/src/windows/text_win.py
@@ -19,7 +19,9 @@ from theming import to_curses_attr, get_theme, dump_tuple
class TextWin(Win):
- def __init__(self, lines_nb_limit=config.get('max_lines_in_memory', 2048)):
+ def __init__(self, lines_nb_limit=None):
+ if lines_nb_limit is None:
+ lines_nb_limit = config.get('max_lines_in_memory')
Win.__init__(self)
self.lines_nb_limit = lines_nb_limit
self.pos = 0
@@ -265,7 +267,7 @@ class TextWin(Win):
lines = self.built_lines[-self.height:]
else:
lines = self.built_lines[-self.height-self.pos:-self.pos]
- with_timestamps = config.get("show_timestamps", True)
+ with_timestamps = config.get("show_timestamps")
self._win.move(0, 0)
self._win.erase()
for y, line in enumerate(lines):
@@ -402,7 +404,7 @@ class TextWin(Win):
def rebuild_everything(self, room):
self.built_lines = []
- with_timestamps = config.get("show_timestamps", True)
+ with_timestamps = config.get('show_timestamps')
for message in room.messages:
self.build_new_message(message, clean=False, timestamp=with_timestamps)
if self.separator_after is message:
@@ -415,7 +417,7 @@ class TextWin(Win):
Find a message, and replace it with a new one
(instead of rebuilding everything in order to correct a message)
"""
- with_timestamps = config.get("show_timestamps", True)
+ with_timestamps = config.get('show_timestamps')
for i in range(len(self.built_lines)-1, -1, -1):
if self.built_lines[i] and self.built_lines[i].msg.identifier == old_id:
index = i
diff --git a/src/xhtml.py b/src/xhtml.py
index b00fe9a9..01e2dfcd 100644
--- a/src/xhtml.py
+++ b/src/xhtml.py
@@ -12,9 +12,13 @@ xhtml code to shell colors,
poezio colors to xhtml code
"""
-import re
+import base64
import curses
+import hashlib
+import re
+from os import path
from slixmpp.xmlstream import ET
+from urllib.parse import unquote
from io import BytesIO
from xml import sax
@@ -178,10 +182,12 @@ colors = {
whitespace_re = re.compile(r'\s+')
xhtml_attr_re = re.compile(r'\x19-?\d[^}]*}|\x19[buaio]')
+xhtml_data_re = re.compile(r'data:image/([a-z]+);base64,(.+)')
xhtml_simple_attr_re = re.compile(r'\x19\d')
-def get_body_from_message_stanza(message, use_xhtml=False):
+def get_body_from_message_stanza(message, use_xhtml=False,
+ tmp_dir=None, extract_images=False):
"""
Returns a string with xhtml markups converted to
poezio colors if there's an xhtml_im element, or
@@ -191,7 +197,8 @@ def get_body_from_message_stanza(message, use_xhtml=False):
xhtml = message['html'].xml
xhtml_body = xhtml.find('{http://www.w3.org/1999/xhtml}body')
if xhtml_body:
- content = xhtml_to_poezio_colors(xhtml_body)
+ content = xhtml_to_poezio_colors(xhtml_body, tmp_dir=tmp_dir,
+ extract_images=extract_images)
content = content if content else message['body']
return content or " "
return message['body']
@@ -281,7 +288,7 @@ def trim(string):
return re.sub(whitespace_re, ' ', string)
class XHTMLHandler(sax.ContentHandler):
- def __init__(self, force_ns=False):
+ def __init__(self, force_ns=False, tmp_dir=None, extract_images=False):
self.builder = []
self.formatting = []
self.attrs = []
@@ -291,6 +298,9 @@ class XHTMLHandler(sax.ContentHandler):
# do not care about xhtml-in namespace
self.force_ns = force_ns
+ self.tmp_dir = tmp_dir
+ self.extract_images = extract_images
+
@property
def result(self):
return ''.join(self.builder).strip()
@@ -331,7 +341,22 @@ class XHTMLHandler(sax.ContentHandler):
elif name == 'em':
self.append_formatting('\x19i')
elif name == 'img':
- builder.append(trim(attrs['src']))
+ if re.match(xhtml_data_re, attrs['src']) and self.extract_images:
+ type_, data = [i for i in re.split(xhtml_data_re, attrs['src']) if i]
+ bin_data = base64.b64decode(unquote(data))
+ filename = hashlib.sha1(bin_data).hexdigest() + '.' + type_
+ filepath = path.join(self.tmp_dir, filename)
+ if not path.exists(filepath):
+ try:
+ with open(filepath, 'wb') as fd:
+ fd.write(bin_data)
+ builder.append('file://%s' % filepath)
+ except Exception as e:
+ builder.append('[Error while saving image: %s]' % e)
+ else:
+ builder.append('file://%s' % filepath)
+ else:
+ builder.append(trim(attrs['src']))
if 'alt' in attrs:
builder.append(' (%s)' % trim(attrs['alt']))
elif name == 'ul':
@@ -389,13 +414,14 @@ class XHTMLHandler(sax.ContentHandler):
if 'title' in attrs:
builder.append(' [' + attrs['title'] + ']')
-def xhtml_to_poezio_colors(xml, force=False):
+def xhtml_to_poezio_colors(xml, force=False, tmp_dir=None, extract_images=None):
if isinstance(xml, str):
xml = xml.encode('utf8')
elif not isinstance(xml, bytes):
xml = ET.tostring(xml)
- handler = XHTMLHandler(force_ns=force)
+ handler = XHTMLHandler(force_ns=force, tmp_dir=tmp_dir,
+ extract_images=extract_images)
parser = sax.make_parser()
parser.setFeature(sax.handler.feature_namespaces, True)
parser.setContentHandler(handler)
diff --git a/test/test_common.py b/test/test_common.py
new file mode 100644
index 00000000..315318bd
--- /dev/null
+++ b/test/test_common.py
@@ -0,0 +1,79 @@
+"""
+Test the functions in the `common` module
+"""
+
+import sys
+sys.path.append('src')
+
+import time
+import pytest
+import datetime
+from sleekxmpp import JID
+from datetime import timedelta
+from common import (datetime_tuple, get_utc_time, get_local_time, shell_split,
+ find_argument_quoted, find_argument_unquoted,
+ parse_str_to_secs, parse_secs_to_str, safeJID)
+
+def test_datetime_tuple():
+ time.timezone = 0
+ time.altzone = 0
+
+ assert datetime_tuple('20130226T06:23:12') == datetime.datetime(2013, 2, 26, 6, 23, 12)
+ assert datetime_tuple('2013-02-26T06:23:12+02:00') == datetime.datetime(2013, 2, 26, 4, 23, 12)
+
+ time.timezone = -3600
+ time.altzone = -3600
+
+ assert datetime_tuple('20130226T07:23:12') == datetime.datetime(2013, 2, 26, 8, 23, 12)
+ assert datetime_tuple('2013-02-26T07:23:12+02:00') == datetime.datetime(2013, 2, 26, 6, 23, 12)
+
+def test_utc_time():
+ delta = timedelta(seconds=-3600)
+ d = datetime.datetime.now()
+ time.timezone = -3600; time.altzone = -3600
+ assert get_utc_time(local_time=d) == d + delta
+
+def test_local_time():
+ delta = timedelta(seconds=-3600)
+ d = datetime.datetime.now()
+ time.timezone = -3600
+ time.altzone = -3600
+ assert get_local_time(d) == d - delta
+
+def test_shell_split():
+ assert shell_split('"sdf 1" "toto 2"') == ['sdf 1', 'toto 2']
+ assert shell_split('toto "titi"') == ['toto', 'titi']
+ assert shell_split('toto ""') == ['toto', '']
+ assert shell_split('to"to titi "a" b') == ['to"to', 'titi', 'a', 'b']
+ assert shell_split('"toto titi" toto ""') == ['toto titi', 'toto', '']
+ assert shell_split('toto "titi') == ['toto', 'titi']
+
+def test_argument_quoted():
+ assert find_argument_quoted(4, 'toto titi tata') == 3
+ assert find_argument_quoted(4, '"toto titi" tata') == 0
+ assert find_argument_quoted(8, '"toto" "titi tata"') == 1
+ assert find_argument_quoted(8, '"toto" "titi tata') == 1
+ assert find_argument_quoted(3, '"toto" "titi tata') == 0
+ assert find_argument_quoted(18, '"toto" "titi tata" ') == 2
+
+def test_argument_unquoted():
+ assert find_argument_unquoted(2, 'toto titi tata') == 0
+ assert find_argument_unquoted(3, 'toto titi tata') == 0
+ assert find_argument_unquoted(6, 'toto titi tata') == 1
+ assert find_argument_unquoted(4, 'toto titi tata') == 3
+ assert find_argument_unquoted(25, 'toto titi tata') == 3
+
+def test_parse_str_to_secs():
+ assert parse_str_to_secs("1d3m1h") == 90180
+ assert parse_str_to_secs("1d3mfaiiiiil") == 0
+
+def test_parse_secs_to_str():
+ assert parse_secs_to_str(3601) == '1h1s'
+ assert parse_secs_to_str(0) == '0s'
+
+ with pytest.raises(TypeError):
+ parse_secs_to_str('toto')
+
+def test_safeJID():
+ assert safeJID('toto@titi/tata') == JID('toto@titi/tata')
+ assert safeJID('é_è') == JID('')
diff --git a/test/test_config.py b/test/test_config.py
new file mode 100644
index 00000000..f8d06258
--- /dev/null
+++ b/test/test_config.py
@@ -0,0 +1,114 @@
+"""
+Test the config module
+"""
+
+import tempfile
+import pytest
+import sys
+import os
+
+
+sys.path.append('src')
+
+import config
+
+@pytest.yield_fixture(scope="module")
+def config_obj():
+ file_ = tempfile.NamedTemporaryFile(delete=False)
+ conf = config.Config(file_name=file_.name)
+ yield conf
+ del conf
+ os.unlink(file_.name)
+
+class TestConfigSimple(object):
+ def test_get_set(self, config_obj):
+ config_obj.set_and_save('test', value='coucou')
+ config_obj.set_and_save('test2', value='true')
+ assert config_obj.get('test') == 'coucou'
+ assert config_obj.get('test2') == 'true'
+ assert config_obj.get('toto') == ''
+
+ def test_file_content(self, config_obj):
+ with open(config_obj.file_name, 'r') as fd:
+ data = fd.read()
+ supposed_content = '[Poezio]\ntest = coucou\ntest2 = true\n'
+ assert data == supposed_content
+
+ def test_get_types(self, config_obj):
+
+ config_obj.set_and_save('test_int', '99')
+ config_obj.set_and_save('test_int_neg', '-1')
+ config_obj.set_and_save('test_bool_t', 'true')
+ config_obj.set_and_save('test_bool_f', 'false')
+ config_obj.set_and_save('test_float', '1.5')
+
+ assert config_obj.get('test_int', default=0) == 99
+ assert config_obj.get('test_int_neg', default=0) == -1
+ assert config_obj.get('test_bool_t', default=False) == True
+ assert config_obj.get('test_bool_f', default=True) == False
+ assert config_obj.get('test_float', default=1.0) == 1.5
+
+ def test_remove(self, config_obj):
+ with open(config_obj.file_name, 'r') as fd:
+ data = fd.read()
+
+ supposed_content = ('[Poezio]\ntest = coucou\ntest2 = true\n'
+ 'test_int = 99\ntest_int_neg = -1\ntest_bool_t ='
+ ' true\ntest_bool_f = false\ntest_float = 1.5\n')
+
+ assert data == supposed_content
+
+ config_obj.remove_and_save('test_int')
+ config_obj.remove_and_save('test_int_neg')
+ config_obj.remove_and_save('test_bool_t')
+ config_obj.remove_and_save('test_bool_f')
+ config_obj.remove_and_save('test_float')
+
+ with open(config_obj.file_name, 'r') as fd:
+ data = fd.read()
+
+ supposed_content = '[Poezio]\ntest = coucou\ntest2 = true\n'
+
+ assert data == supposed_content
+
+
+ def test_toggle(self, config_obj):
+ config_obj.set_and_save('test2', value='toggle')
+ assert config_obj.get('test2') == 'false'
+ config_obj.set_and_save('test2', value='toggle')
+ assert config_obj.get('test2') == 'true'
+
+ def test_get_set_default(self, config_obj):
+ assert config_obj.get('doesnotexist', 'toto@tata') == 'toto@tata'
+ assert config_obj.get('doesnotexist2', '1234') == '1234'
+
+class TestConfigSections(object):
+ def test_set_section(self, config_obj):
+ config_obj.set_and_save('option1', 'test', section='NotPoezio')
+ config_obj.set_and_save('option2', 'test2', section='NotPoezio')
+
+ assert config_obj.get('option1', section='NotPoezio') == 'test'
+ assert config_obj.get('option2', section='NotPoezio') == 'test2'
+
+ def test_file_content(self, config_obj):
+ with open(config_obj.file_name, 'r') as fd:
+ data = fd.read()
+ supposed_content = ('[Poezio]\ntest = coucou\ntest2 = true\n'
+ '[NotPoezio]\noption1 = test\noption2 = test2\n')
+ assert data == supposed_content
+
+class TestTabNames(object):
+ def test_get_tabname(self, config_obj):
+ config.post_logging_setup()
+ config_obj.set_and_save('test', value='value.toto@toto.com',
+ section='toto@toto.com')
+ config_obj.set_and_save('test2', value='value2@toto.com',
+ section='@toto.com')
+
+ assert config_obj.get_by_tabname('test', 'toto@toto.com') == 'value.toto@toto.com'
+ assert config_obj.get_by_tabname('test2', 'toto@toto.com') == 'value2@toto.com'
+ assert config_obj.get_by_tabname('test2', 'toto@toto.com', fallback=False) == 'value2@toto.com'
+ assert config_obj.get_by_tabname('test2', 'toto@toto.com', fallback_server=False) == 'true'
+ assert config_obj.get_by_tabname('test_int', 'toto@toto.com', fallback=False) == ''
+
+
diff --git a/test/test_poopt.py b/test/test_poopt.py
new file mode 100644
index 00000000..9b640ff0
--- /dev/null
+++ b/test/test_poopt.py
@@ -0,0 +1,14 @@
+"""
+Test of the poopt module
+"""
+
+import pytest
+import sys
+sys.path.append('src')
+
+from poopt import cut_text
+
+def test_cut_text():
+
+ text = '12345678901234567890'
+ assert cut_text(text, 5) == [(0, 5), (5, 10), (10, 15), (15, 20)]
diff --git a/test/test_theming.py b/test/test_theming.py
new file mode 100644
index 00000000..9cdb4829
--- /dev/null
+++ b/test/test_theming.py
@@ -0,0 +1,26 @@
+"""
+Test the functions in the `theming` module
+"""
+
+import sys
+import pytest
+sys.path.append('src')
+
+from theming import dump_tuple, read_tuple
+
+def test_read_tuple():
+ assert read_tuple('1,-1,u') == ((1, -1), 'u')
+ assert read_tuple('1,2') == ((1, 2), None)
+
+ with pytest.raises(IndexError):
+ read_tuple('1')
+
+ with pytest.raises(ValueError):
+ read_tuple('toto')
+
+def test_dump_tuple():
+ assert dump_tuple((1, 2)) == '1,2'
+ assert dump_tuple((1, )) == '1'
+ assert dump_tuple((1, 2, 'u')) == '1,2,u'
+
+
diff --git a/test/test_windows.py b/test/test_windows.py
new file mode 100644
index 00000000..8fb85295
--- /dev/null
+++ b/test/test_windows.py
@@ -0,0 +1,76 @@
+import pytest
+import sys
+sys.path.append('src')
+
+class ConfigShim(object):
+ def get(self, *args, **kwargs):
+ return ''
+
+import config
+config.config = ConfigShim()
+import core
+
+from windows import Input, HistoryInput, MessageInput, CommandInput
+
+@pytest.fixture
+def input():
+ input = Input()
+ input.rewrite_text = lambda: None
+ return input
+
+class TestInput(object):
+
+ def test_do_command(self, input):
+
+ input.do_command('a')
+ assert input.text == 'a'
+
+ for char in 'coucou':
+ input.do_command(char)
+ assert input.text == 'acoucou'
+
+ def test_empty(self, input):
+ assert input.is_empty() == True
+ input.do_command('a')
+ assert input.is_empty() == False
+
+ def test_key_left(self, input):
+ for char in 'this is a line':
+ input.do_command(char)
+ for i in range(4):
+ input.key_left()
+ for char in 'long ':
+ input.do_command(char)
+
+ assert input.text == 'this is a long line'
+
+ def test_key_right(self, input):
+ for char in 'this is a line':
+ input.do_command(char)
+ for i in range(4):
+ input.key_left()
+ input.key_right()
+
+ for char in 'iii':
+ input.do_command(char)
+
+ assert input.text == 'this is a liiiine'
+
+ def test_key_home(self, input):
+ for char in 'this is a line of text':
+ input.do_command(char)
+ input.do_command('z')
+ input.key_home()
+ input.do_command('a')
+
+ assert input.text == 'athis is a line of textz'
+
+ def test_key_end(self, input):
+ for char in 'this is a line of text':
+ input.do_command(char)
+ input.key_home()
+ input.key_end()
+ input.do_command('z')
+
+ assert input.text == 'this is a line of textz'
+
diff --git a/test/test_xhtml.py b/test/test_xhtml.py
new file mode 100644
index 00000000..58857d67
--- /dev/null
+++ b/test/test_xhtml.py
@@ -0,0 +1,50 @@
+"""
+Test the functions in the `xhtml` module
+"""
+
+import pytest
+import sys
+import xml
+sys.path.append('src')
+
+from xhtml import (poezio_colors_to_html, xhtml_to_poezio_colors,
+ parse_css, clean_text)
+
+def test_clean_text():
+ example_string = '\x191}Toto \x192,-1}titi\x19b Tata'
+ assert clean_text(example_string) == 'Toto titi Tata'
+
+ clean_string = 'toto titi tata'
+ assert clean_text(clean_string) == clean_string
+
+def test_poezio_colors_to_html():
+ base = "<body xmlns='http://www.w3.org/1999/xhtml'><p>"
+ end = "</p></body>"
+ text = '\x191}coucou'
+ assert poezio_colors_to_html(text) == base + '<span style="color: red;">coucou</span>' + end
+
+ text = '\x19bcoucou\x19o toto \x194}titi'
+ assert poezio_colors_to_html(text) == base + '<span style="font-weight: bold;">coucou</span> toto <span style="color: blue;">titi</span>' + end
+
+def test_xhtml_to_poezio_colors():
+ start = b'<body xmlns="http://www.w3.org/1999/xhtml"><p>'
+ end = b'</p></body>'
+ xhtml = start + b'test' + end
+ assert xhtml_to_poezio_colors(xhtml) == 'test'
+
+ xhtml = start + b'<a href="http://perdu.com">salut</a>' + end
+ assert xhtml_to_poezio_colors(xhtml) == '\x19usalut\x19o (http://perdu.com)'
+
+ xhtml = start + b'<a href="http://perdu.com">http://perdu.com</a>' + end
+ assert xhtml_to_poezio_colors(xhtml) == '\x19uhttp://perdu.com\x19o'
+
+ with pytest.raises(xml.sax._exceptions.SAXParseException):
+ xhtml_to_poezio_colors(b'<p>Invalid xml')
+
+def test_parse_css():
+ example_css = 'text-decoration: underline; color: red;'
+ assert parse_css(example_css) == '\x19u\x19196}'
+
+ example_css = 'text-decoration: underline coucou color: red;'
+ assert parse_css(example_css) == ''
+