Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/vfb_util.py : 38%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
3# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7# DO NOT IMPORT GTK HERE: see
8# http://lists.partiwm.org/pipermail/parti-discuss/2008-September/000041.html
9# http://lists.partiwm.org/pipermail/parti-discuss/2008-September/000042.html
10# (also do not import anything that imports gtk)
11from subprocess import Popen, PIPE, call
12import os.path
14from xpra.scripts.config import InitException, get_Xdummy_confdir
15from xpra.util import envbool, envint
16from xpra.os_util import (
17 shellsub,
18 setuidgid, getuid, getgid,
19 strtobytes, bytestostr, osexpand, monotonic_time,
20 pollwait,
21 POSIX, OSX,
22 )
23from xpra.platform.displayfd import read_displayfd, parse_displayfd
26VFB_WAIT = envint("XPRA_VFB_WAIT", 3)
28RESOLUTION_ALIASES = {
29 "QVGA" : (320, 240),
30 "VGA" : (640, 480),
31 "SVGA" : (800, 600),
32 "XGA" : (1024, 768),
33 "1080p" : (1920, 1080),
34 "FHD" : (1920, 1080),
35 "4K" : (3840, 2160),
36 }
38def parse_resolution(s):
39 v = RESOLUTION_ALIASES.get(s.upper())
40 if v:
41 return v
42 res = tuple(int(x) for x in s.replace(",", "x").split("x", 1))
43 assert len(res)==2, "invalid resolution string '%s'" % s
44 return res
45def parse_env_resolution(envkey="XPRA_DEFAULT_VFB_RESOLUTION", default_res="8192x4096"):
46 s = os.environ.get(envkey, default_res)
47 return parse_resolution(s)
49DEFAULT_VFB_RESOLUTION = parse_env_resolution()
50DEFAULT_DESKTOP_VFB_RESOLUTION = parse_env_resolution("XPRA_DEFAULT_DESKTOP_VFB_RESOLUTION", "1280x1024")
51PRIVATE_XAUTH = envbool("XPRA_PRIVATE_XAUTH", False)
52XAUTH_PER_DISPLAY = envbool("XPRA_XAUTH_PER_DISPLAY", True)
55vfb_logger = None
56def get_vfb_logger():
57 global vfb_logger
58 if not vfb_logger:
59 from xpra.log import Logger
60 vfb_logger = Logger("server", "x11", "screen")
61 return vfb_logger
63def osclose(fd):
64 try:
65 os.close(fd)
66 except OSError:
67 pass
69def create_xorg_device_configs(xorg_conf_dir, device_uuid, uid, gid):
70 log = get_vfb_logger()
71 log("create_xorg_device_configs(%s, %s, %i, %i)", xorg_conf_dir, device_uuid, uid, gid)
72 cleanups = []
73 if not device_uuid:
74 return cleanups
76 def makedir(dirname):
77 log("makedir(%s)", dirname)
78 os.mkdir(dirname)
79 os.lchown(dirname, uid, gid)
80 def cleanup_dir():
81 try:
82 log("cleanup_dir() %s", dirname)
83 os.rmdir(dirname)
84 except Exception as e:
85 log("failed to cleanup %s: %s", dirname, e)
86 cleanups.insert(0, cleanup_dir)
88 #create conf dir if needed:
89 d = xorg_conf_dir
90 dirs = []
91 while d and not os.path.exists(d):
92 log("create_device_configs: dir does not exist: %s", d)
93 dirs.insert(0, d)
94 d = os.path.dirname(d)
95 for d in dirs:
96 makedir(d)
98 conf_files = []
99 for i, dev_type in (
100 (0, "pointer"),
101 (1, "touchpad"),
102 ) :
103 f = save_input_conf(xorg_conf_dir, i, dev_type, device_uuid, uid, gid)
104 conf_files.append(f)
105 def cleanup_input_conf_files():
106 for f in conf_files:
107 os.unlink(f)
108 cleanups.insert(0, cleanup_input_conf_files)
109 return cleanups
111#create individual device files:
112def save_input_conf(xorg_conf_dir, i, dev_type, device_uuid, uid, gid):
113 upper_dev_type = dev_type[:1].upper()+dev_type[1:] #ie: Pointer
114 product_name = "Xpra Virtual %s %s" % (upper_dev_type, bytestostr(device_uuid))
115 identifier = "xpra-virtual-%s" % dev_type
116 conf_file = os.path.join(xorg_conf_dir, "%02i-%s.conf" % (i, dev_type))
117 with open(conf_file, "wb") as f:
118 f.write(strtobytes("""Section "InputClass"
119Identifier "%s"
120MatchProduct "%s"
121MatchUSBID "ffff:ffff"
122MatchIs%s "True"
123Driver "libinput"
124Option "AccelProfile" "flat"
125Option "Ignore" "False"
126EndSection
127""" % (identifier, product_name, upper_dev_type)))
128 os.fchown(f.fileno(), uid, gid)
129 #Option "AccelerationProfile" "-1"
130 #Option "AccelerationScheme" "none"
131 #Option "AccelSpeed" "-1"
132 return conf_file
135def get_xauthority_path(display_name, username, uid, gid):
136 assert POSIX
137 def pathexpand(s):
138 return osexpand(s, actual_username=username, uid=uid, gid=gid)
139 filename = os.environ.get("XAUTHORITY")
140 if filename:
141 filename = pathexpand(filename)
142 if os.path.exists(filename):
143 return filename
144 from xpra.platform.xposix.paths import _get_xpra_runtime_dir
145 if PRIVATE_XAUTH:
146 d = _get_xpra_runtime_dir()
147 if XAUTH_PER_DISPLAY:
148 filename = "Xauthority-%s" % display_name.lstrip(":")
149 else:
150 filename = "Xauthority"
151 else:
152 d = "~/"
153 filename = ".Xauthority"
154 return os.path.join(pathexpand(d), filename)
156def start_Xvfb(xvfb_str, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid=None):
157 if not POSIX:
158 raise InitException("starting an Xvfb is not supported on %s" % os.name)
159 if OSX:
160 raise InitException("starting an Xvfb is not supported on MacOS")
161 if not xvfb_str:
162 raise InitException("the 'xvfb' command is not defined")
164 cleanups = []
165 log = get_vfb_logger()
166 log("start_Xvfb%s", (xvfb_str, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid))
167 xauthority = get_xauthority_path(display_name, username, uid, gid)
168 os.environ["XAUTHORITY"] = xauthority
169 if not os.path.exists(xauthority):
170 log("creating XAUTHORITY=%s with data=%s", xauthority, xauth_data)
171 try:
172 with open(xauthority, "a") as f:
173 if getuid()==0 and (uid!=0 or gid!=0):
174 os.fchown(f.fileno(), uid, gid)
175 except Exception as e:
176 #trying to continue anyway!
177 log.error("Error trying to create XAUTHORITY file %s:", xauthority)
178 log.error(" %s", e)
179 else:
180 log("found existing XAUTHORITY file '%s'", xauthority)
181 use_display_fd = display_name[0]=='S'
183 subs = {}
184 def pathexpand(s):
185 return osexpand(s, actual_username=username, uid=uid, gid=gid, subs=subs)
186 subs.update({
187 "DISPLAY" : display_name,
188 "XPRA_LOG_DIR" : pathexpand(os.environ.get("XPRA_LOG_DIR")),
189 })
191 #identify logfile argument if it exists,
192 #as we may have to rename it, or create the directory for it:
193 import shlex
194 xvfb_cmd = shlex.split(xvfb_str)
195 if not xvfb_cmd:
196 raise InitException("cannot start Xvfb, the command definition is missing!")
197 #make sure all path values are expanded:
198 xvfb_cmd = [pathexpand(s) for s in xvfb_cmd]
200 #try to honour initial geometries if specified:
201 if vfb_geom and xvfb_cmd[0].endswith("Xvfb"):
202 #find the '-screen' arguments:
203 #"-screen 0 8192x4096x24+32"
204 try:
205 no_arg = xvfb_cmd.index("0")
206 except ValueError:
207 no_arg = -1
208 geom_str = "%sx%s" % ("x".join(str(x) for x in vfb_geom), pixel_depth)
209 if no_arg>0 and xvfb_cmd[no_arg-1]=="-screen" and len(xvfb_cmd)>(no_arg+1):
210 #found an existing "-screen" argument for this screen number,
211 #replace it:
212 xvfb_cmd[no_arg+1] = geom_str
213 else:
214 #append:
215 xvfb_cmd += ["-screen", "0", geom_str]
216 try:
217 logfile_argindex = xvfb_cmd.index('-logfile')
218 if logfile_argindex+1>=len(xvfb_cmd):
219 raise InitException("invalid xvfb command string: -logfile should not be last")
220 xorg_log_file = xvfb_cmd[logfile_argindex+1]
221 except ValueError:
222 xorg_log_file = None
223 tmp_xorg_log_file = None
224 if xorg_log_file:
225 if use_display_fd:
226 #keep track of it so we can rename it later:
227 tmp_xorg_log_file = xorg_log_file
228 #make sure the Xorg log directory exists:
229 xorg_log_dir = os.path.dirname(xorg_log_file)
230 if not os.path.exists(xorg_log_dir):
231 try:
232 log("creating Xorg log dir '%s'", xorg_log_dir)
233 os.mkdir(xorg_log_dir, 0o700)
234 if POSIX and uid!=getuid() or gid!=getgid():
235 try:
236 os.lchown(xorg_log_dir, uid, gid)
237 except OSError:
238 log("lchown(%s, %i, %i)", xorg_log_dir, uid, gid, exc_info=True)
239 except OSError as e:
240 raise InitException("failed to create the Xorg log directory '%s': %s" % (xorg_log_dir, e)) from None
242 if uinput_uuid:
243 #use uinput:
244 #identify -config xorg.conf argument and replace it with the uinput one:
245 try:
246 config_argindex = xvfb_cmd.index("-config")
247 except ValueError as e:
248 log.warn("Warning: cannot use uinput")
249 log.warn(" '-config' argument not found in the xvfb command")
250 else:
251 if config_argindex+1>=len(xvfb_cmd):
252 raise InitException("invalid xvfb command string: -config should not be last")
253 xorg_conf = xvfb_cmd[config_argindex+1]
254 if xorg_conf.endswith("xorg.conf"):
255 xorg_conf = xorg_conf.replace("xorg.conf", "xorg-uinput.conf")
256 if os.path.exists(xorg_conf):
257 xvfb_cmd[config_argindex+1] = xorg_conf
258 #create uinput device definition files:
259 #(we have to assume that Xorg is configured to use this path..)
260 xorg_conf_dir = pathexpand(get_Xdummy_confdir())
261 cleanups = create_xorg_device_configs(xorg_conf_dir, uinput_uuid, uid, gid)
263 xvfb_executable = xvfb_cmd[0]
264 if (xvfb_executable.endswith("Xorg") or xvfb_executable.endswith("Xdummy")) and pixel_depth>0:
265 xvfb_cmd.append("-depth")
266 xvfb_cmd.append(str(pixel_depth))
267 xvfb = None
268 try:
269 if use_display_fd:
270 def displayfd_err(msg):
271 raise InitException("%s: %s" % (xvfb_executable, msg))
272 r_pipe, w_pipe = os.pipe()
273 try:
274 os.set_inheritable(w_pipe, True) #@UndefinedVariable
275 xvfb_cmd += ["-displayfd", str(w_pipe)]
276 xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name.lstrip(":"))
277 def preexec():
278 os.setpgrp()
279 if getuid()==0 and uid:
280 setuidgid(uid, gid)
281 try:
282 xvfb = Popen(xvfb_cmd, executable=xvfb_executable,
283 preexec_fn=preexec, cwd=cwd, pass_fds=(w_pipe,))
284 except OSError as e:
285 log("Popen%s", (xvfb_cmd, xvfb_executable, cwd), exc_info=True)
286 raise InitException("failed to execute xvfb command %s: %s" % (xvfb_cmd, e)) from None
287 assert xvfb.poll() is None, "xvfb command failed"
288 # Read the display number from the pipe we gave to Xvfb
289 try:
290 buf = read_displayfd(r_pipe)
291 except Exception as e:
292 log("read_displayfd(%s)", r_pipe, exc_info=True)
293 displayfd_err("failed to read displayfd pipe %s: %s" % (r_pipe, e))
294 finally:
295 osclose(r_pipe)
296 osclose(w_pipe)
297 n = parse_displayfd(buf, displayfd_err)
298 new_display_name = ":%s" % n
299 log("Using display number provided by %s: %s", xvfb_executable, new_display_name)
300 if tmp_xorg_log_file:
301 #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log
302 f0 = shellsub(tmp_xorg_log_file, subs)
303 subs["DISPLAY"] = new_display_name
304 #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log
305 f1 = shellsub(tmp_xorg_log_file, subs)
306 if f0 != f1:
307 try:
308 os.rename(f0, f1)
309 except Exception as e:
310 log.warn("Warning: failed to rename Xorg log file,")
311 log.warn(" from '%s' to '%s'" % (f0, f1))
312 log.warn(" %s" % e)
313 display_name = new_display_name
314 else:
315 # use display specified
316 xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name)
317 xvfb_cmd.append(display_name)
318 def preexec():
319 if getuid()==0 and (uid!=0 or gid!=0):
320 setuidgid(uid, gid)
321 else:
322 os.setsid()
323 log("xvfb_cmd=%s", xvfb_cmd)
324 xvfb = Popen(xvfb_cmd, executable=xvfb_executable,
325 stdin=PIPE, preexec_fn=preexec)
327 xauth_add(xauthority, display_name, xauth_data, uid, gid)
328 except Exception as e:
329 if xvfb and xvfb.poll() is None:
330 log.error(" stopping vfb process with pid %i", xvfb.pid)
331 xvfb.terminate()
332 raise
333 log("xvfb process=%s", xvfb)
334 log("display_name=%s", display_name)
335 return xvfb, display_name, cleanups
338def kill_xvfb(xvfb_pid):
339 log = get_vfb_logger()
340 log.info("killing xvfb with pid %s", xvfb_pid)
341 import signal
342 try:
343 os.kill(xvfb_pid, signal.SIGTERM)
344 except OSError as e:
345 log.info("failed to kill xvfb process with pid %s:", xvfb_pid)
346 log.info(" %s", e)
347 xauthority = os.environ.get("XAUTHORITY")
348 if PRIVATE_XAUTH and xauthority and os.path.exists(xauthority):
349 os.unlink(xauthority)
352def set_initial_resolution(res=DEFAULT_VFB_RESOLUTION):
353 try:
354 log = get_vfb_logger()
355 log("set_initial_resolution(%s)", res)
356 from xpra.x11.bindings.randr_bindings import RandRBindings #@UnresolvedImport
357 #try to set a reasonable display size:
358 randr = RandRBindings()
359 if not randr.has_randr():
360 log.warn("Warning: no RandR support,")
361 log.warn(" default virtual display size unchanged")
362 return
363 sizes = randr.get_xrr_screen_sizes()
364 size = randr.get_screen_size()
365 log("RandR available, current size=%s, sizes available=%s", size, sizes)
366 if res not in sizes:
367 log.warn("Warning: cannot set resolution to %s", res)
368 log.warn(" (this resolution is not available)")
369 elif res==size:
370 log("initial resolution already set: %s", res)
371 else:
372 log("RandR setting new screen size to %s", res)
373 randr.set_screen_size(*res)
374 except Exception as e:
375 log("set_initial_resolution(%s)", res, exc_info=True)
376 log.error("Error: failed to set the default screen size:")
377 log.error(" %s", e)
380def xauth_add(filename, display_name, xauth_data, uid, gid):
381 xauth_args = ["-f", filename, "add", display_name, "MIT-MAGIC-COOKIE-1", xauth_data]
382 try:
383 def preexec():
384 os.setsid()
385 if getuid()==0 and uid:
386 setuidgid(uid, gid)
387 xauth_cmd = ["xauth"]+xauth_args
388 start = monotonic_time()
389 code = call(xauth_cmd, preexec_fn=preexec)
390 end = monotonic_time()
391 if code!=0 and (end-start>=10):
392 log = get_vfb_logger()
393 log.warn("Warning: xauth command took %i seconds and failed" % (end-start))
394 #took more than 10 seconds to fail, check for stale locks:
395 import glob
396 if glob.glob("%s-*" % filename):
397 log.warn("Warning: trying to clean some stale xauth locks")
398 xauth_cmd = ["xauth", "-b"]+xauth_args
399 code = call(xauth_cmd, preexec_fn=preexec)
400 if code!=0:
401 raise OSError("non-zero exit code: %s" % code)
402 except OSError as e:
403 #trying to continue anyway!
404 log = get_vfb_logger()
405 log.error("Error adding xauth entry for %s" % display_name)
406 log.error(" using command \"%s\":" % (" ".join(xauth_cmd)))
407 log.error(" %s" % (e,))
409def check_xvfb_process(xvfb=None, cmd="Xvfb", timeout=0, command=None):
410 if xvfb is None:
411 #we don't have a process to check
412 return True
413 if pollwait(xvfb, timeout) is None:
414 #process is running
415 return True
416 log = get_vfb_logger()
417 log.error("")
418 log.error("%s command has terminated! xpra cannot continue", cmd)
419 log.error(" if the display is already running, try a different one,")
420 log.error(" or use the --use-display flag")
421 if command:
422 log.error(" full command: %s", command)
423 log.error("")
424 return False
426def verify_display_ready(xvfb, display_name, shadowing_check=True, log_errors=True, timeout=VFB_WAIT):
427 from xpra.x11.bindings.wait_for_x_server import wait_for_x_server #@UnresolvedImport
428 # Whether we spawned our server or not, it is now running -- or at least
429 # starting. First wait for it to start up:
430 try:
431 wait_for_x_server(strtobytes(display_name), timeout)
432 except Exception as e:
433 log = get_vfb_logger()
434 log("verify_display_ready%s", (xvfb, display_name, shadowing_check), exc_info=True)
435 if log_errors:
436 log.error("Error: failed to connect to display %s" % display_name)
437 log.error(" %s", e)
438 return False
439 if shadowing_check and not check_xvfb_process(xvfb):
440 #if we're here, there is an X11 server, but it isn't the one we started!
441 log = get_vfb_logger()
442 log("verify_display_ready%s display exists, but the vfb process has terminated",
443 (xvfb, display_name, shadowing_check, log_errors))
444 if log_errors:
445 log.error("There is an X11 server already running on display %s:" % display_name)
446 log.error("You may want to use:")
447 log.error(" 'xpra upgrade %s' if an instance of xpra is still connected to it" % display_name)
448 log.error(" 'xpra --use-display start %s' to connect xpra to an existing X11 server only" % display_name)
449 log.error("")
450 return False
451 return True
454def main():
455 import sys
456 display = None
457 if len(sys.argv)>1:
458 display = strtobytes(sys.argv[1])
459 from xpra.x11.bindings.wait_for_x_server import wait_for_x_server #@UnresolvedImport
460 wait_for_x_server(display, VFB_WAIT)
461 print("OK")
464if __name__ == "__main__":
465 main()