We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Windows duplicates Escape key input.
Have you checked closed issues? https://github.com/Textualize/textual/issues?q=is%3Aissue+is%3Aclosed

Please give a brief but clear explanation of the issue. If you can, include a complete working example that demonstrates the bug. **Check it can run without modifications.**

It will be helpful if you run the following command and paste the results:

```
textual diagnose
```

Feel free to add screenshots and / or videos. These can be very helpful!

To reproduce run `textual keys` and press some keys. Pressing `Esc` shows two keys, other keys show only once:
![image](https://github.com/Textualize/textual/assets/70932/4adb33af-6a9e-405e-bf68-3224a6322dc3)

I think the problem is with https://github.com/Textualize/textual/blob/0ad03f3731ae922d1faa6852e14ea60e6932b198/src/textual/drivers/win32.py#L267C64-L267C64 

This line processes both the key down and the key up events for `esc` which results in it appearing as two keys in the input. Simply changing the line to read `if key_event.bKeyDown:` appears to fix the issue as `escape` now only appears once in the input and other keys such as function keys continue to work as before.


<!-- This is valid Markdown, please paste the following directly in to a GitHub issue -->
# Textual Diagnostics

## Versions

| Name    | Value  |
|---------|--------|
| Textual | 0.47.1 |
| Rich    | 13.7.0 |

## Python

| Name           | Value
       |
|----------------|---------------------------------------------------------------------------------------------------------|
| Version        | 3.12.1
       |
| Implementation | CPython
       |
| Compiler       | MSC v.1937 64 bit (AMD64)
       |
| Executable     | C:\Users\dunca\AppData\Local\pypoetry\Cache\virtualenvs\dwarf-copier-_FteSHy5-py3.12\Scripts\python.exe |

## Operating System

| Name    | Value      |
|---------|------------|
| System  | Windows    |
| Release | 11         |
| Version | 10.0.22621 |

## Terminal

| Name                 | Value            |
|----------------------|------------------|
| Terminal Application | Windows Terminal |
| TERM                 | *Not set*        |
| COLORTERM            | *Not set*        |
| FORCE_COLOR          | *Not set*        |
| NO_COLOR             | *Not set*        |

## Rich Console options

| Name           | Value                |
|----------------|----------------------|
| size           | width=116, height=30 |
| legacy_windows | False                |
| min_width      | 1                    |
| max_width      | 116                  |
| is_terminal    | True                 |
| encoding       | utf-8                |
| max_height     | 30                   |
| justify        | None                 |
| overflow       | None                 |
| no_wrap        | False                |
| highlight      | None                 |
| markup         | None                 |
| height         | None                 |


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/textual/drivers/win32.py`
Content:
```
1 from __future__ import annotations
2 
3 import ctypes
4 import msvcrt
5 import sys
6 import threading
7 from asyncio import AbstractEventLoop, run_coroutine_threadsafe
8 from ctypes import Structure, Union, byref, wintypes
9 from ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD
10 from typing import IO, TYPE_CHECKING, Callable, List, Optional
11 
12 from .._xterm_parser import XTermParser
13 from ..events import Event, Resize
14 from ..geometry import Size
15 
16 if TYPE_CHECKING:
17     from ..app import App
18 
19 KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True)  # type: ignore
20 
21 # Console input modes
22 ENABLE_ECHO_INPUT = 0x0004
23 ENABLE_EXTENDED_FLAGS = 0x0080
24 ENABLE_INSERT_MODE = 0x0020
25 ENABLE_LINE_INPUT = 0x0002
26 ENABLE_MOUSE_INPUT = 0x0010
27 ENABLE_PROCESSED_INPUT = 0x0001
28 ENABLE_QUICK_EDIT_MODE = 0x0040
29 ENABLE_WINDOW_INPUT = 0x0008
30 ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
31 
32 # Console output modes
33 ENABLE_PROCESSED_OUTPUT = 0x0001
34 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
35 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
36 DISABLE_NEWLINE_AUTO_RETURN = 0x0008
37 ENABLE_LVB_GRID_WORLDWIDE = 0x0010
38 
39 STD_INPUT_HANDLE = -10
40 STD_OUTPUT_HANDLE = -11
41 
42 WAIT_TIMEOUT = 0x00000102
43 
44 GetStdHandle = KERNEL32.GetStdHandle
45 GetStdHandle.argtypes = [wintypes.DWORD]
46 GetStdHandle.restype = wintypes.HANDLE
47 
48 
49 class COORD(Structure):
50     """https://docs.microsoft.com/en-us/windows/console/coord-str"""
51 
52     _fields_ = [
53         ("X", SHORT),
54         ("Y", SHORT),
55     ]
56 
57 
58 class uChar(Union):
59     """https://docs.microsoft.com/en-us/windows/console/key-event-record-str"""
60 
61     _fields_ = [
62         ("AsciiChar", CHAR),
63         ("UnicodeChar", WCHAR),
64     ]
65 
66 
67 class KEY_EVENT_RECORD(Structure):
68     """https://docs.microsoft.com/en-us/windows/console/key-event-record-str"""
69 
70     _fields_ = [
71         ("bKeyDown", BOOL),
72         ("wRepeatCount", WORD),
73         ("wVirtualKeyCode", WORD),
74         ("wVirtualScanCode", WORD),
75         ("uChar", uChar),
76         ("dwControlKeyState", DWORD),
77     ]
78 
79 
80 class MOUSE_EVENT_RECORD(Structure):
81     """https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str"""
82 
83     _fields_ = [
84         ("dwMousePosition", COORD),
85         ("dwButtonState", DWORD),
86         ("dwControlKeyState", DWORD),
87         ("dwEventFlags", DWORD),
88     ]
89 
90 
91 class WINDOW_BUFFER_SIZE_RECORD(Structure):
92     """https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str"""
93 
94     _fields_ = [("dwSize", COORD)]
95 
96 
97 class MENU_EVENT_RECORD(Structure):
98     """https://docs.microsoft.com/en-us/windows/console/menu-event-record-str"""
99 
100     _fields_ = [("dwCommandId", UINT)]
101 
102 
103 class FOCUS_EVENT_RECORD(Structure):
104     """https://docs.microsoft.com/en-us/windows/console/focus-event-record-str"""
105 
106     _fields_ = [("bSetFocus", BOOL)]
107 
108 
109 class InputEvent(Union):
110     """https://docs.microsoft.com/en-us/windows/console/input-record-str"""
111 
112     _fields_ = [
113         ("KeyEvent", KEY_EVENT_RECORD),
114         ("MouseEvent", MOUSE_EVENT_RECORD),
115         ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
116         ("MenuEvent", MENU_EVENT_RECORD),
117         ("FocusEvent", FOCUS_EVENT_RECORD),
118     ]
119 
120 
121 class INPUT_RECORD(Structure):
122     """https://docs.microsoft.com/en-us/windows/console/input-record-str"""
123 
124     _fields_ = [("EventType", wintypes.WORD), ("Event", InputEvent)]
125 
126 
127 def set_console_mode(file: IO, mode: int) -> bool:
128     """Set the console mode for a given file (stdout or stdin).
129 
130     Args:
131         file: A file like object.
132         mode: New mode.
133 
134     Returns:
135         True on success, otherwise False.
136     """
137     windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore
138     success = KERNEL32.SetConsoleMode(windows_filehandle, mode)
139     return success
140 
141 
142 def get_console_mode(file: IO) -> int:
143     """Get the console mode for a given file (stdout or stdin)
144 
145     Args:
146         file: A file-like object.
147 
148     Returns:
149         The current console mode.
150     """
151     windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore
152     mode = wintypes.DWORD()
153     KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode))
154     return mode.value
155 
156 
157 def enable_application_mode() -> Callable[[], None]:
158     """Enable application mode.
159 
160     Returns:
161         A callable that will restore terminal to previous state.
162     """
163 
164     terminal_in = sys.__stdin__
165     terminal_out = sys.__stdout__
166 
167     current_console_mode_in = get_console_mode(terminal_in)
168     current_console_mode_out = get_console_mode(terminal_out)
169 
170     def restore() -> None:
171         """Restore console mode to previous settings"""
172         set_console_mode(terminal_in, current_console_mode_in)
173         set_console_mode(terminal_out, current_console_mode_out)
174 
175     set_console_mode(
176         terminal_out, current_console_mode_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING
177     )
178     set_console_mode(terminal_in, ENABLE_VIRTUAL_TERMINAL_INPUT)
179     return restore
180 
181 
182 def wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]:
183     """
184     Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
185     Returns `None` on timeout.
186     http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
187     Note that handles should be a list of `HANDLE` objects, not integers. See
188     this comment in the patch by @quark-zju for the reason why:
189         ''' Make sure HANDLE on Windows has a correct size
190         Previously, the type of various HANDLEs are native Python integer
191         types. The ctypes library will treat them as 4-byte integer when used
192         in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually
193         a small integer. Depending on whether the extra 4 bytes are zero-ed out
194         or not, things can happen to work, or break. '''
195     This function returns either `None` or one of the given `HANDLE` objects.
196     (The return value can be tested with the `is` operator.)
197     """
198     arrtype = HANDLE * len(handles)
199     handle_array = arrtype(*handles)
200 
201     ret: int = KERNEL32.WaitForMultipleObjects(
202         len(handle_array), handle_array, BOOL(False), DWORD(timeout)
203     )
204 
205     if ret == WAIT_TIMEOUT:
206         return None
207     else:
208         return handles[ret]
209 
210 
211 class EventMonitor(threading.Thread):
212     """A thread to send key / window events to Textual loop."""
213 
214     def __init__(
215         self,
216         loop: AbstractEventLoop,
217         app: App,
218         exit_event: threading.Event,
219         process_event: Callable[[Event], None],
220     ) -> None:
221         self.loop = loop
222         self.app = app
223         self.exit_event = exit_event
224         self.process_event = process_event
225         super().__init__()
226 
227     def run(self) -> None:
228         exit_requested = self.exit_event.is_set
229         parser = XTermParser(lambda: False)
230 
231         try:
232             read_count = wintypes.DWORD(0)
233             hIn = GetStdHandle(STD_INPUT_HANDLE)
234 
235             MAX_EVENTS = 1024
236             KEY_EVENT = 0x0001
237             WINDOW_BUFFER_SIZE_EVENT = 0x0004
238 
239             arrtype = INPUT_RECORD * MAX_EVENTS
240             input_records = arrtype()
241             ReadConsoleInputW = KERNEL32.ReadConsoleInputW
242             keys: List[str] = []
243             append_key = keys.append
244 
245             while not exit_requested():
246                 # Wait for new events
247                 if wait_for_handles([hIn], 200) is None:
248                     # No new events
249                     continue
250 
251                 # Get new events
252                 ReadConsoleInputW(
253                     hIn, byref(input_records), MAX_EVENTS, byref(read_count)
254                 )
255                 read_input_records = input_records[: read_count.value]
256 
257                 del keys[:]
258                 new_size: Optional[tuple[int, int]] = None
259 
260                 for input_record in read_input_records:
261                     event_type = input_record.EventType
262 
263                     if event_type == KEY_EVENT:
264                         # Key event, store unicode char in keys list
265                         key_event = input_record.Event.KeyEvent
266                         key = key_event.uChar.UnicodeChar
267                         if key_event.bKeyDown or key == "\x1b":
268                             if (
269                                 key_event.dwControlKeyState
270                                 and key_event.wVirtualKeyCode == 0
271                             ):
272                                 continue
273                             append_key(key)
274                     elif event_type == WINDOW_BUFFER_SIZE_EVENT:
275                         # Window size changed, store size
276                         size = input_record.Event.WindowBufferSizeEvent.dwSize
277                         new_size = (size.X, size.Y)
278 
279                 if keys:
280                     # Process keys
281                     #
282                     # https://github.com/Textualize/textual/issues/3178 has
283                     # the context for the encode/decode here.
284                     for event in parser.feed(
285                         "".join(keys).encode("utf-16", "surrogatepass").decode("utf-16")
286                     ):
287                         self.process_event(event)
288                 if new_size is not None:
289                     # Process changed size
290                     self.on_size_change(*new_size)
291 
292         except Exception as error:
293             self.app.log.error("EVENT MONITOR ERROR", error)
294 
295     def on_size_change(self, width: int, height: int) -> None:
296         """Called when terminal size changes."""
297         size = Size(width, height)
298         event = Resize(size, size)
299         run_coroutine_threadsafe(self.app._post_message(event), loop=self.loop)
300
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py
--- a/src/textual/drivers/win32.py
+++ b/src/textual/drivers/win32.py
@@ -264,7 +264,7 @@
                         # Key event, store unicode char in keys list
                         key_event = input_record.Event.KeyEvent
                         key = key_event.uChar.UnicodeChar
