Skip to content

Commit a6d546e

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

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
@@ -3388,10 +3388,10 @@ static PyMethodDef moduleMethods[] =
33883388
};
33893389

33903390
#ifdef WAIT_FOR_STDIN
3391+
#ifndef MS_WINDOWS
33913392

33923393
static int stdin_ready = 0;
33933394

3394-
#ifndef MS_WINDOWS
33953395
static void
33963396
MyFileProc(void *clientData, int mask)
33973397
{
@@ -3410,22 +3410,40 @@ static PyThreadState *event_tstate = NULL;
34103410
static int
34113411
EventHook(void)
34123412
{
3413-
#ifndef MS_WINDOWS
3413+
#ifdef MS_WINDOWS
3414+
HANDLE hStdin;
3415+
DWORD type;
3416+
#else
34143417
int tfile;
3418+
stdin_ready = 0;
34153419
#endif
34163420
PyEval_RestoreThread(event_tstate);
3417-
stdin_ready = 0;
34183421
errorInCmd = 0;
3419-
#ifndef MS_WINDOWS
3422+
#ifdef MS_WINDOWS
3423+
hStdin = GetStdHandle(STD_INPUT_HANDLE);
3424+
type = GetFileType(hStdin);
3425+
while (1) {
3426+
#else
34203427
tfile = fileno(stdin);
34213428
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc,
34223429
(void *)(Py_intptr_t)tfile);
3423-
#endif
34243430
while (!stdin_ready) {
3431+
#endif
34253432
int result;
34263433
#ifdef MS_WINDOWS
3427-
if (_kbhit()) {
3428-
stdin_ready = 1;
3434+
if (type == FILE_TYPE_CHAR) {
3435+
if (_kbhit()) break;
3436+
}
3437+
else if (type == FILE_TYPE_PIPE) {
3438+
DWORD available;
3439+
if (PeekNamedPipe(hStdin, NULL, 0, NULL, &available, NULL)) {
3440+
if (available > 0) break;
3441+
}
3442+
else {
3443+
if (GetLastError() == ERROR_BROKEN_PIPE) break;
3444+
}
3445+
}
3446+
else if (type == FILE_TYPE_DISK) {
34293447
break;
34303448
}
34313449
#endif

0 commit comments

Comments
 (0)