##// END OF EJS Templates
Tweak out the process controller class so it works interactively better...
Mark Wiebe -
Show More
@@ -19,6 +19,7 b' from __future__ import print_function'
19 import os
19 import os
20 import sys
20 import sys
21 import ctypes
21 import ctypes
22 import msvcrt
22
23
23 from ctypes import c_int, POINTER
24 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from ctypes.wintypes import LPCWSTR, HLOCAL
@@ -121,10 +122,10 b' def system(cmd):'
121 utility is meant to be used extensively in IPython, where any return value
122 utility is meant to be used extensively in IPython, where any return value
122 would trigger :func:`sys.displayhook` calls.
123 would trigger :func:`sys.displayhook` calls.
123 """
124 """
124 with AvoidUNCPath() as path:
125 # The controller provides interactivity with both
125 if path is not None:
126 # stdin and stdout
126 cmd = '"pushd %s &&"%s' % (path, cmd)
127 import _process_win32_controller
127 return process_handler(cmd, _system_body)
128 _process_win32_controller.system(cmd)
128
129
129
130
130 def getoutput(cmd):
131 def getoutput(cmd):
@@ -58,15 +58,20 b' LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)'
58 # Win32 API constants needed
58 # Win32 API constants needed
59 ERROR_HANDLE_EOF = 38
59 ERROR_HANDLE_EOF = 38
60 ERROR_BROKEN_PIPE = 109
60 ERROR_BROKEN_PIPE = 109
61 ERROR_NO_DATA = 232
61 HANDLE_FLAG_INHERIT = 0x0001
62 HANDLE_FLAG_INHERIT = 0x0001
62 STARTF_USESTDHANDLES = 0x0100
63 STARTF_USESTDHANDLES = 0x0100
63 CREATE_SUSPENDED = 0x0004
64 CREATE_SUSPENDED = 0x0004
64 CREATE_NEW_CONSOLE = 0x0010
65 CREATE_NEW_CONSOLE = 0x0010
66 CREATE_NO_WINDOW = 0x08000000
65 STILL_ACTIVE = 259
67 STILL_ACTIVE = 259
66 WAIT_TIMEOUT = 0x0102
68 WAIT_TIMEOUT = 0x0102
67 WAIT_FAILED = 0xFFFFFFFF
69 WAIT_FAILED = 0xFFFFFFFF
68 INFINITE = 0xFFFFFFFF
70 INFINITE = 0xFFFFFFFF
69 DUPLICATE_SAME_ACCESS = 0x00000002
71 DUPLICATE_SAME_ACCESS = 0x00000002
72 ENABLE_ECHO_INPUT = 0x0004
73 ENABLE_LINE_INPUT = 0x0002
74 ENABLE_PROCESSED_INPUT = 0x0001
70
75
71 # Win32 API functions needed
76 # Win32 API functions needed
72 GetLastError = ctypes.windll.kernel32.GetLastError
77 GetLastError = ctypes.windll.kernel32.GetLastError
@@ -108,6 +113,18 b' WriteFile = ctypes.windll.kernel32.WriteFile'
108 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
113 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
109 WriteFile.restype = BOOL
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 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
128 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
112 WaitForSingleObject.argtypes = [HANDLE, DWORD]
129 WaitForSingleObject.argtypes = [HANDLE, DWORD]
113 WaitForSingleObject.restype = DWORD
130 WaitForSingleObject.restype = DWORD
@@ -262,7 +279,7 b' class Win32ShellCommandController(object):'
262 siStartInfo.hStdOutput = c_hstdout
279 siStartInfo.hStdOutput = c_hstdout
263 siStartInfo.hStdError = c_hstderr
280 siStartInfo.hStdError = c_hstderr
264 siStartInfo.dwFlags = STARTF_USESTDHANDLES
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 if not CreateProcess(None,
284 if not CreateProcess(None,
268 u"cmd.exe /c " + cmd,
285 u"cmd.exe /c " + cmd,
@@ -307,8 +324,6 b' class Win32ShellCommandController(object):'
307 return self
324 return self
308
325
309 def _stdin_thread(self, handle, hprocess, func, stdout_func):
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 exitCode = DWORD()
327 exitCode = DWORD()
313 bytesWritten = DWORD(0)
328 bytesWritten = DWORD(0)
314 while True:
329 while True:
@@ -351,6 +366,9 b' class Win32ShellCommandController(object):'
351 #print("Calling writefile")
366 #print("Calling writefile")
352 if not WriteFile(handle, data, len(data),
367 if not WriteFile(handle, data, len(data),
353 ctypes.byref(bytesWritten), None):
368 ctypes.byref(bytesWritten), None):
369 # This occurs at exit
370 if GetLastError() == ERROR_NO_DATA:
371 return
354 raise ctypes.WinError()
372 raise ctypes.WinError()
355 #print("Called writefile")
373 #print("Called writefile")
356 data = data[bytesWritten.value:]
374 data = data[bytesWritten.value:]
@@ -385,20 +403,21 b' class Win32ShellCommandController(object):'
385 if stdout_func == None and stdin_func == None and stderr_func == None:
403 if stdout_func == None and stdin_func == None and stderr_func == None:
386 return self._run_stdio()
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 raise RuntimeError("Shell command was initiated with "
407 raise RuntimeError("Shell command was initiated with "
390 "merged stdin/stdout, but a separate stderr_func "
408 "merged stdin/stdout, but a separate stderr_func "
391 "was provided to the run() method")
409 "was provided to the run() method")
392
410
393 # Create a thread for each input/output handle
411 # Create a thread for each input/output handle
412 stdin_thread = None
394 threads = []
413 threads = []
395 if stdin_func:
414 if stdin_func:
396 threads.append(threading.Thread(target=self._stdin_thread,
415 stdin_thread = threading.Thread(target=self._stdin_thread,
397 args=(self.hstdin, self.piProcInfo.hProcess,
416 args=(self.hstdin, self.piProcInfo.hProcess,
398 stdin_func, stdout_func)))
417 stdin_func, stdout_func))
399 threads.append(threading.Thread(target=self._stdout_thread,
418 threads.append(threading.Thread(target=self._stdout_thread,
400 args=(self.hstdout, stdout_func)))
419 args=(self.hstdout, stdout_func)))
401 if self.hstderr != None:
420 if not self.mergeout:
402 if stderr_func == None:
421 if stderr_func == None:
403 stderr_func = stdout_func
422 stderr_func = stdout_func
404 threads.append(threading.Thread(target=self._stdout_thread,
423 threads.append(threading.Thread(target=self._stdout_thread,
@@ -406,6 +425,8 b' class Win32ShellCommandController(object):'
406 # Start the I/O threads and the process
425 # Start the I/O threads and the process
407 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
426 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
408 raise ctypes.WinError()
427 raise ctypes.WinError()
428 if stdin_thread is not None:
429 stdin_thread.start()
409 for thread in threads:
430 for thread in threads:
410 thread.start()
431 thread.start()
411 # Wait for the process to complete
432 # Wait for the process to complete
@@ -416,29 +437,67 b' class Win32ShellCommandController(object):'
416 for thread in threads:
437 for thread in threads:
417 thread.join()
438 thread.join()
418
439
419 def _stdin_raw(self):
440 # Wait for the stdin thread to complete
420 """Uses msvcrt.kbhit/getwch to do read stdin without blocking"""
441 if stdin_thread is not None:
421 if msvcrt.kbhit():
442 stdin_thread.join()
422 #s = msvcrt.getwch()
443
423 s = msvcrt.getwch()
444 def _stdin_raw_nonblock(self):
424 # Key code for Enter is '\r', but need to give back '\n'
445 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
425 if s == u'\r':
446 # WARNING: This is experimental, and produces inconsistent results.
426 s = u'\n'
447 # It's possible for the handle not to be appropriate for use
427 return s
448 # with WaitForSingleObject, among other things.
428 else:
449 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
429 # This should make it poll at about 100 Hz, which
450 result = WaitForSingleObject(handle, 100)
430 # is hopefully good enough to be responsive but
451 if result == WAIT_FAILED:
431 # doesn't waste CPU.
452 raise ctypes.WinError()
432 time.sleep(0.01)
453 elif result == WAIT_TIMEOUT:
454 print(".", end='')
433 return None
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 def _stdout_raw(self, s):
492 def _stdout_raw(self, s):
436 """Writes the string to stdout"""
493 """Writes the string to stdout"""
437 print(s, end='', file=sys.stdout)
494 print(s, end='', file=sys.stdout)
495 sys.stdout.flush()
438
496
439 def _stderr_raw(self, s):
497 def _stderr_raw(self, s):
440 """Writes the string to stdout"""
498 """Writes the string to stdout"""
441 print(s, end='', file=sys.stderr)
499 print(s, end='', file=sys.stderr)
500 sys.stderr.flush()
442
501
443 def _run_stdio(self):
502 def _run_stdio(self):
444 """Runs the process using the system standard I/O.
503 """Runs the process using the system standard I/O.
@@ -447,14 +506,27 b' class Win32ShellCommandController(object):'
447 sys.stdin object is not used. Instead,
506 sys.stdin object is not used. Instead,
448 msvcrt.kbhit/getwch are used asynchronously.
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 return self.run(stdout_func = self._stdout_raw,
519 return self.run(stdout_func = self._stdout_raw,
452 stdin_func = self._stdin_raw,
520 stdin_func = self._stdin_raw_block)
453 stderr_func = self._stderr_raw)
454 else:
521 else:
455 return self.run(stdout_func = self._stdout_raw,
522 return self.run(stdout_func = self._stdout_raw,
456 stdin_func = self._stdin_raw)
523 stdin_func = self._stdin_raw_block,
457
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 def __exit__(self, exc_type, exc_value, traceback):
531 def __exit__(self, exc_type, exc_value, traceback):
460 if self.hstdin:
532 if self.hstdin:
General Comments 0
You need to be logged in to leave comments. Login now