/usr/bin/Xspice is in xserver-xspice 0.1.5-2build1.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | #!/usr/bin/python
"""
Xspice
Xspice is a standard X server that is also a Spice server.
It is implemented as a module with video, mouse and keyboard drivers.
The video driver is mostly the same code as the qxl guest driver, hence
Xspice is kept in the same repository. It can also be used to debug the qxl
driver.
Xspice (this executable) will set a bunch of environment variables that are
used by spiceqxl_drv.so, and then spawn Xorg, giving it the default config file,
which can be overridden as well.
"""
import argparse
import os
import sys
import tempfile
import atexit
import time
import signal
from subprocess import Popen, PIPE
def which(x):
if not x:
return x
if os.path.exists(x):
return x
for p in os.environ['PATH'].split(':'):
candidate = os.path.join(p, x)
if os.path.exists(candidate):
return candidate
print 'Warning: failed to find executable %s' % x
return None
if 'XSPICE_ENABLE_GDB' in os.environ:
cgdb = which('cgdb')
if not cgdb:
cgdb = which('gdb')
else:
cgdb = None
def add_boolean(flag, *args, **kw):
parser.add_argument(flag, action='store_const', const='1',
*args, **kw)
wan_compression_options = ['auto', 'never', 'always']
parser = argparse.ArgumentParser("Xspice",
description="X and Spice server. example usage: Xspice --port 5900 --disable-ticketing :1.0",
usage="Xspice [Xspice and Xorg options intermixed]",
epilog="Any option not parsed by Xspice gets passed to Xorg as is.")
# X-related options
parser.add_argument('--xorg', default=which('Xorg'), help='specify the path to the Xorg binary')
parser.add_argument('--config', default='spiceqxl.xorg.conf', help='specify the path to the Xspice configuration')
parser.add_argument('--auto', action='store_true', help='automatically create a temporary xorg.conf and start the X server')
parser.add_argument('--xsession', help='if given, will run after Xorg launch. Should be a program like x-session-manager')
# Network and security options
add_boolean('--disable-ticketing', help="do not require a client password")
parser.add_argument('--password', help="set the password required to connect to the server")
add_boolean('--sasl', help="use SASL to authenticate to the server")
# Don't use any options that are already used by Xorg (unless we must)
# specifically, don't use -p and -s.
parser.add_argument('--port', type=int, help="use the specified port as Spice's regular unencrypted port")
parser.add_argument('--tls-port', type=int, help='use the specified port as a TLS (encrypted) port', default=0)
parser.add_argument('--x509-dir', help="set the directory where the CA certificate, server key and server certificate are searched for TLS, using the same predefined names QEMU uses")
parser.add_argument('--cacert-file', help="set the CA certificate file location for TLS")
parser.add_argument('--x509-key-file', help="set the server key file location for TLS")
parser.add_argument('--x509-key-password', help="set the server key's password for TLS")
parser.add_argument('--x509-cert-file', help="set the server certificate file location for TLS")
parser.add_argument('--dh-file', help="set the server DH file location for TLS")
parser.add_argument('--tls-ciphers', help="set the TLS ciphers preference order")
add_boolean('--ipv4-only', help="only accept IP v4 connections")
add_boolean('--ipv6-only', help="only accept IP v6 connections")
parser.add_argument('--exit-on-disconnect', action='store_true', help='exit the X server when any client disconnects')
# Monitor configuration options
parser.add_argument('--numheads', type=int, help='number of virtual heads to create')
# Compression options
parser.add_argument('--jpeg-wan-compression',
choices=wan_compression_options,
help="set jpeg wan compression")
parser.add_argument('--zlib-glz-wan-compression',
choices=wan_compression_options,
help="set zlib glz wan compressions")
parser.add_argument('--image-compression',
choices = ['off', 'auto_glz', 'auto_lz', 'quic',
'glz', 'lz'],
help="set image compression")
parser.add_argument('--deferred-fps', type=int, help='if non zero, the driver will render all operations to the frame buffer, and keep track of a changed rectangle list. The changed rectangles will be transmitted at the rate requested (e.g. 10 frames per second). This can dramatically reduce network bandwidth for some use cases')
# TODO - sound support
parser.add_argument('--streaming-video', choices=['off', 'all', 'filter'],
help='set the streaming video method')
parser.add_argument('--video-codecs', help='set a semicolon-separated list of preferred video codecs to use. Each takes the form encoder:codec, with spice:mjpeg being the default and other options being provided by gstreamer for the mjpeg, vp8 and h264 codecs')
# VDAgent options
parser.add_argument('--vdagent', action='store_true', dest='vdagent_enabled', default=False, help='launch vdagent & vdagentd. They provide clipboard & resolution automation')
parser.add_argument('--vdagent-virtio-path', help='virtio socket path used by vdagentd')
parser.add_argument('--vdagent-uinput-path', help='uinput socket path used by vdagent')
parser.add_argument('--vdagent-udcs-path', help='Unix domain socket path used by vdagent and vdagentd')
parser.add_argument('--vdagentd-exec', help='path to spice-vdagentd (used with --vdagent)')
parser.add_argument('--vdagent-exec', help='path to spice-vdagent (used with --vdagent)')
parser.add_argument('--vdagent-no-launch', default=True, action='store_false', dest='vdagent_launch', help='do not launch vdagent & vdagentd, used for debugging or if some external script wants to take care of that')
parser.add_argument('--vdagent-uid', default=str(os.getuid()), help='set vdagent user id. changing it makes sense only in conjunction with --vdagent-no-launch')
parser.add_argument('--vdagent-gid', default=str(os.getgid()), help='set vdagent group id. changing it makes sense only in conjunction with --vdagent-no-launch')
parser.add_argument('--audio-fifo-dir', help="if a directory is given, any file in that directory will be read for audio data to be sent to the client. This is designed to work with PulseAudio's module-pipe-sink")
#TODO
#Option "SpiceAddr" ""
#add_boolean('--agent-mouse')
#Option "EnableImageCache" "True"
#Option "EnableFallbackCache" "True"
#Option "EnableSurfaces" "True"
#parser.add_argument('--playback-compression', choices=['0', '1'], help='enabled by default')
#Option "SpiceDisableCopyPaste" "False"
if cgdb:
parser.add_argument('--cgdb', action='store_true', default=False)
args, xorg_args = parser.parse_known_args(sys.argv[1:])
def agents_new_enough(args):
for f in [args.vdagent_exec, args.vdagentd_exec]:
if not f:
print 'please specify path to vdagent/vdagentd executables'
return False
if not os.path.exists(f):
print 'error: file not found ', f
return False
for f in [args.vdagent_exec, args.vdagentd_exec]:
if Popen(args=[f, '-h'], stdout=PIPE).stdout.read().find('-S') == -1:
return False
return True
if args.vdagent_enabled:
if not args.vdagent_exec:
args.vdagent_exec = 'spice-vdagent'
if not args.vdagentd_exec:
args.vdagentd_exec = 'spice-vdagentd'
args.vdagent_exec = which(args.vdagent_exec)
args.vdagentd_exec = which(args.vdagentd_exec)
if not agents_new_enough(args):
if args.vdagent_enabled:
print("error: vdagent is not new enough to support Xspice")
raise SystemExit
args.vdagent_enabled = False
def tls_files(args):
if args.tls_port == 0:
return {}
files = {}
for k, var in [('ca-cert', 'cacert_file'),
('server-key', 'x509_key_file'),
('server-cert', 'x509_cert_file')]:
files[k] = os.path.join(args.x509_dir, k + '.pem')
if getattr(args, var):
files[k] = getattr(args, var)
return files
# XXX spice-server aborts if it can't find the certificates - avoid by checking
# ourselves. This isn't exhaustive - if the server key requires a password
# and it isn't supplied spice will still abort, and Xorg with it.
for key, filename in tls_files(args).items():
if not os.path.exists(filename):
print "missing %s - %s does not exist" % (key, filename)
sys.exit(1)
def error(msg, exit_code=1):
print "Xspice: %s" % msg
sys.exit(exit_code)
if not args.xorg:
error("Xorg missing")
cleanup_files = []
cleanup_dirs = []
cleanup_processes = []
def cleanup(*args):
for f in cleanup_files:
if os.path.exists(f):
os.remove(f)
for d in cleanup_dirs:
if os.path.exists(d):
os.rmdir(d)
for p in cleanup_processes:
try:
p.kill()
except OSError:
pass
for p in cleanup_processes:
try:
p.wait()
except OSError:
pass
del cleanup_processes[:]
def launch(*args, **kw):
p = Popen(*args, **kw)
cleanup_processes.append(p)
return p
signal.signal(signal.SIGTERM, cleanup)
atexit.register(cleanup)
if args.auto:
temp_dir = tempfile.mkdtemp(prefix="Xspice-")
cleanup_dirs.append(temp_dir)
args.config = temp_dir + "/xorg.conf"
cleanup_files.append(args.config)
cf = open(args.config, "w+")
logfile = temp_dir + "/xorg.log"
cleanup_files.append(logfile)
xorg_args = [ '-logfile', logfile ] + xorg_args
if args.audio_fifo_dir:
options = 'Option "SpicePlaybackFIFODir" "%s"' % args.audio_fifo_dir
else:
options = ''
cf.write("""
Section "Device"
Identifier "XSPICE"
Driver "spiceqxl"
%(options)s
EndSection
Section "InputDevice"
Identifier "XSPICE POINTER"
Driver "xspice pointer"
EndSection
Section "InputDevice"
Identifier "XSPICE KEYBOARD"
Driver "xspice keyboard"
EndSection
Section "Monitor"
Identifier "Configured Monitor"
EndSection
Section "Screen"
Identifier "XSPICE Screen"
Monitor "Configured Monitor"
Device "XSPICE"
EndSection
Section "ServerLayout"
Identifier "XSPICE Example"
Screen "XSPICE Screen"
InputDevice "XSPICE KEYBOARD"
InputDevice "XSPICE POINTER"
EndSection
# Prevent udev from loading vmmouse in a vm and crashing.
Section "ServerFlags"
Option "AutoAddDevices" "False"
EndSection
""" % locals())
cf.flush()
if args.vdagent_enabled:
for f in [args.vdagent_udcs_path, args.vdagent_virtio_path, args.vdagent_uinput_path]:
if f and os.path.exists(f):
os.unlink(f)
if not temp_dir:
temp_dir = tempfile.mkdtemp(prefix="Xspice-")
cleanup_dirs.append(temp_dir)
# Auto generate temporary files for vdagent
if not args.vdagent_udcs_path:
args.vdagent_udcs_path = temp_dir + "/vdagent.udcs"
if not args.vdagent_virtio_path:
args.vdagent_virtio_path = temp_dir + "/vdagent.virtio"
if not args.vdagent_uinput_path:
args.vdagent_uinput_path = temp_dir + "/vdagent.uinput"
cleanup_files.extend([args.vdagent_udcs_path, args.vdagent_virtio_path, args.vdagent_uinput_path])
var_args = ['port', 'tls_port', 'disable_ticketing',
'x509_dir', 'sasl', 'cacert_file', 'x509_cert_file',
'x509_key_file', 'x509_key_password',
'tls_ciphers', 'dh_file', 'password', 'image_compression',
'jpeg_wan_compression', 'zlib_glz_wan_compression',
'streaming_video', 'video_codecs', 'deferred_fps', 'exit_on_disconnect',
'vdagent_enabled', 'vdagent_virtio_path', 'vdagent_uinput_path',
'vdagent_uid', 'vdagent_gid']
for arg in var_args:
if getattr(args, arg) != None:
# The Qxl code doesn't respect booleans, so pass them as 0/1
a = getattr(args, arg)
if a == True:
a = "1"
elif a == False:
a = "0"
else:
a = str(a)
os.environ['XSPICE_' + arg.upper()] = a
# A few arguments don't follow the XSPICE_ convention - handle them manually
if args.numheads:
os.environ['QXL_NUM_HEADS'] = str(args.numheads)
display=""
for arg in xorg_args:
if arg.startswith(":"):
display = arg
if not display:
print "Error: missing display on line (i.e. :3)"
raise SystemExit
os.environ ['DISPLAY'] = display
exec_args = [args.xorg, '-config', args.config]
if cgdb and args.cgdb:
exec_args = [cgdb, '--args'] + exec_args
args.xorg = cgdb
# This is currently mandatory; the driver cannot survive a reset
xorg_args = [ '-noreset' ] + xorg_args
if args.vdagent_enabled:
for f in [args.vdagent_udcs_path, args.vdagent_virtio_path, args.vdagent_uinput_path]:
if os.path.exists(f):
os.unlink(f)
cleanup_files.extend([args.vdagent_udcs_path, args.vdagent_virtio_path, args.vdagent_uinput_path])
xorg = launch(executable=args.xorg, args=exec_args + xorg_args)
time.sleep(2)
retpid,rc = os.waitpid(xorg.pid, os.WNOHANG)
if retpid != 0:
print "Error: X server is not running"
else:
if args.vdagent_enabled and args.vdagent_launch:
# XXX use systemd --user for this?
vdagentd = launch(args=[args.vdagentd_exec, '-f', '-x', '-S', args.vdagent_udcs_path,
'-s', args.vdagent_virtio_path, '-u', args.vdagent_uinput_path])
time.sleep(1)
# TODO wait for uinput pipe open for write
vdagent = launch(args=[args.vdagent_exec, '-x', '-s', args.vdagent_virtio_path, '-S',
args.vdagent_udcs_path])
if args.xsession:
environ = os.environ
os.spawnlpe(os.P_NOWAIT, args.xsession, environ)
try:
xorg.wait()
except KeyboardInterrupt:
# Catch Ctrl-C as that is the common way of ending this script
print "Keyboard Interrupt"
|