Skip to content

Commit acc9560

Browse files
committed
gh-150582: Use _winapi for _pyrepl console mode and input flush
1 parent 2468c35 commit acc9560

5 files changed

Lines changed: 258 additions & 32 deletions

File tree

‎Lib/_pyrepl/windows_console.py‎

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,16 @@ def __init__(
165165
trace('console supports virtual terminal')
166166

167167
# Save original console modes so we can recover on cleanup.
168-
original_input_mode = DWORD()
169-
if not GetConsoleMode(InHandle, original_input_mode):
170-
raise WinError(get_last_error())
171-
trace(f'saved original input mode 0x{original_input_mode.value:x}')
172-
self.__original_input_mode = original_input_mode.value
168+
assert _winapi is not None # to make mypy happy
169+
self.__original_input_mode = _winapi.GetConsoleMode(InHandle)
170+
trace(f'saved original input mode 0x{self.__original_input_mode:x}')
173171

174-
if not SetConsoleMode(
172+
_winapi.SetConsoleMode(
175173
OutHandle,
176174
ENABLE_WRAP_AT_EOL_OUTPUT
177175
| ENABLE_PROCESSED_OUTPUT
178176
| ENABLE_VIRTUAL_TERMINAL_PROCESSING,
179-
):
180-
raise WinError(get_last_error())
177+
)
181178

182179
self.width = 80
183180
self.height = 25
@@ -474,8 +471,8 @@ def prepare(self) -> None:
474471
self.sync_rendered_screen(RenderedScreen.empty(), self.posxy)
475472

476473
if self.__vt_support:
477-
if not SetConsoleMode(InHandle, self.__original_input_mode | ENABLE_VIRTUAL_TERMINAL_INPUT):
478-
raise WinError(get_last_error())
474+
assert _winapi is not None # to make mypy happy
475+
_winapi.SetConsoleMode(InHandle, self.__original_input_mode | ENABLE_VIRTUAL_TERMINAL_INPUT)
479476
self._enable_bracketed_paste()
480477

481478
def restore(self) -> None:
@@ -484,8 +481,8 @@ def restore(self) -> None:
484481
self.__write(ANSIColors.RESET)
485482
# Recover to original mode before running REPL
486483
self._disable_bracketed_paste()
487-
if not SetConsoleMode(InHandle, self.__original_input_mode):
488-
raise WinError(get_last_error())
484+
assert _winapi is not None # to make mypy happy
485+
_winapi.SetConsoleMode(InHandle, self.__original_input_mode)
489486

490487
def _move_relative(self, x: int, y: int) -> None:
491488
"""Moves relative to the current posxy"""
@@ -661,8 +658,8 @@ def flushoutput(self) -> None:
661658

662659
def forgetinput(self) -> None:
663660
"""Forget all pending, but not yet processed input."""
664-
if not FlushConsoleInputBuffer(InHandle):
665-
raise WinError(get_last_error())
661+
assert _winapi is not None # to make mypy happy
662+
_winapi.FlushConsoleInputBuffer(InHandle)
666663

667664
def getpending(self) -> Event:
668665
"""Return the characters that have been typed but not yet
@@ -815,23 +812,10 @@ class INPUT_RECORD(Structure):
815812
]
816813
ScrollConsoleScreenBuffer.restype = BOOL
817814

818-
GetConsoleMode = _KERNEL32.GetConsoleMode
819-
GetConsoleMode.argtypes = [HANDLE, POINTER(DWORD)]
820-
GetConsoleMode.restype = BOOL
821-
822-
SetConsoleMode = _KERNEL32.SetConsoleMode
823-
SetConsoleMode.argtypes = [HANDLE, DWORD]
824-
SetConsoleMode.restype = BOOL
825-
826815
ReadConsoleInput = _KERNEL32.ReadConsoleInputW
827816
ReadConsoleInput.argtypes = [HANDLE, POINTER(INPUT_RECORD), DWORD, POINTER(DWORD)]
828817
ReadConsoleInput.restype = BOOL
829818

830-
831-
FlushConsoleInputBuffer = _KERNEL32.FlushConsoleInputBuffer
832-
FlushConsoleInputBuffer.argtypes = [HANDLE]
833-
FlushConsoleInputBuffer.restype = BOOL
834-
835819
OutHandle = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE)
836820
InHandle = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE)
837821
else:
@@ -841,9 +825,6 @@ def _win_only(*args, **kwargs):
841825

842826
GetConsoleScreenBufferInfo = _win_only
843827
ScrollConsoleScreenBuffer = _win_only
844-
GetConsoleMode = _win_only
845-
SetConsoleMode = _win_only
846828
ReadConsoleInput = _win_only
847-
FlushConsoleInputBuffer = _win_only
848829
OutHandle = 0
849830
InHandle = 0

‎Lib/test/test_pyrepl/test_windows_console.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def test_reset_on_restore(self):
399399
_, con = self.handle_events(events)
400400
con._WindowsConsole__vt_support = True
401401
con._WindowsConsole__original_input_mode = 0
402-
with patch.object(wc, "SetConsoleMode", return_value=1):
402+
with patch.object(wc._winapi, "SetConsoleMode"):
403403
con.restore()
404404
con.out.write.assert_any_call(ANSIColors.RESET.encode(con.encoding))
405405

‎Lib/test/test_winapi.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,38 @@ def test_namedpipe(self):
166166
pipe2.flush()
167167
self.assertEqual((b'testdata', 8), _winapi.PeekNamedPipe(pipe, 8)[:2])
168168

169+
def test_console_mode_non_console_handle(self):
170+
rfd, wfd = _winapi.CreatePipe(None, 0)
171+
self.addCleanup(_winapi.CloseHandle, rfd)
172+
self.addCleanup(_winapi.CloseHandle, wfd)
173+
174+
with self.assertRaises(OSError):
175+
_winapi.GetConsoleMode(rfd)
176+
with self.assertRaises(OSError):
177+
_winapi.SetConsoleMode(rfd, 0)
178+
with self.assertRaises(OSError):
179+
_winapi.FlushConsoleInputBuffer(rfd)
180+
181+
def test_console_mode(self):
182+
try:
183+
handle = _winapi.CreateFile(
184+
"CONIN$",
185+
_winapi.GENERIC_READ | _winapi.GENERIC_WRITE,
186+
3, # 3=FILE_SHARE_READ|FILE_SHARE_WRITE
187+
_winapi.NULL,
188+
_winapi.OPEN_EXISTING,
189+
0,
190+
_winapi.NULL)
191+
except OSError:
192+
self.skipTest("requires a console")
193+
self.addCleanup(_winapi.CloseHandle, handle)
194+
195+
mode = _winapi.GetConsoleMode(handle)
196+
self.assertIsInstance(mode, int)
197+
self.assertIsNone(_winapi.SetConsoleMode(handle, mode))
198+
self.assertEqual(_winapi.GetConsoleMode(handle), mode)
199+
self.assertIsNone(_winapi.FlushConsoleInputBuffer(handle))
200+
169201
def test_event_source_registration(self):
170202
source_name = "PythonTestEventSource"
171203

‎Modules/_winapi.c‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2824,6 +2824,95 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle)
28242824
return result;
28252825
}
28262826

2827+
/*[clinic input]
2828+
_winapi.GetConsoleMode -> DWORD
2829+
2830+
handle: HANDLE
2831+
/
2832+
2833+
Return the current mode of a console buffer.
2834+
2835+
The handle may refer to a console input buffer or a console screen
2836+
buffer.
2837+
[clinic start generated code]*/
2838+
2839+
static DWORD
2840+
_winapi_GetConsoleMode_impl(PyObject *module, HANDLE handle)
2841+
/*[clinic end generated code: output=4c0155d4357673ac input=4768c2370a960135]*/
2842+
{
2843+
DWORD mode;
2844+
BOOL ret;
2845+
2846+
Py_BEGIN_ALLOW_THREADS
2847+
ret = GetConsoleMode(handle, &mode);
2848+
Py_END_ALLOW_THREADS
2849+
2850+
if (!ret) {
2851+
PyErr_SetFromWindowsErr(0);
2852+
return PY_DWORD_MAX;
2853+
}
2854+
2855+
return mode;
2856+
}
2857+
2858+
/*[clinic input]
2859+
_winapi.SetConsoleMode
2860+
2861+
handle: HANDLE
2862+
mode: DWORD(bitwise=True)
2863+
/
2864+
2865+
Set the mode of a console buffer.
2866+
2867+
The handle may refer to a console input buffer or a console screen
2868+
buffer.
2869+
[clinic start generated code]*/
2870+
2871+
static PyObject *
2872+
_winapi_SetConsoleMode_impl(PyObject *module, HANDLE handle, DWORD mode)
2873+
/*[clinic end generated code: output=3351f12c46a8938f input=41ffc3f65998d7e1]*/
2874+
{
2875+
BOOL ret;
2876+
2877+
Py_BEGIN_ALLOW_THREADS
2878+
ret = SetConsoleMode(handle, mode);
2879+
Py_END_ALLOW_THREADS
2880+
2881+
if (!ret) {
2882+
return PyErr_SetFromWindowsErr(0);
2883+
}
2884+
2885+
Py_RETURN_NONE;
2886+
}
2887+
2888+
/*[clinic input]
2889+
_winapi.FlushConsoleInputBuffer
2890+
2891+
handle: HANDLE
2892+
/
2893+
2894+
Flush the console input buffer.
2895+
2896+
All input records currently in the input buffer are discarded.
2897+
[clinic start generated code]*/
2898+
2899+
static PyObject *
2900+
_winapi_FlushConsoleInputBuffer_impl(PyObject *module, HANDLE handle)
2901+
/*[clinic end generated code: output=2edaf920d25233ab input=9684cbfada20c237]*/
2902+
{
2903+
BOOL ret;
2904+
2905+
Py_BEGIN_ALLOW_THREADS
2906+
ret = FlushConsoleInputBuffer(handle);
2907+
Py_END_ALLOW_THREADS
2908+
2909+
if (!ret) {
2910+
return PyErr_SetFromWindowsErr(0);
2911+
}
2912+
2913+
Py_RETURN_NONE;
2914+
}
2915+
28272916

28282917
/*[clinic input]
28292918
_winapi._mimetypes_read_windows_registry
@@ -3232,6 +3321,9 @@ static PyMethodDef winapi_functions[] = {
32323321
_WINAPI_GETACP_METHODDEF
32333322
_WINAPI_GETOEMCP_METHODDEF
32343323
_WINAPI_GETFILETYPE_METHODDEF
3324+
_WINAPI_GETCONSOLEMODE_METHODDEF
3325+
_WINAPI_SETCONSOLEMODE_METHODDEF
3326+
_WINAPI_FLUSHCONSOLEINPUTBUFFER_METHODDEF
32353327
_WINAPI__MIMETYPES_READ_WINDOWS_REGISTRY_METHODDEF
32363328
_WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF
32373329
_WINAPI_COPYFILE2_METHODDEF

‎Modules/clinic/_winapi.c.h‎

Lines changed: 122 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)