Estou procurando uma maneira de iniciar aplicativos do Windows (o host) em uma máquina virtual do Ubuntu (no VMWare Player). Fiquei um pouco empolgado e escrevi os scripts de cliente e servidor listados abaixo. O sistema operacional convidado não é o Windows, portanto, algumas alterações precisarão funcionar para um convidado do Windows. Eu usei esta configuração para que o Git (que roda no guest do Ubuntu) chame o KDiff3 no host ao mesclar.
O seguinte script Python (host_run_server.py) atua como um servidor aceitando comandos do convidado. Ele espera que o convidado forneça um compartilhamento do Samba com o nome GUEST_ROOT_SHARE
(configure isso na parte superior do script) que expõe a raiz do sistema de arquivos. Esse compartilhamento é mapeado para uma unidade GUEST_DRIVE
. Isso é necessário para que o host e o convidado possam acessar os mesmos arquivos. No meu caso, eu já tinha também montado 'Meus Documentos' para uma pasta no convidado para poder usar o git nos arquivos do meu host.
import asyncore, asynchat
import os
import socket
import shlex, subprocess
import threading
# make the root / of the guest accessible as a samba share and map
# this share in the host to GUEST_DRIVE
HOST_IP = '192.168.126.1'
GUEST_IP = '192.168.126.129'
GUEST_ROOT_SHARE = 'root'
GUEST_DRIVE = 'K:'
TCP_PORT = 5005
BUFFER_SIZE = 1024
ENCODING = 'utf-8'
# map network drive
try:
import win32wnet
import pywintypes
from win32netcon import RESOURCETYPE_DISK
network_path = r'\{}\{}'.format(GUEST_IP, GUEST_ROOT_SHARE)
try:
win32wnet.WNetAddConnection2(RESOURCETYPE_DISK, GUEST_DRIVE, network_path)
except pywintypes.error as e:
if (e.args[0] != 85 or
win32wnet.WNetGetUniversalName(GUEST_DRIVE) != network_path):
raise
except ImportError:
pass
# allow GUI applications to pop to front on Windows
try:
import win32gui
from win32con import SPI_SETFOREGROUNDLOCKTIMEOUT
result = win32gui.SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0)
if result is not None:
print("Failed:", result)
except ImportError:
pass
class Handler(asynchat.async_chat):
def __init__(self, sock, map=None):
asynchat.async_chat.__init__(self, sock, map=map)
self.remote_ip, self.remote_port = self.socket.getpeername()
self.log('connected')
self.set_terminator(b'\x00')
self.data = b''
self.state = 'cwd'
def handle_close(self):
remote_ip, remote_port = self.socket.getpeername()
self.log('disconnected')
self.close()
def collect_incoming_data(self, data):
self.data += data
def found_terminator(self):
if self.state == 'cwd':
self.cwd = self.data.decode(ENCODING)
self.state = 'cmd'
self.data = b''
elif self.state == 'cmd':
self.cmd = self.data.decode(ENCODING)
self.reply()
self.state = 'end'
def prepare(self):
cwd = GUEST_DRIVE + self.cwd.replace('/', '\')
self.log('in {}'.format(cwd))
os.chdir(cwd)
cmd_args = []
for arg in shlex.split(self.cmd):
if arg.startswith('[FILE]'):
arg = arg[6:].replace('/', '\')
if arg.startswith('\'):
arg = GUEST_DRIVE + arg
cmd_args.append(arg)
return cwd, cmd_args
def run(self, cwd, cmd_args):
self.log('executing: {}'.format(' '.join(cmd_args)))
try:
p = subprocess.Popen(cmd_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
out, err = p.communicate()
rcode = p.returncode
except WindowsError as e:
out = b''
err = '{}: {}\n'.format(e.__class__.__name__, e.args[1]).encode(ENCODING)
rcode = -1
return rcode, out, err
def reply(self):
cwd, cmd_args = self.prepare()
rc, out, err = self.run(cwd, cmd_args)
self.push(str(len(out)).encode(ENCODING) + b'\x00')
if len(out):
self.push(out)
self.push(str(len(err)).encode(ENCODING) + b'\x00')
if len(err):
self.push(err)
self.push(str(rc).encode(ENCODING) + b'\x00')
def log(self, msg):
print("[{}:{}]\t{}".format(self.remote_ip, self.remote_port, msg))
class HandlerThread(threading.Thread):
def __init__(self, sock):
super().__init__()
self.sock = sock
def run(self):
handler = Handler(self.sock)
asyncore.loop(map=handler._map)
class Server(asyncore.dispatcher):
def __init__(self, host, port, guest_ip):
asyncore.dispatcher.__init__(self, map={})
self.guest_ip = guest_ip
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind((host, port))
self.listen(5)
print("Service started. Listening on {} port {}."
.format(host, port))
def handle_accepted(self, sock, addr):
(guest_ip, guest_port) = addr
if guest_ip == self.guest_ip:
ht = HandlerThread(sock)
ht.start()
else:
print("Ignoring request from {}".format(guest_ip))
server = Server(HOST_IP, TCP_PORT, GUEST_IP)
asyncore.loop(map=server._map)
Abaixo está o script para invocar no lado do convidado (host_run.py).
#!/usr/bin/env python3
import asyncore, asynchat
import os
import socket
import sys
from optparse import OptionParser
HOST_IP = "192.168.126.1"
GUEST_IP = "192.168.126.129"
HOST_IS_WINDOWS = True
TCP_PORT = 5005
BUFFER_SIZE = 1024
ENCODING = 'utf-8'
STD_ENCODING = 'cp1252' if HOST_IS_WINDOWS else ENCODING
class HostRun(asynchat.async_chat):
def __init__(self, host, port):
asynchat.async_chat.__init__(self)
self.set_terminator(b'\x00')
self.data = b''
self.state = 'stdout1'
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def handle_connect(self):
self.push(os.getcwd().encode(ENCODING) + b'\x00')
self.push(command.encode(ENCODING) + b'\x00')
def collect_incoming_data(self, data):
self.data += data
def found_terminator(self):
if self.state == 'stdout1':
stdout_len = int(self.data.decode(ENCODING))
if stdout_len:
self.set_terminator(stdout_len)
self.state = 'stdout2'
else:
self.state = 'stderr1'
elif self.state == 'stdout2':
stdout = self.data.decode(STD_ENCODING)
sys.stdout.write(stdout)
self.set_terminator(b'\x00')
self.state = 'stderr1'
elif self.state == 'stderr1':
stderr_len = int(self.data.decode(ENCODING))
if stderr_len:
self.set_terminator(stderr_len)
self.state = 'stderr2'
else:
self.state = 'rc'
elif self.state == 'stderr2':
stderr = self.data.decode(STD_ENCODING)
sys.stderr.write(stderr)
self.set_terminator(b'\x00')
self.state = 'rc'
elif self.state == 'rc':
rc = int(self.data.decode(ENCODING))
sys.exit(rc)
self.close_when_done()
self.data = b''
def handle_close(self):
remote_ip, remote_port = self.socket.getpeername()
print("%s:%s disconnected" %(remote_ip, remote_port))
self.close()
parser = OptionParser()
(options, args) = parser.parse_args()
command = ' '.join(args)
HostRun(HOST_IP, TCP_PORT)
asyncore.loop()
Os scripts cuidam da tradução de caminhos de arquivos. Para que isso funcione, é necessário preceder os caminhos passados como argumentos para o script de cliente com [FILE]
Primeiro, inicie o script do servidor no host. Agora você pode passar comandos para o script do cliente:
brecht@krubuntu ~ $ ./host_run.py dir [FILE]/home
Isso traduzirá /home
para K:\home
e, portanto, executará dir K:\home
no host. O servidor envia a saída stdout / stderr e retorna o código para o cliente, que o envia de volta para o prompt do shell:
Volume in drive K is root
Volume Serial Number is 64C2-522A
Directory of K:\home
07/22/2012 22:13 <DIR> .
12/04/2012 06:53 <DIR> ..
02/28/2013 21:56 <DIR> brecht
0 File(s) 0 bytes
3 Dir(s) 12,723,302,400 bytes free