win32.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. """
  2. Win32 event loop.
  3. Windows notes:
  4. - Somehow it doesn't seem to work with the 'ProactorEventLoop'.
  5. """
  6. from __future__ import unicode_literals
  7. from ..terminal.win32_input import ConsoleInputReader
  8. from ..win32_types import SECURITY_ATTRIBUTES
  9. from .base import EventLoop, INPUT_TIMEOUT
  10. from .inputhook import InputHookContext
  11. from .utils import TimeIt
  12. from ctypes import windll, pointer
  13. from ctypes.wintypes import DWORD, BOOL, HANDLE
  14. import msvcrt
  15. import threading
  16. __all__ = (
  17. 'Win32EventLoop',
  18. )
  19. WAIT_TIMEOUT = 0x00000102
  20. INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT)
  21. class Win32EventLoop(EventLoop):
  22. """
  23. Event loop for Windows systems.
  24. :param recognize_paste: When True, try to discover paste actions and turn
  25. the event into a BracketedPaste.
  26. """
  27. def __init__(self, inputhook=None, recognize_paste=True):
  28. assert inputhook is None or callable(inputhook)
  29. self._event = HANDLE(_create_event())
  30. self._console_input_reader = ConsoleInputReader(recognize_paste=recognize_paste)
  31. self._calls_from_executor = []
  32. self.closed = False
  33. self._running = False
  34. # Additional readers.
  35. self._read_fds = {} # Maps fd to handler.
  36. # Create inputhook context.
  37. self._inputhook_context = InputHookContext(inputhook) if inputhook else None
  38. def run(self, stdin, callbacks):
  39. if self.closed:
  40. raise Exception('Event loop already closed.')
  41. current_timeout = INPUT_TIMEOUT_MS
  42. self._running = True
  43. while self._running:
  44. # Call inputhook.
  45. with TimeIt() as inputhook_timer:
  46. if self._inputhook_context:
  47. def ready(wait):
  48. " True when there is input ready. The inputhook should return control. "
  49. return bool(self._ready_for_reading(current_timeout if wait else 0))
  50. self._inputhook_context.call_inputhook(ready)
  51. # Calculate remaining timeout. (The inputhook consumed some of the time.)
  52. if current_timeout == -1:
  53. remaining_timeout = -1
  54. else:
  55. remaining_timeout = max(0, current_timeout - int(1000 * inputhook_timer.duration))
  56. # Wait for the next event.
  57. handle = self._ready_for_reading(remaining_timeout)
  58. if handle == self._console_input_reader.handle.value:
  59. # When stdin is ready, read input and reset timeout timer.
  60. keys = self._console_input_reader.read()
  61. for k in keys:
  62. callbacks.feed_key(k)
  63. current_timeout = INPUT_TIMEOUT_MS
  64. elif handle == self._event.value:
  65. # When the Windows Event has been trigger, process the messages in the queue.
  66. windll.kernel32.ResetEvent(self._event)
  67. self._process_queued_calls_from_executor()
  68. elif handle in self._read_fds:
  69. callback = self._read_fds[handle]
  70. callback()
  71. else:
  72. # Fire input timeout event.
  73. callbacks.input_timeout()
  74. current_timeout = -1
  75. def _ready_for_reading(self, timeout=None):
  76. """
  77. Return the handle that is ready for reading or `None` on timeout.
  78. """
  79. handles = [self._event, self._console_input_reader.handle]
  80. handles.extend(self._read_fds.keys())
  81. return _wait_for_handles(handles, timeout)
  82. def stop(self):
  83. self._running = False
  84. def close(self):
  85. self.closed = True
  86. # Clean up Event object.
  87. windll.kernel32.CloseHandle(self._event)
  88. if self._inputhook_context:
  89. self._inputhook_context.close()
  90. self._console_input_reader.close()
  91. def run_in_executor(self, callback):
  92. """
  93. Run a long running function in a background thread.
  94. (This is recommended for code that could block the event loop.)
  95. Similar to Twisted's ``deferToThread``.
  96. """
  97. # Wait until the main thread is idle for an instant before starting the
  98. # executor. (Like in eventloop/posix.py, we start the executor using
  99. # `call_from_executor`.)
  100. def start_executor():
  101. threading.Thread(target=callback).start()
  102. self.call_from_executor(start_executor)
  103. def call_from_executor(self, callback, _max_postpone_until=None):
  104. """
  105. Call this function in the main event loop.
  106. Similar to Twisted's ``callFromThread``.
  107. """
  108. # Append to list of pending callbacks.
  109. self._calls_from_executor.append(callback)
  110. # Set Windows event.
  111. windll.kernel32.SetEvent(self._event)
  112. def _process_queued_calls_from_executor(self):
  113. # Process calls from executor.
  114. calls_from_executor, self._calls_from_executor = self._calls_from_executor, []
  115. for c in calls_from_executor:
  116. c()
  117. def add_reader(self, fd, callback):
  118. " Start watching the file descriptor for read availability. "
  119. h = msvcrt.get_osfhandle(fd)
  120. self._read_fds[h] = callback
  121. def remove_reader(self, fd):
  122. " Stop watching the file descriptor for read availability. "
  123. h = msvcrt.get_osfhandle(fd)
  124. if h in self._read_fds:
  125. del self._read_fds[h]
  126. def _wait_for_handles(handles, timeout=-1):
  127. """
  128. Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
  129. Returns `None` on timeout.
  130. http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
  131. """
  132. arrtype = HANDLE * len(handles)
  133. handle_array = arrtype(*handles)
  134. ret = windll.kernel32.WaitForMultipleObjects(
  135. len(handle_array), handle_array, BOOL(False), DWORD(timeout))
  136. if ret == WAIT_TIMEOUT:
  137. return None
  138. else:
  139. h = handle_array[ret]
  140. return h
  141. def _create_event():
  142. """
  143. Creates a Win32 unnamed Event .
  144. http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
  145. """
  146. return windll.kernel32.CreateEventA(pointer(SECURITY_ATTRIBUTES()), BOOL(True), BOOL(False), None)