summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/commands.py78
-rw-r--r--src/core/completions.py17
-rw-r--r--src/core/core.py322
-rw-r--r--src/core/handlers.py195
4 files changed, 349 insertions, 263 deletions
diff --git a/src/core/commands.py b/src/core/commands.py
index d212de9b..4a8f7f19 100644
--- a/src/core/commands.py
+++ b/src/core/commands.py
@@ -6,15 +6,16 @@ import logging
log = logging.getLogger(__name__)
+import functools
import os
import sys
from datetime import datetime
from gettext import gettext as _
from xml.etree import cElementTree as ET
-from sleekxmpp.xmlstream.stanzabase import StanzaBase
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
+from slixmpp.xmlstream.stanzabase import StanzaBase
+from slixmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.matcher import StanzaPath
import bookmark
import common
@@ -276,7 +277,6 @@ def command_list(self, arg):
self.add_tab(list_tab, True)
cb = list_tab.on_muc_list_item_received
self.xmpp.plugin['xep_0030'].get_items(jid=server,
- block=False,
callback=cb)
def command_version(self, arg):
@@ -439,7 +439,7 @@ def command_bookmark_local(self, arg=''):
new_bookmarks.extend(bookmark.bookmarks)
bookmark.bookmarks = new_bookmarks
bookmark.save_local()
- bookmark.save_remote(self.xmpp)
+ bookmark.save_remote(self.xmpp, None)
self.information('Bookmarks added and saved.', 'Info')
return
else:
@@ -507,12 +507,13 @@ def command_bookmark(self, arg=''):
new_bookmarks.append(b)
new_bookmarks.extend(bookmark.bookmarks)
bookmark.bookmarks = new_bookmarks
-
- if bookmark.save_remote(self.xmpp):
- bookmark.save_local()
- self.information("Bookmarks added.", "Info")
- else:
- self.information("Could not add the bookmarks.", "Info")
+ def _cb(self, iq):
+ if iq["type"] != "error":
+ bookmark.save_local()
+ self.information("Bookmarks added.", "Info")
+ else:
+ self.information("Could not add the bookmarks.", "Info")
+ bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
return
else:
info = safeJID(args[0])
@@ -541,14 +542,16 @@ def command_bookmark(self, arg=''):
if password:
bm.password = password
bm.autojoin = autojoin
- if bookmark.save_remote(self.xmpp):
- self.information('Bookmark added.', 'Info')
+ def _cb(self, iq):
+ if iq["type"] != "error":
+ self.information('Bookmark added.', 'Info')
+ else:
+ self.information("Could not add the bookmarks.", "Info")
+ bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
remote = []
for each in bookmark.bookmarks:
if each.method in ('pep', 'privatexml'):
remote.append(each)
- self.information(_('Your remote bookmarks are now: %s') % remote,
- _('Info'))
def command_bookmarks(self, arg=''):
"""/bookmarks"""
@@ -718,7 +721,6 @@ def command_last_activity(self, arg):
if jid == '':
return self.command_help('last_activity')
self.xmpp.plugin['xep_0012'].get_last_activity(jid,
- block=False,
callback=callback)
def command_mood(self, arg):
@@ -727,7 +729,8 @@ def command_mood(self, arg):
"""
args = common.shell_split(arg)
if not args:
- return self.xmpp.plugin['xep_0107'].stop(block=False)
+ self.xmpp.plugin['xep_0107'].stop()
+ return
mood = args[0]
if mood not in pep.MOODS:
return self.information(_('%s is not a correct value for a mood.')
@@ -737,10 +740,8 @@ def command_mood(self, arg):
text = args[1]
else:
text = None
- self.xmpp.plugin['xep_0107'].publish_mood(mood,
- text,
- callback=dumb_callback,
- block=False)
+ self.xmpp.plugin['xep_0107'].publish_mood(mood, text,
+ callback=dumb_callback)
def command_activity(self, arg):
"""
@@ -749,7 +750,8 @@ def command_activity(self, arg):
args = common.shell_split(arg)
length = len(args)
if not length:
- return self.xmpp.plugin['xep_0108'].stop(block=False)
+ self.xmpp.plugin['xep_0108'].stop()
+ return
general = args[0]
if general not in pep.ACTIVITIES:
return self.information(_('%s is not a correct value for an activity')
@@ -769,11 +771,8 @@ def command_activity(self, arg):
return self.information(_('%s is not a correct value '
'for an activity') % specific,
_('Error'))
- self.xmpp.plugin['xep_0108'].publish_activity(general,
- specific,
- text,
- callback=dumb_callback,
- block=False)
+ self.xmpp.plugin['xep_0108'].publish_activity(general, specific, text,
+ callback=dumb_callback)
def command_gaming(self, arg):
"""
@@ -781,7 +780,8 @@ def command_gaming(self, arg):
"""
args = common.shell_split(arg)
if not args:
- return self.xmpp.plugin['xep_0196'].stop(block=False)
+ self.xmpp.plugin['xep_0196'].stop()
+ return
name = args[0]
if len(args) > 1:
address = args[1]
@@ -789,8 +789,7 @@ def command_gaming(self, arg):
address = None
return self.xmpp.plugin['xep_0196'].publish_gaming(name=name,
server_address=address,
- callback=dumb_callback,
- block=False)
+ callback=dumb_callback)
def command_invite(self, arg):
"""/invite <to> <room> [reason]"""
@@ -834,22 +833,23 @@ def command_quit(self, arg=''):
"""
/quit
"""
+ if not self.xmpp.is_connected():
+ self.exit()
+ return
if len(arg.strip()) != 0:
msg = arg
else:
msg = None
if config.get('enable_user_mood'):
- self.xmpp.plugin['xep_0107'].stop(block=False)
+ self.xmpp.plugin['xep_0107'].stop()
if config.get('enable_user_activity'):
- self.xmpp.plugin['xep_0108'].stop(block=False)
+ self.xmpp.plugin['xep_0108'].stop()
if config.get('enable_user_gaming'):
- self.xmpp.plugin['xep_0196'].stop(block=False)
+ self.xmpp.plugin['xep_0196'].stop()
self.save_config()
self.plugin_manager.disable_plugins()
self.disconnect(msg)
- self.running = False
- self.reset_curses()
- sys.exit()
+ self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
def command_destroy_room(self, arg=''):
"""
@@ -972,15 +972,13 @@ def command_adhoc(self, arg):
if len(arg) > 1:
return self.command_help('ad-hoc')
elif arg:
- jid = safeJID(arg[0]).server
+ jid = safeJID(arg[0])
else:
return self.information('Please provide a jid', 'Error')
list_tab = tabs.AdhocCommandsListTab(jid)
self.add_tab(list_tab, True)
cb = list_tab.on_list_received
- self.xmpp.plugin['xep_0050'].get_commands(jid=jid,
- local=False,
- block=False,
+ self.xmpp.plugin['xep_0050'].get_commands(jid=jid, local=False,
callback=cb)
def command_self(self, arg=None):
diff --git a/src/core/completions.py b/src/core/completions.py
index 9549c13f..7d95321b 100644
--- a/src/core/completions.py
+++ b/src/core/completions.py
@@ -106,22 +106,7 @@ def completion_join(self, the_input):
if the_input.last_completion:
return the_input.new_completion([], 1, quotify=True)
- if jid.server and not jid.user:
- # no room was given: complete the node
- try:
- response = self.xmpp.plugin['xep_0030'].get_items(jid=jid.server, block=True, timeout=1)
- except:
- log.error('/join completion: Unable to get the list of rooms for %s',
- jid.server,
- exc_info=True)
- response = None
- if response:
- items = response['disco_items'].get_items()
- else:
- return True
- items = sorted('%s/%s' % (tup[0], jid.resource) for tup in items)
- return the_input.new_completion(items, 1, quotify=True, override=True)
- elif jid.user:
+ if jid.user:
# we are writing the server: complete the server
serv_list = []
for tab in self.get_tabs(tabs.MucTab):
diff --git a/src/core/core.py b/src/core/core.py
index 52199206..4daeed6c 100644
--- a/src/core/core.py
+++ b/src/core/core.py
@@ -9,7 +9,9 @@ import logging
log = logging.getLogger(__name__)
+import asyncio
import collections
+import shutil
import curses
import os
import pipes
@@ -19,7 +21,7 @@ from threading import Event
from datetime import datetime
from gettext import gettext as _
-from sleekxmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.handler import Callback
import bookmark
import connection
@@ -37,14 +39,13 @@ from config import config, firstrun
from contact import Contact, Resource
from daemon import Executor
from fifo import Fifo
-from keyboard import Keyboard
from logger import logger
from plugin_manager import PluginManager
from roster import roster
from size_manager import SizeManager
from text_buffer import TextBuffer
from theming import get_theme
-from windows import g_lock
+import keyboard
from . import completions
from . import commands
@@ -71,7 +72,7 @@ class Core(object):
self.running = True
self.xmpp = singleton.Singleton(connection.Connection)
self.xmpp.core = self
- self.keyboard = Keyboard()
+ self.keyboard = keyboard.Keyboard()
roster.set_node(self.xmpp.client_roster)
decorators.refresh_wrapper.core = self
self.paused = False
@@ -108,6 +109,13 @@ class Core(object):
self.size = SizeManager(self, windows.Win)
+ # Set to True whenever we consider that we have been disconnected
+ # from the server because of a legitimate reason (bad credentials,
+ # or explicit disconnect from the user for example), in that case we
+ # should not try to auto-reconnect, even if auto_reconnect is true
+ # in the user config.
+ self.legitimate_disconnect = False
+
# global commands, available from all tabs
# a command is tuple of the form:
# (the function executing the command. Takes a string as argument,
@@ -123,6 +131,11 @@ class Core(object):
del self.commands['status']
del self.commands['show']
+ # A list of integers. For example if the user presses Alt+j, 2, 1,
+ # we will insert 2, then 1 in that list, and we will finally build
+ # the number 21 and use it with command_win, before clearing the
+ # list.
+ self.room_number_jump = []
self.key_func = KeyDict()
# Key bindings associated with handlers
# and pseudo-keys used to map actions below.
@@ -188,9 +201,12 @@ class Core(object):
self.key_func.update(key_func)
# Add handlers
+ self.xmpp.add_event_handler('connecting', self.on_connecting)
self.xmpp.add_event_handler('connected', self.on_connected)
+ self.xmpp.add_event_handler('connection_failed', self.on_failed_connection)
self.xmpp.add_event_handler('disconnected', self.on_disconnected)
- self.xmpp.add_event_handler('failed_auth', self.on_failed_auth)
+ self.xmpp.add_event_handler('stream_error', self.on_stream_error)
+ self.xmpp.add_event_handler('failed_all_auth', self.on_failed_all_auth)
self.xmpp.add_event_handler('no_auth', self.on_no_auth)
self.xmpp.add_event_handler("session_start", self.on_session_start)
self.xmpp.add_event_handler("session_start",
@@ -259,8 +275,6 @@ class Core(object):
self.initial_joins = []
- self.timed_events = set()
-
self.connected_events = {}
self.pending_invites = {}
@@ -296,6 +310,8 @@ class Core(object):
theming.update_themes_dir)
self.add_configuration_handler("theme",
self.on_theme_config_change)
+ self.add_configuration_handler("password",
+ self.on_password_change)
self.add_configuration_handler("", self.on_any_config_change)
@@ -374,6 +390,12 @@ class Core(object):
self.information(error_msg, 'Warning')
self.refresh_window()
+ def on_password_change(self, option, value):
+ """
+ Set the new password in the slixmpp.ClientXMPP object
+ """
+ self.xmpp.password = value
+
def sigusr_handler(self, num, stack):
"""
Handle SIGUSR1 (10)
@@ -422,19 +444,14 @@ class Core(object):
log.error("%s received. Exiting…", signals[sig])
if config.get('enable_user_mood'):
- self.xmpp.plugin['xep_0107'].stop(block=False)
+ self.xmpp.plugin['xep_0107'].stop()
if config.get('enable_user_activity'):
- self.xmpp.plugin['xep_0108'].stop(block=False)
+ self.xmpp.plugin['xep_0108'].stop()
if config.get('enable_user_gaming'):
- self.xmpp.plugin['xep_0196'].stop(block=False)
+ self.xmpp.plugin['xep_0196'].stop()
self.plugin_manager.disable_plugins()
- self.disconnect('')
- self.running = False
- try:
- self.reset_curses()
- except: # too bad
- pass
- sys.exit()
+ self.disconnect('%s received' % signals.get(sig))
+ self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
def autoload_plugins(self):
"""
@@ -469,6 +486,11 @@ class Core(object):
' ask for help or tell us how great it is.'),
_('Help'))
self.refresh_window()
+ self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
+
+ def exit(self, event=None):
+ log.debug("exit(%s)" % (event,))
+ asyncio.get_event_loop().stop()
def on_exception(self, typ, value, trace):
"""
@@ -481,7 +503,28 @@ class Core(object):
pass
sys.__excepthook__(typ, value, trace)
- def main_loop(self):
+ def sigwinch_handler(self):
+ """A work-around for ncurses resize stuff, which sucks. Normally, ncurses
+ catches SIGWINCH itself. In its signal handler, it updates the
+ windows structures (for example the size, etc) and it
+ ungetch(KEY_RESIZE). That way, the next time we call getch() we know
+ that a resize occured and we can act on it. BUT poezio doesn’t call
+ getch() until it knows it will return something. The problem is we
+ can’t know that, because stdin is not affected by this KEY_RESIZE
+ value (it is only inserted in a ncurses internal fifo that we can’t
+ access).
+
+ The (ugly) solution is to handle SIGWINCH ourself, trigger the
+ change of the internal windows sizes stored in ncurses module, using
+ sizes that we get using shutil, ungetch the KEY_RESIZE value and
+ then call getch to handle the resize on poezio’s side properly.
+ """
+ size = shutil.get_terminal_size()
+ curses.resizeterm(size.lines, size.columns)
+ curses.ungetch(curses.KEY_RESIZE)
+ self.on_input_readable()
+
+ def on_input_readable(self):
"""
main loop waiting for the user to press a key
"""
@@ -528,39 +571,42 @@ class Core(object):
res.append(current)
return res
- while self.running:
- self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
- big_char_list = [replace_key_with_bound(key)\
- for key in self.read_keyboard()]
- # whether to refresh after ALL keys have been handled
- for char_list in separate_chars_from_bindings(big_char_list):
- if self.paused:
- self.current_tab().input.do_command(char_list[0])
- self.current_tab().input.prompt()
- self.event.set()
- continue
- # Special case for M-x where x is a number
- if len(char_list) == 1:
- char = char_list[0]
- if char.startswith('M-') and len(char) == 3:
- try:
- nb = int(char[2])
- except ValueError:
- pass
- else:
- if self.current_tab().nb == nb:
- self.go_to_previous_tab()
- else:
- self.command_win('%d' % nb)
- # search for keyboard shortcut
- func = self.key_func.get(char, None)
- if func:
- func()
+ log.debug("Input is readable.")
+ big_char_list = [replace_key_with_bound(key)\
+ for key in self.read_keyboard()]
+ log.debug("Got from keyboard: %s", (big_char_list,))
+
+ # whether to refresh after ALL keys have been handled
+ for char_list in separate_chars_from_bindings(big_char_list):
+ if self.paused:
+ self.current_tab().input.do_command(char_list[0])
+ self.current_tab().input.prompt()
+ self.event.set()
+ continue
+ # Special case for M-x where x is a number
+ if len(char_list) == 1:
+ char = char_list[0]
+ if char.startswith('M-') and len(char) == 3:
+ try:
+ nb = int(char[2])
+ except ValueError:
+ pass
else:
- self.do_command(replace_line_breaks(char), False)
+ if self.current_tab().nb == nb:
+ self.go_to_previous_tab()
+ else:
+ self.command_win('%d' % nb)
+ # search for keyboard shortcut
+ func = self.key_func.get(char, None)
+ if func:
+ func()
else:
- self.do_command(''.join(char_list), True)
- self.doupdate()
+ self.do_command(replace_line_breaks(char), False)
+ else:
+ self.do_command(''.join(char_list), True)
+ if self.status.show not in ('xa', 'away'):
+ self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
+ self.doupdate()
def save_config(self):
"""
@@ -703,10 +749,21 @@ class Core(object):
def do_command(self, key, raw):
"""
Execute the action associated with a key
+
+ Or if keyboard.continuation_keys_callback is set, call it instead. See
+ the comment of this variable.
"""
if not key:
return
- return self.current_tab().on_input(key, raw)
+ if keyboard.continuation_keys_callback is not None:
+ # Reset the callback to None BEFORE calling it, because this
+ # callback MAY set a new callback itself, and we don’t want to
+ # erase it in that case
+ cb = keyboard.continuation_keys_callback
+ keyboard.continuation_keys_callback = None
+ cb(key)
+ else:
+ self.current_tab().on_input(key, raw)
def try_execute(self, line):
@@ -724,22 +781,13 @@ class Core(object):
def remove_timed_event(self, event):
"""Remove an existing timed event"""
- if event and event in self.timed_events:
- self.timed_events.remove(event)
+ event.handler.cancel()
def add_timed_event(self, event):
"""Add a new timed event"""
- self.timed_events.add(event)
-
- def check_timed_events(self):
- """Check for the execution of timed events"""
- now = datetime.now()
- for event in self.timed_events:
- if event.has_timed_out(now):
- res = event()
- if not res:
- self.timed_events.remove(event)
- break
+ event.handler = asyncio.get_event_loop().call_later(event.delay,
+ event.callback,
+ *event.args)
####################### XMPP-related actions ##################################
@@ -779,12 +827,15 @@ class Core(object):
Disconnect from remote server and correctly set the states of all
parts of the client (for example, set the MucTabs as not joined, etc)
"""
+ self.legitimate_disconnect = True
msg = msg or ''
for tab in self.get_tabs(tabs.MucTab):
tab.command_part(msg)
self.xmpp.disconnect()
if reconnect:
- self.xmpp.start()
+ # Add a one-time event to reconnect as soon as we are
+ # effectively disconnected
+ self.xmpp.add_event_handler('disconnected', lambda event: self.xmpp.connect(), disposable=True)
def send_message(self, msg):
"""
@@ -815,8 +866,8 @@ class Core(object):
self.xmpp.plugin['xep_0045'].invite(room, jid,
reason=reason or '')
- self.xmpp.plugin['xep_0030'].get_info(jid=jid, block=False,
- timeout=5, callback=callback)
+ self.xmpp.plugin['xep_0030'].get_info(jid=jid, timeout=5,
+ callback=callback)
def get_error_message(self, stanza, deprecated=False):
"""
@@ -1027,17 +1078,24 @@ class Core(object):
Read 2 more chars and go to the tab
with the given number
"""
- char = self.read_keyboard()[0]
- try:
- nb1 = int(char)
- except ValueError:
- return
- char = self.read_keyboard()[0]
- try:
- nb2 = int(char)
- except ValueError:
- return
- self.command_win('%s%s' % (nb1, nb2))
+ def read_next_digit(digit):
+ try:
+ nb = int(digit)
+ except ValueError:
+ # If it is not a number, we do nothing. If it was the first
+ # one, we do not wait for a second one by re-setting the
+ # callback
+ self.room_number_jump.clear()
+ else:
+ self.room_number_jump.append(digit)
+ if len(self.room_number_jump) == 2:
+ arg = "".join(self.room_number_jump)
+ self.room_number_jump.clear()
+ self.command_win(arg)
+ else:
+ # We need to read more digits
+ keyboard.continuation_keys_callback = read_next_digit
+ keyboard.continuation_keys_callback = read_next_digit
def go_to_roster(self):
"Select the roster as the current tab"
@@ -1505,41 +1563,39 @@ class Core(object):
"""
Resize the global_information_win only once at each resize.
"""
- with g_lock:
- if self.information_win_size > tabs.Tab.height - 6:
- self.information_win_size = tabs.Tab.height - 6
- if tabs.Tab.height < 6:
- self.information_win_size = 0
- height = (tabs.Tab.height - 1 - self.information_win_size
- - tabs.Tab.tab_win_height())
- self.information_win.resize(self.information_win_size,
- tabs.Tab.width,
- height,
- 0)
+ if self.information_win_size > tabs.Tab.height - 6:
+ self.information_win_size = tabs.Tab.height - 6
+ if tabs.Tab.height < 6:
+ self.information_win_size = 0
+ height = (tabs.Tab.height - 1 - self.information_win_size
+ - tabs.Tab.tab_win_height())
+ self.information_win.resize(self.information_win_size,
+ tabs.Tab.width,
+ height,
+ 0)
def resize_global_info_bar(self):
"""
Resize the GlobalInfoBar only once at each resize
"""
- with g_lock:
- height, width = self.stdscr.getmaxyx()
- if config.get('enable_vertical_tab_list'):
+ height, width = self.stdscr.getmaxyx()
+ if config.get('enable_vertical_tab_list'):
- if self.size.core_degrade_x:
- return
- try:
- height, _ = self.stdscr.getmaxyx()
- truncated_win = self.stdscr.subwin(height,
- config.get('vertical_tab_list_size'),
- 0, 0)
- except:
- log.error('Curses error on infobar resize', exc_info=True)
- return
- self.left_tab_win = windows.VerticalGlobalInfoBar(truncated_win)
- elif not self.size.core_degrade_y:
- self.tab_win.resize(1, tabs.Tab.width,
- tabs.Tab.height - 2, 0)
- self.left_tab_win = None
+ if self.size.core_degrade_x:
+ return
+ try:
+ height, _ = self.stdscr.getmaxyx()
+ truncated_win = self.stdscr.subwin(height,
+ config.get('vertical_tab_list_size'),
+ 0, 0)
+ except:
+ log.error('Curses error on infobar resize', exc_info=True)
+ return
+ self.left_tab_win = windows.VerticalGlobalInfoBar(truncated_win)
+ elif not self.size.core_degrade_y:
+ self.tab_win.resize(1, tabs.Tab.width,
+ tabs.Tab.height - 2, 0)
+ self.left_tab_win = None
def add_message_to_text_buffer(self, buff, txt,
time=None, nickname=None, history=None):
@@ -1564,46 +1620,38 @@ class Core(object):
Called when we want to resize the screen
"""
# If we have the tabs list on the left, we just give a truncated
- # window to each Tab class, so the draw themself in the portion
- # of the screen that the can occupy, and we draw the tab list
- # on the left remaining space
- with g_lock:
- height, width = self.stdscr.getmaxyx()
+ # window to each Tab class, so they draw themself in the portion of
+ # the screen that they can occupy, and we draw the tab list on the
+ # remaining space, on the left
+ height, width = self.stdscr.getmaxyx()
if (config.get('enable_vertical_tab_list') and
not self.size.core_degrade_x):
- with g_lock:
- try:
- scr = self.stdscr.subwin(0,
- config.get('vertical_tab_list_size'))
- except:
- log.error('Curses error on resize', exc_info=True)
- return
+ try:
+ scr = self.stdscr.subwin(0,
+ config.get('vertical_tab_list_size'))
+ except:
+ log.error('Curses error on resize', exc_info=True)
+ return
else:
scr = self.stdscr
tabs.Tab.resize(scr)
self.resize_global_info_bar()
self.resize_global_information_win()
- with g_lock:
- for tab in self.tabs:
- if config.get('lazy_resize'):
- tab.need_resize = True
- else:
- tab.resize()
- if self.tabs:
- self.full_screen_redraw()
+ for tab in self.tabs:
+ if config.get('lazy_resize'):
+ tab.need_resize = True
+ else:
+ tab.resize()
+ if self.tabs:
+ self.full_screen_redraw()
def read_keyboard(self):
"""
- Get the next keyboard key pressed and returns it.
- get_user_input() has a timeout: it returns None when the timeout
- occurs. In that case we do not return (we loop until we get
- a non-None value), but we check for timed events instead.
+ Get the next keyboard key pressed and returns it. It blocks until
+ something can be read on stdin, this function must be called only if
+ there is something to read. No timeout ever occurs.
"""
- res = self.keyboard.get_user_input(self.stdscr)
- while res is None:
- self.check_timed_events()
- res = self.keyboard.get_user_input(self.stdscr)
- return res
+ return self.keyboard.get_user_input(self.stdscr)
def escape_next_key(self):
"""
@@ -1883,9 +1931,11 @@ class Core(object):
on_groupchat_presence = handlers.on_groupchat_presence
on_failed_connection = handlers.on_failed_connection
on_disconnected = handlers.on_disconnected
- on_failed_auth = handlers.on_failed_auth
+ on_stream_error = handlers.on_stream_error
+ on_failed_all_auth = handlers.on_failed_all_auth
on_no_auth = handlers.on_no_auth
on_connected = handlers.on_connected
+ on_connecting = handlers.on_connecting
on_session_start = handlers.on_session_start
on_status_codes = handlers.on_status_codes
on_groupchat_subject = handlers.on_groupchat_subject
diff --git a/src/core/handlers.py b/src/core/handlers.py
index dfcb3223..50dca216 100644
--- a/src/core/handlers.py
+++ b/src/core/handlers.py
@@ -5,16 +5,19 @@ XMPP-related handlers for the Core class
import logging
log = logging.getLogger(__name__)
+import asyncio
import curses
+import functools
import ssl
+import sys
import time
from hashlib import sha1, sha512
from gettext import gettext as _
from os import path
-from sleekxmpp import InvalidJID
-from sleekxmpp.stanza import Message
-from sleekxmpp.xmlstream.stanzabase import StanzaBase
+from slixmpp import InvalidJID
+from slixmpp.stanza import Message
+from slixmpp.xmlstream.stanzabase import StanzaBase
import bookmark
import common
@@ -49,47 +52,54 @@ def on_session_start_features(self, _):
self.xmpp.plugin['xep_0280'].enable()
self.xmpp.add_event_handler('carbon_received', self.on_carbon_received)
self.xmpp.add_event_handler('carbon_sent', self.on_carbon_sent)
- features = self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain, callback=callback, block=False)
+
+ self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain,
+ callback=callback)
def on_carbon_received(self, message):
"""
Carbon <received/> received
"""
+ def ignore_message(recv):
+ log.debug('%s has category conference, ignoring carbon',
+ recv['from'].server)
+ def receive_message(recv):
+ recv['to'] = self.xmpp.boundjid.full
+ if recv['receipt']:
+ return self.on_receipt(recv)
+ self.on_normal_message(recv)
+
recv = message['carbon_received']
if (recv['from'].bare not in roster or
- roster[recv['from'].bare].subscription == 'none'):
- try:
- if fixes.has_identity(self.xmpp, recv['from'].server,
- identity='conference'):
- log.debug('%s has category conference, ignoring carbon',
- recv['from'].server)
- return
- except:
- log.debug('Traceback when getting the identity of a server:',
- exc_info=True)
- recv['to'] = self.xmpp.boundjid.full
- if recv['receipt']:
- return self.on_receipt(recv)
- self.on_normal_message(recv)
+ roster[recv['from'].bare].subscription == 'none'):
+ fixes.has_identity(self.xmpp, recv['from'].server,
+ identity='conference',
+ on_true=functools.partial(ignore_message, recv),
+ on_false=functools.partial(receive_message, recv))
+ return
+ else:
+ receive_message(recv)
def on_carbon_sent(self, message):
"""
Carbon <sent/> received
"""
+ def ignore_message(sent):
+ log.debug('%s has category conference, ignoring carbon',
+ sent['to'].server)
+ def send_message(sent):
+ sent['from'] = self.xmpp.boundjid.full
+ self.on_normal_message(sent)
+
sent = message['carbon_sent']
if (sent['to'].bare not in roster or
roster[sent['to'].bare].subscription == 'none'):
- try:
- if fixes.has_identity(self.xmpp, sent['to'].server,
- identity='conference'):
- log.debug('%s has category conference, ignoring carbon',
- sent['to'].server)
- return
- except:
- log.debug('Traceback when getting the identity of a server:',
- exc_info=True)
- sent['from'] = self.xmpp.boundjid.full
- self.on_normal_message(sent)
+ fixes.has_identity(self.xmpp, sent['to'].server,
+ identity='conference',
+ on_true=functools.partial(ignore_message, sent),
+ on_false=functools.partial(send_message, sent))
+ else:
+ send_message(sent)
### Invites ###
@@ -171,7 +181,8 @@ def on_message(self, message):
def on_normal_message(self, message):
"""
- When receiving "normal" messages (from someone in our roster)
+ When receiving "normal" messages (not a private message from a
+ muc participant)
"""
if message['type'] == 'error':
return self.information(self.get_error_message(message, deprecated=True), 'Error')
@@ -630,7 +641,7 @@ def on_chatstate_groupchat_conversation(self, message, state):
Chatstate received in a MUC
"""
nick = message['mucnick']
- room_from = message.getMucroom()
+ room_from = message.get_mucroom()
tab = self.get_tab_by_name(room_from, tabs.MucTab)
if tab and tab.get_user_by_name(nick):
self.events.trigger('muc_chatstate', message, tab)
@@ -828,27 +839,40 @@ def on_groupchat_presence(self, presence):
### Connection-related handlers ###
-def on_failed_connection(self):
+def on_failed_connection(self, error):
"""
We cannot contact the remote server
"""
- self.information(_("Connection to remote server failed"), _('Error'))
+ self.information(_("Connection to remote server failed: %s" % (error,)), _('Error'))
def on_disconnected(self, event):
"""
When we are disconnected from remote server
"""
+ # Stop the ping plugin. It would try to send stanza on regular basis
+ self.xmpp.plugin['xep_0199'].disable_keepalive()
roster.modified()
for tab in self.get_tabs(tabs.MucTab):
tab.disconnect()
self.information(_("Disconnected from server."), _('Error'))
+ if not self.legitimate_disconnect and config.get('auto_reconnect', False):
+ self.information(_("Auto-reconnecting."), _('Info'))
+ self.xmpp.connect()
-def on_failed_auth(self, event):
+def on_stream_error(self, event):
+ """
+ When we receive a stream error
+ """
+ if event and event['text']:
+ self.information(_('Stream error: %s') % event['text'], _('Error'))
+
+def on_failed_all_auth(self, event):
"""
Authentication failed
"""
self.information(_("Authentication failed (bad credentials?)."),
_('Error'))
+ self.legitimate_disconnect = True
def on_no_auth(self, event):
"""
@@ -856,6 +880,7 @@ def on_no_auth(self, event):
"""
self.information(_("Authentication failed, no login method available."),
_('Error'))
+ self.legitimate_disconnect = True
def on_connected(self, event):
"""
@@ -863,6 +888,12 @@ def on_connected(self, event):
"""
self.information(_("Connected to server."), 'Info')
+def on_connecting(self, event):
+ """
+ Just before we try to connect to the server
+ """
+ self.legitimate_disconnect = False
+
def on_session_start(self, event):
"""
Called when we are connected and authenticated
@@ -883,32 +914,42 @@ def on_session_start(self, event):
self.events.trigger('send_normal_presence', pres)
pres.send()
bookmark.get_local()
+ def _join_initial_rooms(bookmarks):
+ """Join all rooms given in the iterator `bookmarks`"""
+ for bm in bookmarks:
+ if bm.autojoin or config.get('open_all_bookmarks'):
+ tab = self.get_tab_by_name(bm.jid, tabs.MucTab)
+ nick = bm.nick if bm.nick else self.own_nick
+ if not tab:
+ self.open_new_room(bm.jid, nick, False)
+ self.initial_joins.append(bm.jid)
+ histo_length = config.get('muc_history_length')
+ if histo_length == -1:
+ histo_length = None
+ if histo_length is not None:
+ histo_length = str(histo_length)
+ # do not join rooms that do not have autojoin
+ # but display them anyway
+ if bm.autojoin:
+ muc.join_groupchat(self, bm.jid, nick,
+ passwd=bm.password,
+ maxhistory=histo_length,
+ status=self.status.message,
+ show=self.status.show)
+ def _join_remote_only():
+ remote_bookmarks = (bm for bm in bookmark.bookmarks if (bm.method in ("pep", "privatexml")))
+ _join_initial_rooms(remote_bookmarks)
if not self.xmpp.anon and config.get('use_remote_bookmarks'):
- bookmark.get_remote(self.xmpp)
- for bm in bookmark.bookmarks:
- if bm.autojoin or config.get('open_all_bookmarks'):
- tab = self.get_tab_by_name(bm.jid, tabs.MucTab)
- nick = bm.nick if bm.nick else self.own_nick
- if not tab:
- self.open_new_room(bm.jid, nick, False)
- self.initial_joins.append(bm.jid)
- histo_length = config.get('muc_history_length')
- if histo_length == -1:
- histo_length = None
- if histo_length is not None:
- histo_length = str(histo_length)
- # do not join rooms that do not have autojoin
- # but display them anyway
- if bm.autojoin:
- muc.join_groupchat(self, bm.jid, nick,
- passwd=bm.password,
- maxhistory=histo_length,
- status=self.status.message,
- show=self.status.show)
+ bookmark.get_remote(self.xmpp, _join_remote_only)
+ # join all the available bookmarks. As of yet, this is just the local
+ # ones
+ _join_initial_rooms(bookmark.bookmarks)
if config.get('enable_user_nick'):
- self.xmpp.plugin['xep_0172'].publish_nick(nick=self.own_nick, callback=dumb_callback, block=False)
+ self.xmpp.plugin['xep_0172'].publish_nick(nick=self.own_nick, callback=dumb_callback)
self.xmpp.plugin['xep_0115'].update_caps()
+ # Start the ping's plugin regular event
+ self.xmpp.set_keepalive_values()
### Other handlers ###
@@ -974,7 +1015,7 @@ def on_groupchat_subject(self, message):
Triggered when the topic is changed.
"""
nick_from = message['mucnick']
- room_from = message.getMucroom()
+ room_from = message.get_mucroom()
tab = self.get_tab_by_name(room_from, tabs.MucTab)
subject = message['subject']
if subject is None or not tab:
@@ -1079,7 +1120,7 @@ def incoming_stanza(self, stanza):
def validate_ssl(self, pem):
"""
- Check the server certificate using the sleekxmpp ssl_cert event
+ Check the server certificate using the slixmpp ssl_cert event
"""
if config.get('ignore_certificate'):
return
@@ -1115,19 +1156,31 @@ def validate_ssl(self, pem):
input.resize(1, self.current_tab().width, self.current_tab().height-1, 0)
input.refresh()
self.doupdate()
- self.paused = True
- while input.value is None:
- self.event.wait()
- self.current_tab().input = saved_input
- self.paused = False
- if input.value:
- self.information('Setting new certificate: old: %s, new: %s' % (cert, sha2_found_cert), 'Info')
- log.debug('Setting certificate to %s', sha2_found_cert)
- if not config.silent_set('certificate', sha2_found_cert):
- self.information(_('Unable to write in the config file'), 'Error')
- else:
- self.information('You refused to validate the certificate. You are now disconnected', 'Info')
- self.xmpp.disconnect()
+ old_loop = asyncio.get_event_loop()
+ new_loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(new_loop)
+ new_loop.add_reader(sys.stdin, self.on_input_readable)
+ future = asyncio.Future()
+ @asyncio.coroutine
+ def check_input(future):
+ while input.value is None:
+ yield from asyncio.sleep(0.01)
+ self.current_tab().input = saved_input
+ self.paused = False
+ if input.value:
+ self.information('Setting new certificate: old: %s, new: %s' % (cert, sha2_found_cert), 'Info')
+ log.debug('Setting certificate to %s', sha2_found_cert)
+ if not config.silent_set('certificate', sha2_found_cert):
+ self.information(_('Unable to write in the config file'), 'Error')
+ else:
+ self.information('You refused to validate the certificate. You are now disconnected', 'Info')
+ self.xmpp.disconnect()
+ new_loop.stop()
+ asyncio.set_event_loop(old_loop)
+ asyncio.async(check_input(future))
+ new_loop.run_forever()
+
+
else:
log.debug('First time. Setting certificate to %s', sha2_found_cert)
if not config.silent_set('certificate', sha2_found_cert):