Complete rewrite and removed legacy bash version
This commit is contained in:
0
src/libs/prompt_toolkit/eventloop/__init__.py
Normal file
0
src/libs/prompt_toolkit/eventloop/__init__.py
Normal file
46
src/libs/prompt_toolkit/eventloop/asyncio_base.py
Normal file
46
src/libs/prompt_toolkit/eventloop/asyncio_base.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Eventloop for integration with Python3 asyncio.
|
||||
|
||||
Note that we can't use "yield from", because the package should be installable
|
||||
under Python 2.6 as well, and it should contain syntactically valid Python 2.6
|
||||
code.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__all__ = (
|
||||
'AsyncioTimeout',
|
||||
)
|
||||
|
||||
|
||||
class AsyncioTimeout(object):
|
||||
"""
|
||||
Call the `timeout` function when the timeout expires.
|
||||
Every call of the `reset` method, resets the timeout and starts a new
|
||||
timer.
|
||||
"""
|
||||
def __init__(self, timeout, callback, loop):
|
||||
self.timeout = timeout
|
||||
self.callback = callback
|
||||
self.loop = loop
|
||||
|
||||
self.counter = 0
|
||||
self.running = True
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the timeout. Starts a new timer.
|
||||
"""
|
||||
self.counter += 1
|
||||
local_counter = self.counter
|
||||
|
||||
def timer_timeout():
|
||||
if self.counter == local_counter and self.running:
|
||||
self.callback()
|
||||
|
||||
self.loop.call_later(self.timeout, timer_timeout)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Ignore timeout. Don't call the callback anymore.
|
||||
"""
|
||||
self.running = False
|
113
src/libs/prompt_toolkit/eventloop/asyncio_posix.py
Normal file
113
src/libs/prompt_toolkit/eventloop/asyncio_posix.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Posix asyncio event loop.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..terminal.vt100_input import InputStream
|
||||
from .asyncio_base import AsyncioTimeout
|
||||
from .base import EventLoop, INPUT_TIMEOUT
|
||||
from .callbacks import EventLoopCallbacks
|
||||
from .posix_utils import PosixStdinReader
|
||||
|
||||
import asyncio
|
||||
import signal
|
||||
|
||||
__all__ = (
|
||||
'PosixAsyncioEventLoop',
|
||||
)
|
||||
|
||||
|
||||
class PosixAsyncioEventLoop(EventLoop):
|
||||
def __init__(self, loop=None):
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
self.closed = False
|
||||
|
||||
self._stopped_f = asyncio.Future(loop=self.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def run_as_coroutine(self, stdin, callbacks):
|
||||
"""
|
||||
The input 'event loop'.
|
||||
"""
|
||||
assert isinstance(callbacks, EventLoopCallbacks)
|
||||
|
||||
# Create reader class.
|
||||
stdin_reader = PosixStdinReader(stdin.fileno())
|
||||
|
||||
if self.closed:
|
||||
raise Exception('Event loop already closed.')
|
||||
|
||||
inputstream = InputStream(callbacks.feed_key)
|
||||
|
||||
try:
|
||||
# Create a new Future every time.
|
||||
self._stopped_f = asyncio.Future(loop=self.loop)
|
||||
|
||||
# Handle input timouts
|
||||
def timeout_handler():
|
||||
"""
|
||||
When no input has been received for INPUT_TIMEOUT seconds,
|
||||
flush the input stream and fire the timeout event.
|
||||
"""
|
||||
inputstream.flush()
|
||||
|
||||
callbacks.input_timeout()
|
||||
|
||||
timeout = AsyncioTimeout(INPUT_TIMEOUT, timeout_handler, self.loop)
|
||||
|
||||
# Catch sigwinch
|
||||
def received_winch():
|
||||
self.call_from_executor(callbacks.terminal_size_changed)
|
||||
|
||||
self.loop.add_signal_handler(signal.SIGWINCH, received_winch)
|
||||
|
||||
# Read input data.
|
||||
def stdin_ready():
|
||||
data = stdin_reader.read()
|
||||
inputstream.feed(data)
|
||||
timeout.reset()
|
||||
|
||||
# Quit when the input stream was closed.
|
||||
if stdin_reader.closed:
|
||||
self.stop()
|
||||
|
||||
self.loop.add_reader(stdin.fileno(), stdin_ready)
|
||||
|
||||
# Block this coroutine until stop() has been called.
|
||||
for f in self._stopped_f:
|
||||
yield f
|
||||
|
||||
finally:
|
||||
# Clean up.
|
||||
self.loop.remove_reader(stdin.fileno())
|
||||
self.loop.remove_signal_handler(signal.SIGWINCH)
|
||||
|
||||
# Don't trigger any timeout events anymore.
|
||||
timeout.stop()
|
||||
|
||||
def stop(self):
|
||||
# Trigger the 'Stop' future.
|
||||
self._stopped_f.set_result(True)
|
||||
|
||||
def close(self):
|
||||
# Note: we should not close the asyncio loop itself, because that one
|
||||
# was not created here.
|
||||
self.closed = True
|
||||
|
||||
def run_in_executor(self, callback):
|
||||
self.loop.run_in_executor(None, callback)
|
||||
|
||||
def call_from_executor(self, callback, _max_postpone_until=None):
|
||||
"""
|
||||
Call this function in the main event loop.
|
||||
Similar to Twisted's ``callFromThread``.
|
||||
"""
|
||||
self.loop.call_soon_threadsafe(callback)
|
||||
|
||||
def add_reader(self, fd, callback):
|
||||
" Start watching the file descriptor for read availability. "
|
||||
self.loop.add_reader(fd, callback)
|
||||
|
||||
def remove_reader(self, fd):
|
||||
" Stop watching the file descriptor for read availability. "
|
||||
self.loop.remove_reader(fd)
|
83
src/libs/prompt_toolkit/eventloop/asyncio_win32.py
Normal file
83
src/libs/prompt_toolkit/eventloop/asyncio_win32.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Win32 asyncio event loop.
|
||||
|
||||
Windows notes:
|
||||
- Somehow it doesn't seem to work with the 'ProactorEventLoop'.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .base import EventLoop, INPUT_TIMEOUT
|
||||
from ..terminal.win32_input import ConsoleInputReader
|
||||
from .callbacks import EventLoopCallbacks
|
||||
from .asyncio_base import AsyncioTimeout
|
||||
|
||||
import asyncio
|
||||
|
||||
__all__ = (
|
||||
'Win32AsyncioEventLoop',
|
||||
)
|
||||
|
||||
|
||||
class Win32AsyncioEventLoop(EventLoop):
|
||||
def __init__(self, loop=None):
|
||||
self._console_input_reader = ConsoleInputReader()
|
||||
self.running = False
|
||||
self.closed = False
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
|
||||
@asyncio.coroutine
|
||||
def run_as_coroutine(self, stdin, callbacks):
|
||||
"""
|
||||
The input 'event loop'.
|
||||
"""
|
||||
# Note: We cannot use "yield from", because this package also
|
||||
# installs on Python 2.
|
||||
assert isinstance(callbacks, EventLoopCallbacks)
|
||||
|
||||
if self.closed:
|
||||
raise Exception('Event loop already closed.')
|
||||
|
||||
timeout = AsyncioTimeout(INPUT_TIMEOUT, callbacks.input_timeout, self.loop)
|
||||
self.running = True
|
||||
|
||||
try:
|
||||
while self.running:
|
||||
timeout.reset()
|
||||
|
||||
# Get keys
|
||||
try:
|
||||
g = iter(self.loop.run_in_executor(None, self._console_input_reader.read))
|
||||
while True:
|
||||
yield next(g)
|
||||
except StopIteration as e:
|
||||
keys = e.args[0]
|
||||
|
||||
# Feed keys to input processor.
|
||||
for k in keys:
|
||||
callbacks.feed_key(k)
|
||||
finally:
|
||||
timeout.stop()
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
def close(self):
|
||||
# Note: we should not close the asyncio loop itself, because that one
|
||||
# was not created here.
|
||||
self.closed = True
|
||||
|
||||
self._console_input_reader.close()
|
||||
|
||||
def run_in_executor(self, callback):
|
||||
self.loop.run_in_executor(None, callback)
|
||||
|
||||
def call_from_executor(self, callback, _max_postpone_until=None):
|
||||
self.loop.call_soon_threadsafe(callback)
|
||||
|
||||
def add_reader(self, fd, callback):
|
||||
" Start watching the file descriptor for read availability. "
|
||||
self.loop.add_reader(fd, callback)
|
||||
|
||||
def remove_reader(self, fd):
|
||||
" Stop watching the file descriptor for read availability. "
|
||||
self.loop.remove_reader(fd)
|
85
src/libs/prompt_toolkit/eventloop/base.py
Normal file
85
src/libs/prompt_toolkit/eventloop/base.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from __future__ import unicode_literals
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from six import with_metaclass
|
||||
|
||||
__all__ = (
|
||||
'EventLoop',
|
||||
'INPUT_TIMEOUT',
|
||||
)
|
||||
|
||||
|
||||
#: When to trigger the `onInputTimeout` event.
|
||||
INPUT_TIMEOUT = .5
|
||||
|
||||
|
||||
class EventLoop(with_metaclass(ABCMeta, object)):
|
||||
"""
|
||||
Eventloop interface.
|
||||
"""
|
||||
def run(self, stdin, callbacks):
|
||||
"""
|
||||
Run the eventloop until stop() is called. Report all
|
||||
input/timeout/terminal-resize events to the callbacks.
|
||||
|
||||
:param stdin: :class:`~libs.prompt_toolkit.input.Input` instance.
|
||||
:param callbacks: :class:`~libs.prompt_toolkit.eventloop.callbacks.EventLoopCallbacks` instance.
|
||||
"""
|
||||
raise NotImplementedError("This eventloop doesn't implement synchronous 'run()'.")
|
||||
|
||||
def run_as_coroutine(self, stdin, callbacks):
|
||||
"""
|
||||
Similar to `run`, but this is a coroutine. (For asyncio integration.)
|
||||
"""
|
||||
raise NotImplementedError("This eventloop doesn't implement 'run_as_coroutine()'.")
|
||||
|
||||
@abstractmethod
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the `run` call. (Normally called by
|
||||
:class:`~libs.prompt_toolkit.interface.CommandLineInterface`, when a result
|
||||
is available, or Abort/Quit has been called.)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def close(self):
|
||||
"""
|
||||
Clean up of resources. Eventloop cannot be reused a second time after
|
||||
this call.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_reader(self, fd, callback):
|
||||
"""
|
||||
Start watching the file descriptor for read availability and then call
|
||||
the callback.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def remove_reader(self, fd):
|
||||
"""
|
||||
Stop watching the file descriptor for read availability.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def run_in_executor(self, callback):
|
||||
"""
|
||||
Run a long running function in a background thread. (This is
|
||||
recommended for code that could block the event loop.)
|
||||
Similar to Twisted's ``deferToThread``.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def call_from_executor(self, callback, _max_postpone_until=None):
|
||||
"""
|
||||
Call this function in the main event loop. Similar to Twisted's
|
||||
``callFromThread``.
|
||||
|
||||
:param _max_postpone_until: `None` or `time.time` value. For interal
|
||||
use. If the eventloop is saturated, consider this task to be low
|
||||
priority and postpone maximum until this timestamp. (For instance,
|
||||
repaint is done using low priority.)
|
||||
|
||||
Note: In the past, this used to be a datetime.datetime instance,
|
||||
but apparently, executing `time.time` is more efficient: it
|
||||
does fewer system calls. (It doesn't read /etc/localtime.)
|
||||
"""
|
29
src/libs/prompt_toolkit/eventloop/callbacks.py
Normal file
29
src/libs/prompt_toolkit/eventloop/callbacks.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from __future__ import unicode_literals
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from six import with_metaclass
|
||||
|
||||
__all__ = (
|
||||
'EventLoopCallbacks',
|
||||
)
|
||||
|
||||
|
||||
class EventLoopCallbacks(with_metaclass(ABCMeta, object)):
|
||||
"""
|
||||
This is the glue between the :class:`~libs.prompt_toolkit.eventloop.base.EventLoop`
|
||||
and :class:`~libs.prompt_toolkit.interface.CommandLineInterface`.
|
||||
|
||||
:meth:`~libs.prompt_toolkit.eventloop.base.EventLoop.run` takes an
|
||||
:class:`.EventLoopCallbacks` instance and operates on that one, driving the
|
||||
interface.
|
||||
"""
|
||||
@abstractmethod
|
||||
def terminal_size_changed(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def input_timeout(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def feed_key(self, key):
|
||||
pass
|
107
src/libs/prompt_toolkit/eventloop/inputhook.py
Normal file
107
src/libs/prompt_toolkit/eventloop/inputhook.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Similar to `PyOS_InputHook` of the Python API. Some eventloops can have an
|
||||
inputhook to allow easy integration with other event loops.
|
||||
|
||||
When the eventloop of prompt-toolkit is idle, it can call such a hook. This
|
||||
hook can call another eventloop that runs for a short while, for instance to
|
||||
keep a graphical user interface responsive.
|
||||
|
||||
It's the responsibility of this hook to exit when there is input ready.
|
||||
There are two ways to detect when input is ready:
|
||||
|
||||
- Call the `input_is_ready` method periodically. Quit when this returns `True`.
|
||||
|
||||
- Add the `fileno` as a watch to the external eventloop. Quit when file descriptor
|
||||
becomes readable. (But don't read from it.)
|
||||
|
||||
Note that this is not the same as checking for `sys.stdin.fileno()`. The
|
||||
eventloop of prompt-toolkit allows thread-based executors, for example for
|
||||
asynchronous autocompletion. When the completion for instance is ready, we
|
||||
also want prompt-toolkit to gain control again in order to display that.
|
||||
|
||||
An alternative to using input hooks, is to create a custom `EventLoop` class that
|
||||
controls everything.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import threading
|
||||
from libs.prompt_toolkit.utils import is_windows
|
||||
from .select import select_fds
|
||||
|
||||
__all__ = (
|
||||
'InputHookContext',
|
||||
)
|
||||
|
||||
|
||||
class InputHookContext(object):
|
||||
"""
|
||||
Given as a parameter to the inputhook.
|
||||
"""
|
||||
def __init__(self, inputhook):
|
||||
assert callable(inputhook)
|
||||
|
||||
self.inputhook = inputhook
|
||||
self._input_is_ready = None
|
||||
|
||||
self._r, self._w = os.pipe()
|
||||
|
||||
def input_is_ready(self):
|
||||
"""
|
||||
Return True when the input is ready.
|
||||
"""
|
||||
return self._input_is_ready(wait=False)
|
||||
|
||||
def fileno(self):
|
||||
"""
|
||||
File descriptor that will become ready when the event loop needs to go on.
|
||||
"""
|
||||
return self._r
|
||||
|
||||
def call_inputhook(self, input_is_ready_func):
|
||||
"""
|
||||
Call the inputhook. (Called by a prompt-toolkit eventloop.)
|
||||
"""
|
||||
self._input_is_ready = input_is_ready_func
|
||||
|
||||
# Start thread that activates this pipe when there is input to process.
|
||||
def thread():
|
||||
input_is_ready_func(wait=True)
|
||||
os.write(self._w, b'x')
|
||||
|
||||
threading.Thread(target=thread).start()
|
||||
|
||||
# Call inputhook.
|
||||
self.inputhook(self)
|
||||
|
||||
# Flush the read end of the pipe.
|
||||
try:
|
||||
# Before calling 'os.read', call select.select. This is required
|
||||
# when the gevent monkey patch has been applied. 'os.read' is never
|
||||
# monkey patched and won't be cooperative, so that would block all
|
||||
# other select() calls otherwise.
|
||||
# See: http://www.gevent.org/gevent.os.html
|
||||
|
||||
# Note: On Windows, this is apparently not an issue.
|
||||
# However, if we would ever want to add a select call, it
|
||||
# should use `windll.kernel32.WaitForMultipleObjects`,
|
||||
# because `select.select` can't wait for a pipe on Windows.
|
||||
if not is_windows():
|
||||
select_fds([self._r], timeout=None)
|
||||
|
||||
os.read(self._r, 1024)
|
||||
except OSError:
|
||||
# This happens when the window resizes and a SIGWINCH was received.
|
||||
# We get 'Error: [Errno 4] Interrupted system call'
|
||||
# Just ignore.
|
||||
pass
|
||||
self._input_is_ready = None
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Clean up resources.
|
||||
"""
|
||||
if self._r:
|
||||
os.close(self._r)
|
||||
os.close(self._w)
|
||||
|
||||
self._r = self._w = None
|
311
src/libs/prompt_toolkit/eventloop/posix.py
Normal file
311
src/libs/prompt_toolkit/eventloop/posix.py
Normal file
@@ -0,0 +1,311 @@
|
||||
from __future__ import unicode_literals
|
||||
import fcntl
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
import threading
|
||||
import time
|
||||
|
||||
from libs.prompt_toolkit.terminal.vt100_input import InputStream
|
||||
from libs.prompt_toolkit.utils import DummyContext, in_main_thread
|
||||
from libs.prompt_toolkit.input import Input
|
||||
from .base import EventLoop, INPUT_TIMEOUT
|
||||
from .callbacks import EventLoopCallbacks
|
||||
from .inputhook import InputHookContext
|
||||
from .posix_utils import PosixStdinReader
|
||||
from .utils import TimeIt
|
||||
from .select import AutoSelector, Selector, fd_to_int
|
||||
|
||||
__all__ = (
|
||||
'PosixEventLoop',
|
||||
)
|
||||
|
||||
_now = time.time
|
||||
|
||||
|
||||
class PosixEventLoop(EventLoop):
|
||||
"""
|
||||
Event loop for posix systems (Linux, Mac os X).
|
||||
"""
|
||||
def __init__(self, inputhook=None, selector=AutoSelector):
|
||||
assert inputhook is None or callable(inputhook)
|
||||
assert issubclass(selector, Selector)
|
||||
|
||||
self.running = False
|
||||
self.closed = False
|
||||
self._running = False
|
||||
self._callbacks = None
|
||||
|
||||
self._calls_from_executor = []
|
||||
self._read_fds = {} # Maps fd to handler.
|
||||
self.selector = selector()
|
||||
|
||||
# Create a pipe for inter thread communication.
|
||||
self._schedule_pipe = os.pipe()
|
||||
fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
|
||||
# Create inputhook context.
|
||||
self._inputhook_context = InputHookContext(inputhook) if inputhook else None
|
||||
|
||||
def run(self, stdin, callbacks):
|
||||
"""
|
||||
The input 'event loop'.
|
||||
"""
|
||||
assert isinstance(stdin, Input)
|
||||
assert isinstance(callbacks, EventLoopCallbacks)
|
||||
assert not self._running
|
||||
|
||||
if self.closed:
|
||||
raise Exception('Event loop already closed.')
|
||||
|
||||
self._running = True
|
||||
self._callbacks = callbacks
|
||||
|
||||
inputstream = InputStream(callbacks.feed_key)
|
||||
current_timeout = [INPUT_TIMEOUT] # Nonlocal
|
||||
|
||||
# Create reader class.
|
||||
stdin_reader = PosixStdinReader(stdin.fileno())
|
||||
|
||||
# Only attach SIGWINCH signal handler in main thread.
|
||||
# (It's not possible to attach signal handlers in other threads. In
|
||||
# that case we should rely on a the main thread to call this manually
|
||||
# instead.)
|
||||
if in_main_thread():
|
||||
ctx = call_on_sigwinch(self.received_winch)
|
||||
else:
|
||||
ctx = DummyContext()
|
||||
|
||||
def read_from_stdin():
|
||||
" Read user input. "
|
||||
# Feed input text.
|
||||
data = stdin_reader.read()
|
||||
inputstream.feed(data)
|
||||
|
||||
# Set timeout again.
|
||||
current_timeout[0] = INPUT_TIMEOUT
|
||||
|
||||
# Quit when the input stream was closed.
|
||||
if stdin_reader.closed:
|
||||
self.stop()
|
||||
|
||||
self.add_reader(stdin, read_from_stdin)
|
||||
self.add_reader(self._schedule_pipe[0], None)
|
||||
|
||||
with ctx:
|
||||
while self._running:
|
||||
# Call inputhook.
|
||||
if self._inputhook_context:
|
||||
with TimeIt() as inputhook_timer:
|
||||
def ready(wait):
|
||||
" True when there is input ready. The inputhook should return control. "
|
||||
return self._ready_for_reading(current_timeout[0] if wait else 0) != []
|
||||
self._inputhook_context.call_inputhook(ready)
|
||||
inputhook_duration = inputhook_timer.duration
|
||||
else:
|
||||
inputhook_duration = 0
|
||||
|
||||
# Calculate remaining timeout. (The inputhook consumed some of the time.)
|
||||
if current_timeout[0] is None:
|
||||
remaining_timeout = None
|
||||
else:
|
||||
remaining_timeout = max(0, current_timeout[0] - inputhook_duration)
|
||||
|
||||
# Wait until input is ready.
|
||||
fds = self._ready_for_reading(remaining_timeout)
|
||||
|
||||
# When any of the FDs are ready. Call the appropriate callback.
|
||||
if fds:
|
||||
# Create lists of high/low priority tasks. The main reason
|
||||
# for this is to allow painting the UI to happen as soon as
|
||||
# possible, but when there are many events happening, we
|
||||
# don't want to call the UI renderer 1000x per second. If
|
||||
# the eventloop is completely saturated with many CPU
|
||||
# intensive tasks (like processing input/output), we say
|
||||
# that drawing the UI can be postponed a little, to make
|
||||
# CPU available. This will be a low priority task in that
|
||||
# case.
|
||||
tasks = []
|
||||
low_priority_tasks = []
|
||||
now = None # Lazy load time. (Fewer system calls.)
|
||||
|
||||
for fd in fds:
|
||||
# For the 'call_from_executor' fd, put each pending
|
||||
# item on either the high or low priority queue.
|
||||
if fd == self._schedule_pipe[0]:
|
||||
for c, max_postpone_until in self._calls_from_executor:
|
||||
if max_postpone_until is None:
|
||||
# Execute now.
|
||||
tasks.append(c)
|
||||
else:
|
||||
# Execute soon, if `max_postpone_until` is in the future.
|
||||
now = now or _now()
|
||||
if max_postpone_until < now:
|
||||
tasks.append(c)
|
||||
else:
|
||||
low_priority_tasks.append((c, max_postpone_until))
|
||||
self._calls_from_executor = []
|
||||
|
||||
# Flush all the pipe content.
|
||||
os.read(self._schedule_pipe[0], 1024)
|
||||
else:
|
||||
handler = self._read_fds.get(fd)
|
||||
if handler:
|
||||
tasks.append(handler)
|
||||
|
||||
# Handle everything in random order. (To avoid starvation.)
|
||||
random.shuffle(tasks)
|
||||
random.shuffle(low_priority_tasks)
|
||||
|
||||
# When there are high priority tasks, run all these.
|
||||
# Schedule low priority tasks for the next iteration.
|
||||
if tasks:
|
||||
for t in tasks:
|
||||
t()
|
||||
|
||||
# Postpone low priority tasks.
|
||||
for t, max_postpone_until in low_priority_tasks:
|
||||
self.call_from_executor(t, _max_postpone_until=max_postpone_until)
|
||||
else:
|
||||
# Currently there are only low priority tasks -> run them right now.
|
||||
for t, _ in low_priority_tasks:
|
||||
t()
|
||||
|
||||
else:
|
||||
# Flush all pending keys on a timeout. (This is most
|
||||
# important to flush the vt100 'Escape' key early when
|
||||
# nothing else follows.)
|
||||
inputstream.flush()
|
||||
|
||||
# Fire input timeout event.
|
||||
callbacks.input_timeout()
|
||||
current_timeout[0] = None
|
||||
|
||||
self.remove_reader(stdin)
|
||||
self.remove_reader(self._schedule_pipe[0])
|
||||
|
||||
self._callbacks = None
|
||||
|
||||
def _ready_for_reading(self, timeout=None):
|
||||
"""
|
||||
Return the file descriptors that are ready for reading.
|
||||
"""
|
||||
fds = self.selector.select(timeout)
|
||||
return fds
|
||||
|
||||
def received_winch(self):
|
||||
"""
|
||||
Notify the event loop that SIGWINCH has been received
|
||||
"""
|
||||
# Process signal asynchronously, because this handler can write to the
|
||||
# output, and doing this inside the signal handler causes easily
|
||||
# reentrant calls, giving runtime errors..
|
||||
|
||||
# Furthur, this has to be thread safe. When the CommandLineInterface
|
||||
# runs not in the main thread, this function still has to be called
|
||||
# from the main thread. (The only place where we can install signal
|
||||
# handlers.)
|
||||
def process_winch():
|
||||
if self._callbacks:
|
||||
self._callbacks.terminal_size_changed()
|
||||
|
||||
self.call_from_executor(process_winch)
|
||||
|
||||
def run_in_executor(self, callback):
|
||||
"""
|
||||
Run a long running function in a background thread.
|
||||
(This is recommended for code that could block the event loop.)
|
||||
Similar to Twisted's ``deferToThread``.
|
||||
"""
|
||||
# Wait until the main thread is idle.
|
||||
# We start the thread by using `call_from_executor`. The event loop
|
||||
# favours processing input over `calls_from_executor`, so the thread
|
||||
# will not start until there is no more input to process and the main
|
||||
# thread becomes idle for an instant. This is good, because Python
|
||||
# threading favours CPU over I/O -- an autocompletion thread in the
|
||||
# background would cause a significantly slow down of the main thread.
|
||||
# It is mostly noticable when pasting large portions of text while
|
||||
# having real time autocompletion while typing on.
|
||||
def start_executor():
|
||||
threading.Thread(target=callback).start()
|
||||
self.call_from_executor(start_executor)
|
||||
|
||||
def call_from_executor(self, callback, _max_postpone_until=None):
|
||||
"""
|
||||
Call this function in the main event loop.
|
||||
Similar to Twisted's ``callFromThread``.
|
||||
|
||||
:param _max_postpone_until: `None` or `time.time` value. For interal
|
||||
use. If the eventloop is saturated, consider this task to be low
|
||||
priority and postpone maximum until this timestamp. (For instance,
|
||||
repaint is done using low priority.)
|
||||
"""
|
||||
assert _max_postpone_until is None or isinstance(_max_postpone_until, float)
|
||||
self._calls_from_executor.append((callback, _max_postpone_until))
|
||||
|
||||
if self._schedule_pipe:
|
||||
try:
|
||||
os.write(self._schedule_pipe[1], b'x')
|
||||
except (AttributeError, IndexError, OSError):
|
||||
# Handle race condition. We're in a different thread.
|
||||
# - `_schedule_pipe` could have become None in the meantime.
|
||||
# - We catch `OSError` (actually BrokenPipeError), because the
|
||||
# main thread could have closed the pipe already.
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the event loop.
|
||||
"""
|
||||
self._running = False
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
# Close pipes.
|
||||
schedule_pipe = self._schedule_pipe
|
||||
self._schedule_pipe = None
|
||||
|
||||
if schedule_pipe:
|
||||
os.close(schedule_pipe[0])
|
||||
os.close(schedule_pipe[1])
|
||||
|
||||
if self._inputhook_context:
|
||||
self._inputhook_context.close()
|
||||
|
||||
def add_reader(self, fd, callback):
|
||||
" Add read file descriptor to the event loop. "
|
||||
fd = fd_to_int(fd)
|
||||
self._read_fds[fd] = callback
|
||||
self.selector.register(fd)
|
||||
|
||||
def remove_reader(self, fd):
|
||||
" Remove read file descriptor from the event loop. "
|
||||
fd = fd_to_int(fd)
|
||||
|
||||
if fd in self._read_fds:
|
||||
del self._read_fds[fd]
|
||||
|
||||
self.selector.unregister(fd)
|
||||
|
||||
|
||||
class call_on_sigwinch(object):
|
||||
"""
|
||||
Context manager which Installs a SIGWINCH callback.
|
||||
(This signal occurs when the terminal size changes.)
|
||||
"""
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
self.previous_callback = None
|
||||
|
||||
def __enter__(self):
|
||||
self.previous_callback = signal.signal(signal.SIGWINCH, lambda *a: self.callback())
|
||||
|
||||
def __exit__(self, *a, **kw):
|
||||
if self.previous_callback is None:
|
||||
# Normally, `signal.signal` should never return `None`.
|
||||
# For some reason it happens here:
|
||||
# https://github.com/jonathanslenders/python-prompt-toolkit/pull/174
|
||||
signal.signal(signal.SIGWINCH, 0)
|
||||
else:
|
||||
signal.signal(signal.SIGWINCH, self.previous_callback)
|
82
src/libs/prompt_toolkit/eventloop/posix_utils.py
Normal file
82
src/libs/prompt_toolkit/eventloop/posix_utils.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from codecs import getincrementaldecoder
|
||||
import os
|
||||
import six
|
||||
|
||||
__all__ = (
|
||||
'PosixStdinReader',
|
||||
)
|
||||
|
||||
|
||||
class PosixStdinReader(object):
|
||||
"""
|
||||
Wrapper around stdin which reads (nonblocking) the next available 1024
|
||||
bytes and decodes it.
|
||||
|
||||
Note that you can't be sure that the input file is closed if the ``read``
|
||||
function returns an empty string. When ``errors=ignore`` is passed,
|
||||
``read`` can return an empty string if all malformed input was replaced by
|
||||
an empty string. (We can't block here and wait for more input.) So, because
|
||||
of that, check the ``closed`` attribute, to be sure that the file has been
|
||||
closed.
|
||||
|
||||
:param stdin_fd: File descriptor from which we read.
|
||||
:param errors: Can be 'ignore', 'strict' or 'replace'.
|
||||
On Python3, this can be 'surrogateescape', which is the default.
|
||||
|
||||
'surrogateescape' is preferred, because this allows us to transfer
|
||||
unrecognised bytes to the key bindings. Some terminals, like lxterminal
|
||||
and Guake, use the 'Mxx' notation to send mouse events, where each 'x'
|
||||
can be any possible byte.
|
||||
"""
|
||||
# By default, we want to 'ignore' errors here. The input stream can be full
|
||||
# of junk. One occurrence of this that I had was when using iTerm2 on OS X,
|
||||
# with "Option as Meta" checked (You should choose "Option as +Esc".)
|
||||
|
||||
def __init__(self, stdin_fd,
|
||||
errors=('ignore' if six.PY2 else 'surrogateescape')):
|
||||
assert isinstance(stdin_fd, int)
|
||||
self.stdin_fd = stdin_fd
|
||||
self.errors = errors
|
||||
|
||||
# Create incremental decoder for decoding stdin.
|
||||
# We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because
|
||||
# it could be that we are in the middle of a utf-8 byte sequence.
|
||||
self._stdin_decoder_cls = getincrementaldecoder('utf-8')
|
||||
self._stdin_decoder = self._stdin_decoder_cls(errors=errors)
|
||||
|
||||
#: True when there is nothing anymore to read.
|
||||
self.closed = False
|
||||
|
||||
def read(self, count=1024):
|
||||
# By default we choose a rather small chunk size, because reading
|
||||
# big amounts of input at once, causes the event loop to process
|
||||
# all these key bindings also at once without going back to the
|
||||
# loop. This will make the application feel unresponsive.
|
||||
"""
|
||||
Read the input and return it as a string.
|
||||
|
||||
Return the text. Note that this can return an empty string, even when
|
||||
the input stream was not yet closed. This means that something went
|
||||
wrong during the decoding.
|
||||
"""
|
||||
if self.closed:
|
||||
return b''
|
||||
|
||||
# Note: the following works better than wrapping `self.stdin` like
|
||||
# `codecs.getreader('utf-8')(stdin)` and doing `read(1)`.
|
||||
# Somehow that causes some latency when the escape
|
||||
# character is pressed. (Especially on combination with the `select`.)
|
||||
try:
|
||||
data = os.read(self.stdin_fd, count)
|
||||
|
||||
# Nothing more to read, stream is closed.
|
||||
if data == b'':
|
||||
self.closed = True
|
||||
return ''
|
||||
except OSError:
|
||||
# In case of SIGWINCH
|
||||
data = b''
|
||||
|
||||
return self._stdin_decoder.decode(data)
|
216
src/libs/prompt_toolkit/eventloop/select.py
Normal file
216
src/libs/prompt_toolkit/eventloop/select.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Selectors for the Posix event loop.
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import sys
|
||||
import abc
|
||||
import errno
|
||||
import select
|
||||
import six
|
||||
|
||||
__all__ = (
|
||||
'AutoSelector',
|
||||
'PollSelector',
|
||||
'SelectSelector',
|
||||
'Selector',
|
||||
'fd_to_int',
|
||||
)
|
||||
|
||||
def fd_to_int(fd):
|
||||
assert isinstance(fd, int) or hasattr(fd, 'fileno')
|
||||
|
||||
if isinstance(fd, int):
|
||||
return fd
|
||||
else:
|
||||
return fd.fileno()
|
||||
|
||||
|
||||
class Selector(six.with_metaclass(abc.ABCMeta, object)):
|
||||
@abc.abstractmethod
|
||||
def register(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
|
||||
@abc.abstractmethod
|
||||
def unregister(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
|
||||
@abc.abstractmethod
|
||||
def select(self, timeout):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class AutoSelector(Selector):
|
||||
def __init__(self):
|
||||
self._fds = []
|
||||
|
||||
self._select_selector = SelectSelector()
|
||||
self._selectors = [self._select_selector]
|
||||
|
||||
# When 'select.poll' exists, create a PollSelector.
|
||||
if hasattr(select, 'poll'):
|
||||
self._poll_selector = PollSelector()
|
||||
self._selectors.append(self._poll_selector)
|
||||
else:
|
||||
self._poll_selector = None
|
||||
|
||||
# Use of the 'select' module, that was introduced in Python3.4. We don't
|
||||
# use it before 3.5 however, because this is the point where this module
|
||||
# retries interrupted system calls.
|
||||
if sys.version_info >= (3, 5):
|
||||
self._py3_selector = Python3Selector()
|
||||
self._selectors.append(self._py3_selector)
|
||||
else:
|
||||
self._py3_selector = None
|
||||
|
||||
def register(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
|
||||
self._fds.append(fd)
|
||||
|
||||
for sel in self._selectors:
|
||||
sel.register(fd)
|
||||
|
||||
def unregister(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
|
||||
self._fds.remove(fd)
|
||||
|
||||
for sel in self._selectors:
|
||||
sel.unregister(fd)
|
||||
|
||||
def select(self, timeout):
|
||||
# Try Python 3 selector first.
|
||||
if self._py3_selector:
|
||||
try:
|
||||
return self._py3_selector.select(timeout)
|
||||
except PermissionError:
|
||||
# We had a situation (in pypager) where epoll raised a
|
||||
# PermissionError when a local file descriptor was registered,
|
||||
# however poll and select worked fine. So, in that case, just
|
||||
# try using select below.
|
||||
pass
|
||||
|
||||
try:
|
||||
# Prefer 'select.select', if we don't have much file descriptors.
|
||||
# This is more universal.
|
||||
return self._select_selector.select(timeout)
|
||||
except ValueError:
|
||||
# When we have more than 1024 open file descriptors, we'll always
|
||||
# get a "ValueError: filedescriptor out of range in select()" for
|
||||
# 'select'. In this case, try, using 'poll' instead.
|
||||
if self._poll_selector is not None:
|
||||
return self._poll_selector.select(timeout)
|
||||
else:
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
for sel in self._selectors:
|
||||
sel.close()
|
||||
|
||||
|
||||
class Python3Selector(Selector):
|
||||
"""
|
||||
Use of the Python3 'selectors' module.
|
||||
|
||||
NOTE: Only use on Python 3.5 or newer!
|
||||
"""
|
||||
def __init__(self):
|
||||
assert sys.version_info >= (3, 5)
|
||||
|
||||
import selectors # Inline import: Python3 only!
|
||||
self._sel = selectors.DefaultSelector()
|
||||
|
||||
def register(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
import selectors # Inline import: Python3 only!
|
||||
self._sel.register(fd, selectors.EVENT_READ, None)
|
||||
|
||||
def unregister(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
self._sel.unregister(fd)
|
||||
|
||||
def select(self, timeout):
|
||||
events = self._sel.select(timeout=timeout)
|
||||
return [key.fileobj for key, mask in events]
|
||||
|
||||
def close(self):
|
||||
self._sel.close()
|
||||
|
||||
|
||||
class PollSelector(Selector):
|
||||
def __init__(self):
|
||||
self._poll = select.poll()
|
||||
|
||||
def register(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
self._poll.register(fd, select.POLLIN)
|
||||
|
||||
def unregister(self, fd):
|
||||
assert isinstance(fd, int)
|
||||
|
||||
def select(self, timeout):
|
||||
tuples = self._poll.poll(timeout) # Returns (fd, event) tuples.
|
||||
return [t[0] for t in tuples]
|
||||
|
||||
def close(self):
|
||||
pass # XXX
|
||||
|
||||
|
||||
class SelectSelector(Selector):
|
||||
"""
|
||||
Wrapper around select.select.
|
||||
|
||||
When the SIGWINCH signal is handled, other system calls, like select
|
||||
are aborted in Python. This wrapper will retry the system call.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._fds = []
|
||||
|
||||
def register(self, fd):
|
||||
self._fds.append(fd)
|
||||
|
||||
def unregister(self, fd):
|
||||
self._fds.remove(fd)
|
||||
|
||||
def select(self, timeout):
|
||||
while True:
|
||||
try:
|
||||
return select.select(self._fds, [], [], timeout)[0]
|
||||
except select.error as e:
|
||||
# Retry select call when EINTR
|
||||
if e.args and e.args[0] == errno.EINTR:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def select_fds(read_fds, timeout, selector=AutoSelector):
|
||||
"""
|
||||
Wait for a list of file descriptors (`read_fds`) to become ready for
|
||||
reading. This chooses the most appropriate select-tool for use in
|
||||
prompt-toolkit.
|
||||
"""
|
||||
# Map to ensure that we return the objects that were passed in originally.
|
||||
# Whether they are a fd integer or an object that has a fileno().
|
||||
# (The 'poll' implementation for instance, returns always integers.)
|
||||
fd_map = dict((fd_to_int(fd), fd) for fd in read_fds)
|
||||
|
||||
# Wait, using selector.
|
||||
sel = selector()
|
||||
try:
|
||||
for fd in read_fds:
|
||||
sel.register(fd)
|
||||
|
||||
result = sel.select(timeout)
|
||||
|
||||
if result is not None:
|
||||
return [fd_map[fd_to_int(fd)] for fd in result]
|
||||
finally:
|
||||
sel.close()
|
23
src/libs/prompt_toolkit/eventloop/utils.py
Normal file
23
src/libs/prompt_toolkit/eventloop/utils.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from __future__ import unicode_literals
|
||||
import time
|
||||
|
||||
__all__ = (
|
||||
'TimeIt',
|
||||
)
|
||||
|
||||
|
||||
class TimeIt(object):
|
||||
"""
|
||||
Context manager that times the duration of the code body.
|
||||
The `duration` attribute will contain the execution time in seconds.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.duration = None
|
||||
|
||||
def __enter__(self):
|
||||
self.start = time.time()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.end = time.time()
|
||||
self.duration = self.end - self.start
|
187
src/libs/prompt_toolkit/eventloop/win32.py
Normal file
187
src/libs/prompt_toolkit/eventloop/win32.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Win32 event loop.
|
||||
|
||||
Windows notes:
|
||||
- Somehow it doesn't seem to work with the 'ProactorEventLoop'.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..terminal.win32_input import ConsoleInputReader
|
||||
from ..win32_types import SECURITY_ATTRIBUTES
|
||||
from .base import EventLoop, INPUT_TIMEOUT
|
||||
from .inputhook import InputHookContext
|
||||
from .utils import TimeIt
|
||||
|
||||
from ctypes import windll, pointer
|
||||
from ctypes.wintypes import DWORD, BOOL, HANDLE
|
||||
|
||||
import msvcrt
|
||||
import threading
|
||||
|
||||
__all__ = (
|
||||
'Win32EventLoop',
|
||||
)
|
||||
|
||||
WAIT_TIMEOUT = 0x00000102
|
||||
INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT)
|
||||
|
||||
|
||||
class Win32EventLoop(EventLoop):
|
||||
"""
|
||||
Event loop for Windows systems.
|
||||
|
||||
:param recognize_paste: When True, try to discover paste actions and turn
|
||||
the event into a BracketedPaste.
|
||||
"""
|
||||
def __init__(self, inputhook=None, recognize_paste=True):
|
||||
assert inputhook is None or callable(inputhook)
|
||||
|
||||
self._event = _create_event()
|
||||
self._console_input_reader = ConsoleInputReader(recognize_paste=recognize_paste)
|
||||
self._calls_from_executor = []
|
||||
|
||||
self.closed = False
|
||||
self._running = False
|
||||
|
||||
# Additional readers.
|
||||
self._read_fds = {} # Maps fd to handler.
|
||||
|
||||
# Create inputhook context.
|
||||
self._inputhook_context = InputHookContext(inputhook) if inputhook else None
|
||||
|
||||
def run(self, stdin, callbacks):
|
||||
if self.closed:
|
||||
raise Exception('Event loop already closed.')
|
||||
|
||||
current_timeout = INPUT_TIMEOUT_MS
|
||||
self._running = True
|
||||
|
||||
while self._running:
|
||||
# Call inputhook.
|
||||
with TimeIt() as inputhook_timer:
|
||||
if self._inputhook_context:
|
||||
def ready(wait):
|
||||
" True when there is input ready. The inputhook should return control. "
|
||||
return bool(self._ready_for_reading(current_timeout if wait else 0))
|
||||
self._inputhook_context.call_inputhook(ready)
|
||||
|
||||
# Calculate remaining timeout. (The inputhook consumed some of the time.)
|
||||
if current_timeout == -1:
|
||||
remaining_timeout = -1
|
||||
else:
|
||||
remaining_timeout = max(0, current_timeout - int(1000 * inputhook_timer.duration))
|
||||
|
||||
# Wait for the next event.
|
||||
handle = self._ready_for_reading(remaining_timeout)
|
||||
|
||||
if handle == self._console_input_reader.handle:
|
||||
# When stdin is ready, read input and reset timeout timer.
|
||||
keys = self._console_input_reader.read()
|
||||
for k in keys:
|
||||
callbacks.feed_key(k)
|
||||
current_timeout = INPUT_TIMEOUT_MS
|
||||
|
||||
elif handle == self._event:
|
||||
# When the Windows Event has been trigger, process the messages in the queue.
|
||||
windll.kernel32.ResetEvent(self._event)
|
||||
self._process_queued_calls_from_executor()
|
||||
|
||||
elif handle in self._read_fds:
|
||||
callback = self._read_fds[handle]
|
||||
callback()
|
||||
else:
|
||||
# Fire input timeout event.
|
||||
callbacks.input_timeout()
|
||||
current_timeout = -1
|
||||
|
||||
def _ready_for_reading(self, timeout=None):
|
||||
"""
|
||||
Return the handle that is ready for reading or `None` on timeout.
|
||||
"""
|
||||
handles = [self._event, self._console_input_reader.handle]
|
||||
handles.extend(self._read_fds.keys())
|
||||
return _wait_for_handles(handles, timeout)
|
||||
|
||||
def stop(self):
|
||||
self._running = False
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
# Clean up Event object.
|
||||
windll.kernel32.CloseHandle(self._event)
|
||||
|
||||
if self._inputhook_context:
|
||||
self._inputhook_context.close()
|
||||
|
||||
self._console_input_reader.close()
|
||||
|
||||
def run_in_executor(self, callback):
|
||||
"""
|
||||
Run a long running function in a background thread.
|
||||
(This is recommended for code that could block the event loop.)
|
||||
Similar to Twisted's ``deferToThread``.
|
||||
"""
|
||||
# Wait until the main thread is idle for an instant before starting the
|
||||
# executor. (Like in eventloop/posix.py, we start the executor using
|
||||
# `call_from_executor`.)
|
||||
def start_executor():
|
||||
threading.Thread(target=callback).start()
|
||||
self.call_from_executor(start_executor)
|
||||
|
||||
def call_from_executor(self, callback, _max_postpone_until=None):
|
||||
"""
|
||||
Call this function in the main event loop.
|
||||
Similar to Twisted's ``callFromThread``.
|
||||
"""
|
||||
# Append to list of pending callbacks.
|
||||
self._calls_from_executor.append(callback)
|
||||
|
||||
# Set Windows event.
|
||||
windll.kernel32.SetEvent(self._event)
|
||||
|
||||
def _process_queued_calls_from_executor(self):
|
||||
# Process calls from executor.
|
||||
calls_from_executor, self._calls_from_executor = self._calls_from_executor, []
|
||||
for c in calls_from_executor:
|
||||
c()
|
||||
|
||||
def add_reader(self, fd, callback):
|
||||
" Start watching the file descriptor for read availability. "
|
||||
h = msvcrt.get_osfhandle(fd)
|
||||
self._read_fds[h] = callback
|
||||
|
||||
def remove_reader(self, fd):
|
||||
" Stop watching the file descriptor for read availability. "
|
||||
h = msvcrt.get_osfhandle(fd)
|
||||
if h in self._read_fds:
|
||||
del self._read_fds[h]
|
||||
|
||||
|
||||
def _wait_for_handles(handles, timeout=-1):
|
||||
"""
|
||||
Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
|
||||
Returns `None` on timeout.
|
||||
|
||||
http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
|
||||
"""
|
||||
arrtype = HANDLE * len(handles)
|
||||
handle_array = arrtype(*handles)
|
||||
|
||||
ret = windll.kernel32.WaitForMultipleObjects(
|
||||
len(handle_array), handle_array, BOOL(False), DWORD(timeout))
|
||||
|
||||
if ret == WAIT_TIMEOUT:
|
||||
return None
|
||||
else:
|
||||
h = handle_array[ret]
|
||||
return h
|
||||
|
||||
|
||||
def _create_event():
|
||||
"""
|
||||
Creates a Win32 unnamed Event .
|
||||
|
||||
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
|
||||
"""
|
||||
return windll.kernel32.CreateEventA(pointer(SECURITY_ATTRIBUTES()), BOOL(True), BOOL(False), None)
|
Reference in New Issue
Block a user