def on_save(self, _button, callback=None): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() # Do not modify slug if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() if self.lutris_config.game_config_id == TEMP_CONFIG: self.lutris_config.game_config_id = self.get_config_id() runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.runner_name = self.runner_name self.game.config = self.lutris_config self.game.directory = runner.game_path self.game.is_installed = True if self.runner_name in ('steam', 'winesteam'): self.game.steamid = self.lutris_config.game_config['appid'] self.game.save() self.destroy() logger.debug("Saved %s", name) self.saved = True if callback: callback()
def _monitor_task(self, thread): if not thread.is_running: logger.debug("Thread QUIT") self._iter_commands() self.heartbeat = None return False return True
def execute(self, data): """Run an executable file.""" args = [] if isinstance(data, dict): self._check_required_params("file", data, "execute") file_ref = data["file"] args_string = data.get("args", "") for arg in shlex.split(args_string): args.append(self._substitute(arg)) else: file_ref = data # Determine whether 'file' value is a file id or a path exec_path = self._get_file(file_ref) or self._substitute(file_ref) if not exec_path: raise ScriptingError("Unable to find file %s" % file_ref, file_ref) if not os.path.exists(exec_path): raise ScriptingError("Unable to find required executable", exec_path) self.chmodx(exec_path) terminal = data.get("terminal") if terminal: terminal = system.get_default_terminal() command = [exec_path] + args logger.debug("Executing %s" % command) thread = LutrisThread(command, env=runtime.get_env(), term=terminal, cwd=self.target_path) self.abort_current_task = thread.killall thread.run() self.abort_current_task = None
def on_done(self, _result, error): if error: logger.error("Download failed: %s", error) self.state = self.ERROR self.error = error if self.file_pointer: self.file_pointer.close() self.file_pointer = None return if self.state == self.CANCELLED: return logger.debug("Finished downloading %s", self.url) if not self.downloaded_size: logger.warning("Downloaded file is empty") if not self.full_size: self.progress_fraction = 1.0 self.progress_percentage = 100 self.state = self.COMPLETED self.file_pointer.close() self.file_pointer = None if self.callback: self.callback()
def move(self, params): """Move a file or directory into a destination folder.""" self._check_required_params(['src', 'dst'], params, 'move') src, dst = self._get_move_paths(params) logger.debug("Moving %s to %s" % (src, dst)) if not os.path.exists(src): raise ScriptingError("I can't move %s, it does not exist" % src) if os.path.isfile(src): src_filename = os.path.basename(src) src_dir = os.path.dirname(src) dst_path = os.path.join(dst, src_filename) if src_dir == dst: logger.info("Source file is the same as destination, skipping") elif os.path.exists(dst_path): # May not be the best choice, but it's the safest. # Maybe should display confirmation dialog (Overwrite / Skip) ? logger.info("Destination file exists, skipping") else: self._killable_process(shutil.move, src, dst) else: try: self._killable_process(shutil.move, src, dst) except shutil.Error: raise ScriptingError("Can't move %s \nto destination %s" % (src, dst)) if os.path.isfile(src) and params['src'] in self.game_files.keys(): # Change game file reference so it can be used as executable self.game_files['src'] = src
def on_steam_game_installed(self, *args): logger.debug("Steam game installed") if self.steam_data['platform'] == 'windows': runner_class = winesteam.winesteam else: runner_class = steam.steam self._append_steam_data_to_files(runner_class)
def install(self, installer_path=None): logger.debug("Installing steam from %s", installer_path) if not self.is_wine_installed(): super(winesteam, self).install() if not installer_path: installer_path = download_steam() self.msi_exec(installer_path, quiet=True)
def xboxdrv(self, config): command = ("pkexec xboxdrv --daemon --detach-kernel-driver " "--dbus session --silent %s" % config) logger.debug("xboxdrv command: %s", command) thread = LutrisThread(command) thread.start()
def play(self): """Runs the game""" rom = self.game_config.get('main_file') or '' machine = self.game_config.get('machine') if self.runner_config.get("fs"): fullscreen = "1" else: fullscreen = "0" resolution = get_current_resolution() (resolutionx, resolutiony) = resolution.split("x") xres = str(resolutionx) yres = str(resolutiony) options = ["-fs", fullscreen, "-" + machine + ".xres", xres, "-" + machine + ".yres", yres, "-" + machine + ".stretch", "1", "-" + machine + ".special", "hq4x", "-" + machine + ".videoip", "1"] joy_ids = self.find_joysticks() if len(joy_ids) > 0: controls = self.set_joystick_controls(joy_ids) for control in controls: options.append(control) else: logger.debug("No Joystick found") if not os.path.exists(rom): return {'error': 'FILE_NOT_FOUND', 'file': rom} command = [self.get_executable()] for option in options: command.append(option) command.append(rom) return {'command': command}
def check_config(force_wipe=False): """Check if initial configuration is correct.""" directories = [CONFIG_DIR, join(CONFIG_DIR, "runners"), join(CONFIG_DIR, "games"), DATA_DIR, join(DATA_DIR, "covers"), join(DATA_DIR, "icons"), join(DATA_DIR, "banners"), join(DATA_DIR, "runners"), join(DATA_DIR, "lib"), CACHE_DIR, join(CACHE_DIR, "installer"), join(CACHE_DIR, "tmp")] for directory in directories: if not os.path.exists(directory): logger.debug("creating directory %s" % directory) os.mkdir(directory) if force_wipe: os.remove(PGA_DB) if not os.path.isfile(PGA_DB) or force_wipe: logger.debug("creating PGA database in %s" % PGA_DB) pga.create()
def create_table(name, schema): fields = ", ".join([field_to_string(**f) for f in schema]) fields = "(%s)" % fields query = "CREATE TABLE IF NOT EXISTS %s %s" % (name, fields) logger.debug("[PGAQuery] %s", query) with sql.db_cursor(PGA_DB) as cursor: cursor.execute(query)
def install(self, version=None, downloader=None, callback=None): """Install runner using package management systems.""" logger.debug( "Installing %s (version=%s, downloader=%s, callback=%s)", self.name, version, downloader, callback, ) runner = self.get_runner_version(version) if not runner: raise RunnerInstallationError( "{} is not available for the {} architecture".format( self.name, self.arch ) ) if not downloader: raise RuntimeError("Missing mandatory downloader for runner %s" % self) opts = { "downloader": downloader, "callback": callback } if "wine" in self.name: opts["merge_single"] = True opts["dest"] = os.path.join( settings.RUNNER_DIR, self.name, "{}-{}".format(runner["version"], runner["architecture"]) ) if self.name == "libretro" and version: opts["merge_single"] = False opts["dest"] = os.path.join(settings.RUNNER_DIR, "retroarch/cores") self.download_and_extract(runner["url"], **opts)
def get_path_from_config(config, appid): """ Given a steam config, return path for game 'appid' """ if not config or 'apps' not in config: return False game_config = config["apps"].get(appid) if not game_config: return False if game_config.get('HasAllLocalContent'): installdir = game_config['installdir'].replace("\\\\", "/") if not installdir: return False if installdir.startswith('C'): installdir = os.path.join(os.path.expanduser('~'), '.wine/drive_c', installdir[3:]) elif installdir[1] == ':': # Trim Windows path installdir = installdir[2:] logger.debug("Steam game found at %s" % installdir) if os.path.exists(installdir): return installdir elif os.path.exists(installdir.replace('steamapps', 'SteamApps')): return installdir.replace('steamapps', 'SteamApps') else: logger.debug("Path %s not found" % installdir) return False
def on_game_quit(self): """Restore some settings and cleanup after game quit.""" self.heartbeat = None quit_time = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime()) logger.debug("%s stopped at %s", self.name, quit_time) self.state = self.STATE_STOPPED os.chdir(os.path.expanduser('~')) if self.resolution_changed\ or self.runner.system_config.get('reset_desktop'): display.change_resolution(self.original_outputs) if self.runner.system_config.get('use_us_layout'): subprocess.Popen(['setxkbmap'], env=os.environ).communicate() if self.runner.system_config.get('restore_gamma'): display.restore_gamma() if self.runner.system_config.get('xboxdrv') \ and self.xboxdrv_thread.is_running: self.xboxdrv_thread.stop() if self.game_thread: self.game_thread.stop() self.process_return_codes() if self.exit_main_loop: exit()
def change_resolution(resolution): """change desktop resolution""" logger.debug("Switching resolution to %s", resolution) if resolution not in get_resolutions(): logger.warning("Resolution %s doesn't exist.") else: subprocess.Popen("xrandr -s %s" % resolution, shell=True)
def execute(command, env=None, cwd=None, log_errors=False): """Execute a system command and return its results.""" existing_env = os.environ.copy() if env: existing_env.update(env) logger.debug(' '.join('{}={}'.format(k, v) for k, v in env.iteritems())) logger.debug("Executing %s", ' '.join(command)) # Piping stderr can cause slowness in the programs, use carefully # (especially when using regedit with wine) if log_errors: stderr_config = subprocess.PIPE else: stderr_config = None try: stdout, stderr = subprocess.Popen(command, shell=False, stdout=subprocess.PIPE, stderr=stderr_config, env=existing_env, cwd=cwd).communicate() except OSError as ex: logger.error('Could not run command %s: %s', command, ex) return if stderr and log_errors: logger.error(stderr) return stdout.strip()
def extract_archive(path, to_directory='.', merge_single=True): path = os.path.abspath(path) logger.debug("Extracting %s to %s", path, to_directory) if path.endswith('.zip'): opener, mode = zipfile.ZipFile, 'r' elif path.endswith('.tar.gz') or path.endswith('.tgz'): opener, mode = tarfile.open, 'r:gz' elif path.endswith('.gz'): decompress_gz(path, to_directory) return elif path.endswith('.tar.bz2') or path.endswith('.tbz'): opener, mode = tarfile.open, 'r:bz2' else: raise RuntimeError( "Could not extract `%s` as no appropriate extractor is found" % path) destination = os.path.join(to_directory, ".lutris_extracted") handler = opener(path, mode) handler.extractall(destination) handler.close() if merge_single: extracted = os.listdir(destination) if len(extracted) == 1: destination = os.path.join(destination, extracted[0]) for f in os.listdir(destination): source = os.path.join(destination, f) destination = os.path.join(to_directory, f) merge_folders(source, destination) logger.debug("Finished extracting %s", path)
def on_game_stop(self): """TODO: Call this once it is possible to monitor Steam games""" if bool(self.runner_config.get("quit_steam_on_exit")): logger.debug("Game configured to stop Steam on exit") self.shutdown() return True return False
def stop(self): """The kill command runs wineserver -k""" wine_path = self.get_wine_path() wine_root = os.path.dirname(wine_path) wineserver = os.path.join(wine_root, wine_root) logger.debug("Killing wineserver") os.popen(wineserver + " -k")
def cancel(self): """Request download stop and remove destination file.""" logger.debug("Download cancelled") self.thread.stop_request.set() self.cancelled = True if os.path.isfile(self.dest): os.remove(self.dest)
def download_icon(game, icon_type, overwrite=False, callback=None): icon_url = get_icon_url(game, icon_type) icon_path = get_icon_path(game, icon_type) icon_downloaded = http.download_asset(icon_url, icon_path, overwrite) if icon_downloaded and callback: logger.debug("Downloaded %s for %s" % (icon_type, game)) callback(game)
def move(self, params): """Move a file or directory into a destination folder.""" self._check_required_params(["src", "dst"], params, "move") src, dst = self._get_move_paths(params) logger.debug("Moving %s to %s", src, dst) if not os.path.exists(src): raise ScriptingError("I can't move %s, it does not exist" % src) if os.path.isfile(src): if os.path.dirname(src) == dst: logger.info("Source file is the same as destination, skipping") return if os.path.exists(os.path.join(dst, os.path.basename(src))): # May not be the best choice, but it's the safest. # Maybe should display confirmation dialog (Overwrite / Skip) ? logger.info("Destination file exists, skipping") return try: if self._is_cached_file(src): action = shutil.copy else: action = shutil.move self._killable_process(action, src, dst) except shutil.Error: raise ScriptingError("Can't move %s \nto destination %s" % (src, dst))
def on_save(self, _button): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() # Do not modify slug if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game(self.slug) self.game.config = self.lutris_config if not self.lutris_config.game_slug: self.lutris_config.game_slug = self.slug self.lutris_config.save() runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.runner_name = self.runner_name self.game.directory = runner.game_path self.game.is_installed = True self.game.save() self.destroy() logger.debug("Saved %s", name) self.saved = True
def create_prefix(prefix, wine_path=None, arch='win32'): """Create a new Wine prefix.""" logger.debug("Creating a %s prefix in %s", arch, prefix) # Avoid issue of 64bit Wine refusing to create win32 prefix # over an existing empty folder. if os.path.isdir(prefix) and not os.listdir(prefix): os.rmdir(prefix) if not wine_path: wine_path = wine().get_executable() wineboot_path = os.path.join(os.path.dirname(wine_path), 'wineboot') env = { 'WINEARCH': arch, 'WINEPREFIX': prefix } system.execute([wineboot_path], env=env) for i in range(20): time.sleep(.25) if os.path.exists(os.path.join(prefix, 'user.reg')): break if not os.path.exists(os.path.join(prefix, 'user.reg')): logger.error('No user.reg found after prefix creation. ' 'Prefix might not be valid') logger.info('%s Prefix created in %s', arch, prefix) prefix_manager = WinePrefixManager(prefix) prefix_manager.setup_defaults()
def play(self): game_exe = self.game_exe arguments = self.game_config.get('args') or '' if not os.path.exists(game_exe): return {'error': 'FILE_NOT_FOUND', 'file': game_exe} launch_info = {} launch_info['env'] = self.get_env(full=False) if self.runner_config.get('xinput'): xinput_path = self.get_xinput_path() if xinput_path: logger.debug('Preloading %s', xinput_path) launch_info['ld_preload'] = self.get_xinput_path() else: logger.error('Missing koku-xinput-wine.so, Xinput won\'t be enabled') command = [self.get_executable()] if game_exe.endswith(".msi"): command.append('msiexec') command.append('/i') if game_exe.endswith('.lnk'): command.append('start') command.append('/unix') command.append(game_exe) if arguments: for arg in shlex.split(arguments): command.append(arg) launch_info['command'] = command return launch_info
def get_wine_versions(): """Return the list of Wine versions installed""" versions = [] for build in sorted(WINE_PATHS.keys()): version = get_system_wine_version(WINE_PATHS[build]) if version: versions.append(build) if system.path_exists(WINE_DIR): dirs = version_sort(os.listdir(WINE_DIR), reverse=True) for dirname in dirs: if is_version_installed(dirname): versions.append(dirname) for proton_path in get_proton_paths(): proton_versions = [p for p in os.listdir(proton_path) if "Proton" in p] for version in proton_versions: path = os.path.join(proton_path, version, "dist/bin/wine") if os.path.isfile(path): versions.append(version) if POL_PATH: for arch in ['x86', 'amd64']: builds_path = os.path.join(POL_PATH, "wine/linux-%s" % arch) if not system.path_exists(builds_path): continue for version in os.listdir(builds_path): if system.path_exists(os.path.join(builds_path, version, "bin/wine")): logger.debug("Adding PoL version %s", version) versions.append("PlayOnLinux %s-%s" % (version, arch)) else: logger.warning(os.path.join(builds_path, "bin/wine")) return versions
def set_regedit(path, key, value='', type='REG_SZ', wine_path=None, prefix=None, arch='win32'): """Add keys to the windows registry. Path is something like HKEY_CURRENT_USER\Software\Wine\Direct3D """ logger.debug("Setting wine registry key : %s\\%s to %s", path, key, value) reg_path = os.path.join(settings.CACHE_DIR, 'winekeys.reg') formatted_value = { 'REG_SZ': '"%s"' % value, 'REG_DWORD': 'dword:' + value, 'REG_BINARY': 'hex:' + value.replace(' ', ','), 'REG_MULTI_SZ': 'hex(2):' + value, 'REG_EXPAND_SZ': 'hex(7):' + value, } # Make temporary reg file reg_file = open(reg_path, "w") reg_file.write(dedent( """ REGEDIT4 [%s] "%s"=%s """ % (path, key, formatted_value[type]))) reg_file.close() set_regedit_file(reg_path, wine_path=wine_path, prefix=prefix, arch=arch) os.remove(reg_path)
def find_joysticks(self): """ Detect connected joysticks and return their ids """ joy_ids = [] if not self.is_installed: return [] output = subprocess.Popen( [self.get_executable(), "dummy"], stdout=subprocess.PIPE, universal_newlines=True, ).communicate()[0] ouput = output.split("\n") found = False joy_list = [] for line in ouput: if found and "Joystick" in line: joy_list.append(line) else: found = False if "Initializing joysticks" in line: found = True for joy in joy_list: index = joy.find("Unique ID:") joy_id = joy[index + 11:] logger.debug("Joystick found id %s ", joy_id) joy_ids.append(joy_id) return joy_ids
def download_banner(game, overwrite=False, callback=None): banner_url = settings.INSTALLER_URL + '%s.jpg' % game banner_path = get_banner_path(game) cover_downloaded = http.download_asset(banner_url, banner_path, overwrite) if cover_downloaded and callback: logger.debug("Icon downloaded for %s", game) callback(game)
def install(self, version=None, downloader=None, callback=None): """Install runner using package management systems.""" logger.debug("Installing %s (version=%s, downloader=%s, callback=%s)", self.name, version, downloader, callback) runner_info = self.get_runner_info(version) if not runner_info: raise RunnerInstallationError( '{} is not available for the {} architecture'.format( self.name, self.arch ) ) dialogs.ErrorDialog( ) return False opts = {} if downloader: opts['downloader'] = downloader if callback: opts['callback'] = callback if 'wine' in self.name: version = runner_info['version'] opts['merge_single'] = True dirname = '{}-{}'.format(version, runner_info['architecture']) opts['dest'] = os.path.join(settings.RUNNER_DIR, self.name, dirname) if self.name == 'libretro' and version: opts['merge_single'] = False opts['dest'] = os.path.join(settings.RUNNER_DIR, 'retroarch/cores') url = runner_info['url'] self.download_and_extract(url, **opts)
def configure_game(self, prelaunched, error=None): """Get the game ready to start, applying all the options This methods sets the game_runtime_config attribute. """ if error: logger.error(error) dialogs.ErrorDialog(str(error)) if not prelaunched: logger.error("Game prelaunch unsuccessful") dialogs.ErrorDialog("An error prevented the game from running") self.state = self.STATE_STOPPED self.emit('game-stop') return system_config = self.runner.system_config self.original_outputs = sorted( display.get_outputs(), key=lambda e: e.name == system_config.get("display") ) gameplay_info = self.runner.play() if "error" in gameplay_info: self.show_error_message(gameplay_info) self.state = self.STATE_STOPPED self.emit('game-stop') return logger.debug("Launching %s: %s", self.name, gameplay_info) logger.debug("Game info: %s", json.dumps(gameplay_info, indent=2)) env = {} sdl_gamecontrollerconfig = system_config.get("sdl_gamecontrollerconfig") if sdl_gamecontrollerconfig: path = os.path.expanduser(sdl_gamecontrollerconfig) if system.path_exists(path): with open(path, "r") as controllerdb_file: sdl_gamecontrollerconfig = controllerdb_file.read() env["SDL_GAMECONTROLLERCONFIG"] = sdl_gamecontrollerconfig sdl_video_fullscreen = system_config.get("sdl_video_fullscreen") or "" env["SDL_VIDEO_FULLSCREEN_DISPLAY"] = sdl_video_fullscreen restrict_to_display = system_config.get("display") if restrict_to_display != "off": if restrict_to_display == "primary": restrict_to_display = None for output in self.original_outputs: if output.primary: restrict_to_display = output.name break if not restrict_to_display: logger.warning("No primary display set") else: found = False for output in self.original_outputs: if output.name == restrict_to_display: found = True break if not found: logger.warning("Selected display %s not found", restrict_to_display) restrict_to_display = None if restrict_to_display: display.turn_off_except(restrict_to_display) time.sleep(3) self.resolution_changed = True resolution = system_config.get("resolution") if resolution != "off": display.change_resolution(resolution) time.sleep(3) self.resolution_changed = True if system_config.get("reset_pulse"): audio.reset_pulse() self.killswitch = system_config.get("killswitch") if self.killswitch and not system.path_exists(self.killswitch): # Prevent setting a killswitch to a file that doesn't exists self.killswitch = None # Command launch_arguments = gameplay_info["command"] optimus = system_config.get("optimus") if optimus == "primusrun" and system.find_executable("primusrun"): launch_arguments.insert(0, "primusrun") elif optimus == "optirun" and system.find_executable("optirun"): launch_arguments.insert(0, "virtualgl") launch_arguments.insert(0, "-b") launch_arguments.insert(0, "optirun") elif optimus == "pvkrun" and system.find_executable("pvkrun"): launch_arguments.insert(0, "pvkrun") xephyr = system_config.get("xephyr") or "off" if xephyr != "off": if not system.find_executable("Xephyr"): raise GameConfigError( "Unable to find Xephyr, install it or disable the Xephyr option" ) xephyr_depth = "8" if xephyr == "8bpp" else "16" xephyr_resolution = system_config.get("xephyr_resolution") or "640x480" xephyr_command = [ "Xephyr", ":2", "-ac", "-screen", xephyr_resolution + "x" + xephyr_depth, "-glamor", "-reset", "-terminate" ] if system_config.get("xephyr_fullscreen"): xephyr_command.append("-fullscreen") xephyr_thread = MonitoredCommand(xephyr_command) xephyr_thread.start() time.sleep(3) env["DISPLAY"] = ":2" if system_config.get("use_us_layout"): setxkbmap_command = ["setxkbmap", "-model", "pc101", "us", "-print"] xkbcomp_command = ["xkbcomp", "-", os.environ.get("DISPLAY", ":0")] xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE) subprocess.Popen( setxkbmap_command, env=os.environ, stdout=xkbcomp.stdin ).communicate() xkbcomp.communicate() pulse_latency = system_config.get("pulse_latency") if pulse_latency: env["PULSE_LATENCY_MSEC"] = "60" fps_limit = system_config.get("fps_limit") or "" if fps_limit: strangle_cmd = system.find_executable("strangle") launch_arguments = [strangle_cmd, fps_limit] + launch_arguments prefix_command = system_config.get("prefix_command") or "" if prefix_command: launch_arguments = ( shlex.split(os.path.expandvars(prefix_command)) + launch_arguments ) single_cpu = system_config.get("single_cpu") or False if single_cpu: logger.info("The game will run on a single CPU core") launch_arguments.insert(0, "0") launch_arguments.insert(0, "-c") launch_arguments.insert(0, "taskset") terminal = system_config.get("terminal") if terminal: terminal = system_config.get("terminal_app", system.get_default_terminal()) if terminal and not system.find_executable(terminal): dialogs.ErrorDialog( "The selected terminal application " "could not be launched:\n" "%s" % terminal ) self.state = self.STATE_STOPPED self.emit('game-stop') return # Env vars game_env = gameplay_info.get("env") or self.runner.get_env() env.update(game_env) # LD_PRELOAD ld_preload = gameplay_info.get("ld_preload") if ld_preload: env["LD_PRELOAD"] = ld_preload # Feral gamemode gamemode = system_config.get("gamemode") if gamemode: env["LD_PRELOAD"] = ":".join( [ path for path in [ env.get("LD_PRELOAD"), "/usr/$LIB/libgamemodeauto.so", ] if path ] ) # LD_LIBRARY_PATH game_ld_libary_path = gameplay_info.get("ld_library_path") if game_ld_libary_path: ld_library_path = env.get("LD_LIBRARY_PATH") if not ld_library_path: ld_library_path = "$LD_LIBRARY_PATH" env["LD_LIBRARY_PATH"] = ":".join([game_ld_libary_path, ld_library_path]) include_processes = shlex.split(system_config.get("include_processes", "")) exclude_processes = shlex.split(system_config.get("exclude_processes", "")) self.game_runtime_config = { "args": launch_arguments, "env": env, "terminal": terminal, "include_processes": include_processes, "exclude_processes": exclude_processes } if system_config.get("disable_compositor"): self.set_desktop_compositing(False) # xboxdrv setup xboxdrv_config = system_config.get("xboxdrv") if xboxdrv_config: self.xboxdrv_start(xboxdrv_config) prelaunch_command = system_config.get("prelaunch_command") if system.path_exists(prelaunch_command): self.prelaunch_executor = MonitoredCommand( [prelaunch_command], include_processes=[os.path.basename(prelaunch_command)], cwd=self.directory, ) self.prelaunch_executor.start() logger.info("Running %s in the background", prelaunch_command) if system_config.get("prelaunch_wait"): self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.prelaunch_beat) else: self.start_game()
def wipe_game_cache(self): logger.debug("Deleting games from service-games for %s", self.id) sql.db_delete(PGA_DB, "service_games", "service", self.id)
def login(self, parent=None): logger.debug("Connecting to %s", self.name) dialog = WebConnectDialog(self, parent) dialog.set_modal(True) dialog.show()
def on_confirm(self, button): # pylint: disable=unused-argument logger.debug("Action %s confirmed", self.action) self.action_confirmed = True self.destroy()
def on_button_toggled(self, button, action): # pylint: disable=unused-argument logger.debug("Action set to %s", action) self.action = action
def notify_steam_game_changed(self, appmanifest): logger.debug(appmanifest) logger.debug(appmanifest.states)
def do_play(self, prelaunched, _error=None): if not prelaunched: self.state = self.STATE_STOPPED return system_config = self.runner.system_config self.original_outputs = sorted(display.get_outputs(), key=lambda e: e[0] == system_config.get('display')) gameplay_info = self.runner.play() env = {} logger.debug("Launching %s: %s" % (self.name, gameplay_info)) if 'error' in gameplay_info: self.show_error_message(gameplay_info) self.state = self.STATE_STOPPED return sdl_gamecontrollerconfig = system_config.get('sdl_gamecontrollerconfig') if sdl_gamecontrollerconfig: path = os.path.expanduser(sdl_gamecontrollerconfig) if os.path.exists(path): with open(path, "r") as f: sdl_gamecontrollerconfig = f.read() env['SDL_GAMECONTROLLERCONFIG'] = sdl_gamecontrollerconfig sdl_video_fullscreen = system_config.get('sdl_video_fullscreen') or '' env['SDL_VIDEO_FULLSCREEN_DISPLAY'] = sdl_video_fullscreen restrict_to_display = system_config.get('display') if restrict_to_display != 'off': display.turn_off_except(restrict_to_display) time.sleep(3) self.resolution_changed = True resolution = system_config.get('resolution') if resolution != 'off': display.change_resolution(resolution) time.sleep(3) self.resolution_changed = True if system_config.get('reset_pulse'): audio.reset_pulse() self.killswitch = system_config.get('killswitch') if self.killswitch and not os.path.exists(self.killswitch): # Prevent setting a killswitch to a file that doesn't exists self.killswitch = None # Command launch_arguments = gameplay_info['command'] primusrun = system_config.get('primusrun') if primusrun and system.find_executable('primusrun'): launch_arguments.insert(0, 'primusrun') xephyr = system_config.get('xephyr') or 'off' if xephyr != 'off': if xephyr == '8bpp': xephyr_depth = '8' else: xephyr_depth = '16' xephyr_resolution = system_config.get('xephyr_resolution') or '640x480' xephyr_command = ['Xephyr', ':2', '-ac', '-screen', xephyr_resolution + 'x' + xephyr_depth, '-glamor', '-reset', '-terminate', '-fullscreen'] xephyr_thread = LutrisThread(xephyr_command) xephyr_thread.start() time.sleep(3) env['DISPLAY'] = ':2' if system_config.get('use_us_layout'): setxkbmap_command = ['setxkbmap', '-model', 'pc101', 'us', '-print'] xkbcomp_command = ['xkbcomp', '-', os.environ.get('DISPLAY', ':0')] xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE) subprocess.Popen(setxkbmap_command, env=os.environ, stdout=xkbcomp.stdin).communicate() xkbcomp.communicate() pulse_latency = system_config.get('pulse_latency') if pulse_latency: env['PULSE_LATENCY_MSEC'] = '60' prefix_command = system_config.get("prefix_command") or '' if prefix_command: launch_arguments = shlex.split(prefix_command) + launch_arguments single_cpu = system_config.get('single_cpu') or False if single_cpu: logger.info('The game will run on a single CPU core') launch_arguments.insert(0, '0') launch_arguments.insert(0, '-c') launch_arguments.insert(0, 'taskset') terminal = system_config.get('terminal') if terminal: terminal = system_config.get("terminal_app", system.get_default_terminal()) if terminal and not system.find_executable(terminal): dialogs.ErrorDialog("The selected terminal application " "could not be launched:\n" "%s" % terminal) self.state = self.STATE_STOPPED return # Env vars game_env = gameplay_info.get('env') or {} env.update(game_env) system_env = system_config.get('env') or {} env.update(system_env) ld_preload = gameplay_info.get('ld_preload') or '' env["LD_PRELOAD"] = ld_preload dri_prime = system_config.get('dri_prime') if dri_prime: env["DRI_PRIME"] = "1" else: env["DRI_PRIME"] = "0" # Runtime management ld_library_path = "" if self.runner.use_runtime(): runtime_env = runtime.get_env() if 'STEAM_RUNTIME' in runtime_env and 'STEAM_RUNTIME' not in env: env['STEAM_RUNTIME'] = runtime_env['STEAM_RUNTIME'] if 'LD_LIBRARY_PATH' in runtime_env: ld_library_path = runtime_env['LD_LIBRARY_PATH'] game_ld_libary_path = gameplay_info.get('ld_library_path') if game_ld_libary_path: if not ld_library_path: ld_library_path = '$LD_LIBRARY_PATH' ld_library_path = ":".join([game_ld_libary_path, ld_library_path]) env["LD_LIBRARY_PATH"] = ld_library_path # /Env vars include_processes = shlex.split(system_config.get('include_processes', '')) exclude_processes = shlex.split(system_config.get('exclude_processes', '')) monitoring_disabled = system_config.get('disable_monitoring') process_watch = not monitoring_disabled self.game_thread = LutrisThread(launch_arguments, runner=self.runner, env=env, rootpid=gameplay_info.get('rootpid'), watch=process_watch, term=terminal, log_buffer=self.log_buffer, include_processes=include_processes, exclude_processes=exclude_processes) if hasattr(self.runner, 'stop'): self.game_thread.set_stop_command(self.runner.stop) self.game_thread.start() self.state = self.STATE_RUNNING # xboxdrv setup xboxdrv_config = system_config.get('xboxdrv') if xboxdrv_config: self.xboxdrv_start(xboxdrv_config) if monitoring_disabled: logger.info("Process monitoring disabled") else: self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
def remove_folder(path): if os.path.exists(path): logger.debug("Removing folder %s" % path) if os.path.samefile(os.path.expanduser('~'), path): raise RuntimeError("Lutris tried to erase home directory!") shutil.rmtree(path)
def watch_children(self): """Poke at the running process(es). Return: bool: True to keep monitoring, False to stop (Used by GLib.timeout_add) """ if not self.game_process: logger.error('No game process available') return False if not self.is_running: logger.error('Game is not running') return False if not self.ready_state: # Don't monitor processes until the thread is in a ready state self.cycles_without_children = 0 return True processes, num_children, num_watched_children, terminated_children = self.get_processes( ) if num_watched_children > 0 and not self.monitoring_started: logger.debug("Start process monitoring") self.monitoring_started = True for key in processes: if processes[key] != self.monitored_processes[key]: self.monitored_processes[key] = processes[key] logger.debug("Processes %s: %s", key, ', '.join(processes[key]) or 'none') if self.runner and hasattr(self.runner, 'watch_game_process'): if not self.runner.watch_game_process(): self.is_running = False return False if num_watched_children == 0: time_since_start = time.time() - self.startup_time if self.monitoring_started or time_since_start > WARMUP_TIME: self.cycles_without_children += 1 cycles_left = MAX_CYCLES_WITHOUT_CHILDREN - self.cycles_without_children if cycles_left: if cycles_left < 4: logger.debug("Thread aborting in %d cycle", cycles_left) else: logger.warning("Thread aborting now") else: self.cycles_without_children = 0 if self.cycles_without_children >= MAX_CYCLES_WITHOUT_CHILDREN: self.is_running = False resume_stop = self.stop() if not resume_stop: logger.info("Full shutdown prevented") return False if num_children == 0: logger.debug("No children left in thread") self.game_process.communicate() else: logger.debug('%d processes are still active', num_children) if self.is_zombie(): logger.warning( 'Zombie process detected, killing game process') self.game_process.kill() self.return_code = self.game_process.returncode return False if terminated_children and terminated_children == num_watched_children: try: # Really not sure if that's necessary... self.game_process.wait(2) except subprocess.TimeoutExpired: logger.warning("Processes are still running") return True
def get_default_terminal(): terms = get_terminal_apps() if terms: return terms[0] else: logger.debug("Couldn't find a terminal emulator.")
def check_runner_install(self): """Check if the runner is installed before starting the installation Install the required runner(s) if necessary. This should handle runner dependencies (wine for winesteam) or runners used for installer tasks. """ required_runners = [] runner = self.get_runner_class(self.runner) if runner.depends_on is not None: required_runners.append(runner.depends_on()) required_runners.append(runner()) for command in self.script.get("installer", []): command_name, command_params = self._get_command_name_and_params( command) if command_name == "task": runner_name, _task_name = self._get_task_runner_and_name( command_params["name"]) runner_names = [r.name for r in required_runners] if runner_name not in runner_names: required_runners.append( self.get_runner_class(runner_name)()) logger.debug("Required runners: %s", required_runners) for runner in required_runners: params = {} if self.runner == "libretro": params["core"] = self.script["game"]["core"] if self.runner.startswith("wine"): # Force the wine version to be installed params["fallback"] = False params["min_version"] = wine.MIN_SAFE_VERSION version = self._get_runner_version() if version: params["version"] = version else: # Looking up default wine version default_wine = runner.get_runner_version() or {} if "version" in default_wine: logger.debug("Default wine version is %s", default_wine["version"]) # Set the version to both the is_installed params and # the script itself so the version gets saved at the # end of the install. if self.runner not in self.script: self.script[self.runner] = {} params["version"] = self.script[ self.runner]["version"] = "{}-{}".format( default_wine["version"], default_wine["architecture"]) else: logger.error( "Failed to get default wine version (got %s)", default_wine) if not runner.is_installed(**params): logger.info("Runner %s needs to be installed") self.runners_to_install.append(runner) if self.runner.startswith("wine") and not get_system_wine_version(): WineNotInstalledWarning(parent=self.parent) self.install_runners()
def shutdown(): """Cleanly quit Steam.""" logger.debug("Shutting down Steam") if is_running(): subprocess.call(["steam", "-shutdown"])
def on_game_collection_changed(self, _sender): """Simple method used to refresh the view""" logger.debug("Game collection changed") self.emit("view-updated") return True
def _save_game(self): """Write the game configuration in the DB and config file. This needs to be unfucked """ if self.extends: logger.info( "This is an extension to %s, not creating a new game entry", self.extends, ) return configpath = make_game_config_id(self.slug) config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) if self.requires: # Load the base game config required_game = pga.get_game_by_field(self.requires, field="installer_slug") base_config = LutrisConfig( runner_slug=self.runner, game_config_id=required_game["configpath"]) config = base_config.game_level else: config = {"game": {}} self.game_id = pga.add_or_update( name=self.game_name, runner=self.runner, slug=self.game_slug, directory=self.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, id=self.game_id, ) game = Game(self.game_id) game.save() logger.debug("Saved game entry %s (%d)", self.game_slug, self.game_id) # Config update if "system" in self.script: config["system"] = self._substitute_config(self.script["system"]) if self.runner in self.script and self.script[self.runner]: config[self.runner] = self._substitute_config( self.script[self.runner]) # Game options such as exe or main_file can be added at the root of the # script as a shortcut, this integrates them into the game config # properly launcher, launcher_value = _get_game_launcher(self.script) if isinstance(launcher_value, list): game_files = [] for game_file in launcher_value: if game_file in self.game_files: game_files.append(self.game_files[game_file]) else: game_files.append(game_file) config["game"][launcher] = game_files elif launcher_value: if launcher_value in self.game_files: launcher_value = self.game_files[launcher_value] elif self.target_path and os.path.exists( os.path.join(self.target_path, launcher_value)): launcher_value = os.path.join(self.target_path, launcher_value) config["game"][launcher] = launcher_value if "game" in self.script: try: config["game"].update(self.script["game"]) except ValueError: raise ScriptingError("Invalid 'game' section", self.script["game"]) config["game"] = self._substitute_config(config["game"]) yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config) game.emit("game-installed")
def watch_children(self): """Poke at the running process(es).""" if not self.game_process: logger.error('No game process available') return False if not self.is_running: logger.error('Game is not running') return False if not self.ready_state: # Don't monitor processes until the thread is in a ready state self.cycles_without_children = 0 return True processes, num_children, num_watched_children, terminated_children = self.get_processes( ) if num_watched_children > 0 and not self.monitoring_started: logger.debug("Start process monitoring") self.monitoring_started = True for key in processes: if processes[key] != self.monitored_processes[key]: self.monitored_processes[key] = processes[key] logger.debug("Processes %s: %s", key, ', '.join(processes[key]) or 'none') if self.runner and hasattr(self.runner, 'watch_game_process'): if not self.runner.watch_game_process(): self.is_running = False return False if num_watched_children == 0: time_since_start = time.time() - self.startup_time if self.monitoring_started or time_since_start > WARMUP_TIME: self.cycles_without_children += 1 cycles_left = MAX_CYCLES_WITHOUT_CHILDREN - self.cycles_without_children if cycles_left: if cycles_left < 4: logger.debug("Thread aborting in %d cycle", cycles_left) else: logger.warning("Thread aborting now") else: self.cycles_without_children = 0 if self.cycles_without_children >= MAX_CYCLES_WITHOUT_CHILDREN: self.is_running = False # Remove logger early to avoid issues with zombie processes # (unconfirmed) if self.stdout_monitor: logger.debug("Detaching logger") GLib.source_remove(self.stdout_monitor) resume_stop = self.stop() if not resume_stop: logger.info("Full shutdown prevented") return False if num_children == 0: logger.debug("No children left in thread") self.game_process.communicate() else: logger.debug('%d processes are still active', num_children) if self.is_zombie(): logger.warning( 'Zombie process detected, killing game process') self.game_process.kill() self.return_code = self.game_process.returncode return False if terminated_children and terminated_children == num_watched_children: logger.debug("Waiting for processes to exit") try: self.game_process.wait(2) except subprocess.TimeoutExpired: logger.warning("Processes are still running") return True if self.stdout_monitor: logger.debug("Removing stdout monitor") GLib.source_remove(self.stdout_monitor) logger.debug("Thread is no longer running") self.is_running = False self.restore_environment() return False return True
def show_update_runtime_dialog(): if os.environ.get("LUTRIS_SKIP_INIT"): logger.debug("Skipping initialization") else: init_dialog = LutrisInitDialog(update_runtime) init_dialog.run()
def login(self, parent=None): """Connect to GOG""" logger.debug("Connecting to GOG") dialog = WebConnectDialog(self, parent) dialog.set_modal(True) dialog.show()
def load(self): user_id, _persona_id, _user_name = self.get_identity() games = self.get_library(user_id) logger.debug(games)
def winekill(prefix, arch=WINE_DEFAULT_ARCH, wine_path=None, env=None, initial_pids=None): """Kill processes in Wine prefix.""" initial_pids = initial_pids or [] if not wine_path: wine = import_runner("wine") wine_path = wine().get_executable() wine_root = os.path.dirname(wine_path) if not env: env = {"WINEARCH": arch, "WINEPREFIX": prefix} command = [os.path.join(wine_root, "wineserver"), "-k"] logger.debug("Killing all wine processes: %s", command) logger.debug("\tWine prefix: %s", prefix) logger.debug("\tWine arch: %s", arch) if initial_pids: logger.debug("\tInitial pids: %s", initial_pids) system.execute(command, env=env, quiet=True) logger.debug("Waiting for wine processes to terminate") # Wineserver needs time to terminate processes num_cycles = 0 while True: num_cycles += 1 running_processes = [ pid for pid in initial_pids if system.path_exists("/proc/%s" % pid) ] if not running_processes: break if num_cycles > 20: logger.warning( "Some wine processes are still running: %s", ", ".join(running_processes), ) break time.sleep(0.1) logger.debug("Done waiting.")
def configure_game(self, prelaunched, error=None): # noqa: C901 """Get the game ready to start, applying all the options This methods sets the game_runtime_config attribute. """ # pylint: disable=too-many-locals,too-many-branches,too-many-statements # TODO: split into multiple methods to reduce complexity (42) if error: logger.error(error) dialogs.ErrorDialog(str(error)) if not prelaunched: logger.error("Game prelaunch unsuccessful") dialogs.ErrorDialog(_("An error prevented the game from running")) self.state = self.STATE_STOPPED self.emit("game-stop") return system_config = self.runner.system_config self.original_outputs = DISPLAY_MANAGER.get_config() gameplay_info = self.runner.play() if "error" in gameplay_info: self.show_error_message(gameplay_info) self.state = self.STATE_STOPPED self.emit("game-stop") return logger.debug("Launching %s", self.name) logger.debug(json.dumps(gameplay_info, indent=2)) env = {} sdl_gamecontrollerconfig = system_config.get( "sdl_gamecontrollerconfig") if sdl_gamecontrollerconfig: path = os.path.expanduser(sdl_gamecontrollerconfig) if system.path_exists(path): with open(path, "r") as controllerdb_file: sdl_gamecontrollerconfig = controllerdb_file.read() env["SDL_GAMECONTROLLERCONFIG"] = sdl_gamecontrollerconfig sdl_video_fullscreen = system_config.get("sdl_video_fullscreen") or "" env["SDL_VIDEO_FULLSCREEN_DISPLAY"] = sdl_video_fullscreen restrict_to_display = system_config.get("display") if restrict_to_display != "off": if restrict_to_display == "primary": restrict_to_display = None for output in self.original_outputs: if output.primary: restrict_to_display = output.name break if not restrict_to_display: logger.warning("No primary display set") else: found = False for output in self.original_outputs: if output.name == restrict_to_display: found = True break if not found: logger.warning("Selected display %s not found", restrict_to_display) restrict_to_display = None if restrict_to_display: turn_off_except(restrict_to_display) time.sleep(3) self.resolution_changed = True resolution = system_config.get("resolution") if resolution != "off": DISPLAY_MANAGER.set_resolution(resolution) time.sleep(3) self.resolution_changed = True if system_config.get("reset_pulse"): audio.reset_pulse() self.killswitch = system_config.get("killswitch") if self.killswitch and not system.path_exists(self.killswitch): # Prevent setting a killswitch to a file that doesn't exists self.killswitch = None # Command launch_arguments = gameplay_info["command"] optimus = system_config.get("optimus") if optimus == "primusrun" and system.find_executable("primusrun"): launch_arguments.insert(0, "primusrun") elif optimus == "optirun" and system.find_executable("optirun"): launch_arguments.insert(0, "virtualgl") launch_arguments.insert(0, "-b") launch_arguments.insert(0, "optirun") elif optimus == "pvkrun" and system.find_executable("pvkrun"): launch_arguments.insert(0, "pvkrun") xephyr = system_config.get("xephyr") or "off" if xephyr != "off": if not system.find_executable("Xephyr"): raise GameConfigError( "Unable to find Xephyr, install it or disable the Xephyr option" ) xephyr_depth = "8" if xephyr == "8bpp" else "16" xephyr_resolution = system_config.get( "xephyr_resolution") or "640x480" xephyr_command = [ "Xephyr", ":2", "-ac", "-screen", xephyr_resolution + "x" + xephyr_depth, "-glamor", "-reset", "-terminate", ] if system_config.get("xephyr_fullscreen"): xephyr_command.append("-fullscreen") xephyr_thread = MonitoredCommand(xephyr_command) xephyr_thread.start() time.sleep(3) env["DISPLAY"] = ":2" if system_config.get("use_us_layout"): setxkbmap_command = [ "setxkbmap", "-model", "pc101", "us", "-print" ] xkbcomp_command = ["xkbcomp", "-", os.environ.get("DISPLAY", ":0")] xkbcomp = subprocess.Popen(xkbcomp_command, stdin=subprocess.PIPE) subprocess.Popen(setxkbmap_command, env=os.environ, stdout=xkbcomp.stdin).communicate() xkbcomp.communicate() if system_config.get("aco"): env["RADV_PERFTEST"] = "aco" pulse_latency = system_config.get("pulse_latency") if pulse_latency: env["PULSE_LATENCY_MSEC"] = "60" vk_icd = system_config.get("vk_icd") if vk_icd and vk_icd != "off" and system.path_exists(vk_icd): env["VK_ICD_FILENAMES"] = vk_icd fps_limit = system_config.get("fps_limit") or "" if fps_limit: strangle_cmd = system.find_executable("strangle") if strangle_cmd: launch_arguments = [strangle_cmd, fps_limit] + launch_arguments else: logger.warning( "libstrangle is not available on this system, FPS limiter disabled" ) prefix_command = system_config.get("prefix_command") or "" if prefix_command: launch_arguments = ( shlex.split(os.path.expandvars(prefix_command)) + launch_arguments) single_cpu = system_config.get("single_cpu") or False if single_cpu: logger.info("The game will run on a single CPU core") launch_arguments.insert(0, "0") launch_arguments.insert(0, "-c") launch_arguments.insert(0, "taskset") terminal = system_config.get("terminal") if terminal: terminal = system_config.get("terminal_app", system.get_default_terminal()) if terminal and not system.find_executable(terminal): dialogs.ErrorDialog( _("The selected terminal application could not be launched:\n%s" ) % terminal) self.state = self.STATE_STOPPED self.emit("game-stop") return # Env vars game_env = gameplay_info.get("env") or self.runner.get_env() env.update(game_env) env["game_name"] = self.name # Prime vars prime = system_config.get("prime") if prime: env["__NV_PRIME_RENDER_OFFLOAD"] = "1" env["__GLX_VENDOR_LIBRARY_NAME"] = "nvidia" env["__VK_LAYER_NV_optimus"] = "NVIDIA_only" # LD_PRELOAD ld_preload = gameplay_info.get("ld_preload") if ld_preload: env["LD_PRELOAD"] = ld_preload # Feral gamemode gamemode = system_config.get( "gamemode") and LINUX_SYSTEM.gamemode_available() if gamemode: if system.find_executable("gamemoderun"): launch_arguments.insert(0, "gamemoderun") else: env["LD_PRELOAD"] = ":".join([ path for path in [ env.get("LD_PRELOAD"), "libgamemodeauto.so", ] if path ]) # LD_LIBRARY_PATH game_ld_libary_path = gameplay_info.get("ld_library_path") if game_ld_libary_path: ld_library_path = env.get("LD_LIBRARY_PATH") if not ld_library_path: ld_library_path = "$LD_LIBRARY_PATH" env["LD_LIBRARY_PATH"] = ":".join( [game_ld_libary_path, ld_library_path]) include_processes = shlex.split( system_config.get("include_processes", "")) exclude_processes = shlex.split( system_config.get("exclude_processes", "")) self.game_runtime_config = { "args": launch_arguments, "env": env, "terminal": terminal, "include_processes": include_processes, "exclude_processes": exclude_processes, } if system_config.get("disable_compositor"): self.set_desktop_compositing(False) prelaunch_command = system_config.get("prelaunch_command") if prelaunch_command: command_array = shlex.split(prelaunch_command) if system.path_exists(command_array[0]): self.prelaunch_executor = MonitoredCommand( command_array, include_processes=[os.path.basename(command_array[0])], env=self.game_runtime_config["env"], cwd=self.directory, ) self.prelaunch_executor.start() logger.info("Running %s in the background", prelaunch_command) if system_config.get("prelaunch_wait"): self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.prelaunch_beat) else: self.start_game()
def launch(self, game): """Launch a Lutris game""" logger.debug("Adding game %s (%s) to running games", game, id(game)) self.running_games.append(game) game.connect("game-stop", self.on_game_stop) game.play()
def notify_finish(self, runtime): """A runtime has finished downloading""" logger.debug("Runtime %s is now updated and available", runtime.name) self.current_updates -= 1 if self.current_updates == 0: logger.info("Runtime updated")
def refresh_icon(self, game_slug): logger.debug("Getting icon for %s", game_slug) AsyncCall(self.fetch_icon, None, game_slug)
def launch(self, game): """Launch a Lutris game""" logger.debug("Adding game %s (%s) to running games", game, id(game)) self.running_games.append(game) game.play()
def shutdown(self): logger.debug("Stopping all winesteam processes") super(winesteam, self).stop()
def on_media_loaded(self, response): for slug in self.games_to_refresh: logger.debug("Refreshing %s", slug) self.refresh_icon(slug)
def on_files_ready(self, _widget, files_ready): """Toggle state of continue button based on ready state""" logger.debug("Files are ready: %s", files_ready) self.continue_button.set_sensitive(files_ready)
def stop(self): if self.killall_on_exit(): logger.debug("Game configured to stop Steam on exit") self.shutdown() return True return False
def execute(command, env=None, cwd=None, log_errors=False, quiet=False, shell=False): """ Execute a system command and return its results. Params: command (list): A list containing an executable and its parameters env (dict): Dict of values to add to the current environment cwd (str): Working directory log_errors (bool): Pipe stderr to stdout (might cause slowdowns) quiet (bool): Do not display log messages Returns: str: stdout output """ # Check if the executable exists if not command: logger.error("No executable provided!") return if os.path.isabs(command[0]) and not path_exists(command[0]): logger.error("No executable found in %s", command) return if not quiet: logger.debug("Executing %s", " ".join(command)) # Set up environment existing_env = os.environ.copy() if env: if not quiet: logger.debug(" ".join("{}={}".format(k, v) for k, v in env.items())) env = {k: v for k, v in env.items() if v is not None} existing_env.update(env) # Piping stderr can cause slowness in the programs, use carefully # (especially when using regedit with wine) if log_errors: stderr_handler = subprocess.PIPE stderr_needs_closing = False else: stderr_handler = open(os.devnull, "w") stderr_needs_closing = True try: stdout, stderr = subprocess.Popen( command, shell=shell, stdout=subprocess.PIPE, stderr=stderr_handler, env=existing_env, cwd=cwd, ).communicate() except (OSError, TypeError) as ex: logger.error("Could not run command %s (env: %s): %s", command, env, ex) return finally: if stderr_needs_closing: stderr_handler.close() if stderr and log_errors: logger.error(stderr) return stdout.decode(errors="replace").strip()
def extract_archive(path, to_directory=".", merge_single=True, extractor=None): path = os.path.abspath(path) mode = None logger.debug("Extracting %s to %s", path, to_directory) if extractor is None: if path.endswith(".tar.gz") or path.endswith(".tgz"): extractor = "tgz" elif path.endswith(".tar.xz") or path.endswith(".txz"): extractor = "txz" elif path.endswith(".tar"): extractor = "tar" elif path.endswith(".tar.bz2") or path.endswith(".tbz"): extractor = "bz2" elif path.endswith(".gz"): extractor = "gzip" elif is_7zip_supported(path, None): extractor = None else: raise RuntimeError( "Could not extract `%s` - no appropriate extractor found" % path) if extractor == "tgz": opener, mode = tarfile.open, "r:gz" elif extractor == "txz": opener, mode = tarfile.open, "r:xz" elif extractor == "tar": opener, mode = tarfile.open, "r:" elif extractor == "bz2": opener, mode = tarfile.open, "r:bz2" elif extractor == "gzip": decompress_gz(path, to_directory) return elif extractor is None or is_7zip_supported(path, extractor): opener = "7zip" else: raise RuntimeError( "Could not extract `%s` - unknown format specified" % path) temp_name = ".extract-" + str(uuid.uuid4())[:8] temp_path = temp_dir = os.path.join(to_directory, temp_name) try: _do_extract(path, temp_path, opener, mode, extractor) except (OSError, zlib.error, tarfile.ReadError) as ex: logger.exception("Extraction failed: %s", ex) raise ExtractFailure(str(ex)) if merge_single: extracted = os.listdir(temp_path) if len(extracted) == 1: temp_path = os.path.join(temp_path, extracted[0]) if os.path.isfile(temp_path): destination_path = os.path.join(to_directory, extracted[0]) if os.path.isfile(destination_path): logger.warning("Overwrite existing file %s", destination_path) os.remove(destination_path) shutil.move(temp_path, to_directory) os.removedirs(temp_dir) else: for archive_file in os.listdir(temp_path): source_path = os.path.join(temp_path, archive_file) destination_path = os.path.join(to_directory, archive_file) # logger.debug("Moving extracted files from %s to %s", source_path, destination_path) if system.path_exists(destination_path): logger.warning("Overwrite existing path %s", destination_path) if os.path.isfile(destination_path): os.remove(destination_path) shutil.move(source_path, destination_path) elif os.path.isdir(destination_path): try: system.merge_folders(source_path, destination_path) except OSError as ex: logger.error("Failed to merge to destination %s: %s", destination_path, ex) raise ExtractFailure(str(ex)) else: shutil.move(source_path, destination_path) system.remove_folder(temp_dir) logger.debug("Finished extracting %s to %s", path, to_directory) return path, to_directory