Я искал способ запуска приложений Windows (хост) с виртуальной машины Ubuntu (под VMWare Player). Я немного увлекся и написал сценарии клиента и сервера, перечисленные ниже. Гостевая ОС - это не Windows, поэтому для работы с гостем Windows потребуются некоторые изменения. Я использовал эту настройку, чтобы Git (работающий на гостевой системе Ubuntu) вызывал KDiff3 на хосте при слиянии.
Следующий скрипт Python (host_run_server.py) действует как сервер, принимающий команды от гостя. Он ожидает, что гость предоставит общий ресурс Samba с именем GUEST_ROOT_SHARE
(установите его в верхней части скрипта), который открывает корень файловой системы. Этот общий ресурс сопоставлен с диском GUEST_DRIVE
. Это необходимо для того, чтобы хост и гость могли получить доступ к одним и тем же файлам. В моем случае я уже смонтировал «Мои документы» в папке гостя, чтобы иметь возможность использовать git для файлов моего хоста.
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)
Ниже приведен скрипт для вызова на гостевой стороне (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()
Сценарии заботятся о переводе путей к файлам. Для того чтобы это работало, вам необходимо добавить пути, переданные в качестве аргументов клиентскому скрипту, с помощью [FILE]
Сначала запустите серверный скрипт на хосте. Теперь вы можете передавать команды клиентскому скрипту:
brecht@krubuntu ~ $ ./host_run.py dir [FILE]/home
Это переведет /home
в K:\home
и, таким образом, выполнит команду dir K:\home
на хосте. Сервер отправляет вывод stdout/stderr и код возврата обратно клиенту, который выплевывает его обратно в приглашение оболочки:
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