inputhook.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. """
  2. Similar to `PyOS_InputHook` of the Python API. Some eventloops can have an
  3. inputhook to allow easy integration with other event loops.
  4. When the eventloop of prompt-toolkit is idle, it can call such a hook. This
  5. hook can call another eventloop that runs for a short while, for instance to
  6. keep a graphical user interface responsive.
  7. It's the responsibility of this hook to exit when there is input ready.
  8. There are two ways to detect when input is ready:
  9. - Call the `input_is_ready` method periodically. Quit when this returns `True`.
  10. - Add the `fileno` as a watch to the external eventloop. Quit when file descriptor
  11. becomes readable. (But don't read from it.)
  12. Note that this is not the same as checking for `sys.stdin.fileno()`. The
  13. eventloop of prompt-toolkit allows thread-based executors, for example for
  14. asynchronous autocompletion. When the completion for instance is ready, we
  15. also want prompt-toolkit to gain control again in order to display that.
  16. An alternative to using input hooks, is to create a custom `EventLoop` class that
  17. controls everything.
  18. """
  19. from __future__ import unicode_literals
  20. import os
  21. import threading
  22. from prompt_toolkit.utils import is_windows
  23. from .select import select_fds
  24. __all__ = (
  25. 'InputHookContext',
  26. )
  27. class InputHookContext(object):
  28. """
  29. Given as a parameter to the inputhook.
  30. """
  31. def __init__(self, inputhook):
  32. assert callable(inputhook)
  33. self.inputhook = inputhook
  34. self._input_is_ready = None
  35. self._r, self._w = os.pipe()
  36. def input_is_ready(self):
  37. """
  38. Return True when the input is ready.
  39. """
  40. return self._input_is_ready(wait=False)
  41. def fileno(self):
  42. """
  43. File descriptor that will become ready when the event loop needs to go on.
  44. """
  45. return self._r
  46. def call_inputhook(self, input_is_ready_func):
  47. """
  48. Call the inputhook. (Called by a prompt-toolkit eventloop.)
  49. """
  50. self._input_is_ready = input_is_ready_func
  51. # Start thread that activates this pipe when there is input to process.
  52. def thread():
  53. input_is_ready_func(wait=True)
  54. os.write(self._w, b'x')
  55. threading.Thread(target=thread).start()
  56. # Call inputhook.
  57. self.inputhook(self)
  58. # Flush the read end of the pipe.
  59. try:
  60. # Before calling 'os.read', call select.select. This is required
  61. # when the gevent monkey patch has been applied. 'os.read' is never
  62. # monkey patched and won't be cooperative, so that would block all
  63. # other select() calls otherwise.
  64. # See: http://www.gevent.org/gevent.os.html
  65. # Note: On Windows, this is apparently not an issue.
  66. # However, if we would ever want to add a select call, it
  67. # should use `windll.kernel32.WaitForMultipleObjects`,
  68. # because `select.select` can't wait for a pipe on Windows.
  69. if not is_windows():
  70. select_fds([self._r], timeout=None)
  71. os.read(self._r, 1024)
  72. except OSError:
  73. # This happens when the window resizes and a SIGWINCH was received.
  74. # We get 'Error: [Errno 4] Interrupted system call'
  75. # Just ignore.
  76. pass
  77. self._input_is_ready = None
  78. def close(self):
  79. """
  80. Clean up resources.
  81. """
  82. if self._r:
  83. os.close(self._r)
  84. os.close(self._w)
  85. self._r = self._w = None