Search code examples
pythonwindowsstdinstdiofilehandle

WIndows: subprocess making new console window, losing stdin/out


I'm using Windows Vista and Python 2.7.2, but answers needn't be in Python.

So I can start and interact with a subprocesses stdin/stdout normally (using python), for command-line programs such as `dir'.
- however -
the program I now want to call likes to make a new console window for itself on Windows (not curses), with new handles, even when run from a pre-existing cmd.exe window. (Odd, as it's the "remote control" interface of VLC.) Is there any way of either:

  1. getting the handles for the process-made console's stdin/out; or
  2. getting the new shell to run within the old (like invoking bash from within bash)?

Failing that, so that I can hack the subprocesses' code, how would a new console be set up in Windows and in/output transferred?

Edit: I.e.

>>> p = Popen(args=['vlc','-I','rc'],stdin=PIPE,stdout=PIPE)
# [New console appears with text, asking for commands]
>>> p.stdin.write("quit\r\n")
Traceback:
    File "<stdin>", line 1, in <module>
IOError: [Errno 22] Invalid argument
>>> p.stdout.readline()
''
>>> p.stdout.readline()
''
# [...]

But the new console window that comes up doesn't accept keyboard input either.

Whereas normally:

>>> p = Popen(args=['cmd'],stdin=PIPE,stdout=PIPE)
>>> p.stdin.write("dir\r\n")
>>> p.stdin.flush()
>>> p.stdout.readline() #Don't just do this IRL, may block.
'Microsoft Windows [Version...

Solution

  • I haven't gotten the rc interface to work with a piped stdin/stdout on Windows; I get IOError at all attempts to communicate or write directly to stdin. There's an option --rc-fake-tty that lets the rc interface be scripted on Linux, but it's not available in Windows -- at least not in my somewhat dated version of VLC (1.1.4). Using the socket interface, on the other hand, seems to work fine.

    The structure assigned to the startupinfo option -- and used by the Win32 CreateProcess function -- can be configured to hide a process window. However, for the VLC rc console, I think it's simpler to use the existing --rc-quiet option. In general, here's how to configure startupinfo to hide a process window:

    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    subprocess.Popen(cmd, startupinfo=startupinfo)
    

    Just to be complete -- in case using pipes is failing on your system too -- here's a little demo I cooked up using the --rc-host option to communicate using a socket. It also uses --rc-quiet to hide the console. This just prints the help and quits. I haven't tested anything else. I checked that it works in Python versions 2.7.2 and 3.2.2. (I know you didn't ask for this, but maybe it will be useful to you nonetheless.)

    import socket
    import subprocess
    from select import select
    
    try:
        import winreg
    except ImportError:
        import _winreg as winreg
    
    def _get_vlc_path():
        views = [(winreg.HKEY_CURRENT_USER, 0),
                 (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY),
                 (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY)]
        subkey = r'Software\VideoLAN\VLC'
        access = winreg.KEY_QUERY_VALUE
        for hroot, flag in views:
            try:
                with winreg.OpenKey(hroot, subkey, 0, access | flag) as hkey:
                    value, type_id = winreg.QueryValueEx(hkey, None)
                    if type_id == winreg.REG_SZ:
                        return value
            except WindowsError:
                pass
        raise SystemExit("Error: VLC not found.")
    
    g_vlc_path = _get_vlc_path()
    
    def send_command(sock, cmd, get_result=False):
        try:
            cmd = (cmd + '\n').encode('ascii')
        except AttributeError:
            cmd += b'\n'
        sent = total = sock.send(cmd)
        while total < len(cmd):
            sent = sock.send(cmd[total:])
            if sent == 0:
                raise socket.error('Socket connection broken.')
            total += sent
        if get_result:
            return receive_result(sock)
    
    def receive_result(sock):
        data = bytearray()
        sock.setblocking(0)
        while select([sock], [], [], 1.0)[0]:
            chunk = sock.recv(1024)
            if chunk == b'': 
                raise socket.error('Socket connection broken.')
            data.extend(chunk)
        sock.setblocking(1)
        return data.decode('utf-8')
    
    def main(address, port):
        import time
        rc_host = '{0}:{1}'.format(address, port)
        vlc = subprocess.Popen([g_vlc_path, '-I', 'rc', '--rc-host', rc_host, 
                                '--rc-quiet'])
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            sock.connect((address, port))
            help_msg = send_command(sock, 'help', True)
            print(help_msg)
            send_command(sock, 'quit')
        except socket.error as e:
            exit("Error: " + e.args[0])
        finally:
            sock.close()
            time.sleep(0.5)
            if vlc.poll() is None:
                vlc.terminate()
    
    if __name__ == '__main__':
        main('localhost', 12345)