def get_executable(self): if self.runner_config.get("lsi_steam") and system.find_executable("lsi-steam"): return system.find_executable("lsi-steam") runner_executable = self.runner_config.get("runner_executable") if runner_executable and os.path.isfile(runner_executable): return runner_executable return system.find_executable(self.runner_executable)
def get_optirun_choices(): """Return menu choices (label, value) for Optimus""" choices = [("Off", "off")] if system.find_executable("primusrun"): choices.append(("primusrun", "primusrun")) if system.find_executable("optirun"): choices.append(("optirun/virtualgl", "optirun")) if system.find_executable("pvkrun"): choices.append(("primus vk", "pvkrun")) return choices
def install(self): """Install runner using package management systems.""" # Return false if runner has no package, must be then another method # and install method should be overridden by the specific runner if not hasattr(self, 'package'): return False if self.is_installable is False: ErrorDialog('This runner is not yet installable') return False package_installer_candidates = ( 'gpk-install-package-name' 'software-center', ) package_installer = None for candidate in package_installer_candidates: if find_executable(candidate): package_installer = candidate break if not package_installer: logger.error("The distribution you're running is not supported.") logger.error("Edit runners/runner.py to add support for it") return False if not self.package: ErrorDialog('This runner is not yet installable') logger.error("The requested runner %s can't be installed", self.__class__.__name__) return False subprocess.Popen("%s %s" % (package_installer, self.package), shell=True, stderr=subprocess.PIPE) return True
def is_installed(self): custom_path = self.runner_config.get('custom_wine_path', '') if self.wine_version == 'system': if system.find_executable('wine'): return True else: dialogs.ErrorDialog( "Wine is not installed on your system.\n" "Let's fall back on Wine " + DEFAULT_WINE + " bundled with Lutris, alright?\n\n" "(To get rid of this message, either install Wine \n" "or change the Wine version in the game's configuration.)") elif self.wine_version == 'custom': if os.path.exists(custom_path): return True else: dialogs.ErrorDialog( "Your custom Wine version can't be launched.\n" "Let's fall back on Wine " + DEFAULT_WINE + " bundled with Lutris, alright? \n\n" "(To get rid of this message, fix your " "Custom Wine path \n" "or change the Wine version in the game's configuration.)") if os.path.exists(self.get_executable()): return True return False
def get_path_for_version(self, version): if version in WINE_PATHS.keys(): return system.find_executable(WINE_PATHS[version]) elif version == 'custom': return self.runner_config.get('custom_wine_path', '') else: return os.path.join(WINE_DIR, version, 'bin/wine')
def run(self): """Run the thread.""" logger.debug("Command env: " + self.env_string) logger.debug("Running command: " + self.command_string) # Store provided environment variables so they can be used by future # processes. for key, value in self.env.items(): logger.debug('Storing environment variable %s to %s', key, value) self.original_env[key] = os.environ.get(key) os.environ[key] = value # Reset library paths if they were not provided for key in ('LD_LIBRARY_PATH', 'LD_PRELOAD'): if key not in self.env and os.environ.get(key): del os.environ[key] # Copy the resulting environment to what will be passed to the process env = os.environ.copy() env.update(self.env) if self.terminal and system.find_executable(self.terminal): self.game_process = self.run_in_terminal() else: self.terminal = False self.game_process = self.execute_process(self.command, env) if not self.game_process: return if self.watch: GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) self.stdout_monitor = GLib.io_add_watch(self.game_process.stdout, GLib.IO_IN | GLib.IO_HUP, self.on_stdout_output) else: self.game_process.stdout.close()
def get_executable(self): scummvm_path = os.path.join(settings.DATA_DIR, 'runners/scummvm/scummvm') if not os.path.exists(scummvm_path): return find_executable("scummvm") else: return scummvm_path
def get_command_args(app): """Return a tuple with absolute command path and an argument string""" command = shlex.split(app.get_commandline()) # remove %U etc. and change %% to % in arguments args = list(map(lambda arg: re.sub("%[^%]", "", arg).replace("%%", "%"), command[1:])) exe = command[0] if not exe.startswith("/"): exe = system.find_executable(exe) return exe, subprocess.list2cmdline(args)
def update_desktop_icons(): """Update Icon for GTK+ desktop manager Other desktop manager icon cache commands must be added here if needed """ gtk_update_icon_cache = system.find_executable("gtk-update-icon-cache") if gtk_update_icon_cache: os.system( "gtk-update-icon-cache -tf %s" % os.path.join(GLib.get_user_data_dir(), "icons", "hicolor") )
def extract_7zip(path, dest, archive_type=None): _7zip_path = os.path.join(settings.RUNTIME_DIR, 'p7zip/7z') if not system.path_exists(_7zip_path): _7zip_path = system.find_executable('7z') if not system.path_exists(_7zip_path): raise OSError("7zip is not found in the lutris runtime or on the system") command = [_7zip_path, 'x', path, '-o{}'.format(dest), '-aoa'] if archive_type: command.append('-t{}'.format(archive_type)) subprocess.call(command)
def get_executable(self): # Lutris provided emulator pcsxr_path = os.path.join(settings.RUNNER_DIR, 'pcsxr/bin/pcsxr') if os.path.exists(pcsxr_path): return pcsxr_path # System wide available emulator candidates = ('pcsx', 'pcsxr') for candidate in candidates: executable = system.find_executable(candidate) if executable: return executable
def is_installed(self): """Return True if runner is installed else False.""" # Check 'get_executable' first if hasattr(self, 'get_executable'): executable = self.get_executable() if executable and os.path.exists(executable): return True # Fallback to 'executable' attribute (ssytem-wide install) if not getattr(self, 'executable', None): return False return bool(system.find_executable(self.executable))
def get_games(): """Return the list of games stored in the XDG menu.""" game_list = [] apps = Gio.AppInfo.get_all() for app in apps: if app.get_nodisplay() or app.get_is_hidden(): continue # Check app has an executable if not app.get_executable(): continue appid = os.path.splitext(app.get_id())[0] exe = None args = [] # must be in Game category categories = app.get_categories() if not categories: continue categories = list(filter(None, categories.lower().split(';'))) if 'game' not in categories: continue # contains a blacklisted category ok = True for category in categories: if category in map(str.lower, IGNORED_CATEGORIES): ok = False if not ok: continue # game is blacklisted if appid.lower() in map(str.lower, IGNORED_GAMES): continue # executable is blacklisted if app.get_executable().lower() in IGNORED_EXECUTABLES: continue cli = shlex.split(app.get_commandline()) exe = cli[0] args = cli[1:] # remove %U etc. and change %% to % in arguments args = list(map(lambda arg: re.sub('%[^%]', '', arg).replace('%%', '%'), args)) args = subprocess.list2cmdline(args) if not exe.startswith('/'): exe = system.find_executable(exe) game_list.append((app.get_display_name(), appid, exe, args)) return game_list
def is_installed_systemwide(): """Return whether Wine is installed outside of Lutris""" for build in WINE_PATHS.values(): if system.find_executable(build): if ( build == "wine" and system.path_exists("/usr/lib/wine/wine64") and not system.path_exists("/usr/lib/wine/wine") ): logger.warning("wine32 is missing from system") return False return True return False
def is_installed(self): if self.wine_version == 'system': if system.find_executable('wine'): return True else: return False elif self.wine_version == 'custom': custom_path = self.runner_config.get('custom_wine_path', '') if os.path.exists(custom_path): return True else: return False return os.path.exists(self.get_executable())
def winetricks(app, prefix=None, winetricks_env=None, silent=True, blocking=False): """Execute winetricks.""" path = (system.find_executable('winetricks') or os.path.join(datapath.get(), 'bin/winetricks')) arch = detect_prefix_arch(prefix) or 'win32' if not winetricks_env: winetricks_env = wine().get_executable() if str(silent).lower() in ('yes', 'on', 'true'): args = "-q " + app else: args = app wineexec(None, prefix=prefix, winetricks_env=winetricks_env, wine_path=path, arch=arch, args=args, blocking=blocking)
def _check_binary_dependencies(self): """Check if all required binaries are installed on the system. This reads a `require-binaries` entry in the script, parsed the same way as the `requires` entry. """ binary_dependencies = unpack_dependencies(self.script.get("require-binaries")) for dependency in binary_dependencies: if isinstance(dependency, tuple): installed_binaries = { dependency_option: bool(system.find_executable(dependency_option)) for dependency_option in dependency } if not any(installed_binaries.values()): raise ScriptingError( "This installer requires %s on your system" % " or ".join(dependency) ) else: if not system.find_executable(dependency): raise ScriptingError( "This installer requires %s on your system" % dependency )
def is_installed(self): """Return True if runner is installed else False""" is_installed = False if not self.executable: return False if hasattr(self, 'get_executable'): if os.path.exists(self.get_executable()): return True result = find_executable(self.executable) if result == '': is_installed = False else: is_installed = True return is_installed
def get_pids(self, wine_path=None): """Return a list of pids of processes using the current wine exe.""" if wine_path: exe = wine_path else: exe = self.get_executable() if not exe.startswith('/'): exe = system.find_executable(exe) pids = system.get_pids_using_file(exe) if self.wine_arch == 'win64': wine64 = exe + '64' pids_64 = system.get_pids_using_file(wine64) pids = pids | pids_64 return pids
def get_path_for_version(self, version): """Return the absolute path of a wine executable for a given version""" # logger.debug("Getting path for Wine %s", version) if version in WINE_PATHS.keys(): return system.find_executable(WINE_PATHS[version]) if "Proton" in version: for proton_path in get_proton_paths(): if os.path.isfile(os.path.join(proton_path, version, "dist/bin/wine")): return os.path.join(proton_path, version, "dist/bin/wine") if version.startswith("PlayOnLinux"): version, arch = version.split()[1].rsplit("-", 1) return os.path.join(POL_PATH, "wine", "linux-" + arch, version, "bin/wine") if version == "custom": return self.runner_config.get("custom_wine_path", "") return os.path.join(WINE_DIR, version, "bin/wine")
def get_executable(self): """Return the path to the Wine executable.""" path = WINE_DIR custom_path = self.runner_config.get('custom_wine_path', '') version = self.wine_version if version == 'system': if system.find_executable('wine'): return 'wine' # Fall back on bundled Wine version = DEFAULT_WINE elif version == 'custom': if os.path.exists(custom_path): return custom_path version = DEFAULT_WINE return os.path.join(path, version, 'bin/wine')
def cancel_installation(self, widget=None): """Ask a confirmation before cancelling the install""" confirm_cancel_dialog = QuestionDialog( { "question": "Are you sure you want to cancel the installation?", "title": "Cancel installation?", } ) if confirm_cancel_dialog.result != Gtk.ResponseType.YES: logger.warning("Attempting to terminate with the system wineserver. " "This is most likely to fail or to have no effect.") system.execute([system.find_executable("wineserver"), "-k9"]) return True if self.interpreter: self.interpreter.revert() self.interpreter.cleanup() self.destroy()
def get_path_for_version(self, version): """Return the absolute path of a wine executable for a given version""" # logger.debug("Getting path for Wine %s", version) if version in WINE_PATHS.keys(): return system.find_executable(WINE_PATHS[version]) if "Proton" in version: for proton_path in get_proton_paths(): if os.path.isfile( os.path.join(proton_path, version, "dist/bin/wine")): return os.path.join(proton_path, version, "dist/bin/wine") if version.startswith("PlayOnLinux"): version, arch = version.split()[1].rsplit("-", 1) return os.path.join(POL_PATH, "wine", "linux-" + arch, version, "bin/wine") if version == "custom": return self.runner_config.get("custom_wine_path", "") return os.path.join(WINE_DIR, version, "bin/wine")
def run(self): """Run the thread""" logger.debug("Thread running: %s", self.command) GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) if self.terminal and find_executable(self.terminal): self.run_in_terminal() else: self.game_process = subprocess.Popen(self.command, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.path, env=self.env) for line in iter(self.game_process.stdout.readline, ''): self.stdout += line if self.debug_output: sys.stdout.write(line)
def get_pids(self, wine_path=None): """Return a list of pids of processes using the current wine exe.""" if wine_path: exe = wine_path else: exe = self.get_executable() if not exe.startswith("/"): exe = system.find_executable(exe) pids = system.get_pids_using_file(exe) if self.wine_arch == "win64" and os.path.basename(exe) == "wine": pids = pids | system.get_pids_using_file(exe + "64") # Add wineserver PIDs to the mix (at least one occurence of fuser not # picking the games's PID from wine/wine64 but from wineserver for some # unknown reason. pids = pids | system.get_pids_using_file( os.path.join(os.path.dirname(exe), "wineserver")) return pids
def is_installed(self): """Return True if runner is installed else False.""" is_installed = False # Check 'get_executable' first if hasattr(self, 'get_executable'): executable = self.get_executable() if executable and os.path.exists(executable): return True # Fallback to 'executable' attribute (ssytem-wide install) if not getattr(self, 'executable', None): return False result = find_executable(self.executable) if result == '': is_installed = False else: is_installed = True return is_installed
def extract_exe(path, dest): if check_inno_exe(path): decompress_gog(path, dest) else: # use 7za to check if exe is an archive _7zip_path = os.path.join(settings.RUNTIME_DIR, "p7zip/7za") if not system.path_exists(_7zip_path): _7zip_path = system.find_executable("7za") if not system.path_exists(_7zip_path): raise OSError( "7zip is not found in the lutris runtime or on the system") command = [_7zip_path, "t", path] return_code = subprocess.call(command) if return_code == 0: extract_7zip(path, dest) else: raise RuntimeError( "specified exe is not an archive or GOG setup file")
def run(self): """Run the thread.""" logger.debug("Command env: " + self.env_string) logger.debug("Running command: " + self.command_string) if self.watch: GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) if self.terminal and find_executable(self.terminal): self.run_in_terminal() else: self.terminal = False env = os.environ.copy() env.update(self.env) self.game_process = self.execute_process(self.command, env) for line in iter(self.game_process.stdout.readline, ''): self.stdout += line if self.debug_output: sys.stdout.write(line)
def get_wine_version(wine_path="wine"): """Return the version of Wine installed on the system.""" if wine_path != "wine" and not system.path_exists(wine_path): return if wine_path == "wine" and not system.find_executable("wine"): return if os.path.isabs(wine_path): wine_stats = os.stat(wine_path) if wine_stats.st_size < 2000: # This version is a script, ignore it return version = system.read_process_output([wine_path, "--version"]) if not version: logger.error("Error reading wine version for %s", wine_path) return if version.startswith("wine-"): version = version[5:] return version
def get_executable(self): """Return the path to the Wine executable.""" path = WINE_DIR custom_path = self.runner_config.get('custom_wine_path', '') version = self.wine_version if version == 'system': if find_executable('wine'): return 'wine' # Fall back on bundled Wine version = DEFAULT_WINE elif version == 'custom': if os.path.exists(custom_path): return custom_path version = DEFAULT_WINE version += '-i386' return os.path.join(path, version, 'bin/wine')
def _get_graphics_adapters(): """Return the list of graphics cards available on a system Returns: list: list of tuples containing PCI ID and description of the VGA adapter """ lspci_path = system.find_executable("lspci") if not lspci_path: logger.warning("lspci is not available. List of graphics cards not available") return [] return [ (pci_id, vga_desc.split(": ")[1]) for pci_id, vga_desc in [ line.split(maxsplit=1) for line in system.execute(lspci_path).split("\n") if "VGA" in line ] ]
def __init__( self, command, runner=None, env=None, term=None, cwd=None, include_processes=None, exclude_processes=None, log_buffer=None, ): self.ready_state = True if env is None: self.env = {} else: self.env = env # not clear why this needs to be added, the path is already added in # the wrappper script. self.env['PYTHONPATH'] = ':'.join(sys.path) self.original_env = {} self.command = command self.runner = runner self.stop_func = lambda: True self.game_process = None self.return_code = None self.terminal = system.find_executable(term) self.is_running = True self.stdout = "" self.daemon = True self.error = None self.log_handlers = [ self.log_handler_stdout, self.log_handler_console_output, ] self.set_log_buffer(log_buffer) self.stdout_monitor = None self.watch_children_running = False self.include_processes = include_processes or [] self.exclude_processes = exclude_processes or [] # Keep a copy of previously running processes self.cwd = self.get_cwd(cwd)
def run(self): """Run the thread.""" logger.debug("Command env: " + self.env_string) logger.debug("Running command: " + self.command_string) if self.watch: GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) # Store provided environment variables so they can be used by future # processes. for key, value in self.env.items(): logger.debug('Storing environment variable %s to %s', key, value) self.original_env[key] = os.environ.get(key) os.environ[key] = value # Reset library paths if they were not provided for key in ('LD_LIBRARY_PATH', 'LD_PRELOAD'): if key not in self.env and os.environ.get(key): del os.environ[key] # Copy the resulting environment to what will be passed to the process env = os.environ.copy() env.update(self.env) if self.terminal and system.find_executable(self.terminal): self.run_in_terminal() else: self.terminal = False self.game_process = self.execute_process(self.command, env) if not self.game_process: return for line in iter(self.game_process.stdout.readline, b''): if not self.is_running: break try: line = line.decode() except UnicodeDecodeError: line = '' if not line: continue self.stdout += line if self.debug_output: with contextlib.suppress(BlockingIOError): sys.stdout.write(line) sys.stdout.flush()
def joy2key(self, config): """Run a joy2key thread.""" if not system.find_executable('joy2key'): logger.error("joy2key is not installed") return win = "grep %s" % config['window'] if 'notwindow' in config: win += ' | grep -v %s' % config['notwindow'] wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win buttons = config['buttons'] axis = "Left Right Up Down" rcfile = os.path.expanduser("~/.joy2keyrc") rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else '' command = "sleep 5 " command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % ( wid, rc_option, buttons, axis) joy2key_thread = LutrisThread(command) self.game_thread.attach_thread(joy2key_thread) joy2key_thread.start()
def prelaunch(self): def check_shutdown(is_running, times=10): for i in range(1, times): time.sleep(1) if not is_running(): return True # If using primusrun, shutdown existing Steam first primusrun = self.system_config.get('primusrun') if primusrun and system.find_executable('primusrun'): if is_running(): logger.info("Waiting for Steam shutdown...") shutdown() if not check_shutdown(is_running): logger.info("Steam does not shut down, killing it...") kill() if not check_shutdown(is_running, 5): logger.error("Failed to shut down Steam :(") return False return True
def get_pids(self, wine_path=None): """Return a list of pids of processes using the current wine exe.""" if wine_path: exe = wine_path else: exe = self.get_executable() if not exe.startswith("/"): exe = system.find_executable(exe) pids = system.get_pids_using_file(exe) if self.wine_arch == "win64" and os.path.basename(exe) == "wine": pids = pids | system.get_pids_using_file(exe + "64") # Add wineserver PIDs to the mix (at least one occurence of fuser not # picking the games's PID from wine/wine64 but from wineserver for some # unknown reason. pids = pids | system.get_pids_using_file( os.path.join(os.path.dirname(exe), "wineserver") ) return pids
def _get_graphics_adapters(): """Return the list of graphics cards available on a system Returns: list: list of tuples containing PCI ID and description of the display controller """ lspci_path = system.find_executable("lspci") dev_subclasses = ["VGA", "XGA", "3D controller", "Display controller"] if not lspci_path: logger.warning("lspci is not available. List of graphics cards not available") return [] return [ (pci_id, device_desc.split(": ")[1]) for pci_id, device_desc in [ line.split(maxsplit=1) for line in system.execute(lspci_path).split("\n") if any(subclass in line for subclass in dev_subclasses) ] ]
def __init__( self, command, runner=None, env=None, term=None, watch=True, cwd=None, include_processes=None, exclude_processes=None, log_buffer=None, ): self.ready_state = True if env is None: self.env = {} else: self.env = env self.original_env = {} self.command = command self.runner = runner self.stop_func = lambda: True self.game_process = None self.return_code = None self.terminal = system.find_executable(term) self.watch = watch self.is_running = True self.stdout = "" self.daemon = True self.error = None self.log_handlers = [ self.log_handler_stdout, self.log_handler_console_output, ] self.set_log_buffer(log_buffer) self.stdout_monitor = None self.watch_children_running = False # Keep a copy of previously running processes self.cwd = self.get_cwd(cwd) self.process_monitor = ProcessMonitor( include_processes, exclude_processes, "run_in_term.sh" if self.terminal else None)
def get_system_wine_version(wine_path="wine"): """Return the version of Wine installed on the system.""" if wine_path != "wine" and not system.path_exists(wine_path): return if wine_path == "wine" and not system.find_executable("wine"): return if os.path.isabs(wine_path): wine_stats = os.stat(wine_path) if wine_stats.st_size < 2000: # This version is a script, ignore it return try: version = subprocess.check_output([wine_path, "--version"]).decode().strip() except (OSError, subprocess.CalledProcessError) as ex: logger.exception("Error reading wine version for %s: %s", wine_path, ex) return else: if version.startswith("wine-"): version = version[5:] return version
def joy2key(self, config): """Run a joy2key thread.""" if not system.find_executable('joy2key'): logger.error("joy2key is not installed") return win = "grep %s" % config['window'] if 'notwindow' in config: win += ' | grep -v %s' % config['notwindow'] wid = "xwininfo -root -tree | %s | awk '{print $1}'" % win buttons = config['buttons'] axis = "Left Right Up Down" rcfile = os.path.expanduser("~/.joy2keyrc") rc_option = '-rcfile %s' % rcfile if os.path.exists(rcfile) else '' command = "sleep 5 " command += "&& joy2key $(%s) -X %s -buttons %s -axis %s" % ( wid, rc_option, buttons, axis ) joy2key_thread = LutrisThread(command) self.game_thread.attach_thread(joy2key_thread) joy2key_thread.start()
def run(self): """Run the thread""" logger.debug("Command env: " + self.env_string) logger.debug("Running command: " + self.command_string) GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) if self.terminal and find_executable(self.terminal): self.run_in_terminal() else: env = os.environ.copy() env.update(self.env) self.game_process = subprocess.Popen(self.command, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.path, env=env) os.chdir(os.path.expanduser('~')) for line in iter(self.game_process.stdout.readline, ''): self.stdout += line if self.debug_output: sys.stdout.write(line)
def decompress_gog(file_path, destination_path): _innoextract_path = os.path.join(settings.RUNTIME_DIR, "innoextract/innoextract") if not system.path_exists(_innoextract_path): _innoextract_path = system.find_executable("innoextract") if not system.path_exists(_innoextract_path): raise OSError( "innoextract is not found in the lutris runtime or on the system") # innoextract cannot do mkdir -p, so we have to do it here: try: os.makedirs(destination_path) except OSError as e: if e.errno != errno.EEXIST: raise OSError( "cannot make output directory for extracting setup file") command = [ _innoextract_path, "-g", "-d", destination_path, "-e", file_path ] return_code = subprocess.call(command) if return_code != 0: raise RuntimeError("innoextract failed to extract GOG setup file")
def run(self): """Run the thread.""" logger.debug("Command env: " + self.env_string) logger.debug("Running command: " + self.command_string) if self.terminal and system.find_executable(self.terminal): self.game_process = self.run_in_terminal() else: self.terminal = False env = self.apply_environment() self.game_process = self.execute_process(self.command, env) if not self.game_process: logger.warning("No game process available") return if self.watch: GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) self.stdout_monitor = GLib.io_add_watch(self.game_process.stdout, GLib.IO_IN | GLib.IO_HUP, self.on_stdout_output)
def winetricks( app, prefix=None, arch=None, silent=True, wine_path=None, config=None, env=None, disable_runtime=False, ): """Execute winetricks.""" wine_config = config or LutrisConfig(runner_slug="wine") winetricks_path = os.path.join(settings.RUNTIME_DIR, "winetricks/winetricks") if (wine_config.runner_config.get("system_winetricks") or not system.path_exists(winetricks_path)): winetricks_path = system.find_executable("winetricks") if not winetricks_path: raise RuntimeError("No installation of winetricks found") if wine_path: winetricks_wine = wine_path else: wine = import_runner("wine") winetricks_wine = wine().get_executable() if arch not in ("win32", "win64"): arch = detect_arch(prefix, winetricks_wine) args = app if str(silent).lower() in ("yes", "on", "true"): args = "--unattended " + args return wineexec( None, prefix=prefix, winetricks_wine=winetricks_wine, wine_path=winetricks_path, arch=arch, args=args, config=config, env=env, disable_runtime=disable_runtime, )
def __init__( self, command, runner=None, env=None, term=None, cwd=None, include_processes=None, exclude_processes=None, log_buffer=None, title=None, ): # pylint: disable=too-many-arguments self.ready_state = True self.env = self.get_environment(env) self.command = command self.runner = runner self.stop_func = lambda: True self.game_process = None self.prevent_on_stop = False self.return_code = None self.terminal = system.find_executable(term) self.is_running = True self.error = None self.log_handlers = [ self.log_handler_stdout, self.log_handler_console_output, ] self.set_log_buffer(log_buffer) self.stdout_monitor = None self.include_processes = include_processes or [] self.exclude_processes = exclude_processes or [] self.cwd = self.get_cwd(cwd) self._stdout = io.StringIO() self._title = title if title else command[0]
def install(self): """Install runner using package management systems.""" # Prioritize provided tarballs. tarball = self.tarballs.get(self.arch) if tarball: self.download_and_extract(tarball) return True # Return false if runner has no package, must be then another method # and install method should be overridden by the specific runner if not hasattr(self, 'package'): return False package_installer_candidates = ( 'gpk-install-package-name', 'software-center', ) package_installer = None for candidate in package_installer_candidates: if find_executable(candidate): package_installer = candidate break if not package_installer: logger.error("The distribution you're running is not supported.") logger.error("Edit runners/runner.py to add support for it") return False if not self.package: dialogs.ErrorDialog('This runner is not yet installable') logger.error("The requested runner %s can't be installed", self.__class__.__name__) return False subprocess.Popen("%s %s" % (package_installer, self.package), shell=True, stderr=subprocess.PIPE) return True
def cancel_installation(self, widget=None): """Ask a confirmation before cancelling the install""" remove_checkbox = Gtk.CheckButton.new_with_label("Remove game files") if self.interpreter: remove_checkbox.set_active(self.interpreter.game_dir_created) remove_checkbox.show() confirm_cancel_dialog = QuestionDialog( { "question": "Are you sure you want to cancel the installation?", "title": "Cancel installation?", "widgets": [remove_checkbox] } ) if confirm_cancel_dialog.result != Gtk.ResponseType.YES: logger.warning("Attempting to terminate with the system wineserver. " "This is most likely to fail or to have no effect.") system.execute([system.find_executable("wineserver"), "-k9"]) return True if self.interpreter: self.interpreter.game_dir_created = remove_checkbox.get_active() self.interpreter.revert() self.interpreter.cleanup() self.destroy()
def get_launcher_path(game_slug, game_id): """Return the path of a XDG game launcher. When legacy is set, it will return the old path with only the slug, otherwise it will return the path with slug + id """ xdg_executable = 'xdg-user-dir' if not system.find_executable(xdg_executable): logger.error("%s not found", xdg_executable) return desktop_dir = subprocess.Popen([xdg_executable, 'DESKTOP'], stdout=subprocess.PIPE).communicate()[0] desktop_dir = str(desktop_dir).strip() legacy_launcher_path = os.path.join( desktop_dir, get_xdg_basename(game_slug, game_id, legacy=True) ) # First check if legacy path exists, for backward compatibility if system.path_exists(legacy_launcher_path): return legacy_launcher_path # Otherwise return new path, whether it exists or not return os.path.join( desktop_dir, get_xdg_basename(game_slug, game_id, legacy=False) )
def get_executable(self, version=None): """Return the path to the Wine executable. A specific version can be specified if needed. """ path = WINE_DIR custom_path = self.runner_config.get('custom_wine_path', '') if version is None: version = self.get_version() if not version: return if version in WINE_PATHS.keys(): abs_path = system.find_executable(WINE_PATHS[version]) if abs_path: return abs_path # Fall back on bundled Wine version = get_default_version() elif version == 'custom': if os.path.exists(custom_path): return custom_path version = get_default_version() return os.path.join(path, version, 'bin/wine')
def start_xephyr(self, display=":2"): """Start a monitored Xephyr instance""" if not system.find_executable("Xephyr"): raise GameConfigError("Unable to find Xephyr, install it or disable the Xephyr option") xephyr_depth = "8" if self.runner.system_config.get("xephyr") == "8bpp" else "16" xephyr_resolution = self.runner.system_config.get("xephyr_resolution") or "640x480" xephyr_command = [ "Xephyr", display, "-ac", "-screen", xephyr_resolution + "x" + xephyr_depth, "-glamor", "-reset", "-terminate", ] if self.runner.system_config.get("xephyr_fullscreen"): xephyr_command.append("-fullscreen") xephyr_thread = MonitoredCommand(xephyr_command) xephyr_thread.start() time.sleep(3) return display
def run(self): """Run the thread.""" logger.debug("Command env: %s", self.env_string) logger.debug("Running command: %s", self.command_string) result = ctypes.CDLL(find_library('c')).prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0, 0) if result == -1: logger.warning("PR_SET_CHILD_SUBREAPER failed, process watching may fail") if self.terminal and system.find_executable(self.terminal): self.game_process = self.run_in_terminal() else: self.terminal = False env = self.apply_environment() self.game_process = self.execute_process(self.command, env) if not self.game_process: logger.warning("No game process available") return if self.watch: GLib.timeout_add(HEARTBEAT_DELAY, self.watch_children) self.stdout_monitor = GLib.io_add_watch(self.game_process.stdout, GLib.IO_IN | GLib.IO_HUP, self.on_stdout_output)
"type": "bool", "default": False, "advanced": True, "help": ("Disable desktop effects while game is running, " "reducing stuttering and increasing performance"), }, { "option": "reset_pulse", "type": "bool", "label": "Reset PulseAudio", "default": False, "advanced": True, "condition": system.find_executable("pulseaudio"), "help": "Restart PulseAudio before launching the game.", }, { "option": "pulse_latency", "type": "bool", "label": "Reduce PulseAudio latency", "default": False, "advanced": True, "condition": system.find_executable("pulseaudio"),
def play(self): """Launch the game.""" if not self.runner: dialogs.ErrorDialog("Invalid game configuration: Missing runner") return False if not self.prelaunch(): return False system_config = self.runner.system_config self.original_outputs = display.get_outputs() gameplay_info = self.runner.play() logger.debug("Launching %s: %s" % (self.name, gameplay_info)) if 'error' in gameplay_info: show_error_message(gameplay_info) return False launch_arguments = gameplay_info['command'] restrict_to_display = system_config.get('display') 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: display.change_resolution(resolution) time.sleep(3) self.resolution_changed = True if system_config.get('reset_pulse'): audio.reset_pulse() primusrun = system_config.get('primusrun') if primusrun and system.find_executable('primusrun'): launch_arguments.insert(0, 'primusrun') prefix_command = system_config.get("prefix_command", '').strip() if prefix_command: launch_arguments.insert(0, prefix_command) ld_preload = gameplay_info.get('ld_preload') if ld_preload: launch_arguments.insert(0, 'LD_PRELOAD="{}"'.format(ld_preload)) ld_library_path = [] if self.use_runtime(system_config): runtime64_path = os.path.join(settings.RUNTIME_DIR, "lib64") if os.path.exists(runtime64_path): ld_library_path.append(runtime64_path) runtime32_path = os.path.join(settings.RUNTIME_DIR, "lib32") if os.path.exists(runtime32_path): ld_library_path.append(runtime32_path) game_ld_libary_path = gameplay_info.get('ld_library_path') if game_ld_libary_path: ld_library_path.append(game_ld_libary_path) if ld_library_path: ld_full = ':'.join(ld_library_path) ld_arg = 'LD_LIBRARY_PATH="{}:$LD_LIBRARY_PATH"'.format(ld_full) launch_arguments.insert(0, ld_arg) env = gameplay_info.get('env') or [] for var in env: launch_arguments.insert(0, var) killswitch = system_config.get('killswitch') self.game_thread = LutrisThread(" ".join(launch_arguments), path=self.runner.working_dir, killswitch=killswitch) if hasattr(self.runner, 'stop'): self.game_thread.set_stop_command(self.runner.stop) self.game_thread.start() if 'joy2key' in gameplay_info: self.joy2key(gameplay_info['joy2key']) xboxdrv_config = system_config.get('xboxdrv') if xboxdrv_config: self.xboxdrv_start(xboxdrv_config) if self.runner.is_watchable: # Create heartbeat every self.heartbeat = GLib.timeout_add(5000, self.poke_process)
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 = display.get_outputs() 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_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.strip(): launch_arguments.insert(0, prefix_command) 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 # 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 self.game_thread = LutrisThread(launch_arguments, runner=self.runner, env=env, rootpid=gameplay_info.get('rootpid'), term=terminal) if hasattr(self.runner, 'stop'): self.game_thread.set_stop_command(self.runner.stop) self.game_thread.start() self.state = self.STATE_RUNNING if 'joy2key' in gameplay_info: self.joy2key(gameplay_info['joy2key']) xboxdrv_config = system_config.get('xboxdrv') if xboxdrv_config: self.xboxdrv_start(xboxdrv_config) self.heartbeat = GLib.timeout_add(HEARTBEAT_DELAY, self.beat)
"closed or when they crash. This is when this option comes \n" "into play to save your bacon."), }, { "option": "gamescope", "type": "bool", "label": _("Enable gamescope"), "default": False, "advanced": True, "condition": bool(system.find_executable("gamescope")), "help": _("Use gamescope to draw the game window isolated from your desktop.\n" "Use Ctrl+Super+F to toggle fullscreen"), }, { "option": "gamescope_output_res", "type": "string", "label": _("Gamescope output resolution"), "default": False, "advanced": True, "condition": bool(system.find_executable("gamescope")), "help": _("Resolution of the window on your desktop"), }, { "option": "gamescope_game_res",
def has_steam(self): """Return whether Steam is installed locally""" return bool(system.find_executable("steam"))
def get_launch_parameters(runner, gameplay_info): system_config = runner.system_config launch_arguments = gameplay_info["command"] env = {} # Optimus 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") mango_args, mango_env = get_mangohud_conf(system_config) if mango_args: launch_arguments = mango_args + launch_arguments env.update(mango_env) # Libstrangle 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") env.update(runner.get_env()) env.update(gameplay_info.get("env") or {}) # Set environment variables dependent on gameplay info # LD_PRELOAD ld_preload = gameplay_info.get("ld_preload") if ld_preload: env["LD_PRELOAD"] = ld_preload # 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]) # Feral gamemode gamemode = system_config.get( "gamemode") and LINUX_SYSTEM.gamemode_available() if gamemode and system.find_executable("gamemoderun"): launch_arguments.insert(0, "gamemoderun") return launch_arguments, env