##// END OF EJS Templates
Tweak out the process controller class so it works interactively better...
Mark Wiebe -
Show More
@@ -1,183 +1,184 b''
1 1 """Windows-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # stdlib
19 19 import os
20 20 import sys
21 21 import ctypes
22 import msvcrt
22 23
23 24 from ctypes import c_int, POINTER
24 25 from ctypes.wintypes import LPCWSTR, HLOCAL
25 26 from subprocess import STDOUT
26 27
27 28 # our own imports
28 29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 30 from . import py3compat
30 31 from . import text
31 32
32 33 #-----------------------------------------------------------------------------
33 34 # Function definitions
34 35 #-----------------------------------------------------------------------------
35 36
36 37 class AvoidUNCPath(object):
37 38 """A context manager to protect command execution from UNC paths.
38 39
39 40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 41 This context manager temporarily changes directory to the 'C:' drive on
41 42 entering, and restores the original working directory on exit.
42 43
43 44 The context manager returns the starting working directory *if* it made a
44 45 change and None otherwise, so that users can apply the necessary adjustment
45 46 to their system calls in the event of a change.
46 47
47 48 Example
48 49 -------
49 50 ::
50 51 cmd = 'dir'
51 52 with AvoidUNCPath() as path:
52 53 if path is not None:
53 54 cmd = '"pushd %s &&"%s' % (path, cmd)
54 55 os.system(cmd)
55 56 """
56 57 def __enter__(self):
57 58 self.path = os.getcwdu()
58 59 self.is_unc_path = self.path.startswith(r"\\")
59 60 if self.is_unc_path:
60 61 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 62 os.chdir("C:")
62 63 return self.path
63 64 else:
64 65 # We return None to signal that there was no change in the working
65 66 # directory
66 67 return None
67 68
68 69 def __exit__(self, exc_type, exc_value, traceback):
69 70 if self.is_unc_path:
70 71 os.chdir(self.path)
71 72
72 73
73 74 def _find_cmd(cmd):
74 75 """Find the full path to a .bat or .exe using the win32api module."""
75 76 try:
76 77 from win32api import SearchPath
77 78 except ImportError:
78 79 raise ImportError('you need to have pywin32 installed for this to work')
79 80 else:
80 81 PATH = os.environ['PATH']
81 82 extensions = ['.exe', '.com', '.bat', '.py']
82 83 path = None
83 84 for ext in extensions:
84 85 try:
85 86 path = SearchPath(PATH, cmd + ext)[0]
86 87 except:
87 88 pass
88 89 if path is None:
89 90 raise OSError("command %r not found" % cmd)
90 91 else:
91 92 return path
92 93
93 94
94 95 def _system_body(p):
95 96 """Callback for _system."""
96 97 enc = text.getdefaultencoding()
97 98 for line in read_no_interrupt(p.stdout).splitlines():
98 99 line = line.decode(enc, 'replace')
99 100 print(line, file=sys.stdout)
100 101 for line in read_no_interrupt(p.stderr).splitlines():
101 102 line = line.decode(enc, 'replace')
102 103 print(line, file=sys.stderr)
103 104
104 105 # Wait to finish for returncode
105 106 return p.wait()
106 107
107 108
108 109 def system(cmd):
109 110 """Win32 version of os.system() that works with network shares.
110 111
111 112 Note that this implementation returns None, as meant for use in IPython.
112 113
113 114 Parameters
114 115 ----------
115 116 cmd : str
116 117 A command to be executed in the system shell.
117 118
118 119 Returns
119 120 -------
120 121 None : we explicitly do NOT return the subprocess status code, as this
121 122 utility is meant to be used extensively in IPython, where any return value
122 123 would trigger :func:`sys.displayhook` calls.
123 124 """
124 with AvoidUNCPath() as path:
125 if path is not None:
126 cmd = '"pushd %s &&"%s' % (path, cmd)
127 return process_handler(cmd, _system_body)
125 # The controller provides interactivity with both
126 # stdin and stdout
127 import _process_win32_controller
128 _process_win32_controller.system(cmd)
128 129
129 130
130 131 def getoutput(cmd):
131 132 """Return standard output of executing cmd in a shell.
132 133
133 134 Accepts the same arguments as os.system().
134 135
135 136 Parameters
136 137 ----------
137 138 cmd : str
138 139 A command to be executed in the system shell.
139 140
140 141 Returns
141 142 -------
142 143 stdout : str
143 144 """
144 145
145 146 with AvoidUNCPath() as path:
146 147 if path is not None:
147 148 cmd = '"pushd %s &&"%s' % (path, cmd)
148 149 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
149 150
150 151 if out is None:
151 152 out = ''
152 153 return out
153 154
154 155 try:
155 156 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
156 157 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
157 158 CommandLineToArgvW.res_types = [POINTER(LPCWSTR)]
158 159 LocalFree = ctypes.windll.kernel32.LocalFree
159 160 LocalFree.res_type = HLOCAL
160 161 LocalFree.arg_types = [HLOCAL]
161 162
162 163 def arg_split(commandline, posix=False, strict=True):
163 164 """Split a command line's arguments in a shell-like manner.
164 165
165 166 This is a special version for windows that use a ctypes call to CommandLineToArgvW
166 167 to do the argv splitting. The posix paramter is ignored.
167 168
168 169 If strict=False, process_common.arg_split(...strict=False) is used instead.
169 170 """
170 171 #CommandLineToArgvW returns path to executable if called with empty string.
171 172 if commandline.strip() == "":
172 173 return []
173 174 if not strict:
174 175 # not really a cl-arg, fallback on _process_common
175 176 return py_arg_split(commandline, posix=posix, strict=strict)
176 177 argvn = c_int()
177 178 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
178 179 result_array_type = LPCWSTR * argvn.value
179 180 result = [arg for arg in result_array_type.from_address(result_pointer)]
180 181 retval = LocalFree(result_pointer)
181 182 return result
182 183 except AttributeError:
183 184 arg_split = py_arg_split
@@ -1,502 +1,574 b''
1 1 """Windows-specific implementation of process utilities with direct WinAPI.
2 2
3 3 This file is meant to be used by process.py
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 from __future__ import print_function
14 14
15 15 # stdlib
16 16 import os, sys, time, threading
17 17 import ctypes, msvcrt
18 18
19 19 # Win32 API types needed for the API calls
20 20 from ctypes import POINTER
21 21 from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
22 22 ULONG, LPCWSTR
23 23 LPDWORD = POINTER(DWORD)
24 24 LPHANDLE = POINTER(HANDLE)
25 25 ULONG_PTR = POINTER(ULONG)
26 26 class SECURITY_ATTRIBUTES(ctypes.Structure):
27 27 _fields_ = [("nLength", DWORD),
28 28 ("lpSecurityDescriptor", LPVOID),
29 29 ("bInheritHandle", BOOL)]
30 30 LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
31 31 class STARTUPINFO(ctypes.Structure):
32 32 _fields_ = [("cb", DWORD),
33 33 ("lpReserved", LPCWSTR),
34 34 ("lpDesktop", LPCWSTR),
35 35 ("lpTitle", LPCWSTR),
36 36 ("dwX", DWORD),
37 37 ("dwY", DWORD),
38 38 ("dwXSize", DWORD),
39 39 ("dwYSize", DWORD),
40 40 ("dwXCountChars", DWORD),
41 41 ("dwYCountChars", DWORD),
42 42 ("dwFillAttribute", DWORD),
43 43 ("dwFlags", DWORD),
44 44 ("wShowWindow", WORD),
45 45 ("cbReserved2", WORD),
46 46 ("lpReserved2", LPVOID),
47 47 ("hStdInput", HANDLE),
48 48 ("hStdOutput", HANDLE),
49 49 ("hStdError", HANDLE)]
50 50 LPSTARTUPINFO = POINTER(STARTUPINFO)
51 51 class PROCESS_INFORMATION(ctypes.Structure):
52 52 _fields_ = [("hProcess", HANDLE),
53 53 ("hThread", HANDLE),
54 54 ("dwProcessId", DWORD),
55 55 ("dwThreadId", DWORD)]
56 56 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
57 57
58 58 # Win32 API constants needed
59 59 ERROR_HANDLE_EOF = 38
60 60 ERROR_BROKEN_PIPE = 109
61 ERROR_NO_DATA = 232
61 62 HANDLE_FLAG_INHERIT = 0x0001
62 63 STARTF_USESTDHANDLES = 0x0100
63 64 CREATE_SUSPENDED = 0x0004
64 65 CREATE_NEW_CONSOLE = 0x0010
66 CREATE_NO_WINDOW = 0x08000000
65 67 STILL_ACTIVE = 259
66 68 WAIT_TIMEOUT = 0x0102
67 69 WAIT_FAILED = 0xFFFFFFFF
68 70 INFINITE = 0xFFFFFFFF
69 71 DUPLICATE_SAME_ACCESS = 0x00000002
72 ENABLE_ECHO_INPUT = 0x0004
73 ENABLE_LINE_INPUT = 0x0002
74 ENABLE_PROCESSED_INPUT = 0x0001
70 75
71 76 # Win32 API functions needed
72 77 GetLastError = ctypes.windll.kernel32.GetLastError
73 78 GetLastError.argtypes = []
74 79 GetLastError.restype = DWORD
75 80
76 81 CreateFile = ctypes.windll.kernel32.CreateFileW
77 82 CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
78 83 CreateFile.restype = HANDLE
79 84
80 85 CreatePipe = ctypes.windll.kernel32.CreatePipe
81 86 CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
82 87 LPSECURITY_ATTRIBUTES, DWORD]
83 88 CreatePipe.restype = BOOL
84 89
85 90 CreateProcess = ctypes.windll.kernel32.CreateProcessW
86 91 CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
87 92 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
88 93 LPPROCESS_INFORMATION]
89 94 CreateProcess.restype = BOOL
90 95
91 96 GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
92 97 GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
93 98 GetExitCodeProcess.restype = BOOL
94 99
95 100 GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
96 101 GetCurrentProcess.argtypes = []
97 102 GetCurrentProcess.restype = HANDLE
98 103
99 104 ResumeThread = ctypes.windll.kernel32.ResumeThread
100 105 ResumeThread.argtypes = [HANDLE]
101 106 ResumeThread.restype = DWORD
102 107
103 108 ReadFile = ctypes.windll.kernel32.ReadFile
104 109 ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
105 110 ReadFile.restype = BOOL
106 111
107 112 WriteFile = ctypes.windll.kernel32.WriteFile
108 113 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
109 114 WriteFile.restype = BOOL
110 115
116 GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
117 GetConsoleMode.argtypes = [HANDLE, LPDWORD]
118 GetConsoleMode.restype = BOOL
119
120 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
121 SetConsoleMode.argtypes = [HANDLE, DWORD]
122 SetConsoleMode.restype = BOOL
123
124 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
125 FlushConsoleInputBuffer.argtypes = [HANDLE]
126 FlushConsoleInputBuffer.restype = BOOL
127
111 128 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
112 129 WaitForSingleObject.argtypes = [HANDLE, DWORD]
113 130 WaitForSingleObject.restype = DWORD
114 131
115 132 DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
116 133 DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
117 134 DWORD, BOOL, DWORD]
118 135 DuplicateHandle.restype = BOOL
119 136
120 137 SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
121 138 SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
122 139 SetHandleInformation.restype = BOOL
123 140
124 141 CloseHandle = ctypes.windll.kernel32.CloseHandle
125 142 CloseHandle.argtypes = [HANDLE]
126 143 CloseHandle.restype = BOOL
127 144
128 145 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
129 146 CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
130 147 CommandLineToArgvW.restype = POINTER(LPCWSTR)
131 148
132 149 LocalFree = ctypes.windll.kernel32.LocalFree
133 150 LocalFree.argtypes = [HLOCAL]
134 151 LocalFree.restype = HLOCAL
135 152
136 153 class AvoidUNCPath(object):
137 154 """A context manager to protect command execution from UNC paths.
138 155
139 156 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
140 157 This context manager temporarily changes directory to the 'C:' drive on
141 158 entering, and restores the original working directory on exit.
142 159
143 160 The context manager returns the starting working directory *if* it made a
144 161 change and None otherwise, so that users can apply the necessary adjustment
145 162 to their system calls in the event of a change.
146 163
147 164 Example
148 165 -------
149 166 ::
150 167 cmd = 'dir'
151 168 with AvoidUNCPath() as path:
152 169 if path is not None:
153 170 cmd = '"pushd %s &&"%s' % (path, cmd)
154 171 os.system(cmd)
155 172 """
156 173 def __enter__(self):
157 174 self.path = os.getcwdu()
158 175 self.is_unc_path = self.path.startswith(r"\\")
159 176 if self.is_unc_path:
160 177 # change to c drive (as cmd.exe cannot handle UNC addresses)
161 178 os.chdir("C:")
162 179 return self.path
163 180 else:
164 181 # We return None to signal that there was no change in the working
165 182 # directory
166 183 return None
167 184
168 185 def __exit__(self, exc_type, exc_value, traceback):
169 186 if self.is_unc_path:
170 187 os.chdir(self.path)
171 188
172 189
173 190 class Win32ShellCommandController(object):
174 191 """Runs a shell command in a 'with' context.
175 192
176 193 This implementation is Win32-specific.
177 194
178 195 Example:
179 196 # Runs the command interactively with default console stdin/stdout
180 197 with ShellCommandController('python -i') as scc:
181 198 scc.run()
182 199
183 200 # Runs the command using the provided functions for stdin/stdout
184 201 def my_stdout_func(s):
185 202 # print or save the string 's'
186 203 write_to_stdout(s)
187 204 def my_stdin_func():
188 205 # If input is available, return it as a string.
189 206 if input_available():
190 207 return get_input()
191 208 # If no input available, return None after a short delay to
192 209 # keep from blocking.
193 210 else:
194 211 time.sleep(0.01)
195 212 return None
196 213
197 214 with ShellCommandController('python -i') as scc:
198 215 scc.run(my_stdout_func, my_stdin_func)
199 216 """
200 217
201 218 def __init__(self, cmd, mergeout = True):
202 219 """Initializes the shell command controller.
203 220
204 221 The cmd is the program to execute, and mergeout is
205 222 whether to blend stdout and stderr into one output
206 223 in stdout. Merging them together in this fashion more
207 224 reliably keeps stdout and stderr in the correct order
208 225 especially for interactive shell usage.
209 226 """
210 227 self.cmd = cmd
211 228 self.mergeout = mergeout
212 229
213 230 def __enter__(self):
214 231 cmd = self.cmd
215 232 mergeout = self.mergeout
216 233
217 234 self.hstdout, self.hstdin, self.hstderr = None, None, None
218 235 self.piProcInfo = None
219 236 try:
220 237 p_hstdout, c_hstdout, p_hstderr, \
221 238 c_hstderr, p_hstdin, c_hstdin = [None]*6
222 239
223 240 # SECURITY_ATTRIBUTES with inherit handle set to True
224 241 saAttr = SECURITY_ATTRIBUTES()
225 242 saAttr.nLength = ctypes.sizeof(saAttr)
226 243 saAttr.bInheritHandle = True
227 244 saAttr.lpSecurityDescriptor = None
228 245
229 246 def create_pipe(uninherit):
230 247 """Creates a Windows pipe, which consists of two handles.
231 248
232 249 The 'uninherit' parameter controls which handle is not
233 250 inherited by the child process.
234 251 """
235 252 handles = HANDLE(), HANDLE()
236 253 if not CreatePipe(ctypes.byref(handles[0]),
237 254 ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
238 255 raise ctypes.WinError()
239 256 if not SetHandleInformation(handles[uninherit],
240 257 HANDLE_FLAG_INHERIT, 0):
241 258 raise ctypes.WinError()
242 259 return handles[0].value, handles[1].value
243 260
244 261 p_hstdout, c_hstdout = create_pipe(uninherit=0)
245 262 # 'mergeout' signals that stdout and stderr should be merged.
246 263 # We do that by using one pipe for both of them.
247 264 if mergeout:
248 265 c_hstderr = HANDLE()
249 266 if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
250 267 GetCurrentProcess(), ctypes.byref(c_hstderr),
251 268 0, True, DUPLICATE_SAME_ACCESS):
252 269 raise ctypes.WinError()
253 270 else:
254 271 p_hstderr, c_hstderr = create_pipe(uninherit=0)
255 272 c_hstdin, p_hstdin = create_pipe(uninherit=1)
256 273
257 274 # Create the process object
258 275 piProcInfo = PROCESS_INFORMATION()
259 276 siStartInfo = STARTUPINFO()
260 277 siStartInfo.cb = ctypes.sizeof(siStartInfo)
261 278 siStartInfo.hStdInput = c_hstdin
262 279 siStartInfo.hStdOutput = c_hstdout
263 280 siStartInfo.hStdError = c_hstderr
264 281 siStartInfo.dwFlags = STARTF_USESTDHANDLES
265 dwCreationFlags = CREATE_SUSPENDED # | CREATE_NEW_CONSOLE
282 dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
266 283
267 284 if not CreateProcess(None,
268 285 u"cmd.exe /c " + cmd,
269 286 None, None, True, dwCreationFlags,
270 287 None, None, ctypes.byref(siStartInfo),
271 288 ctypes.byref(piProcInfo)):
272 289 raise ctypes.WinError()
273 290
274 291 # Close this process's versions of the child handles
275 292 CloseHandle(c_hstdin)
276 293 c_hstdin = None
277 294 CloseHandle(c_hstdout)
278 295 c_hstdout = None
279 296 if c_hstderr != None:
280 297 CloseHandle(c_hstderr)
281 298 c_hstderr = None
282 299
283 300 # Transfer ownership of the parent handles to the object
284 301 self.hstdin = p_hstdin
285 302 p_hstdin = None
286 303 self.hstdout = p_hstdout
287 304 p_hstdout = None
288 305 if not mergeout:
289 306 self.hstderr = p_hstderr
290 307 p_hstderr = None
291 308 self.piProcInfo = piProcInfo
292 309
293 310 finally:
294 311 if p_hstdin:
295 312 CloseHandle(p_hstdin)
296 313 if c_hstdin:
297 314 CloseHandle(c_hstdin)
298 315 if p_hstdout:
299 316 CloseHandle(p_hstdout)
300 317 if c_hstdout:
301 318 CloseHandle(c_hstdout)
302 319 if p_hstderr:
303 320 CloseHandle(p_hstderr)
304 321 if c_hstderr:
305 322 CloseHandle(c_hstderr)
306 323
307 324 return self
308 325
309 326 def _stdin_thread(self, handle, hprocess, func, stdout_func):
310 # TODO: Use WaitForInputIdle to avoid calling func() until
311 # an input is actually requested.
312 327 exitCode = DWORD()
313 328 bytesWritten = DWORD(0)
314 329 while True:
315 330 #print("stdin thread loop start")
316 331 # Get the input string (may be bytes or unicode)
317 332 data = func()
318 333
319 334 # None signals to poll whether the process has exited
320 335 if data is None:
321 336 #print("checking for process completion")
322 337 if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
323 338 raise ctypes.WinError()
324 339 if exitCode.value != STILL_ACTIVE:
325 340 return
326 341 # TESTING: Does zero-sized writefile help?
327 342 if not WriteFile(handle, "", 0,
328 343 ctypes.byref(bytesWritten), None):
329 344 raise ctypes.WinError()
330 345 continue
331 346 #print("\nGot str %s\n" % repr(data), file=sys.stderr)
332 347
333 348 # Encode the string to the console encoding
334 349 if isinstance(data, unicode): #FIXME: Python3
335 350 data = data.encode('utf_8')
336 351
337 352 # What we have now must be a string of bytes
338 353 if not isinstance(data, str): #FIXME: Python3
339 354 raise RuntimeError("internal stdin function string error")
340 355
341 356 # An empty string signals EOF
342 357 if len(data) == 0:
343 358 return
344 359
345 360 # In a windows console, sometimes the input is echoed,
346 361 # but sometimes not. How do we determine when to do this?
347 362 stdout_func(data)
348 363 # WriteFile may not accept all the data at once.
349 364 # Loop until everything is processed
350 365 while len(data) != 0:
351 366 #print("Calling writefile")
352 367 if not WriteFile(handle, data, len(data),
353 368 ctypes.byref(bytesWritten), None):
369 # This occurs at exit
370 if GetLastError() == ERROR_NO_DATA:
371 return
354 372 raise ctypes.WinError()
355 373 #print("Called writefile")
356 374 data = data[bytesWritten.value:]
357 375
358 376 def _stdout_thread(self, handle, func):
359 377 # Allocate the output buffer
360 378 data = ctypes.create_string_buffer(4096)
361 379 while True:
362 380 bytesRead = DWORD(0)
363 381 if not ReadFile(handle, data, 4096,
364 382 ctypes.byref(bytesRead), None):
365 383 le = GetLastError()
366 384 if le == ERROR_BROKEN_PIPE:
367 385 return
368 386 else:
369 387 raise ctypes.WinError()
370 388 # FIXME: Python3
371 389 s = data.value[0:bytesRead.value]
372 390 #print("\nv: %s" % repr(s), file=sys.stderr)
373 391 func(s.decode('utf_8', 'replace'))
374 392
375 393 def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
376 394 """Runs the process, using the provided functions for I/O.
377 395
378 396 The function stdin_func should return strings whenever a
379 397 character or characters become available.
380 398 The functions stdout_func and stderr_func are called whenever
381 399 something is printed to stdout or stderr, respectively.
382 400 These functions are called from different threads (but not
383 401 concurrently, because of the GIL).
384 402 """
385 403 if stdout_func == None and stdin_func == None and stderr_func == None:
386 404 return self._run_stdio()
387 405
388 if stderr_func != None and self.hstderr == None:
406 if stderr_func != None and self.mergeout:
389 407 raise RuntimeError("Shell command was initiated with "
390 408 "merged stdin/stdout, but a separate stderr_func "
391 409 "was provided to the run() method")
392 410
393 411 # Create a thread for each input/output handle
412 stdin_thread = None
394 413 threads = []
395 414 if stdin_func:
396 threads.append(threading.Thread(target=self._stdin_thread,
415 stdin_thread = threading.Thread(target=self._stdin_thread,
397 416 args=(self.hstdin, self.piProcInfo.hProcess,
398 stdin_func, stdout_func)))
417 stdin_func, stdout_func))
399 418 threads.append(threading.Thread(target=self._stdout_thread,
400 419 args=(self.hstdout, stdout_func)))
401 if self.hstderr != None:
420 if not self.mergeout:
402 421 if stderr_func == None:
403 422 stderr_func = stdout_func
404 423 threads.append(threading.Thread(target=self._stdout_thread,
405 424 args=(self.hstderr, stderr_func)))
406 425 # Start the I/O threads and the process
407 426 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
408 427 raise ctypes.WinError()
428 if stdin_thread is not None:
429 stdin_thread.start()
409 430 for thread in threads:
410 431 thread.start()
411 432 # Wait for the process to complete
412 433 if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
413 434 WAIT_FAILED:
414 435 raise ctypes.WinError()
415 436 # Wait for the I/O threads to complete
416 437 for thread in threads:
417 438 thread.join()
418 439
419 def _stdin_raw(self):
420 """Uses msvcrt.kbhit/getwch to do read stdin without blocking"""
421 if msvcrt.kbhit():
422 #s = msvcrt.getwch()
423 s = msvcrt.getwch()
424 # Key code for Enter is '\r', but need to give back '\n'
425 if s == u'\r':
426 s = u'\n'
427 return s
428 else:
429 # This should make it poll at about 100 Hz, which
430 # is hopefully good enough to be responsive but
431 # doesn't waste CPU.
432 time.sleep(0.01)
440 # Wait for the stdin thread to complete
441 if stdin_thread is not None:
442 stdin_thread.join()
443
444 def _stdin_raw_nonblock(self):
445 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
446 # WARNING: This is experimental, and produces inconsistent results.
447 # It's possible for the handle not to be appropriate for use
448 # with WaitForSingleObject, among other things.
449 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
450 result = WaitForSingleObject(handle, 100)
451 if result == WAIT_FAILED:
452 raise ctypes.WinError()
453 elif result == WAIT_TIMEOUT:
454 print(".", end='')
433 455 return None
456 else:
457 data = ctypes.create_string_buffer(256)
458 bytesRead = DWORD(0)
459 print('?', end='')
460
461 if not ReadFile(handle, data, 256,
462 ctypes.byref(bytesRead), None):
463 raise ctypes.WinError()
464 # This ensures the non-blocking works with an actual console
465 # Not checking the error, so the processing will still work with
466 # other handle types
467 FlushConsoleInputBuffer(handle)
468
469 data = data.value
470 data = data.replace('\r\n', '\n')
471 data = data.replace('\r', '\n')
472 print(repr(data) + " ", end='')
473 return data
474
475 def _stdin_raw_block(self):
476 """Use a blocking stdin read"""
477 # The big problem with the blocking read is that it doesn't
478 # exit when it's supposed to in all contexts. An extra
479 # key-press may be required to trigger the exit.
480 try:
481 data = sys.stdin.read(1)
482 data = data.replace('\r', '\n')
483 return data
484 except WindowsError as we:
485 if we.winerror == ERROR_NO_DATA:
486 # This error occurs when the pipe is closed
487 return None
488 else:
489 # Otherwise let the error propagate
490 raise we
434 491
435 492 def _stdout_raw(self, s):
436 493 """Writes the string to stdout"""
437 494 print(s, end='', file=sys.stdout)
495 sys.stdout.flush()
438 496
439 497 def _stderr_raw(self, s):
440 498 """Writes the string to stdout"""
441 499 print(s, end='', file=sys.stderr)
500 sys.stderr.flush()
442 501
443 502 def _run_stdio(self):
444 503 """Runs the process using the system standard I/O.
445 504
446 505 IMPORTANT: stdin needs to be asynchronous, so the Python
447 506 sys.stdin object is not used. Instead,
448 507 msvcrt.kbhit/getwch are used asynchronously.
449 508 """
450 if self.hstderr != None:
509 # Disable Line and Echo mode
510 #lpMode = DWORD()
511 #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
512 #if GetConsoleMode(handle, ctypes.byref(lpMode)):
513 # set_console_mode = True
514 # if not SetConsoleMode(handle, lpMode.value &
515 # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
516 # raise ctypes.WinError()
517
518 if self.mergeout:
451 519 return self.run(stdout_func = self._stdout_raw,
452 stdin_func = self._stdin_raw,
453 stderr_func = self._stderr_raw)
520 stdin_func = self._stdin_raw_block)
454 521 else:
455 522 return self.run(stdout_func = self._stdout_raw,
456 stdin_func = self._stdin_raw)
457
523 stdin_func = self._stdin_raw_block,
524 stderr_func = self._stderr_raw)
525
526 # Restore the previous console mode
527 #if set_console_mode:
528 # if not SetConsoleMode(handle, lpMode.value):
529 # raise ctypes.WinError()
458 530
459 531 def __exit__(self, exc_type, exc_value, traceback):
460 532 if self.hstdin:
461 533 CloseHandle(self.hstdin)
462 534 self.hstdin = None
463 535 if self.hstdout:
464 536 CloseHandle(self.hstdout)
465 537 self.hstdout = None
466 538 if self.hstderr:
467 539 CloseHandle(self.hstderr)
468 540 self.hstderr = None
469 541 if self.piProcInfo != None:
470 542 CloseHandle(self.piProcInfo.hProcess)
471 543 CloseHandle(self.piProcInfo.hThread)
472 544 self.piProcInfo = None
473 545
474 546
475 547 def system(cmd):
476 548 """Win32 version of os.system() that works with network shares.
477 549
478 550 Note that this implementation returns None, as meant for use in IPython.
479 551
480 552 Parameters
481 553 ----------
482 554 cmd : str
483 555 A command to be executed in the system shell.
484 556
485 557 Returns
486 558 -------
487 559 None : we explicitly do NOT return the subprocess status code, as this
488 560 utility is meant to be used extensively in IPython, where any return value
489 561 would trigger :func:`sys.displayhook` calls.
490 562 """
491 563 with AvoidUNCPath() as path:
492 564 if path is not None:
493 565 cmd = '"pushd %s &&"%s' % (path, cmd)
494 566 with Win32ShellCommandController(cmd) as scc:
495 567 scc.run()
496 568
497 569
498 570 if __name__ == "__main__":
499 571 print("Test starting!")
500 572 #system("cmd")
501 573 system("python -i")
502 574 print("Test finished!")
General Comments 0
You need to be logged in to leave comments. Login now