Skip to content

Commit 9b45b69

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

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
@@ -3353,10 +3353,10 @@ static PyMethodDef moduleMethods[] =
33533353
};
33543354

33553355
#ifdef WAIT_FOR_STDIN
3356+
#ifndef MS_WINDOWS
33563357

33573358
static int stdin_ready = 0;
33583359

3359-
#ifndef MS_WINDOWS
33603360
static void
33613361
MyFileProc(void *clientData, int mask)
33623362
{
@@ -3375,22 +3375,40 @@ static PyThreadState *event_tstate = NULL;
33753375
static int
33763376
EventHook(void)
33773377
{
3378-
#ifndef MS_WINDOWS
3378+
#ifdef MS_WINDOWS
3379+
HANDLE hStdin;
3380+
DWORD type;
3381+
#else
33793382
int tfile;
3383+
stdin_ready = 0;
33803384
#endif
33813385
PyEval_RestoreThread(event_tstate);
3382-
stdin_ready = 0;
33833386
errorInCmd = 0;
3384-
#ifndef MS_WINDOWS
3387+
#ifdef MS_WINDOWS
3388+
hStdin = GetStdHandle(STD_INPUT_HANDLE);
3389+
type = GetFileType(hStdin);
3390+
while (1) {
3391+
#else
33853392
tfile = fileno(stdin);
33863393
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc,
33873394
(void *)(Py_intptr_t)tfile);
3388-
#endif
33893395
while (!stdin_ready) {
3396+
#endif
33903397
int result;
33913398
#ifdef MS_WINDOWS
3392-
if (_kbhit()) {
3393-
stdin_ready = 1;
3399+
if (type == FILE_TYPE_CHAR) {
3400+
if (_kbhit()) break;
3401+
}
3402+
else if (type == FILE_TYPE_PIPE) {
3403+
DWORD available;
3404+
if (PeekNamedPipe(hStdin, NULL, 0, NULL, &available, NULL)) {
3405+
if (available > 0) break;
3406+
}
3407+
else {
3408+
if (GetLastError() == ERROR_BROKEN_PIPE) break;
3409+
}
3410+
}
3411+
else if (type == FILE_TYPE_DISK) {
33943412
break;
33953413
}
33963414
#endif

0 commit comments

Comments
 (0)