Verified Commit 545ad1bd authored by mathieui's avatar mathieui
Browse files

Merge branch 'master' of git.poez.io:poezio into slix

Conflicts:
	src/core/handlers.py
	src/tabs/xmltab.py
parents 37fe4be7 088c6c6a
......@@ -116,9 +116,12 @@ use_bookmarks_method =
# use this option to force the use of local bookmarks
# possible values are: anything/false
use_remote_bookmarks = true
# Whether you want all bookmarks, even those without
# autojoin, to be open on startup
open_all_bookmarks = false
# What will be put after the name, when using autocompletion at the
# beginning of the input. A space will always be added after that
after_completion = ,
......@@ -461,8 +464,9 @@ show_useless_separator = false
exec_remote = false
# Path of the FIFO in which the remote commands will be sent.
# The "poezio.fifo" file will be created in this directory
# Used with exec_remote set to true, see the documentation of /link for details
# Defaults to ./poezio.fifo
# Defaults to ./
remote_fifo_path =
# Defines if all tabs are resized at the same time (if set to false)
......
......@@ -541,10 +541,16 @@ XML tab commands
~~~~~~~~~~~~~~~~
.. glossary::
:sorted:
/clear [XML tab version]
Clear the current buffer.
/dump
**Usage:** ``/dump <filename>``
Write the content of the XML buffer into a file.
/reset
Reset the stanza filter.
......
......@@ -62,8 +62,11 @@ and certificate validation.
**Default value:** ``[empty]``
The fingerprint of the SSL certificate as a hexadecimal string, you should
not touch it, except if know what you are doing.
The SHA-2 fingerprint of the SSL certificate as a hexadecimal string,
you should not touch it, except if know what you are doing.
.. note:: the fingerprint was previously stored in SHA-1, and has been
silently upgraded to SHA-2 if the SHA-1 still matched.
ciphers
......@@ -194,6 +197,13 @@ Options related to account configuration, nickname…
The status message poezio will send when connecting.
open_all_bookmarks
**Default value:** ``false``
If this option is set to ``true``, all remote bookmarks, even
those that do not have autojoin, will be opened on startup.
(the tabs without autojoin will not be joined)
......@@ -860,9 +870,11 @@ Other
remote_fifo_path
**Default value:** ``./poezio.fifo``
**Default value:** ``./``
The path of the FIFO used to send the commands (see the :term:`exec_remote` option).
Poezio will try to create a :file:`poezio.fifo` file in this directory.
save_status
......
......@@ -53,10 +53,11 @@ The following events are poezio-only events, for SleekXMPP events, check out
- **message:** :py:class:`~sleekxmpp.Message` that will be sent
- **tab:** :py:class:`~tabs.ConversationTab` source
Same thing than :term:`conversation_say`, but after XHTML generation of the body, if needed.
This means you must not insert any colors in the body in the handler, since
it may lead to send invalid XML. This hook is less safe than :term:`conversation_say` and
you should probably not need it.
Same thing than :term:`conversation_say`, but after XHTML generation
of the body, if needed. This means you must not insert any colors
in the body in the handler, since it may lead to send
invalid XML. This hook is less safe than :term:`conversation_say`
and you should probably not need it.
muc_msg
- **message:** :py:class:`~sleekxmpp.Message` received
......@@ -96,7 +97,7 @@ The following events are poezio-only events, for SleekXMPP events, check out
normal_presence
- **presence:** :py:class:`~sleekxmpp.Presence` received
- **resource:** :py:class:`Resource <str>` that emitted the :py:class:`~sleekxmpp.Presence`
- **resource:** :py:class:`Resource <str>` that emitted the :py:class:`~sleekxmpp.Presence`
Triggered when a presence is received from a contact.
......@@ -104,18 +105,26 @@ The following events are poezio-only events, for SleekXMPP events, check out
- **presence:** :py:class:`~sleekxmpp.Presence` received
- **tab:** :py:class:`~tabs.MucTab` source
Triggered when a presence is received from someone in a :py:class:`~tabs.MucTab`.
Triggered when a presence is received from someone in a
:py:class:`~tabs.MucTab`.
joining_muc
- **presence:** :py:class:`~~sleekxmpp.Presence` to be sent
Triggered when joining a MUC. The presence can thus be modified
before being sent.
changing_nick
- **presence:** :py:class:`~~sleekxmpp.Presence` to be sent
Triggered when joining a MUC. The presence can thus be modified before being sent.
Triggered when the user changes his/her nickname on a MUC. The
presence can thus be modified before being sent.
send_normal_presence
- **presence:** :py:class:`~sleekxmpp.Presence` sent
Triggered when poezio sends a new :py:class:`~sleekxmpp.Presence` stanza. The presence can thus be modified before being sent.
Triggered when poezio sends a new :py:class:`~sleekxmpp.Presence`
stanza. The presence can thus be modified before being sent.
muc_join
- **presence:** :py:class:`~sleekxmpp.Presence` received
......@@ -148,7 +157,8 @@ The following events are poezio-only events, for SleekXMPP events, check out
- **message**:py:class:`~sleekxmpp.Message` received
- **tab:** :py:class:`~tabs.PrivateTab` source
Triggered when a private message (that goes in a :py:class:`~tabs.PrivateTab`)
is ignored automatically by poezio.
Triggered when a private message (that goes in a
:py:class:`~tabs.PrivateTab`) is ignored automatically by poezio.
**tab** is always ``None``, except when a tab has already been opened.
**tab** is always ``None``, except when a tab has already been
opened.
......@@ -15,6 +15,8 @@ Key bindings listing
--------------------
Some key bindings are available only in some tabs, others are global.
.. _global-keys:
Global keys
~~~~~~~~~~~
These keys work in **any** tab.
......@@ -46,6 +48,8 @@ highlight > message > non-empty input).
**Alt-C**: Scroll the information buffer down.
.. _input-keys:
Input keys
~~~~~~~~~~
These keys concern only the inputs.
......@@ -75,9 +79,11 @@ for example in conjunction with the bind command, to help you know how to
bind something to a key combination without having to remember how to write
them by hand.
.. _chattab-keys:
Chat tab input keys
~~~~~~~~~~~~~~~~~~~
These keys work in any conversation tab (MultiUserChat, Private or
Conversation tabs).
......@@ -118,6 +124,8 @@ current conversation, if any.
- u: Underlined
- o: Stop formatting
.. _muctab-keys:
MultiUserChat tab input keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......@@ -133,8 +141,11 @@ These keys work only in the MultiUserChat tab.
**tabulation**: Complete a nick.
.. _muclisttab-keys:
MultiUserChat List tab input keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These keys work only in the MultiUserChat List tab (obtained with :term:`/list`).
**Up**: Go up one row.
......@@ -152,6 +163,8 @@ These keys work only in the MultiUserChat List tab (obtained with :term:`/list`)
**PageDown**: Scroll a page of chats down.
.. _rostertab-keys:
Roster tab input keys
~~~~~~~~~~~~~~~~~~~~~
......@@ -184,8 +197,11 @@ These keys work only in the Roster tab (the tab number 0).
**PageDown**: Scroll a page of contacts down.
.. _forms-keys:
Data Forms tab keys
~~~~~~~~~~~~~~~~~~~
**Ctrl+y**: Validate the form, send it and close the tab.
**Ctrl+g**: Cancel that form (do not send your changes) and close the
......
......@@ -8,13 +8,13 @@ Poezio is composed of tabs which can be of various types. Each tab type has
a distinct interface, list of commands and list of key shortcuts, in addition
to the global commands and key shortcuts.
The Tab list
~~~~~~~~~~~~
Tab list
~~~~~~~~
Since Poezio 0.7.5, there are now two ways to show the available tabs:
There are two ways of showing the open tabs:
The old way: horizontal list
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Horizontal list
^^^^^^^^^^^^^^^
This is the default method.
......@@ -24,8 +24,8 @@ has a number, each time you open a new tab, it gets the next available number.
.. figure:: ./images/tab_bar.png
:alt: Example of 5 opened tabs
The new way: vertical list
^^^^^^^^^^^^^^^^^^^^^^^^^^
Vertical list
^^^^^^^^^^^^^
On all tabs, you get a pane on the left side of the screen that shows a list
of the opened tabs. As stated above, each tab has a number, and each time you
......@@ -57,6 +57,8 @@ Generalities
:ref:`global-commands`
:ref:`Global shortcuts <global-keys>`
The tab numbered **0** is always the **roster** tab, the other tabs can be of any
type.
......@@ -65,11 +67,11 @@ type.
The status of a tab is represented by its color:
* **blue** (tab **0**): an inactive tab of any type, nothing new to see
* **Blue** (tab **0**): an inactive tab of any type, nothing new to see
there.
* **purple** (tab **1**): a :ref:`muctab` with at least one new
* **Purple** (tab **1**): a :ref:`muctab` with at least one new
unread message.
* **green** (tab **2**): a tab of a private conversation (:ref:`privatetab` or :ref:`conversationtab`)
* **Green** (tab **2**): a tab of a private conversation (:ref:`privatetab` or :ref:`conversationtab`)
with a new message to read.
* **Cyan** (tab **3**): the current tab.
* **Red** (tab **4**): a :ref:`muctab` with at least one new highlight
......@@ -92,19 +94,22 @@ Roster tab
:ref:`Specific commands <rostertab-commands>`
:ref:`Specific shortcuts <rostertab-keys>`
This is a unique tab, always numbered **0**. It contains the list of your
contacts. You can add/remove/edit/search contacts from there, and you can open
a conversation with them.
contacts. You can add (:term:`/add`, :term:`/accept`), remove
(:term:`/remove`) and search contacts from there, and you can open
a conversation with them (``Enter`` key).
Use the **direction arrows** to browse the list, the ``Space`` key to fold or unfold a group
or a contact.
Use the **direction arrows** (↑↓) to browse the list, the ``Space`` key to
fold or unfold a group or a contact.
.. figure:: ./images/roster.png
:alt: The roster tab
#. The area where information messages are displayed.
#. The actual list of contacts. The first level is group, the second is the
contacts and the third is the resources of you online contacts.
#. Area where information messages are displayed.
#. Actual list of contacts. The first level is group, the second is the
contacts and the third is the resources of your online contacts.
#. More informations about the selected contact.
.. _muctab:
......@@ -114,6 +119,10 @@ MultiUserChat tab
:ref:`Specific commands <muctab-commands>`
:ref:`Specific shortcuts <muctab-keys>`
:ref:`Chat shortcuts <chattab-keys>`
This tab contains a multi-user conversation.
.. figure:: ./images/muc.png
......@@ -164,6 +173,8 @@ Private tab
~~~~~~~~~~~
:ref:`Specific commands <privatetab-commands>`
:ref:`Chat shortcuts <chattab-keys>`
This is the tab opened with the :term:`/query` command in a :ref:`muctab`, letting you talk in private
with a participant of a multi-user chat.
......@@ -180,6 +191,8 @@ Conversation tab
:ref:`Specific commands <conversationtab-commands>`
:ref:`Chat shortcuts <chattab-keys>`
A tab opened from the roster or :term:`/message`, to talk in private with one of your contacts.
.. figure:: ./images/conversation.png
......@@ -194,6 +207,8 @@ status message of the contact. Plugins may add some elements to the status line.
Dataforms tab
~~~~~~~~~~~~~
:ref:`Specific shortcuts <formtab-keys>`
This tab lets you view a form received from a remote entity, edit the values and
send everything back. It is mostly used to configure MUCs with the
:term:`/configure` command but can actually be used for almost anything.
......@@ -211,6 +226,8 @@ You can then send the completed form using ``Ctrl+y`` or cancel using ``Ctrl+g``
List tab
~~~~~~~~
:ref:`Specific shortcuts <muclisttab-keys>`
This tab lists all public rooms on a MUC service (with the :term:`/list` command).
It is currently very limited but will be improved in the future. There currently
is no way to search a room.
......
......@@ -41,8 +41,8 @@ Install the python module:
cd pure-python-otr
python3 setup.py install --user
You can also use pip with the requirements.txt at the root of
the poezio directory.
You can also use pip in a virtualenv (built-in as pyvenv_ with python since 3.3)
with the requirements.txt at the root of the poezio directory.
Usage
......@@ -143,25 +143,34 @@ Configuration
Allow OTRv1
timeout
**Default:** ``3``
The number of seconds poezio will wait until notifying you
that the OTR session was not established. A negative or null
value will disable this notification.
log
**Default:** false
Log conversations (OTR start/end marker, and messages).
The :term:`allow_v1`, :term:`allow_v2`, :term:`decode_html`
The :term:`allow_v1`, :term:`allow_v2`, :term:`decode_xhtml`
and :term:`log` configuration parameters are tab-specific.
Important details
-----------------
The OTR session is considered for a full jid, but the trust is considered
with a bare JID. This is important to know in the case of Private Chats, since
you cannot always get the real the JID of your contact (or check if the same
nick is used by different people).
The OTR session is considered for a full JID (e.g. toto@example/**client1**),
but the trust is set with a bare JID (e.g. toto@example). This is important
in the case of Private Chats (in a chatroom), since you cannot always get the
real JID of your contact (or check if the same nick is used by different people).
.. _Off The Record messaging: http://wiki.xmpp.org/web/OTR
.. _pyvenv: https://docs.python.org/3/using/scripts.html#pyvenv-creating-virtual-environments
"""
from gettext import gettext as _
import potr
import logging
......@@ -588,6 +597,7 @@ class Plugin(BasePlugin):
name = tab.name
color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID)
color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT)
color_normal = '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT)
if isinstance(tab, DynamicConversationTab) and tab.locked_resource:
name = safeJID(tab.name)
name.resource = tab.locked_resource
......@@ -597,15 +607,54 @@ class Plugin(BasePlugin):
context.disconnect()
elif arg == 'start' or arg == 'refresh':
otr = self.get_context(name)
secs = self.config.get('timeout', 3)
def notify_otr_timeout():
if otr.state != STATE_ENCRYPTED:
text = _('%(jid_c)s%(jid)s%(info)s did not enable'
' OTR after %(sec)s seconds.') % {
'jid': tab.name,
'info': color_info,
'jid_c': color_jid,
'sec': secs}
tab.add_message(text, typ=0)
self.core.refresh_window()
if secs > 0:
event = self.api.create_delayed_event(secs, notify_otr_timeout)
self.api.add_timed_event(event)
self.core.xmpp.send_message(mto=name, mtype='chat',
mbody=self.contexts[name].sendMessage(0, b'?OTRv?').decode())
text = _('%(info)sOTR request to %(jid_c)s%(jid)s%(info)s sent.') % {
'jid': tab.name,
'info': color_info,
'jid_c': color_jid}
tab.add_message(text, typ=0)
elif arg == 'ourfpr':
fpr = self.account.getPrivkey()
self.api.information('Your OTR key fingerprint is %s' % fpr, 'OTR')
text = _('%(info)sYour OTR key fingerprint is %(norm)s%(fpr)s.') % {
'jid': tab.name,
'info': color_info,
'norm': color_normal,
'fpr': fpr}
tab.add_message(text, typ=0)
elif arg == 'fpr':
if name in self.contexts:
ctx = self.contexts[name]
self.api.information('The key fingerprint for %s is %s' % (name, ctx.getCurrentKey()) , 'OTR')
if ctx.getCurrentKey() is not None:
text = _('%(info)sThe key fingerprint for %(jid_c)s'
'%(jid)s%(info)s is %(norm)s%(fpr)s%(info)s.') % {
'jid': tab.name,
'info': color_info,
'norm': color_normal,
'jid_c': color_jid,
'fpr': ctx.getCurrentKey()}
tab.add_message(text, typ=0)
else:
text = _('%(jid_c)s%(jid)s%(info)s has no'
' key currently in use.') % {
'jid': tab.name,
'info': color_info,
'jid_c': color_jid}
tab.add_message(text, typ=0)
elif arg == 'drop':
# drop the privkey (and obviously, end the current conversations before that)
for context in self.contexts.values():
......
......@@ -19,6 +19,28 @@ Usage
If there is a message at 21:12:23, it will be put in the input. If there
isn’t, you will get a warning.
Options
-------
.. glossary::
:sorted:
before_quote
**Default value:** ``[empty]``
Text to insert before the quote. ``%(nick)s`` and ``%(time)s`` can
be used to insert the nick of the user who sent the message or the
time of the message.
after_quote
**Default value:** ``[empty]``
Text to insert after the quote. ``%(nick)s`` and ``%(time)s`` can
be used to insert the nick of the user who sent the message or the
time of the message.
"""
from plugin import BasePlugin
from xhtml import clean_text
......
......@@ -61,7 +61,7 @@ class Plugin(BasePlugin):
if not nick in self.tabs[tab]:
self.tabs[tab][nick] = []
self.tabs[tab][nick].append(msg)
self.api.information('Will tell %s' % nick, 'Info')
self.api.information('Message for %s queued' % nick, 'Info')
def command_untell(self, args):
"""/untell <nick>"""
......@@ -72,10 +72,11 @@ class Plugin(BasePlugin):
if not nick in self.tabs[tab]:
return
del self.tabs[tab][nick]
self.api.information('Messages for %s unqueued' % nick, 'Info')
def completion_untell(self, the_input):
tab = self.api.current_tab()
if not tab in self.tabs:
return the_input.auto_completion([], '')
return the_input.auto_completion(list(self.tabs[tab]), '')
return the_input.auto_completion(list(self.tabs[tab]), '', quotify=False)
......@@ -943,7 +943,7 @@ def command_xml_tab(self, arg=''):
def command_adhoc(self, arg):
arg = arg.split()
if len(arg) > 1:
return self.command_help('list')
return self.command_help('ad-hoc')
elif arg:
jid = safeJID(arg[0])
else:
......
......@@ -9,7 +9,7 @@ import curses
import ssl
import time
import functools
from hashlib import sha1
from hashlib import sha1, sha512
from gettext import gettext as _
from slixmpp import InvalidJID
......@@ -891,24 +891,25 @@ def on_session_start(self, event):
def _join_initial_rooms(bookmarks):
"""Join all rooms given in the iterator `bookmarks`"""
for bm in 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)
if histo_length == -1:
histo_length = None
if histo_length is not None:
histo_length = str(histo_length)
# do not join rooms that do not have autojoin
# but display them anyway
if bm.autojoin:
muc.join_groupchat(self, bm.jid, nick,
passwd=bm.password,
maxhistory=histo_length,
status=self.status.message,
show=self.status.show)
if bm.autojoin or config.get('open_all_bookmarks', False):
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)
if histo_length == -1:
histo_length = None
if histo_length is not None:
histo_length = str(histo_length)
# do not join rooms that do not have autojoin
# but display them anyway
if bm.autojoin:
muc.join_groupchat(self, bm.jid, nick,
passwd=bm.password,
maxhistory=histo_length,
status=self.status.message,
show=self.status.show)
def _join_remote_only():
remote_bookmarks = (bm for bm in bookmark.bookmarks if (bm.method in ("pep", "privatexml")))
_join_initial_rooms(remote_bookmarks)
......@@ -1101,16 +1102,27 @@ def validate_ssl(self, pem):
config.set_and_save('certificate', cert)
der = ssl.PEM_cert_to_DER_cert(pem)
digest = sha1(der).hexdigest().upper()
found_cert = ':'.join(i + j for i, j in zip(digest[::2], digest[1::2]))
sha1_digest = sha1(der).hexdigest().upper()
sha1_found_cert = ':'.join(i + j for i, j in zip(sha1_digest[::2], sha1_digest[1::2]))
sha2_digest = sha512(der).hexdigest().upper()
sha2_found_cert = ':'.join(i + j for i, j in zip(sha2_digest[::2], sha2_digest[1::2]))
if cert:
if found_cert == cert:
log.debug('Cert %s OK', found_cert)
if sha1_found_cert == cert:
log.debug('Cert %s OK', sha1_found_cert)
log.debug('Current hash is SHA-1, moving to SHA-2 (%s)',
sha2_found_cert)
config.set_and_save('certificate', sha2_found_cert)
return
elif sha2_found_cert == cert:
log.debug('Cert %s OK', sha2_found_cert)
return
else:
saved_input = self.current_tab().input
log.debug('\nWARNING: CERTIFICATE CHANGED old: %s, new: %s\n', cert, found_cert)
input = windows.YesNoInput(text="WARNING! Server certificate has changed, accept? (y/n) (%s)" % found_cert)
log.debug('\nWARNING: CERTIFICATE CHANGED old: %s, new: %s\n', cert, sha2_found_cert)
self.information('New certificate found (sha-2 hash:'
' %s)\nPlease validate or abort' % sha2_found_cert,
'Warning')
input = windows.YesNoInput(text="WARNING! Server certificate has changed, accept? (y/n)")
self.current_tab().input = input
input.resize(1, self.current_tab().width, self.current_tab().height-1, 0)
input.refresh()
......@@ -1121,16 +1133,16 @@ def validate_ssl(self, pem):
self.current_tab().input = saved_input
self.paused = False
if input.value:
self.information('Setting new certificate: old: %s, new: %s' % (cert, found_cert), 'Info')
log.debug('Setting certificate to %s', found_cert)
if not config.silent_set('certificate', found_cert):
self.information('Setting new certificate: old: %s, new: %s' % (cert, sha2_found_cert), 'Info')
log.debug('Setting certificate to %s', sha2_found_cert)
if not config.silent_set('certificate', sha2_found_cert):
self.information(_('Unable to write in the config file'), 'Error')
else:
self.information('You refused to validate the certificate. You are now disconnected', 'Info')
self.xmpp.disconnect()
else:
log.debug('First time. Setting certificate to %s', found_cert)
if not config.silent_set('certificate', found_cert):
log.debug('First time. Setting certificate to %s', sha2_found_cert)
if not config.silent_set('certificate', sha2_found_cert):
self.information(_('Unable to write in the config file'), 'Error')
def _composing_tab_state(tab, state):
......
......@@ -385,11 +385,15 @@ class DynamicConversationTab(ConversationTab):
assert(resource)
if resource != self.locked_resource: