Hide keyboard shortcuts

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. 

6 

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 

13 

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 

24 

25 

26VFB_WAIT = envint("XPRA_VFB_WAIT", 3) 

27 

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 } 

37 

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) 

48 

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) 

53 

54 

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 

62 

63def osclose(fd): 

64 try: 

65 os.close(fd) 

66 except OSError: 

67 pass 

68 

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 

75 

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) 

87 

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) 

97 

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 

110 

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 

133 

134 

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) 

155 

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") 

163 

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' 

182 

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 }) 

190 

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] 

199 

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 

241 

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) 

262 

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) 

326 

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 

336 

337 

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) 

350 

351 

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) 

378 

379 

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,)) 

408 

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 

425 

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 

452 

453 

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") 

462 

463 

464if __name__ == "__main__": 

465 main()