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. |
|
|
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 |
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. |
|
|
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