def osexpand(s, actual_username="", uid=0, gid=0, subs=None): def expanduser(s): if actual_username and s.startswith("~/"): #replace "~/" with "~$actual_username/" return os.path.expanduser("~%s/%s" % (actual_username, s[2:])) return os.path.expanduser(s) d = dict(subs or {}) d.update({ "PID": os.getpid(), "HOME": expanduser("~/"), }) if os.name == "posix": d.update({ "UID": uid or os.geteuid(), "GID": gid or os.getegid(), }) if not OSX: from xpra.platform.xposix.paths import get_runtime_dir d["XDG_RUNTIME_DIR"] = os.environ.get("XDG_RUNTIME_DIR", get_runtime_dir()) if actual_username: d["USERNAME"] = actual_username d["USER"] = actual_username #first, expand the substitutions themselves, #as they may contain references to other variables: ssub = {} for k, v in d.items(): ssub[k] = expanduser(shellsub(str(v), d)) return os.path.expandvars(expanduser(shellsub(expanduser(s), ssub)))
def init_pulseaudio(self): soundlog("init_pulseaudio() pulseaudio=%s, pulseaudio_command=%s", self.pulseaudio, self.pulseaudio_command) if self.pulseaudio is False: return if not self.pulseaudio_command: soundlog.warn("Warning: pulseaudio command is not defined") return #environment initialization: # 1) make sure that the sound subprocess will use the devices # we define in the pulseaudio command # (it is too difficult to parse the pulseaudio_command, # so we just hope that it matches this): # Note: speaker is the source and microphone the sink, # because things are reversed on the server. os.environ.update({ "XPRA_PULSE_SOURCE_DEVICE_NAME": "Xpra-Speaker", "XPRA_PULSE_SINK_DEVICE_NAME": "Xpra-Microphone", }) # 2) whitelist the env vars that pulseaudio may use: PA_ENV_WHITELIST = ( "DBUS_SESSION_BUS_ADDRESS", "DBUS_SESSION_BUS_PID", "DBUS_SESSION_BUS_WINDOWID", "DISPLAY", "HOME", "HOSTNAME", "LANG", "PATH", "PWD", "SHELL", "XAUTHORITY", "XDG_CURRENT_DESKTOP", "XDG_SESSION_TYPE", "XPRA_PULSE_SOURCE_DEVICE_NAME", "XPRA_PULSE_SINK_DEVICE_NAME", ) env = dict((k, v) for k, v in self.get_child_env().items() if k in PA_ENV_WHITELIST) # 3) use a private pulseaudio server, so each xpra # session can have its own server, # create a directory for each display: if PRIVATE_PULSEAUDIO and POSIX and not OSX: from xpra.platform.xposix.paths import _get_xpra_runtime_dir, get_runtime_dir rd = osexpand(get_runtime_dir()) if not os.path.exists(rd) or not os.path.isdir(rd): log.warn("Warning: the runtime directory '%s' does not exist,") log.warn(" cannot start a private pulseaudio server") else: xpra_rd = _get_xpra_runtime_dir() assert xpra_rd, "bug: no xpra runtime dir" display = os.environ.get("DISPLAY") self.pulseaudio_private_dir = osexpand( os.path.join(xpra_rd, "pulse-%s" % display)) if not os.path.exists(self.pulseaudio_private_dir): os.mkdir(self.pulseaudio_private_dir, 0o700) env["XDG_RUNTIME_DIR"] = self.pulseaudio_private_dir self.pulseaudio_private_socket = os.path.join( self.pulseaudio_private_dir, "pulse", "native") os.environ[ "XPRA_PULSE_SERVER"] = self.pulseaudio_private_socket import shlex cmd = shlex.split(self.pulseaudio_command) cmd = list(osexpand(x) for x in cmd) #find the absolute path to the command: pa_cmd = cmd[0] if not os.path.isabs(pa_cmd): pa_path = None for x in os.environ.get("PATH", "").split(os.path.pathsep): t = os.path.join(x, pa_cmd) if os.path.exists(t): pa_path = t break if not pa_path: msg = "pulseaudio not started: '%s' command not found" % pa_cmd if self.pulseaudio is None: soundlog.info(msg) else: soundlog.warn(msg) return cmd[0] = pa_cmd started_at = monotonic_time() def pulseaudio_warning(): soundlog.warn( "Warning: pulseaudio has terminated shortly after startup.") soundlog.warn( " pulseaudio is limited to a single instance per user account," ) soundlog.warn(" and one may be running already for user '%s'.", get_username()) soundlog.warn( " To avoid this warning, either fix the pulseaudio command line" ) soundlog.warn(" or use the 'pulseaudio=no' option.") def pulseaudio_ended(proc): soundlog( "pulseaudio_ended(%s) pulseaudio_proc=%s, returncode=%s, closing=%s", proc, self.pulseaudio_proc, proc.returncode, self._closing) if self.pulseaudio_proc is None or self._closing: #cleared by cleanup already, ignore return elapsed = monotonic_time() - started_at if elapsed < 2: self.timeout_add(1000, pulseaudio_warning) else: soundlog.warn( "Warning: the pulseaudio server process has terminated after %i seconds", int(elapsed)) self.pulseaudio_proc = None import subprocess try: soundlog("pulseaudio cmd=%s", " ".join(cmd)) soundlog("pulseaudio env=%s", env) self.pulseaudio_proc = subprocess.Popen(cmd, stdin=None, env=env, shell=False, close_fds=True) except Exception as e: soundlog("Popen(%s)", cmd, exc_info=True) soundlog.error("Error: failed to start pulseaudio:") soundlog.error(" %s", e) return self.add_process(self.pulseaudio_proc, "pulseaudio", cmd, ignore=True, callback=pulseaudio_ended) if self.pulseaudio_proc: soundlog.info("pulseaudio server started with pid %s", self.pulseaudio_proc.pid) if self.pulseaudio_private_socket: soundlog.info(" private server socket path:") soundlog.info(" '%s'", self.pulseaudio_private_socket) os.environ[ "PULSE_SERVER"] = "unix:%s" % self.pulseaudio_private_socket def configure_pulse(): p = self.pulseaudio_proc if p is None or p.poll() is not None: return for i, x in enumerate(self.pulseaudio_configure_commands): proc = subprocess.Popen(x, stdin=None, env=env, shell=True, close_fds=True) self.add_process(proc, "pulseaudio-configure-command-%i" % i, x, ignore=True) self.timeout_add(2 * 1000, configure_pulse)