##// 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 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
@@ -121,10 +122,10 b' def system(cmd):'
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):
@@ -58,15 +58,20 b' LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)'
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
@@ -108,6 +113,18 b' 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
@@ -262,7 +279,7 b' class Win32ShellCommandController(object):'
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,
@@ -307,8 +324,6 b' class Win32ShellCommandController(object):'
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:
@@ -351,6 +366,9 b' class Win32ShellCommandController(object):'
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:]
@@ -385,20 +403,21 b' class Win32ShellCommandController(object):'
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,
@@ -406,6 +425,8 b' class Win32ShellCommandController(object):'
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
@@ -416,29 +437,67 b' class Win32ShellCommandController(object):'
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.
@@ -447,14 +506,27 b' class Win32ShellCommandController(object):'
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:
General Comments 0
You need to be logged in to leave comments. Login now