win32.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. from __future__ import annotations
  2. import os
  3. import sys
  4. from abc import abstractmethod
  5. from asyncio import get_running_loop
  6. from contextlib import contextmanager
  7. from ..utils import SPHINX_AUTODOC_RUNNING
  8. assert sys.platform == "win32"
  9. # Do not import win32-specific stuff when generating documentation.
  10. # Otherwise RTD would be unable to generate docs for this module.
  11. if not SPHINX_AUTODOC_RUNNING:
  12. import msvcrt
  13. from ctypes import windll
  14. from ctypes import Array, pointer
  15. from ctypes.wintypes import DWORD, HANDLE
  16. from typing import Callable, ContextManager, Iterable, Iterator, TextIO
  17. from prompt_toolkit.eventloop import run_in_executor_with_context
  18. from prompt_toolkit.eventloop.win32 import create_win32_event, wait_for_handles
  19. from prompt_toolkit.key_binding.key_processor import KeyPress
  20. from prompt_toolkit.keys import Keys
  21. from prompt_toolkit.mouse_events import MouseButton, MouseEventType
  22. from prompt_toolkit.win32_types import (
  23. INPUT_RECORD,
  24. KEY_EVENT_RECORD,
  25. MOUSE_EVENT_RECORD,
  26. STD_INPUT_HANDLE,
  27. EventTypes,
  28. )
  29. from .ansi_escape_sequences import REVERSE_ANSI_SEQUENCES
  30. from .base import Input
  31. __all__ = [
  32. "Win32Input",
  33. "ConsoleInputReader",
  34. "raw_mode",
  35. "cooked_mode",
  36. "attach_win32_input",
  37. "detach_win32_input",
  38. ]
  39. # Win32 Constants for MOUSE_EVENT_RECORD.
  40. # See: https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
  41. FROM_LEFT_1ST_BUTTON_PRESSED = 0x1
  42. RIGHTMOST_BUTTON_PRESSED = 0x2
  43. MOUSE_MOVED = 0x0001
  44. MOUSE_WHEELED = 0x0004
  45. class _Win32InputBase(Input):
  46. """
  47. Base class for `Win32Input` and `Win32PipeInput`.
  48. """
  49. def __init__(self) -> None:
  50. self.win32_handles = _Win32Handles()
  51. @property
  52. @abstractmethod
  53. def handle(self) -> HANDLE:
  54. pass
  55. class Win32Input(_Win32InputBase):
  56. """
  57. `Input` class that reads from the Windows console.
  58. """
  59. def __init__(self, stdin: TextIO | None = None) -> None:
  60. super().__init__()
  61. self.console_input_reader = ConsoleInputReader()
  62. def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]:
  63. """
  64. Return a context manager that makes this input active in the current
  65. event loop.
  66. """
  67. return attach_win32_input(self, input_ready_callback)
  68. def detach(self) -> ContextManager[None]:
  69. """
  70. Return a context manager that makes sure that this input is not active
  71. in the current event loop.
  72. """
  73. return detach_win32_input(self)
  74. def read_keys(self) -> list[KeyPress]:
  75. return list(self.console_input_reader.read())
  76. def flush(self) -> None:
  77. pass
  78. @property
  79. def closed(self) -> bool:
  80. return False
  81. def raw_mode(self) -> ContextManager[None]:
  82. return raw_mode()
  83. def cooked_mode(self) -> ContextManager[None]:
  84. return cooked_mode()
  85. def fileno(self) -> int:
  86. # The windows console doesn't depend on the file handle, so
  87. # this is not used for the event loop (which uses the
  88. # handle instead). But it's used in `Application.run_system_command`
  89. # which opens a subprocess with a given stdin/stdout.
  90. return sys.stdin.fileno()
  91. def typeahead_hash(self) -> str:
  92. return "win32-input"
  93. def close(self) -> None:
  94. self.console_input_reader.close()
  95. @property
  96. def handle(self) -> HANDLE:
  97. return self.console_input_reader.handle
  98. class ConsoleInputReader:
  99. """
  100. :param recognize_paste: When True, try to discover paste actions and turn
  101. the event into a BracketedPaste.
  102. """
  103. # Keys with character data.
  104. mappings = {
  105. b"\x1b": Keys.Escape,
  106. b"\x00": Keys.ControlSpace, # Control-Space (Also for Ctrl-@)
  107. b"\x01": Keys.ControlA, # Control-A (home)
  108. b"\x02": Keys.ControlB, # Control-B (emacs cursor left)
  109. b"\x03": Keys.ControlC, # Control-C (interrupt)
  110. b"\x04": Keys.ControlD, # Control-D (exit)
  111. b"\x05": Keys.ControlE, # Control-E (end)
  112. b"\x06": Keys.ControlF, # Control-F (cursor forward)
  113. b"\x07": Keys.ControlG, # Control-G
  114. b"\x08": Keys.ControlH, # Control-H (8) (Identical to '\b')
  115. b"\x09": Keys.ControlI, # Control-I (9) (Identical to '\t')
  116. b"\x0a": Keys.ControlJ, # Control-J (10) (Identical to '\n')
  117. b"\x0b": Keys.ControlK, # Control-K (delete until end of line; vertical tab)
  118. b"\x0c": Keys.ControlL, # Control-L (clear; form feed)
  119. b"\x0d": Keys.ControlM, # Control-M (enter)
  120. b"\x0e": Keys.ControlN, # Control-N (14) (history forward)
  121. b"\x0f": Keys.ControlO, # Control-O (15)
  122. b"\x10": Keys.ControlP, # Control-P (16) (history back)
  123. b"\x11": Keys.ControlQ, # Control-Q
  124. b"\x12": Keys.ControlR, # Control-R (18) (reverse search)
  125. b"\x13": Keys.ControlS, # Control-S (19) (forward search)
  126. b"\x14": Keys.ControlT, # Control-T
  127. b"\x15": Keys.ControlU, # Control-U
  128. b"\x16": Keys.ControlV, # Control-V
  129. b"\x17": Keys.ControlW, # Control-W
  130. b"\x18": Keys.ControlX, # Control-X
  131. b"\x19": Keys.ControlY, # Control-Y (25)
  132. b"\x1a": Keys.ControlZ, # Control-Z
  133. b"\x1c": Keys.ControlBackslash, # Both Control-\ and Ctrl-|
  134. b"\x1d": Keys.ControlSquareClose, # Control-]
  135. b"\x1e": Keys.ControlCircumflex, # Control-^
  136. b"\x1f": Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.)
  137. b"\x7f": Keys.Backspace, # (127) Backspace (ASCII Delete.)
  138. }
  139. # Keys that don't carry character data.
  140. keycodes = {
  141. # Home/End
  142. 33: Keys.PageUp,
  143. 34: Keys.PageDown,
  144. 35: Keys.End,
  145. 36: Keys.Home,
  146. # Arrows
  147. 37: Keys.Left,
  148. 38: Keys.Up,
  149. 39: Keys.Right,
  150. 40: Keys.Down,
  151. 45: Keys.Insert,
  152. 46: Keys.Delete,
  153. # F-keys.
  154. 112: Keys.F1,
  155. 113: Keys.F2,
  156. 114: Keys.F3,
  157. 115: Keys.F4,
  158. 116: Keys.F5,
  159. 117: Keys.F6,
  160. 118: Keys.F7,
  161. 119: Keys.F8,
  162. 120: Keys.F9,
  163. 121: Keys.F10,
  164. 122: Keys.F11,
  165. 123: Keys.F12,
  166. }
  167. LEFT_ALT_PRESSED = 0x0002
  168. RIGHT_ALT_PRESSED = 0x0001
  169. SHIFT_PRESSED = 0x0010
  170. LEFT_CTRL_PRESSED = 0x0008
  171. RIGHT_CTRL_PRESSED = 0x0004
  172. def __init__(self, recognize_paste: bool = True) -> None:
  173. self._fdcon = None
  174. self.recognize_paste = recognize_paste
  175. # When stdin is a tty, use that handle, otherwise, create a handle from
  176. # CONIN$.
  177. self.handle: HANDLE
  178. if sys.stdin.isatty():
  179. self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
  180. else:
  181. self._fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY)
  182. self.handle = HANDLE(msvcrt.get_osfhandle(self._fdcon))
  183. def close(self) -> None:
  184. "Close fdcon."
  185. if self._fdcon is not None:
  186. os.close(self._fdcon)
  187. def read(self) -> Iterable[KeyPress]:
  188. """
  189. Return a list of `KeyPress` instances. It won't return anything when
  190. there was nothing to read. (This function doesn't block.)
  191. http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
  192. """
  193. max_count = 2048 # Max events to read at the same time.
  194. read = DWORD(0)
  195. arrtype = INPUT_RECORD * max_count
  196. input_records = arrtype()
  197. # Check whether there is some input to read. `ReadConsoleInputW` would
  198. # block otherwise.
  199. # (Actually, the event loop is responsible to make sure that this
  200. # function is only called when there is something to read, but for some
  201. # reason this happened in the asyncio_win32 loop, and it's better to be
  202. # safe anyway.)
  203. if not wait_for_handles([self.handle], timeout=0):
  204. return
  205. # Get next batch of input event.
  206. windll.kernel32.ReadConsoleInputW(
  207. self.handle, pointer(input_records), max_count, pointer(read)
  208. )
  209. # First, get all the keys from the input buffer, in order to determine
  210. # whether we should consider this a paste event or not.
  211. all_keys = list(self._get_keys(read, input_records))
  212. # Fill in 'data' for key presses.
  213. all_keys = [self._insert_key_data(key) for key in all_keys]
  214. # Correct non-bmp characters that are passed as separate surrogate codes
  215. all_keys = list(self._merge_paired_surrogates(all_keys))
  216. if self.recognize_paste and self._is_paste(all_keys):
  217. gen = iter(all_keys)
  218. k: KeyPress | None
  219. for k in gen:
  220. # Pasting: if the current key consists of text or \n, turn it
  221. # into a BracketedPaste.
  222. data = []
  223. while k and (
  224. not isinstance(k.key, Keys)
  225. or k.key in {Keys.ControlJ, Keys.ControlM}
  226. ):
  227. data.append(k.data)
  228. try:
  229. k = next(gen)
  230. except StopIteration:
  231. k = None
  232. if data:
  233. yield KeyPress(Keys.BracketedPaste, "".join(data))
  234. if k is not None:
  235. yield k
  236. else:
  237. yield from all_keys
  238. def _insert_key_data(self, key_press: KeyPress) -> KeyPress:
  239. """
  240. Insert KeyPress data, for vt100 compatibility.
  241. """
  242. if key_press.data:
  243. return key_press
  244. if isinstance(key_press.key, Keys):
  245. data = REVERSE_ANSI_SEQUENCES.get(key_press.key, "")
  246. else:
  247. data = ""
  248. return KeyPress(key_press.key, data)
  249. def _get_keys(
  250. self, read: DWORD, input_records: Array[INPUT_RECORD]
  251. ) -> Iterator[KeyPress]:
  252. """
  253. Generator that yields `KeyPress` objects from the input records.
  254. """
  255. for i in range(read.value):
  256. ir = input_records[i]
  257. # Get the right EventType from the EVENT_RECORD.
  258. # (For some reason the Windows console application 'cmder'
  259. # [http://gooseberrycreative.com/cmder/] can return '0' for
  260. # ir.EventType. -- Just ignore that.)
  261. if ir.EventType in EventTypes:
  262. ev = getattr(ir.Event, EventTypes[ir.EventType])
  263. # Process if this is a key event. (We also have mouse, menu and
  264. # focus events.)
  265. if isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown:
  266. yield from self._event_to_key_presses(ev)
  267. elif isinstance(ev, MOUSE_EVENT_RECORD):
  268. yield from self._handle_mouse(ev)
  269. @staticmethod
  270. def _merge_paired_surrogates(key_presses: list[KeyPress]) -> Iterator[KeyPress]:
  271. """
  272. Combines consecutive KeyPresses with high and low surrogates into
  273. single characters
  274. """
  275. buffered_high_surrogate = None
  276. for key in key_presses:
  277. is_text = not isinstance(key.key, Keys)
  278. is_high_surrogate = is_text and "\ud800" <= key.key <= "\udbff"
  279. is_low_surrogate = is_text and "\udc00" <= key.key <= "\udfff"
  280. if buffered_high_surrogate:
  281. if is_low_surrogate:
  282. # convert high surrogate + low surrogate to single character
  283. fullchar = (
  284. (buffered_high_surrogate.key + key.key)
  285. .encode("utf-16-le", "surrogatepass")
  286. .decode("utf-16-le")
  287. )
  288. key = KeyPress(fullchar, fullchar)
  289. else:
  290. yield buffered_high_surrogate
  291. buffered_high_surrogate = None
  292. if is_high_surrogate:
  293. buffered_high_surrogate = key
  294. else:
  295. yield key
  296. if buffered_high_surrogate:
  297. yield buffered_high_surrogate
  298. @staticmethod
  299. def _is_paste(keys: list[KeyPress]) -> bool:
  300. """
  301. Return `True` when we should consider this list of keys as a paste
  302. event. Pasted text on windows will be turned into a
  303. `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably
  304. the best possible way to detect pasting of text and handle that
  305. correctly.)
  306. """
  307. # Consider paste when it contains at least one newline and at least one
  308. # other character.
  309. text_count = 0
  310. newline_count = 0
  311. for k in keys:
  312. if not isinstance(k.key, Keys):
  313. text_count += 1
  314. if k.key == Keys.ControlM:
  315. newline_count += 1
  316. return newline_count >= 1 and text_count >= 1
  317. def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> list[KeyPress]:
  318. """
  319. For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances.
  320. """
  321. assert isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown
  322. result: KeyPress | None = None
  323. control_key_state = ev.ControlKeyState
  324. u_char = ev.uChar.UnicodeChar
  325. # Use surrogatepass because u_char may be an unmatched surrogate
  326. ascii_char = u_char.encode("utf-8", "surrogatepass")
  327. # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be the
  328. # unicode code point truncated to 1 byte. See also:
  329. # https://github.com/ipython/ipython/issues/10004
  330. # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389
  331. if u_char == "\x00":
  332. if ev.VirtualKeyCode in self.keycodes:
  333. result = KeyPress(self.keycodes[ev.VirtualKeyCode], "")
  334. else:
  335. if ascii_char in self.mappings:
  336. if self.mappings[ascii_char] == Keys.ControlJ:
  337. u_char = (
  338. "\n" # Windows sends \n, turn into \r for unix compatibility.
  339. )
  340. result = KeyPress(self.mappings[ascii_char], u_char)
  341. else:
  342. result = KeyPress(u_char, u_char)
  343. # First we handle Shift-Control-Arrow/Home/End (need to do this first)
  344. if (
  345. (
  346. control_key_state & self.LEFT_CTRL_PRESSED
  347. or control_key_state & self.RIGHT_CTRL_PRESSED
  348. )
  349. and control_key_state & self.SHIFT_PRESSED
  350. and result
  351. ):
  352. mapping: dict[str, str] = {
  353. Keys.Left: Keys.ControlShiftLeft,
  354. Keys.Right: Keys.ControlShiftRight,
  355. Keys.Up: Keys.ControlShiftUp,
  356. Keys.Down: Keys.ControlShiftDown,
  357. Keys.Home: Keys.ControlShiftHome,
  358. Keys.End: Keys.ControlShiftEnd,
  359. Keys.Insert: Keys.ControlShiftInsert,
  360. Keys.PageUp: Keys.ControlShiftPageUp,
  361. Keys.PageDown: Keys.ControlShiftPageDown,
  362. }
  363. result.key = mapping.get(result.key, result.key)
  364. # Correctly handle Control-Arrow/Home/End and Control-Insert/Delete keys.
  365. if (
  366. control_key_state & self.LEFT_CTRL_PRESSED
  367. or control_key_state & self.RIGHT_CTRL_PRESSED
  368. ) and result:
  369. mapping = {
  370. Keys.Left: Keys.ControlLeft,
  371. Keys.Right: Keys.ControlRight,
  372. Keys.Up: Keys.ControlUp,
  373. Keys.Down: Keys.ControlDown,
  374. Keys.Home: Keys.ControlHome,
  375. Keys.End: Keys.ControlEnd,
  376. Keys.Insert: Keys.ControlInsert,
  377. Keys.Delete: Keys.ControlDelete,
  378. Keys.PageUp: Keys.ControlPageUp,
  379. Keys.PageDown: Keys.ControlPageDown,
  380. }
  381. result.key = mapping.get(result.key, result.key)
  382. # Turn 'Tab' into 'BackTab' when shift was pressed.
  383. # Also handle other shift-key combination
  384. if control_key_state & self.SHIFT_PRESSED and result:
  385. mapping = {
  386. Keys.Tab: Keys.BackTab,
  387. Keys.Left: Keys.ShiftLeft,
  388. Keys.Right: Keys.ShiftRight,
  389. Keys.Up: Keys.ShiftUp,
  390. Keys.Down: Keys.ShiftDown,
  391. Keys.Home: Keys.ShiftHome,
  392. Keys.End: Keys.ShiftEnd,
  393. Keys.Insert: Keys.ShiftInsert,
  394. Keys.Delete: Keys.ShiftDelete,
  395. Keys.PageUp: Keys.ShiftPageUp,
  396. Keys.PageDown: Keys.ShiftPageDown,
  397. }
  398. result.key = mapping.get(result.key, result.key)
  399. # Turn 'Space' into 'ControlSpace' when control was pressed.
  400. if (
  401. (
  402. control_key_state & self.LEFT_CTRL_PRESSED
  403. or control_key_state & self.RIGHT_CTRL_PRESSED
  404. )
  405. and result
  406. and result.data == " "
  407. ):
  408. result = KeyPress(Keys.ControlSpace, " ")
  409. # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot
  410. # detect this combination. But it's really practical on Windows.)
  411. if (
  412. (
  413. control_key_state & self.LEFT_CTRL_PRESSED
  414. or control_key_state & self.RIGHT_CTRL_PRESSED
  415. )
  416. and result
  417. and result.key == Keys.ControlJ
  418. ):
  419. return [KeyPress(Keys.Escape, ""), result]
  420. # Return result. If alt was pressed, prefix the result with an
  421. # 'Escape' key, just like unix VT100 terminals do.
  422. # NOTE: Only replace the left alt with escape. The right alt key often
  423. # acts as altgr and is used in many non US keyboard layouts for
  424. # typing some special characters, like a backslash. We don't want
  425. # all backslashes to be prefixed with escape. (Esc-\ has a
  426. # meaning in E-macs, for instance.)
  427. if result:
  428. meta_pressed = control_key_state & self.LEFT_ALT_PRESSED
  429. if meta_pressed:
  430. return [KeyPress(Keys.Escape, ""), result]
  431. else:
  432. return [result]
  433. else:
  434. return []
  435. def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> list[KeyPress]:
  436. """
  437. Handle mouse events. Return a list of KeyPress instances.
  438. """
  439. event_flags = ev.EventFlags
  440. button_state = ev.ButtonState
  441. event_type: MouseEventType | None = None
  442. button: MouseButton = MouseButton.NONE
  443. # Scroll events.
  444. if event_flags & MOUSE_WHEELED:
  445. if button_state > 0:
  446. event_type = MouseEventType.SCROLL_UP
  447. else:
  448. event_type = MouseEventType.SCROLL_DOWN
  449. else:
  450. # Handle button state for non-scroll events.
  451. if button_state == FROM_LEFT_1ST_BUTTON_PRESSED:
  452. button = MouseButton.LEFT
  453. elif button_state == RIGHTMOST_BUTTON_PRESSED:
  454. button = MouseButton.RIGHT
  455. # Move events.
  456. if event_flags & MOUSE_MOVED:
  457. event_type = MouseEventType.MOUSE_MOVE
  458. # No key pressed anymore: mouse up.
  459. if event_type is None:
  460. if button_state > 0:
  461. # Some button pressed.
  462. event_type = MouseEventType.MOUSE_DOWN
  463. else:
  464. # No button pressed.
  465. event_type = MouseEventType.MOUSE_UP
  466. data = ";".join(
  467. [
  468. button.value,
  469. event_type.value,
  470. str(ev.MousePosition.X),
  471. str(ev.MousePosition.Y),
  472. ]
  473. )
  474. return [KeyPress(Keys.WindowsMouseEvent, data)]
  475. class _Win32Handles:
  476. """
  477. Utility to keep track of which handles are connectod to which callbacks.
  478. `add_win32_handle` starts a tiny event loop in another thread which waits
  479. for the Win32 handle to become ready. When this happens, the callback will
  480. be called in the current asyncio event loop using `call_soon_threadsafe`.
  481. `remove_win32_handle` will stop this tiny event loop.
  482. NOTE: We use this technique, so that we don't have to use the
  483. `ProactorEventLoop` on Windows and we can wait for things like stdin
  484. in a `SelectorEventLoop`. This is important, because our inputhook
  485. mechanism (used by IPython), only works with the `SelectorEventLoop`.
  486. """
  487. def __init__(self) -> None:
  488. self._handle_callbacks: dict[int, Callable[[], None]] = {}
  489. # Windows Events that are triggered when we have to stop watching this
  490. # handle.
  491. self._remove_events: dict[int, HANDLE] = {}
  492. def add_win32_handle(self, handle: HANDLE, callback: Callable[[], None]) -> None:
  493. """
  494. Add a Win32 handle to the event loop.
  495. """
  496. handle_value = handle.value
  497. if handle_value is None:
  498. raise ValueError("Invalid handle.")
  499. # Make sure to remove a previous registered handler first.
  500. self.remove_win32_handle(handle)
  501. loop = get_running_loop()
  502. self._handle_callbacks[handle_value] = callback
  503. # Create remove event.
  504. remove_event = create_win32_event()
  505. self._remove_events[handle_value] = remove_event
  506. # Add reader.
  507. def ready() -> None:
  508. # Tell the callback that input's ready.
  509. try:
  510. callback()
  511. finally:
  512. run_in_executor_with_context(wait, loop=loop)
  513. # Wait for the input to become ready.
  514. # (Use an executor for this, the Windows asyncio event loop doesn't
  515. # allow us to wait for handles like stdin.)
  516. def wait() -> None:
  517. # Wait until either the handle becomes ready, or the remove event
  518. # has been set.
  519. result = wait_for_handles([remove_event, handle])
  520. if result is remove_event:
  521. windll.kernel32.CloseHandle(remove_event)
  522. return
  523. else:
  524. loop.call_soon_threadsafe(ready)
  525. run_in_executor_with_context(wait, loop=loop)
  526. def remove_win32_handle(self, handle: HANDLE) -> Callable[[], None] | None:
  527. """
  528. Remove a Win32 handle from the event loop.
  529. Return either the registered handler or `None`.
  530. """
  531. if handle.value is None:
  532. return None # Ignore.
  533. # Trigger remove events, so that the reader knows to stop.
  534. try:
  535. event = self._remove_events.pop(handle.value)
  536. except KeyError:
  537. pass
  538. else:
  539. windll.kernel32.SetEvent(event)
  540. try:
  541. return self._handle_callbacks.pop(handle.value)
  542. except KeyError:
  543. return None
  544. @contextmanager
  545. def attach_win32_input(
  546. input: _Win32InputBase, callback: Callable[[], None]
  547. ) -> Iterator[None]:
  548. """
  549. Context manager that makes this input active in the current event loop.
  550. :param input: :class:`~prompt_toolkit.input.Input` object.
  551. :param input_ready_callback: Called when the input is ready to read.
  552. """
  553. win32_handles = input.win32_handles
  554. handle = input.handle
  555. if handle.value is None:
  556. raise ValueError("Invalid handle.")
  557. # Add reader.
  558. previous_callback = win32_handles.remove_win32_handle(handle)
  559. win32_handles.add_win32_handle(handle, callback)
  560. try:
  561. yield
  562. finally:
  563. win32_handles.remove_win32_handle(handle)
  564. if previous_callback:
  565. win32_handles.add_win32_handle(handle, previous_callback)
  566. @contextmanager
  567. def detach_win32_input(input: _Win32InputBase) -> Iterator[None]:
  568. win32_handles = input.win32_handles
  569. handle = input.handle
  570. if handle.value is None:
  571. raise ValueError("Invalid handle.")
  572. previous_callback = win32_handles.remove_win32_handle(handle)
  573. try:
  574. yield
  575. finally:
  576. if previous_callback:
  577. win32_handles.add_win32_handle(handle, previous_callback)
  578. class raw_mode:
  579. """
  580. ::
  581. with raw_mode(stdin):
  582. ''' the windows terminal is now in 'raw' mode. '''
  583. The ``fileno`` attribute is ignored. This is to be compatible with the
  584. `raw_input` method of `.vt100_input`.
  585. """
  586. def __init__(self, fileno: int | None = None) -> None:
  587. self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
  588. def __enter__(self) -> None:
  589. # Remember original mode.
  590. original_mode = DWORD()
  591. windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode))
  592. self.original_mode = original_mode
  593. self._patch()
  594. def _patch(self) -> None:
  595. # Set raw
  596. ENABLE_ECHO_INPUT = 0x0004
  597. ENABLE_LINE_INPUT = 0x0002
  598. ENABLE_PROCESSED_INPUT = 0x0001
  599. windll.kernel32.SetConsoleMode(
  600. self.handle,
  601. self.original_mode.value
  602. & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT),
  603. )
  604. def __exit__(self, *a: object) -> None:
  605. # Restore original mode
  606. windll.kernel32.SetConsoleMode(self.handle, self.original_mode)
  607. class cooked_mode(raw_mode):
  608. """
  609. ::
  610. with cooked_mode(stdin):
  611. ''' The pseudo-terminal stdin is now used in cooked mode. '''
  612. """
  613. def _patch(self) -> None:
  614. # Set cooked.
  615. ENABLE_ECHO_INPUT = 0x0004
  616. ENABLE_LINE_INPUT = 0x0002
  617. ENABLE_PROCESSED_INPUT = 0x0001
  618. windll.kernel32.SetConsoleMode(
  619. self.handle,
  620. self.original_mode.value
  621. | (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT),
  622. )