Skip to content

Commit d97d52c

Browse files
[3.15] gh-140146: Fix for stdin redirection to a pipe with interactive tkinter on Windows (GH-148819) (GH-152560)
(cherry picked from commit 6d209cb) Co-authored-by: mdehoon <mjldehoon@yahoo.com>
1 parent ac30e9b commit d97d52c

3 files changed

Lines changed: 83 additions & 7 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# test_tkinter_pipe.py
2+
import unittest
3+
import subprocess
4+
import sys
5+
from test import support
6+
7+
8+
@unittest.skipUnless(support.has_subprocess_support, "test requires subprocess")
9+
class TkinterPipeTest(unittest.TestCase):
10+
11+
def test_tkinter_pipe_buffered(self):
12+
args = [sys.executable, "-i"]
13+
proc = subprocess.Popen(args,
14+
stdin=subprocess.PIPE,
15+
stdout=subprocess.PIPE,
16+
stderr=subprocess.PIPE)
17+
proc.stdin.write(b"import tkinter\n")
18+
proc.stdin.write(b"interpreter = tkinter.Tcl()\n")
19+
proc.stdin.write(b"print('hello')\n")
20+
proc.stdin.write(b"print('goodbye')\n")
21+
proc.stdin.write(b"quit()\n")
22+
stdout, stderr = proc.communicate(timeout=support.SHORT_TIMEOUT)
23+
stdout = stdout.decode()
24+
self.assertEqual(stdout.split(), ['hello', 'goodbye'])
25+
26+
def test_tkinter_pipe_unbuffered(self):
27+
args = [sys.executable, "-i", "-u"]
28+
proc = subprocess.Popen(args,
29+
stdin=subprocess.PIPE,
30+
stdout=subprocess.PIPE,
31+
stderr=subprocess.PIPE)
32+
proc.stdin.write(b"import tkinter\n")
33+
proc.stdin.write(b"interpreter = tkinter.Tcl()\n")
34+
35+
proc.stdin.write(b"print('hello')\n")
36+
proc.stdin.flush()
37+
stdout = proc.stdout.readline()
38+
stdout = stdout.decode()
39+
self.assertEqual(stdout.strip(), 'hello')
40+
41+
proc.stdin.write(b"print('hello again')\n")
42+
proc.stdin.flush()
43+
stdout = proc.stdout.readline()
44+
stdout = stdout.decode()
45+
self.assertEqual(stdout.strip(), 'hello again')
46+
47+
proc.stdin.write(b"print('goodbye')\n")
48+
proc.stdin.write(b"quit()\n")
49+
stdout, stderr = proc.communicate(timeout=support.SHORT_TIMEOUT)
50+
stdout = stdout.decode()
51+
self.assertEqual(stdout.strip(), 'goodbye')
52+
53+
54+
if __name__ == "__main__":
55+
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Prevent :mod:`tkinter` from hanging on Windows if stdin is redirected to a pipe in an
2+
interactive session. This is helpful for testing interactive usage of
3+
tkinter from a script, for example as part of the cpython test suite.

Modules/_tkinter.c

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3429,10 +3429,10 @@ static PyMethodDef moduleMethods[] =
34293429
};
34303430

34313431
#ifdef WAIT_FOR_STDIN
3432+
#ifndef MS_WINDOWS
34323433

34333434
static int stdin_ready = 0;
34343435

3435-
#ifndef MS_WINDOWS
34363436
static void
34373437
MyFileProc(void *clientData, int mask)
34383438
{
@@ -3451,22 +3451,40 @@ static PyThreadState *event_tstate = NULL;
34513451
static int
34523452
EventHook(void)
34533453
{
3454-
#ifndef MS_WINDOWS
3454+
#ifdef MS_WINDOWS
3455+
HANDLE hStdin;
3456+
DWORD type;
3457+
#else
34553458
int tfile;
3459+
stdin_ready = 0;
34563460
#endif
34573461
PyEval_RestoreThread(event_tstate);
3458-
stdin_ready = 0;
34593462
errorInCmd = 0;
3460-
#ifndef MS_WINDOWS
3463+
#ifdef MS_WINDOWS
3464+
hStdin = GetStdHandle(STD_INPUT_HANDLE);
3465+
type = GetFileType(hStdin);
3466+
while (1) {
3467+
#else
34613468
tfile = fileno(stdin);
34623469
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc,
34633470
(void *)(Py_intptr_t)tfile);
3464-
#endif
34653471
while (!stdin_ready) {
3472+
#endif
34663473
int result;
34673474
#ifdef MS_WINDOWS
3468-
if (_kbhit()) {
3469-
stdin_ready = 1;
3475+
if (type == FILE_TYPE_CHAR) {
3476+
if (_kbhit()) break;
3477+
}
3478+
else if (type == FILE_TYPE_PIPE) {
3479+
DWORD available;
3480+
if (PeekNamedPipe(hStdin, NULL, 0, NULL, &available, NULL)) {
3481+
if (available > 0) break;
3482+
}
3483+
else {
3484+
if (GetLastError() == ERROR_BROKEN_PIPE) break;
3485+
}
3486+
}
3487+
else if (type == FILE_TYPE_DISK) {
34703488
break;
34713489
}
34723490
#endif

0 commit comments

Comments
 (0)