##// END OF EJS Templates
Short in the dark windows issue
M Bussonnier -
Show More
@@ -1,210 +1,213
1 """Common utilities for the various process_* implementations.
1 """Common utilities for the various process_* implementations.
2
2
3 This file is only meant to be imported by the platform-specific implementations
3 This file is only meant to be imported by the platform-specific implementations
4 of subprocess utilities, and it contains tools that are common to all of them.
4 of subprocess utilities, and it contains tools that are common to all of them.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import subprocess
17 import subprocess
18 import shlex
18 import shlex
19 import sys
19 import sys
20 import os
20 import os
21
21 from typing import Callable, Optional, Union, List
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Function definitions
25 # Function definitions
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 def read_no_interrupt(p):
28 def read_no_interrupt(p: subprocess.Popen):
29 """Read from a pipe ignoring EINTR errors.
29 """Read from a pipe ignoring EINTR errors.
30
30
31 This is necessary because when reading from pipes with GUI event loops
31 This is necessary because when reading from pipes with GUI event loops
32 running in the background, often interrupts are raised that stop the
32 running in the background, often interrupts are raised that stop the
33 command from completing."""
33 command from completing."""
34 import errno
34 import errno
35
35
36 try:
36 try:
37 return p.read()
37 return p.read()
38 except IOError as err:
38 except IOError as err:
39 if err.errno != errno.EINTR:
39 if err.errno != errno.EINTR:
40 raise
40 raise
41
41
42
42
43 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 def process_handler(cmd:Union[str, List[str]], callback:Callable[[subprocess.Popen], int], stderr=subprocess.PIPE) -> Optional[int]:
44 """Open a command in a shell subprocess and execute a callback.
44 """Open a command in a shell subprocess and execute a callback.
45
45
46 This function provides common scaffolding for creating subprocess.Popen()
46 This function provides common scaffolding for creating subprocess.Popen()
47 calls. It creates a Popen object and then calls the callback with it.
47 calls. It creates a Popen object and then calls the callback with it.
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 cmd : str or list
51 cmd : str or list
52 A command to be executed by the system, using :class:`subprocess.Popen`.
52 A command to be executed by the system, using :class:`subprocess.Popen`.
53 If a string is passed, it will be run in the system shell. If a list is
53 If a string is passed, it will be run in the system shell. If a list is
54 passed, it will be used directly as arguments.
54 passed, it will be used directly as arguments.
55 callback : callable
55 callback : callable
56 A one-argument function that will be called with the Popen object.
56 A one-argument function that will be called with the Popen object.
57 stderr : file descriptor number, optional
57 stderr : file descriptor number, optional
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 the same file descriptor as its stdout. This is useful to read stdout
60 the same file descriptor as its stdout. This is useful to read stdout
61 and stderr combined in the order they are generated.
61 and stderr combined in the order they are generated.
62
62
63 Returns
63 Returns
64 -------
64 -------
65 The return value of the provided callback is returned.
65 The return value of the provided callback is returned.
66 """
66 """
67 sys.stdout.flush()
67 sys.stdout.flush()
68 sys.stderr.flush()
68 sys.stderr.flush()
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 close_fds = sys.platform != 'win32'
70 if sys.platform == "win32" and stderr != subprocess.PIPE:
71 close_fds = False
72 else:
73 close_fds = True
71 # Determine if cmd should be run with system shell.
74 # Determine if cmd should be run with system shell.
72 shell = isinstance(cmd, str)
75 shell = isinstance(cmd, str)
73 # On POSIX systems run shell commands with user-preferred shell.
76 # On POSIX systems run shell commands with user-preferred shell.
74 executable = None
77 executable = None
75 if shell and os.name == 'posix' and 'SHELL' in os.environ:
78 if shell and os.name == 'posix' and 'SHELL' in os.environ:
76 executable = os.environ['SHELL']
79 executable = os.environ['SHELL']
77 p = subprocess.Popen(cmd, shell=shell,
80 p = subprocess.Popen(cmd, shell=shell,
78 executable=executable,
81 executable=executable,
79 stdin=subprocess.PIPE,
82 stdin=subprocess.PIPE,
80 stdout=subprocess.PIPE,
83 stdout=subprocess.PIPE,
81 stderr=stderr,
84 stderr=stderr,
82 close_fds=close_fds)
85 close_fds=close_fds)
83
86
84 try:
87 try:
85 out = callback(p)
88 out = callback(p)
86 except KeyboardInterrupt:
89 except KeyboardInterrupt:
87 print('^C')
90 print('^C')
88 sys.stdout.flush()
91 sys.stdout.flush()
89 sys.stderr.flush()
92 sys.stderr.flush()
90 out = None
93 out = None
91 finally:
94 finally:
92 # Make really sure that we don't leave processes behind, in case the
95 # Make really sure that we don't leave processes behind, in case the
93 # call above raises an exception
96 # call above raises an exception
94 # We start by assuming the subprocess finished (to avoid NameErrors
97 # We start by assuming the subprocess finished (to avoid NameErrors
95 # later depending on the path taken)
98 # later depending on the path taken)
96 if p.returncode is None:
99 if p.returncode is None:
97 try:
100 try:
98 p.terminate()
101 p.terminate()
99 p.poll()
102 p.poll()
100 except OSError:
103 except OSError:
101 pass
104 pass
102 # One last try on our way out
105 # One last try on our way out
103 if p.returncode is None:
106 if p.returncode is None:
104 try:
107 try:
105 p.kill()
108 p.kill()
106 except OSError:
109 except OSError:
107 pass
110 pass
108
111
109 return out
112 return out
110
113
111
114
112 def getoutput(cmd):
115 def getoutput(cmd):
113 """Run a command and return its stdout/stderr as a string.
116 """Run a command and return its stdout/stderr as a string.
114
117
115 Parameters
118 Parameters
116 ----------
119 ----------
117 cmd : str or list
120 cmd : str or list
118 A command to be executed in the system shell.
121 A command to be executed in the system shell.
119
122
120 Returns
123 Returns
121 -------
124 -------
122 output : str
125 output : str
123 A string containing the combination of stdout and stderr from the
126 A string containing the combination of stdout and stderr from the
124 subprocess, in whatever order the subprocess originally wrote to its
127 subprocess, in whatever order the subprocess originally wrote to its
125 file descriptors (so the order of the information in this string is the
128 file descriptors (so the order of the information in this string is the
126 correct order as would be seen if running the command in a terminal).
129 correct order as would be seen if running the command in a terminal).
127 """
130 """
128 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
131 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
129 if out is None:
132 if out is None:
130 return ''
133 return ''
131 return py3compat.decode(out)
134 return py3compat.decode(out)
132
135
133
136
134 def getoutputerror(cmd):
137 def getoutputerror(cmd):
135 """Return (standard output, standard error) of executing cmd in a shell.
138 """Return (standard output, standard error) of executing cmd in a shell.
136
139
137 Accepts the same arguments as os.system().
140 Accepts the same arguments as os.system().
138
141
139 Parameters
142 Parameters
140 ----------
143 ----------
141 cmd : str or list
144 cmd : str or list
142 A command to be executed in the system shell.
145 A command to be executed in the system shell.
143
146
144 Returns
147 Returns
145 -------
148 -------
146 stdout : str
149 stdout : str
147 stderr : str
150 stderr : str
148 """
151 """
149 return get_output_error_code(cmd)[:2]
152 return get_output_error_code(cmd)[:2]
150
153
151 def get_output_error_code(cmd):
154 def get_output_error_code(cmd):
152 """Return (standard output, standard error, return code) of executing cmd
155 """Return (standard output, standard error, return code) of executing cmd
153 in a shell.
156 in a shell.
154
157
155 Accepts the same arguments as os.system().
158 Accepts the same arguments as os.system().
156
159
157 Parameters
160 Parameters
158 ----------
161 ----------
159 cmd : str or list
162 cmd : str or list
160 A command to be executed in the system shell.
163 A command to be executed in the system shell.
161
164
162 Returns
165 Returns
163 -------
166 -------
164 stdout : str
167 stdout : str
165 stderr : str
168 stderr : str
166 returncode: int
169 returncode: int
167 """
170 """
168
171
169 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
172 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
170 if out_err is None:
173 if out_err is None:
171 return '', '', p.returncode
174 return '', '', p.returncode
172 out, err = out_err
175 out, err = out_err
173 return py3compat.decode(out), py3compat.decode(err), p.returncode
176 return py3compat.decode(out), py3compat.decode(err), p.returncode
174
177
175 def arg_split(s, posix=False, strict=True):
178 def arg_split(s, posix=False, strict=True):
176 """Split a command line's arguments in a shell-like manner.
179 """Split a command line's arguments in a shell-like manner.
177
180
178 This is a modified version of the standard library's shlex.split()
181 This is a modified version of the standard library's shlex.split()
179 function, but with a default of posix=False for splitting, so that quotes
182 function, but with a default of posix=False for splitting, so that quotes
180 in inputs are respected.
183 in inputs are respected.
181
184
182 if strict=False, then any errors shlex.split would raise will result in the
185 if strict=False, then any errors shlex.split would raise will result in the
183 unparsed remainder being the last element of the list, rather than raising.
186 unparsed remainder being the last element of the list, rather than raising.
184 This is because we sometimes use arg_split to parse things other than
187 This is because we sometimes use arg_split to parse things other than
185 command-line args.
188 command-line args.
186 """
189 """
187
190
188 lex = shlex.shlex(s, posix=posix)
191 lex = shlex.shlex(s, posix=posix)
189 lex.whitespace_split = True
192 lex.whitespace_split = True
190 # Extract tokens, ensuring that things like leaving open quotes
193 # Extract tokens, ensuring that things like leaving open quotes
191 # does not cause this to raise. This is important, because we
194 # does not cause this to raise. This is important, because we
192 # sometimes pass Python source through this (e.g. %timeit f(" ")),
195 # sometimes pass Python source through this (e.g. %timeit f(" ")),
193 # and it shouldn't raise an exception.
196 # and it shouldn't raise an exception.
194 # It may be a bad idea to parse things that are not command-line args
197 # It may be a bad idea to parse things that are not command-line args
195 # through this function, but we do, so let's be safe about it.
198 # through this function, but we do, so let's be safe about it.
196 lex.commenters='' #fix for GH-1269
199 lex.commenters='' #fix for GH-1269
197 tokens = []
200 tokens = []
198 while True:
201 while True:
199 try:
202 try:
200 tokens.append(next(lex))
203 tokens.append(next(lex))
201 except StopIteration:
204 except StopIteration:
202 break
205 break
203 except ValueError:
206 except ValueError:
204 if strict:
207 if strict:
205 raise
208 raise
206 # couldn't parse, get remaining blob as last token
209 # couldn't parse, get remaining blob as last token
207 tokens.append(lex.token)
210 tokens.append(lex.token)
208 break
211 break
209
212
210 return tokens
213 return tokens
@@ -1,184 +1,200
1 """Windows-specific implementation of process utilities.
1 """Windows-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 # stdlib
17 # stdlib
18 import os
18 import os
19 import sys
19 import sys
20 import ctypes
20 import ctypes
21 import time
21 import time
22
22
23 from ctypes import c_int, POINTER
23 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from subprocess import STDOUT, TimeoutExpired
25 from subprocess import STDOUT, TimeoutExpired
26 from threading import Thread
26 from threading import Thread
27 import subprocess
27
28
28 # our own imports
29 # our own imports
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 from . import py3compat
31 from . import py3compat
31 from .encoding import DEFAULT_ENCODING
32 from .encoding import DEFAULT_ENCODING
32
33
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34 # Function definitions
35 # Function definitions
35 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
36
37
37 class AvoidUNCPath(object):
38 class AvoidUNCPath:
38 """A context manager to protect command execution from UNC paths.
39 """A context manager to protect command execution from UNC paths.
39
40
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 This context manager temporarily changes directory to the 'C:' drive on
42 This context manager temporarily changes directory to the 'C:' drive on
42 entering, and restores the original working directory on exit.
43 entering, and restores the original working directory on exit.
43
44
44 The context manager returns the starting working directory *if* it made a
45 The context manager returns the starting working directory *if* it made a
45 change and None otherwise, so that users can apply the necessary adjustment
46 change and None otherwise, so that users can apply the necessary adjustment
46 to their system calls in the event of a change.
47 to their system calls in the event of a change.
47
48
48 Examples
49 Examples
49 --------
50 --------
50 ::
51 ::
51 cmd = 'dir'
52 cmd = 'dir'
52 with AvoidUNCPath() as path:
53 with AvoidUNCPath() as path:
53 if path is not None:
54 if path is not None:
54 cmd = '"pushd %s &&"%s' % (path, cmd)
55 cmd = '"pushd %s &&"%s' % (path, cmd)
55 os.system(cmd)
56 os.system(cmd)
56 """
57 """
57 def __enter__(self):
58 def __enter__(self):
58 self.path = os.getcwd()
59 self.path = os.getcwd()
59 self.is_unc_path = self.path.startswith(r"\\")
60 self.is_unc_path = self.path.startswith(r"\\")
60 if self.is_unc_path:
61 if self.is_unc_path:
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 os.chdir("C:")
63 os.chdir("C:")
63 return self.path
64 return self.path
64 else:
65 else:
65 # We return None to signal that there was no change in the working
66 # We return None to signal that there was no change in the working
66 # directory
67 # directory
67 return None
68 return None
68
69
69 def __exit__(self, exc_type, exc_value, traceback):
70 def __exit__(self, exc_type, exc_value, traceback):
70 if self.is_unc_path:
71 if self.is_unc_path:
71 os.chdir(self.path)
72 os.chdir(self.path)
72
73
73
74
74 def _system_body(p):
75 def _system_body(p: subprocess.Popen) -> int:
75 """Callback for _system."""
76 """Callback for _system."""
76 enc = DEFAULT_ENCODING
77 enc = DEFAULT_ENCODING
77
78
78 def stdout_read():
79 def stdout_read():
79 for line in read_no_interrupt(p.stdout).splitlines():
80 try:
80 line = line.decode(enc, 'replace')
81 for line in read_no_interrupt(p.stdout).splitlines():
81 print(line, file=sys.stdout)
82 line = line.decode(enc, 'replace')
83 print(line, file=sys.stdout)
84 except Exception as e:
85 print(f"Error reading stdout: {e}", file=sys.stderr)
82
86
83 def stderr_read():
87 def stderr_read():
84 for line in read_no_interrupt(p.stderr).splitlines():
88 try:
85 line = line.decode(enc, 'replace')
89 for line in read_no_interrupt(p.stderr).splitlines():
86 print(line, file=sys.stderr)
90 line = line.decode(enc, 'replace')
91 print(line, file=sys.stderr)
92 except Exception as e:
93 print(f"Error reading stderr: {e}", file=sys.stderr)
87
94
88 Thread(target=stdout_read).start()
95 stdout_thread = Thread(target=stdout_read)
89 Thread(target=stderr_read).start()
96 stderr_thread = Thread(target=stderr_read)
97
98 stdout_thread.start()
99 stderr_thread.start()
90
100
91 # Wait to finish for returncode. Unfortunately, Python has a bug where
101 # Wait to finish for returncode. Unfortunately, Python has a bug where
92 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
102 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
93 # a loop instead of just doing `return p.wait()`.
103 # a loop instead of just doing `return p.wait()`
94 while True:
104 while True:
95 result = p.poll()
105 result = p.poll()
96 if result is None:
106 if result is None:
97 time.sleep(0.01)
107 time.sleep(0.01)
98 else:
108 else:
99 return result
109 break
110
111 # Join the threads to ensure they complete before returning
112 stdout_thread.join()
113 stderr_thread.join()
114
115 return result
100
116
101
117
102 def system(cmd):
118 def system(cmd: str):
103 """Win32 version of os.system() that works with network shares.
119 """Win32 version of os.system() that works with network shares.
104
120
105 Note that this implementation returns None, as meant for use in IPython.
121 Note that this implementation returns None, as meant for use in IPython.
106
122
107 Parameters
123 Parameters
108 ----------
124 ----------
109 cmd : str or list
125 cmd : str or list
110 A command to be executed in the system shell.
126 A command to be executed in the system shell.
111
127
112 Returns
128 Returns
113 -------
129 -------
114 int : child process' exit code.
130 int : child process' exit code.
115 """
131 """
116 # The controller provides interactivity with both
132 # The controller provides interactivity with both
117 # stdin and stdout
133 # stdin and stdout
118 #import _process_win32_controller
134 #import _process_win32_controller
119 #_process_win32_controller.system(cmd)
135 #_process_win32_controller.system(cmd)
120
136
121 with AvoidUNCPath() as path:
137 with AvoidUNCPath() as path:
122 if path is not None:
138 if path is not None:
123 cmd = '"pushd %s &&"%s' % (path, cmd)
139 cmd = '"pushd %s &&"%s' % (path, cmd)
124 return process_handler(cmd, _system_body)
140 return process_handler(cmd, _system_body)
125
141
126 def getoutput(cmd):
142 def getoutput(cmd):
127 """Return standard output of executing cmd in a shell.
143 """Return standard output of executing cmd in a shell.
128
144
129 Accepts the same arguments as os.system().
145 Accepts the same arguments as os.system().
130
146
131 Parameters
147 Parameters
132 ----------
148 ----------
133 cmd : str or list
149 cmd : str or list
134 A command to be executed in the system shell.
150 A command to be executed in the system shell.
135
151
136 Returns
152 Returns
137 -------
153 -------
138 stdout : str
154 stdout : str
139 """
155 """
140
156
141 with AvoidUNCPath() as path:
157 with AvoidUNCPath() as path:
142 if path is not None:
158 if path is not None:
143 cmd = '"pushd %s &&"%s' % (path, cmd)
159 cmd = '"pushd %s &&"%s' % (path, cmd)
144 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
160 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
145
161
146 if out is None:
162 if out is None:
147 out = b''
163 out = b''
148 return py3compat.decode(out)
164 return py3compat.decode(out)
149
165
150 try:
166 try:
151 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
167 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
152 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
168 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
153 CommandLineToArgvW.restype = POINTER(LPCWSTR)
169 CommandLineToArgvW.restype = POINTER(LPCWSTR)
154 LocalFree = ctypes.windll.kernel32.LocalFree
170 LocalFree = ctypes.windll.kernel32.LocalFree
155 LocalFree.res_type = HLOCAL
171 LocalFree.res_type = HLOCAL
156 LocalFree.arg_types = [HLOCAL]
172 LocalFree.arg_types = [HLOCAL]
157
173
158 def arg_split(commandline, posix=False, strict=True):
174 def arg_split(commandline, posix=False, strict=True):
159 """Split a command line's arguments in a shell-like manner.
175 """Split a command line's arguments in a shell-like manner.
160
176
161 This is a special version for windows that use a ctypes call to CommandLineToArgvW
177 This is a special version for windows that use a ctypes call to CommandLineToArgvW
162 to do the argv splitting. The posix parameter is ignored.
178 to do the argv splitting. The posix parameter is ignored.
163
179
164 If strict=False, process_common.arg_split(...strict=False) is used instead.
180 If strict=False, process_common.arg_split(...strict=False) is used instead.
165 """
181 """
166 #CommandLineToArgvW returns path to executable if called with empty string.
182 #CommandLineToArgvW returns path to executable if called with empty string.
167 if commandline.strip() == "":
183 if commandline.strip() == "":
168 return []
184 return []
169 if not strict:
185 if not strict:
170 # not really a cl-arg, fallback on _process_common
186 # not really a cl-arg, fallback on _process_common
171 return py_arg_split(commandline, posix=posix, strict=strict)
187 return py_arg_split(commandline, posix=posix, strict=strict)
172 argvn = c_int()
188 argvn = c_int()
173 result_pointer = CommandLineToArgvW(commandline.lstrip(), ctypes.byref(argvn))
189 result_pointer = CommandLineToArgvW(commandline.lstrip(), ctypes.byref(argvn))
174 result_array_type = LPCWSTR * argvn.value
190 result_array_type = LPCWSTR * argvn.value
175 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
191 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
176 retval = LocalFree(result_pointer)
192 retval = LocalFree(result_pointer)
177 return result
193 return result
178 except AttributeError:
194 except AttributeError:
179 arg_split = py_arg_split
195 arg_split = py_arg_split
180
196
181 def check_pid(pid):
197 def check_pid(pid):
182 # OpenProcess returns 0 if no such process (of ours) exists
198 # OpenProcess returns 0 if no such process (of ours) exists
183 # positive int otherwise
199 # positive int otherwise
184 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
200 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
General Comments 0
You need to be logged in to leave comments. Login now