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. |
|
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 |
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. |
|
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. | |
|
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='') | |||
|
455 | return None | |||
428 | else: |
|
456 | else: | |
429 | # This should make it poll at about 100 Hz, which |
|
457 | data = ctypes.create_string_buffer(256) | |
430 | # is hopefully good enough to be responsive but |
|
458 | bytesRead = DWORD(0) | |
431 | # doesn't waste CPU. |
|
459 | print('?', end='') | |
432 | time.sleep(0.01) |
|
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 | |||
433 | return None |
|
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, | |
|
524 | stderr_func = self._stderr_raw) | |||
457 |
|
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