-                        if key_event.bKeyDown or key == "\x1b":
+                        if key_event.bKeyDown:
                             if (
                                 key_event.dwControlKeyState
                                 and key_event.wVirtualKeyCode == 0

{"golden_diff": "diff --git a/src/textual/drivers/win32.py b/src/textual/drivers/win32.py\n--- a/src/textual/drivers/win32.py\n+++ b/src/textual/drivers/win32.py\n@@ -264,7 +264,7 @@\n                         # Key event, store unicode char in keys list\n                         key_event = input_record.Event.KeyEvent\n                         key = key_event.uChar.UnicodeChar\n-                        if key_event.bKeyDown or key == \"\\x1b\":\n+                        if key_event.bKeyDown:\n                             if (\n                                 key_event.dwControlKeyState\n                                 and key_event.wVirtualKeyCode == 0\n", "issue": "Windows duplicates Escape key input.\nHave you checked closed issues? https://github.com/Textualize/textual/issues?q=is%3Aissue+is%3Aclosed\r\n\r\nPlease give a brief but clear explanation of the issue. If you can, include a complete working example that demonstrates the bug. **Check it can run without modifications.**\r\n\r\nIt will be helpful if you run the following command and paste the results:\r\n\r\n```\r\ntextual diagnose\r\n```\r\n\r\nFeel free to add screenshots and / or videos. These can be very helpful!\r\n\r\nTo reproduce run `textual keys` and press some keys. Pressing `Esc` shows two keys, other keys show only once:\r\n![image](https://github.com/Textualize/textual/assets/70932/4adb33af-6a9e-405e-bf68-3224a6322dc3)\r\n\r\nI think the problem is with https://github.com/Textualize/textual/blob/0ad03f3731ae922d1faa6852e14ea60e6932b198/src/textual/drivers/win32.py#L267C64-L267C64 \r\n\r\nThis line processes both the key down and the key up events for `esc` which results in it appearing as two keys in the input. Simply changing the line to read `if key_event.bKeyDown:` appears to fix the issue as `escape` now only appears once in the input and other keys such as function keys continue to work as before.\r\n\r\n\r\n<!-- This is valid Markdown, please paste the following directly in to a GitHub issue -->\r\n# Textual Diagnostics\r\n\r\n## Versions\r\n\r\n| Name    | Value  |\r\n|---------|--------|\r\n| Textual | 0.47.1 |\r\n| Rich    | 13.7.0 |\r\n\r\n## Python\r\n\r\n| Name           | Value\r\n       |\r\n|----------------|---------------------------------------------------------------------------------------------------------|\r\n| Version        | 3.12.1\r\n       |\r\n| Implementation | CPython\r\n       |\r\n| Compiler       | MSC v.1937 64 bit (AMD64)\r\n       |\r\n| Executable     | C:\\Users\\dunca\\AppData\\Local\\pypoetry\\Cache\\virtualenvs\\dwarf-copier-_FteSHy5-py3.12\\Scripts\\python.exe |\r\n\r\n## Operating System\r\n\r\n| Name    | Value      |\r\n|---------|------------|\r\n| System  | Windows    |\r\n| Release | 11         |\r\n| Version | 10.0.22621 |\r\n\r\n## Terminal\r\n\r\n| Name                 | Value            |\r\n|----------------------|------------------|\r\n| Terminal Application | Windows Terminal |\r\n| TERM                 | *Not set*        |\r\n| COLORTERM            | *Not set*        |\r\n| FORCE_COLOR          | *Not set*        |\r\n| NO_COLOR             | *Not set*        |\r\n\r\n## Rich Console options\r\n\r\n| Name           | Value                |\r\n|----------------|----------------------|\r\n| size           | width=116, height=30 |\r\n| legacy_windows | False                |\r\n| min_width      | 1                    |\r\n| max_width      | 116                  |\r\n| is_terminal    | True                 |\r\n| encoding       | utf-8                |\r\n| max_height     | 30                   |\r\n| justify        | None                 |\r\n| overflow       | None                 |\r\n| no_wrap        | False                |\r\n| highlight      | None                 |\r\n| markup         | None                 |\r\n| height         | None                 |\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport ctypes\nimport msvcrt\nimport sys\nimport threading\nfrom asyncio import AbstractEventLoop, run_coroutine_threadsafe\nfrom ctypes import Structure, Union, byref, wintypes\nfrom ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD\nfrom typing import IO, TYPE_CHECKING, Callable, List, Optional\n\nfrom .._xterm_parser import XTermParser\nfrom ..events import Event, Resize\nfrom ..geometry import Size\n\nif TYPE_CHECKING:\n    from ..app import App\n\nKERNEL32 = ctypes.WinDLL(\"kernel32\", use_last_error=True)  # type: ignore\n\n# Console input modes\nENABLE_ECHO_INPUT = 0x0004\nENABLE_EXTENDED_FLAGS = 0x0080\nENABLE_INSERT_MODE = 0x0020\nENABLE_LINE_INPUT = 0x0002\nENABLE_MOUSE_INPUT = 0x0010\nENABLE_PROCESSED_INPUT = 0x0001\nENABLE_QUICK_EDIT_MODE = 0x0040\nENABLE_WINDOW_INPUT = 0x0008\nENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200\n\n# Console output modes\nENABLE_PROCESSED_OUTPUT = 0x0001\nENABLE_WRAP_AT_EOL_OUTPUT = 0x0002\nENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004\nDISABLE_NEWLINE_AUTO_RETURN = 0x0008\nENABLE_LVB_GRID_WORLDWIDE = 0x0010\n\nSTD_INPUT_HANDLE = -10\nSTD_OUTPUT_HANDLE = -11\n\nWAIT_TIMEOUT = 0x00000102\n\nGetStdHandle = KERNEL32.GetStdHandle\nGetStdHandle.argtypes = [wintypes.DWORD]\nGetStdHandle.restype = wintypes.HANDLE\n\n\nclass COORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/coord-str\"\"\"\n\n    _fields_ = [\n        (\"X\", SHORT),\n        (\"Y\", SHORT),\n    ]\n\n\nclass uChar(Union):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/key-event-record-str\"\"\"\n\n    _fields_ = [\n        (\"AsciiChar\", CHAR),\n        (\"UnicodeChar\", WCHAR),\n    ]\n\n\nclass KEY_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/key-event-record-str\"\"\"\n\n    _fields_ = [\n        (\"bKeyDown\", BOOL),\n        (\"wRepeatCount\", WORD),\n        (\"wVirtualKeyCode\", WORD),\n        (\"wVirtualScanCode\", WORD),\n        (\"uChar\", uChar),\n        (\"dwControlKeyState\", DWORD),\n    ]\n\n\nclass MOUSE_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str\"\"\"\n\n    _fields_ = [\n        (\"dwMousePosition\", COORD),\n        (\"dwButtonState\", DWORD),\n        (\"dwControlKeyState\", DWORD),\n        (\"dwEventFlags\", DWORD),\n    ]\n\n\nclass WINDOW_BUFFER_SIZE_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str\"\"\"\n\n    _fields_ = [(\"dwSize\", COORD)]\n\n\nclass MENU_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/menu-event-record-str\"\"\"\n\n    _fields_ = [(\"dwCommandId\", UINT)]\n\n\nclass FOCUS_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/focus-event-record-str\"\"\"\n\n    _fields_ = [(\"bSetFocus\", BOOL)]\n\n\nclass InputEvent(Union):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/input-record-str\"\"\"\n\n    _fields_ = [\n        (\"KeyEvent\", KEY_EVENT_RECORD),\n        (\"MouseEvent\", MOUSE_EVENT_RECORD),\n        (\"WindowBufferSizeEvent\", WINDOW_BUFFER_SIZE_RECORD),\n        (\"MenuEvent\", MENU_EVENT_RECORD),\n        (\"FocusEvent\", FOCUS_EVENT_RECORD),\n    ]\n\n\nclass INPUT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/input-record-str\"\"\"\n\n    _fields_ = [(\"EventType\", wintypes.WORD), (\"Event\", InputEvent)]\n\n\ndef set_console_mode(file: IO, mode: int) -> bool:\n    \"\"\"Set the console mode for a given file (stdout or stdin).\n\n    Args:\n        file: A file like object.\n        mode: New mode.\n\n    Returns:\n        True on success, otherwise False.\n    \"\"\"\n    windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore\n    success = KERNEL32.SetConsoleMode(windows_filehandle, mode)\n    return success\n\n\ndef get_console_mode(file: IO) -> int:\n    \"\"\"Get the console mode for a given file (stdout or stdin)\n\n    Args:\n        file: A file-like object.\n\n    Returns:\n        The current console mode.\n    \"\"\"\n    windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore\n    mode = wintypes.DWORD()\n    KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode))\n    return mode.value\n\n\ndef enable_application_mode() -> Callable[[], None]:\n    \"\"\"Enable application mode.\n\n    Returns:\n        A callable that will restore terminal to previous state.\n    \"\"\"\n\n    terminal_in = sys.__stdin__\n    terminal_out = sys.__stdout__\n\n    current_console_mode_in = get_console_mode(terminal_in)\n    current_console_mode_out = get_console_mode(terminal_out)\n\n    def restore() -> None:\n        \"\"\"Restore console mode to previous settings\"\"\"\n        set_console_mode(terminal_in, current_console_mode_in)\n        set_console_mode(terminal_out, current_console_mode_out)\n\n    set_console_mode(\n        terminal_out, current_console_mode_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING\n    )\n    set_console_mode(terminal_in, ENABLE_VIRTUAL_TERMINAL_INPUT)\n    return restore\n\n\ndef wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]:\n    \"\"\"\n    Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.\n    Returns `None` on timeout.\n    http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx\n    Note that handles should be a list of `HANDLE` objects, not integers. See\n    this comment in the patch by @quark-zju for the reason why:\n        ''' Make sure HANDLE on Windows has a correct size\n        Previously, the type of various HANDLEs are native Python integer\n        types. The ctypes library will treat them as 4-byte integer when used\n        in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually\n        a small integer. Depending on whether the extra 4 bytes are zero-ed out\n        or not, things can happen to work, or break. '''\n    This function returns either `None` or one of the given `HANDLE` objects.\n    (The return value can be tested with the `is` operator.)\n    \"\"\"\n    arrtype = HANDLE * len(handles)\n    handle_array = arrtype(*handles)\n\n    ret: int = KERNEL32.WaitForMultipleObjects(\n        len(handle_array), handle_array, BOOL(False), DWORD(timeout)\n    )\n\n    if ret == WAIT_TIMEOUT:\n        return None\n    else:\n        return handles[ret]\n\n\nclass EventMonitor(threading.Thread):\n    \"\"\"A thread to send key / window events to Textual loop.\"\"\"\n\n    def __init__(\n        self,\n        loop: AbstractEventLoop,\n        app: App,\n        exit_event: threading.Event,\n        process_event: Callable[[Event], None],\n    ) -> None:\n        self.loop = loop\n        self.app = app\n        self.exit_event = exit_event\n        self.process_event = process_event\n        super().__init__()\n\n    def run(self) -> None:\n        exit_requested = self.exit_event.is_set\n        parser = XTermParser(lambda: False)\n\n        try:\n            read_count = wintypes.DWORD(0)\n            hIn = GetStdHandle(STD_INPUT_HANDLE)\n\n            MAX_EVENTS = 1024\n            KEY_EVENT = 0x0001\n            WINDOW_BUFFER_SIZE_EVENT = 0x0004\n\n            arrtype = INPUT_RECORD * MAX_EVENTS\n            input_records = arrtype()\n            ReadConsoleInputW = KERNEL32.ReadConsoleInputW\n            keys: List[str] = []\n            append_key = keys.append\n\n            while not exit_requested():\n                # Wait for new events\n                if wait_for_handles([hIn], 200) is None:\n                    # No new events\n                    continue\n\n                # Get new events\n                ReadConsoleInputW(\n                    hIn, byref(input_records), MAX_EVENTS, byref(read_count)\n                )\n                read_input_records = input_records[: read_count.value]\n\n                del keys[:]\n                new_size: Optional[tuple[int, int]] = None\n\n                for input_record in read_input_records:\n                    event_type = input_record.EventType\n\n                    if event_type == KEY_EVENT:\n                        # Key event, store unicode char in keys list\n                        key_event = input_record.Event.KeyEvent\n                        key = key_event.uChar.UnicodeChar\n                        if key_event.bKeyDown or key == \"\\x1b\":\n                            if (\n                                key_event.dwControlKeyState\n                                and key_event.wVirtualKeyCode == 0\n                            ):\n                                continue\n                            append_key(key)\n                    elif event_type == WINDOW_BUFFER_SIZE_EVENT:\n                        # Window size changed, store size\n                        size = input_record.Event.WindowBufferSizeEvent.dwSize\n                        new_size = (size.X, size.Y)\n\n                if keys:\n                    # Process keys\n                    #\n                    # https://github.com/Textualize/textual/issues/3178 has\n                    # the context for the encode/decode here.\n                    for event in parser.feed(\n                        \"\".join(keys).encode(\"utf-16\", \"surrogatepass\").decode(\"utf-16\")\n                    ):\n                        self.process_event(event)\n                if new_size is not None:\n                    # Process changed size\n                    self.on_size_change(*new_size)\n\n        except Exception as error:\n            self.app.log.error(\"EVENT MONITOR ERROR\", error)\n\n    def on_size_change(self, width: int, height: int) -> None:\n        \"\"\"Called when terminal size changes.\"\"\"\n        size = Size(width, height)\n        event = Resize(size, size)\n        run_coroutine_threadsafe(self.app._post_message(event), loop=self.loop)\n", "path": "src/textual/drivers/win32.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport ctypes\nimport msvcrt\nimport sys\nimport threading\nfrom asyncio import AbstractEventLoop, run_coroutine_threadsafe\nfrom ctypes import Structure, Union, byref, wintypes\nfrom ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD\nfrom typing import IO, TYPE_CHECKING, Callable, List, Optional\n\nfrom .._xterm_parser import XTermParser\nfrom ..events import Event, Resize\nfrom ..geometry import Size\n\nif TYPE_CHECKING:\n    from ..app import App\n\nKERNEL32 = ctypes.WinDLL(\"kernel32\", use_last_error=True)  # type: ignore\n\n# Console input modes\nENABLE_ECHO_INPUT = 0x0004\nENABLE_EXTENDED_FLAGS = 0x0080\nENABLE_INSERT_MODE = 0x0020\nENABLE_LINE_INPUT = 0x0002\nENABLE_MOUSE_INPUT = 0x0010\nENABLE_PROCESSED_INPUT = 0x0001\nENABLE_QUICK_EDIT_MODE = 0x0040\nENABLE_WINDOW_INPUT = 0x0008\nENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200\n\n# Console output modes\nENABLE_PROCESSED_OUTPUT = 0x0001\nENABLE_WRAP_AT_EOL_OUTPUT = 0x0002\nENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004\nDISABLE_NEWLINE_AUTO_RETURN = 0x0008\nENABLE_LVB_GRID_WORLDWIDE = 0x0010\n\nSTD_INPUT_HANDLE = -10\nSTD_OUTPUT_HANDLE = -11\n\nWAIT_TIMEOUT = 0x00000102\n\nGetStdHandle = KERNEL32.GetStdHandle\nGetStdHandle.argtypes = [wintypes.DWORD]\nGetStdHandle.restype = wintypes.HANDLE\n\n\nclass COORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/coord-str\"\"\"\n\n    _fields_ = [\n        (\"X\", SHORT),\n        (\"Y\", SHORT),\n    ]\n\n\nclass uChar(Union):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/key-event-record-str\"\"\"\n\n    _fields_ = [\n        (\"AsciiChar\", CHAR),\n        (\"UnicodeChar\", WCHAR),\n    ]\n\n\nclass KEY_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/key-event-record-str\"\"\"\n\n    _fields_ = [\n        (\"bKeyDown\", BOOL),\n        (\"wRepeatCount\", WORD),\n        (\"wVirtualKeyCode\", WORD),\n        (\"wVirtualScanCode\", WORD),\n        (\"uChar\", uChar),\n        (\"dwControlKeyState\", DWORD),\n    ]\n\n\nclass MOUSE_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str\"\"\"\n\n    _fields_ = [\n        (\"dwMousePosition\", COORD),\n        (\"dwButtonState\", DWORD),\n        (\"dwControlKeyState\", DWORD),\n        (\"dwEventFlags\", DWORD),\n    ]\n\n\nclass WINDOW_BUFFER_SIZE_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str\"\"\"\n\n    _fields_ = [(\"dwSize\", COORD)]\n\n\nclass MENU_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/menu-event-record-str\"\"\"\n\n    _fields_ = [(\"dwCommandId\", UINT)]\n\n\nclass FOCUS_EVENT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/focus-event-record-str\"\"\"\n\n    _fields_ = [(\"bSetFocus\", BOOL)]\n\n\nclass InputEvent(Union):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/input-record-str\"\"\"\n\n    _fields_ = [\n        (\"KeyEvent\", KEY_EVENT_RECORD),\n        (\"MouseEvent\", MOUSE_EVENT_RECORD),\n        (\"WindowBufferSizeEvent\", WINDOW_BUFFER_SIZE_RECORD),\n        (\"MenuEvent\", MENU_EVENT_RECORD),\n        (\"FocusEvent\", FOCUS_EVENT_RECORD),\n    ]\n\n\nclass INPUT_RECORD(Structure):\n    \"\"\"https://docs.microsoft.com/en-us/windows/console/input-record-str\"\"\"\n\n    _fields_ = [(\"EventType\", wintypes.WORD), (\"Event\", InputEvent)]\n\n\ndef set_console_mode(file: IO, mode: int) -> bool:\n    \"\"\"Set the console mode for a given file (stdout or stdin).\n\n    Args:\n        file: A file like object.\n        mode: New mode.\n\n    Returns:\n        True on success, otherwise False.\n    \"\"\"\n    windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore\n    success = KERNEL32.SetConsoleMode(windows_filehandle, mode)\n    return success\n\n\ndef get_console_mode(file: IO) -> int:\n    \"\"\"Get the console mode for a given file (stdout or stdin)\n\n    Args:\n        file: A file-like object.\n\n    Returns:\n        The current console mode.\n    \"\"\"\n    windows_filehandle = msvcrt.get_osfhandle(file.fileno())  # type: ignore\n    mode = wintypes.DWORD()\n    KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode))\n    return mode.value\n\n\ndef enable_application_mode() -> Callable[[], None]:\n    \"\"\"Enable application mode.\n\n    Returns:\n        A callable that will restore terminal to previous state.\n    \"\"\"\n\n    terminal_in = sys.__stdin__\n    terminal_out = sys.__stdout__\n\n    current_console_mode_in = get_console_mode(terminal_in)\n    current_console_mode_out = get_console_mode(terminal_out)\n\n    def restore() -> None:\n        \"\"\"Restore console mode to previous settings\"\"\"\n        set_console_mode(terminal_in, current_console_mode_in)\n        set_console_mode(terminal_out, current_console_mode_out)\n\n    set_console_mode(\n        terminal_out, current_console_mode_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING\n    )\n    set_console_mode(terminal_in, ENABLE_VIRTUAL_TERMINAL_INPUT)\n    return restore\n\n\ndef wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]:\n    \"\"\"\n    Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.\n    Returns `None` on timeout.\n    http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx\n    Note that handles should be a list of `HANDLE` objects, not integers. See\n    this comment in the patch by @quark-zju for the reason why:\n        ''' Make sure HANDLE on Windows has a correct size\n        Previously, the type of various HANDLEs are native Python integer\n        types. The ctypes library will treat them as 4-byte integer when used\n        in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually\n        a small integer. Depending on whether the extra 4 bytes are zero-ed out\n        or not, things can happen to work, or break. '''\n    This function returns either `None` or one of the given `HANDLE` objects.\n    (The return value can be tested with the `is` operator.)\n    \"\"\"\n    arrtype = HANDLE * len(handles)\n    handle_array = arrtype(*handles)\n\n    ret: int = KERNEL32.WaitForMultipleObjects(\n        len(handle_array), handle_array, BOOL(False), DWORD(timeout)\n    )\n\n    if ret == WAIT_TIMEOUT:\n        return None\n    else:\n        return handles[ret]\n\n\nclass EventMonitor(threading.Thread):\n    \"\"\"A thread to send key / window events to Textual loop.\"\"\"\n\n    def __init__(\n        self,\n        loop: AbstractEventLoop,\n        app: App,\n        exit_event: threading.Event,\n        process_event: Callable[[Event], None],\n    ) -> None:\n        self.loop = loop\n        self.app = app\n        self.exit_event = exit_event\n        self.process_event = process_event\n        super().__init__()\n\n    def run(self) -> None:\n        exit_requested = self.exit_event.is_set\n        parser = XTermParser(lambda: False)\n\n        try:\n            read_count = wintypes.DWORD(0)\n            hIn = GetStdHandle(STD_INPUT_HANDLE)\n\n            MAX_EVENTS = 1024\n            KEY_EVENT = 0x0001\n            WINDOW_BUFFER_SIZE_EVENT = 0x0004\n\n            arrtype = INPUT_RECORD * MAX_EVENTS\n            input_records = arrtype()\n            ReadConsoleInputW = KERNEL32.ReadConsoleInputW\n            keys: List[str] = []\n            append_key = keys.append\n\n            while not exit_requested():\n                # Wait for new events\n                if wait_for_handles([hIn], 200) is None:\n                    # No new events\n                    continue\n\n                # Get new events\n                ReadConsoleInputW(\n                    hIn, byref(input_records), MAX_EVENTS, byref(read_count)\n                )\n                read_input_records = input_records[: read_count.value]\n\n                del keys[:]\n                new_size: Optional[tuple[int, int]] = None\n\n                for input_record in read_input_records:\n                    event_type = input_record.EventType\n\n                    if event_type == KEY_EVENT:\n                        # Key event, store unicode char in keys list\n                        key_event = input_record.Event.KeyEvent\n                        key = key_event.uChar.UnicodeChar\n                        if key_event.bKeyDown:\n                            if (\n                                key_event.dwControlKeyState\n                                and key_event.wVirtualKeyCode == 0\n                            ):\n                                continue\n                            append_key(key)\n                    elif event_type == WINDOW_BUFFER_SIZE_EVENT:\n                        # Window size changed, store size\n                        size = input_record.Event.WindowBufferSizeEvent.dwSize\n                        new_size = (size.X, size.Y)\n\n                if keys:\n                    # Process keys\n                    #\n                    # https://github.com/Textualize/textual/issues/3178 has\n                    # the context for the encode/decode here.\n                    for event in parser.feed(\n                        \"\".join(keys).encode(\"utf-16\", \"surrogatepass\").decode(\"utf-16\")\n                    ):\n                        self.process_event(event)\n                if new_size is not None:\n                    # Process changed size\n                    self.on_size_change(*new_size)\n\n        except Exception as error:\n            self.app.log.error(\"EVENT MONITOR ERROR\", error)\n\n    def on_size_change(self, width: int, height: int) -> None:\n        \"\"\"Called when terminal size changes.\"\"\"\n        size = Size(width, height)\n        event = Resize(size, size)\n        run_coroutine_threadsafe(self.app._post_message(event), loop=self.loop)\n", "path": "src/textual/drivers/win32.py"}]}