Esempio n. 1
0
def send_notification(title, text, file_path_to_icon=""):
    if NOTIFY_SUPPORT:
        notification = Notify.Notification.new(title, text, file_path_to_icon)
        notification.show()
    else:
        logger.info(title)
        logger.info(text)
Esempio n. 2
0
 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
Esempio n. 3
0
 def get_game_path_from_appid(self, appid):
     """Return the game directory."""
     for apps_path in self.get_steamapps_dirs():
         game_path = get_path_from_appmanifest(apps_path, appid)
         if game_path:
             return game_path
     logger.info("Data path for SteamApp %s not found.", appid)
Esempio n. 4
0
 def get_gog_download_links(self):
     """Return a list of downloadable links for a GOG game"""
     gog_service = GogService()
     if not gog_service.is_available():
         logger.info("You are not connected to GOG")
         connect_gog()
     if not gog_service.is_available():
         raise UnavailableGame
     gog_installers = self.get_gog_installers(gog_service)
     if len(gog_installers) > 1:
         raise ScriptingError("Don't know how to deal with multiple installers yet.")
     try:
         installer = gog_installers[0]
     except IndexError:
         raise UnavailableGame
     download_links = []
     for game_file in installer.get('files', []):
         downlink = game_file.get("downlink")
         if not downlink:
             logger.error("No download information for %s", installer)
             continue
         download_info = gog_service.get_download_info(downlink)
         for field in ('checksum', 'downlink'):
             url = download_info[field]
             logger.info("Adding %s to download links", url)
             download_links.append(download_info[field])
     return download_links
Esempio n. 5
0
def mark_as_installed(appid, runner_name, game_info):
    for key in ['name', 'slug']:
        assert game_info[key]
    logger.info("Setting %s as installed" % game_info['name'])
    config_id = (game_info.get('config_path') or make_game_config_id(game_info['slug']))
    game_id = pga.add_or_update(
        name=game_info['name'],
        runner=runner_name,
        slug=game_info['slug'],
        installed=1,
        configpath=config_id,
        installer_slug=game_info['installer_slug']
    )

    config = LutrisConfig(
        runner_slug=runner_name,
        game_config_id=config_id,
    )
    config.raw_game_config.update({
        'appid': appid,
        'exe': game_info['exe'],
        'args': game_info['args']
    })
    config.raw_system_config.update({
        'disable_runtime': True
    })
    config.save()
    return game_id
Esempio n. 6
0
def sync_with_lutris():
    desktop_games = {
        game['slug']: game
        for game in pga.get_games_where(runner='linux',
                                        installer_slug=INSTALLER_SLUG,
                                        installed=1)
    }
    seen = set()

    for name, appid, exe, args in get_games():
        slug = slugify(name) or slugify(appid)
        if not all([name, slug, appid]):
            logger.error("Failed to load desktop game \"{}\" (app: {}, slug: {})".format(name, appid, slug))
            continue
        else:
            logger.info("Found desktop game \"{}\" (app: {}, slug: {})".format(name, appid, slug))
        seen.add(slug)

        if slug not in desktop_games.keys():
            game_info = {
                'name': name,
                'slug': slug,
                'config_path': slug + '-' + INSTALLER_SLUG,
                'installer_slug': INSTALLER_SLUG,
                'exe': exe,
                'args': args
            }
            mark_as_installed(appid, 'linux', game_info)

    for slug in set(desktop_games.keys()).difference(seen):
        mark_as_uninstalled(desktop_games[slug])
Esempio n. 7
0
    def on_remove_clicked(self, widget, runner, runner_label):
        if not runner.is_installed():
            logger.warning("Runner %s is not installed", runner)
            return

        if runner.multiple_versions:
            logger.info("Removing multiple versions")
            builder = get_builder_from_file('runner-remove-all-versions-dialog.ui')
            builder.connect_signals(self)
            remove_confirm_button = builder.get_object('remove_confirm_button')
            remove_confirm_button.connect(
                "clicked",
                self.on_remove_all_clicked,
                runner,
                runner_label
            )
            all_versions_label = builder.get_object('runner_all_versions_label')
            all_versions_label.set_markup(all_versions_label.get_label() % runner.human_name)
            self.all_versions_dialog = builder.get_object('runner_remove_all_versions_dialog')
            self.all_versions_dialog.set_parent(self.dialog)
            self.all_versions_dialog.show()
        else:
            builder = get_builder_from_file('runner-remove-confirm-dialog.ui')
            builder.connect_signals(self)
            remove_confirm_button = builder.get_object('remove_confirm_button')
            remove_confirm_button.connect(
                "clicked",
                self.on_remove_confirm_clicked,
                runner,
                runner_label
            )
            runner_remove_label = builder.get_object('runner_remove_label')
            runner_remove_label.set_markup(runner_remove_label.get_label() % runner.human_name)
            self.remove_confirm_dialog = builder.get_object('runner_remove_confirm_dialog')
            self.remove_confirm_dialog.show()
Esempio n. 8
0
    def iter_game_files(self):
        if self.files:
            # Create cache dir if needed
            if not os.path.exists(self.download_cache_path):
                os.mkdir(self.download_cache_path)

            if self.target_path and self.should_create_target:
                os.makedirs(self.target_path)
                self.reversion_data['created_main_dir'] = True

        if len(self.game_files) < len(self.files):
            logger.info(
                "Downloading file %d of %d",
                len(self.game_files) + 1, len(self.files)
            )
            file_index = len(self.game_files)
            try:
                current_file = self.files[file_index]
            except KeyError:
                raise ScriptingError("Error getting file %d in %s",
                                     file_index, self.files)
            self._download_file(current_file)
        else:
            self.current_command = 0
            self._prepare_commands()
Esempio n. 9
0
 def apply_to_registry(self, file_path, arch):
     logger.info("Applying %s to registry", file_path)
     subprocess.call([
         self.get_winebin(arch),
         "regedit",
         os.path.join(self.tmpdir, file_path)
     ])
Esempio n. 10
0
File: wine.py Progetto: Freso/lutris
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()
Esempio n. 11
0
    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))
Esempio n. 12
0
    def get_runner_version(self, version=None):
        logger.info(
            "Getting runner information for %s%s",
            self.name,
            "(version: %s)" % version if version else "",
        )
        runner_info = self.get_runner_info()
        if not runner_info:
            logger.error("Failed to get runner information")
            return

        versions = runner_info.get("versions") or []
        arch = self.arch
        if version:
            if version.endswith("-i386") or version.endswith("-x86_64"):
                version, arch = version.rsplit("-", 1)
            versions = [v for v in versions if v["version"] == version]
        versions_for_arch = [v for v in versions if v["architecture"] == arch]
        if len(versions_for_arch) == 1:
            return versions_for_arch[0]

        if len(versions_for_arch) > 1:
            default_version = [v for v in versions_for_arch if v["default"] is True]
            if default_version:
                return default_version[0]
        elif len(versions) == 1 and system.LINUX_SYSTEM.is_64_bit:
            return versions[0]
        elif len(versions) > 1 and system.LINUX_SYSTEM.is_64_bit:
            default_version = [v for v in versions if v["default"] is True]
            if default_version:
                return default_version[0]
        # If we didn't find a proper version yet, return the first available.
        if len(versions_for_arch) >= 1:
            return versions_for_arch[0]
Esempio n. 13
0
    def iter_game_files(self):
        """Iterate through game files, downloading them or querying them from the user"""
        if self.files:
            if (
                    self.target_path
                    and not system.path_exists(self.target_path)
                    and self.creates_game_folder
            ):
                try:
                    os.makedirs(self.target_path)
                except PermissionError:
                    raise ScriptingError(
                        "Lutris does not have the necessary permissions to install to path:",
                        self.target_path,
                    )
                self.game_dir_created = True

        if len(self.game_files) < len(self.files):
            logger.info(
                "Downloading file %d of %d", len(self.game_files) + 1, len(self.files)
            )
            file_index = len(self.game_files)
            try:
                current_file = self.files[file_index]
            except KeyError:
                raise ScriptingError(
                    "Error getting file %d in %s" % file_index, self.files
                )
            self._download_file(current_file)
        else:
            self.current_command = 0
            self._prepare_commands()
Esempio n. 14
0
 def use_runtime(self):
     if runtime.RUNTIME_DISABLED:
         logger.info("Runtime disabled by environment")
         return False
     if self.system_config.get("disable_runtime"):
         logger.info("Runtime disabled by system configuration")
         return False
     return True
Esempio n. 15
0
 def toggle_connection(self, is_connected, username=None):
     self.props.application.set_connect_state(is_connected)
     if is_connected:
         connection_status = username
         logger.info('Connected to lutris.net as %s', connection_status)
     else:
         connection_status = "Not connected"
     self.connection_label.set_text(connection_status)
Esempio n. 16
0
    def install(self, cabfile, component):
        """Install `component` from `cabfile`"""
        logger.info("Installing %s from %s", component, cabfile)

        for file_path, arch in self.get_registry_files(self.extract_from_cab(cabfile, component)):
            self.apply_to_registry(file_path, arch)

        self.cleanup()
Esempio n. 17
0
 def shutdown(self):
     """Orders Steam to shutdown"""
     logger.info("Shutting down Steam")
     shutdown_command = MonitoredCommand(
         (self.launch_args + ["-shutdown"]),
         runner=self,
         env=self.get_env(os_env=False)
     )
     shutdown_command.start()
Esempio n. 18
0
 def disable_dxvk_dll(self, system_dir, dxvk_arch, dll):
     """Remove DXVK DLL from Wine prefix"""
     wine_dll_path = os.path.join(system_dir, "%s.dll" % dll)
     if self.is_dxvk_dll(wine_dll_path):
         logger.info("Removing "+self.base_name.upper()+" dll %s/%s", system_dir, dll)
         os.remove(wine_dll_path)
     # Restoring original version (may not be needed)
     if system.path_exists(wine_dll_path + ".orig"):
         shutil.move(wine_dll_path + ".orig", wine_dll_path)
Esempio n. 19
0
 def toggle_connection(self, is_connected, username=None):
     """Sets or unset connected state for the current user"""
     self.connect_button.props.visible = not is_connected
     self.register_button.props.visible = not is_connected
     self.disconnect_button.props.visible = is_connected
     self.sync_button.props.visible = is_connected
     if is_connected:
         self.connection_label.set_text(username)
         logger.info("Connected to lutris.net as %s", username)
Esempio n. 20
0
    def prelaunch(self):
        config_file = self.get_config_file()
        if not os.path.exists(config_file):
            logger.warning("Unable to find retroarch config. Except erratic behavior")
            return True
        retro_config = RetroConfig(config_file)

        retro_config['libretro_directory'] = get_default_cores_directory()
        retro_config['libretro_info_path'] = get_default_info_directory()

        # Change assets path to the Lutris provided one if necessary
        assets_directory = os.path.expanduser(retro_config['assets_directory'])
        if system.path_is_empty(assets_directory):
            retro_config['assets_directory'] = get_default_assets_directory()
        retro_config.save()

        core = self.game_config.get('core')
        info_file = os.path.join(get_default_info_directory(),
                                 '{}_libretro.info'.format(core))
        if os.path.exists(info_file):
            core_config = RetroConfig(info_file)
            try:
                firmware_count = int(core_config['firmware_count'])
            except (ValueError, TypeError):
                firmware_count = 0
            system_path = self.get_system_directory(retro_config)
            notes = core_config['notes'] or ''
            checksums = {}
            if notes.startswith('Suggested md5sums:'):
                parts = notes.split('|')
                for part in parts[1:]:
                    checksum, filename = part.split(' = ')
                    checksums[filename] = checksum
            for index in range(firmware_count):
                firmware_filename = core_config['firmware%d_path' % index]
                firmware_path = os.path.join(system_path, firmware_filename)
                if os.path.exists(firmware_path):
                    if firmware_filename in checksums:
                        checksum = system.get_md5_hash(firmware_path)
                        if checksum == checksums[firmware_filename]:
                            checksum_status = 'Checksum good'
                        else:
                            checksum_status = 'Checksum failed'
                    else:
                        checksum_status = 'No checksum info'
                    logger.info("Firmware '{}' found ({})".format(firmware_filename,
                                                                  checksum_status))
                else:
                    logger.warning("Firmware '{}' not found!".format(firmware_filename))

                # Before closing issue #431
                # TODO check for firmware*_opt and display an error message if
                # firmware is missing
                # TODO Add dialog for copying the firmware in the correct
                # location

        return True
Esempio n. 21
0
 def execute_command(command):
     """Execute an arbitrary command in a Lutris context
     with the runtime enabled and monitored by a MonitoredCommand
     """
     logger.info("Running command '%s'", command)
     monitored_command = exec_command(command)
     try:
         GLib.MainLoop().run()
     except KeyboardInterrupt:
         monitored_command.stop()
Esempio n. 22
0
 def run(self, timestamp):
     if self.is_running():
         self.lutris_window.window.present_with_time(timestamp)
     else:
         logger.info("Welcome to Lutris")
         self.running = True
         self.lutris_window = LutrisWindow(service=self)
         GObject.threads_init()
         Gtk.main()
         self.running = False
Esempio n. 23
0
def mark_as_uninstalled(game_info):
    assert 'id' in game_info
    assert 'name' in game_info
    logger.info('Setting %s as uninstalled' % game_info['name'])
    game_id = pga.add_or_update(
        id=game_info['id'],
        runner='',
        installed=0
    )
    return game_id
Esempio n. 24
0
 def on_execute_script_clicked(self, _widget):
     """Execute the game's associated script"""
     manual_command = self.game.runner.system_config.get("manual_command")
     if path_exists(manual_command):
         MonitoredCommand(
             [manual_command],
             include_processes=[os.path.basename(manual_command)],
             cwd=self.game.directory,
         ).start()
         logger.info("Running %s in the background", manual_command)
Esempio n. 25
0
 def clear_discord_rich_presence(self):
     """Dispatch a request to Discord to clear presence"""
     if self.rpc_enabled:
         connected = self.ensure_discord_connected()
         if connected:
             try:
                 logger.info('Attempting to clear Discord status.')
                 self.rpc_client.clear()
             except PyPresenceException as e:
                 logger.error("Unable to clear Discord: %s", e)
                 self.ensure_discord_disconnected()
Esempio n. 26
0
def migrate():
    current_version = int(settings.read_setting("migration_version") or 0)
    if current_version >= MIGRATION_VERSION:
        return
    for i in range(current_version, MIGRATION_VERSION):
        for migration_name in MIGRATIONS[i]:
            logger.info("Running migration: %s", migration_name)
            migration = get_migration_module(migration_name)
            migration.migrate()

    settings.write_setting("migration_version", MIGRATION_VERSION)
Esempio n. 27
0
def mark_as_uninstalled(game_info):
    for key in ('id', 'name'):
        if key not in game_info:
            raise ValueError("Missing %s field in %s" % (key, game_info))
    logger.info('Setting %s as uninstalled' % game_info['name'])
    game_id = pga.add_or_update(
        id=game_info['id'],
        runner='',
        installed=0
    )
    return game_id
Esempio n. 28
0
 def execute_command(self, command):
     """
         Execute an arbitrary command in a Lutris context
         with the runtime enabled and monitored by LutrisThread
     """
     logger.info("Running command '{}'".format(command))
     thread = exec_in_thread(command)
     try:
         GLib.MainLoop().run()
     except KeyboardInterrupt:
         thread.stop()
Esempio n. 29
0
 def get_download_info(self, downlink):
     """Return file download information"""
     logger.info("Getting download info for %s", downlink)
     try:
         response = self.make_api_request(downlink)
     except HTTPError:
         raise UnavailableGame()
     for field in ("checksum", "downlink"):
         field_url = response[field]
         parsed = urlparse(field_url)
         response[field + "_filename"] = os.path.basename(parsed.path)
     return response
Esempio n. 30
0
def kill_pid(pid):
    """Terminate a process referenced by its PID"""
    try:
        pid = int(pid)
    except ValueError:
        logger.error("Invalid pid %s")
        return
    logger.info("Killing PID %s", pid)
    try:
        os.kill(pid, signal.SIGKILL)
    except OSError:
        logger.error("Could not kill process %s", pid)
Esempio n. 31
0
def mark_as_uninstalled(game_info):
    assert 'id' in game_info
    assert 'name' in game_info
    logger.info('Setting %s as uninstalled' % game_info['name'])
    game_id = pga.add_or_update(id=game_info['id'], runner='', installed=0)
    return game_id
Esempio n. 32
0
 def do_shutdown(self):
     logger.info("Shutting down Lutris")
     Gtk.Application.do_shutdown(self)
     if self.window:
         self.window.destroy()
Esempio n. 33
0
    def watch_children(self):
        """Poke at the running process(es)."""
        if not self.game_process or not self.is_running:
            logger.error('No game process available')
            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 {}: {}".format(
                    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
        max_cycles_reached = (self.cycles_without_children >=
                              MAX_CYCLES_WITHOUT_CHILDREN)

        if num_children == 0 or max_cycles_reached:
            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
Esempio n. 34
0
 def do_shutdown(self):  # pylint: disable=arguments-differ
     logger.info("Shutting down Lutris")
     Gtk.Application.do_shutdown(self)
     if self.window:
         self.window.destroy()
Esempio n. 35
0
 def check_files_ready(self):
     """Checks if all installer files are ready and emit a signal if so"""
     if self.is_ready:
         self.emit("files-ready", self.is_ready)
     else:
         logger.info("Waiting for user to provide files")
Esempio n. 36
0
    def _write_config(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.set_platform_from_runner()
        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)
        if not self.extends:
            game.emit("game-installed")
Esempio n. 37
0
 def do_shutdown(self):  # pylint: disable=arguments-differ
     logger.info("Shutting down Lutris")
     if self.window:
         settings.write_setting("selected_category", self.window.selected_category)
         self.window.destroy()
     Gtk.Application.do_shutdown(self)
Esempio n. 38
0
 def on_close(self, *args):
     try:
         self.dialog.destroy()
     except:
         logger.info("Tell strider that he can't write an about dialog")
Esempio n. 39
0
    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 = 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: %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:
                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")
        if gamemode:
            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 system.path_exists(prelaunch_command):
            self.prelaunch_executor = MonitoredCommand(
                [prelaunch_command],
                include_processes=[os.path.basename(prelaunch_command)],
                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()
Esempio n. 40
0
    def prelaunch(self):
        config_file = self.get_config_file()

        # Create retroarch.cfg if it doesn't exist.
        if not system.path_exists(config_file):
            f = open(config_file, "w")
            f.write("# Lutris RetroArch Configuration")
            f.close()

            # Build the default config settings.
            retro_config = RetroConfig(config_file)
            retro_config["libretro_directory"] = get_default_config_path(
                "cores")
            retro_config["libretro_info_path"] = get_default_config_path(
                "info")
            retro_config["content_database_path"] = get_default_config_path(
                "database/rdb")
            retro_config["cheat_database_path"] = get_default_config_path(
                "database/cht")
            retro_config["cursor_directory"] = get_default_config_path(
                "database/cursors")
            retro_config["screenshot_directory"] = get_default_config_path(
                "screenshots")
            retro_config[
                "input_remapping_directory"] = get_default_config_path(
                    "remaps")
            retro_config["video_shader_dir"] = get_default_config_path(
                "shaders")
            retro_config["core_assets_directory"] = get_default_config_path(
                "downloads")
            retro_config["thumbnails_directory"] = get_default_config_path(
                "thumbnails")
            retro_config["playlist_directory"] = get_default_config_path(
                "playlists")
            retro_config["joypad_autoconfig_dir"] = get_default_config_path(
                "autoconfig")
            retro_config["rgui_config_directory"] = get_default_config_path(
                "config")
            retro_config["overlay_directory"] = get_default_config_path(
                "overlay")
            retro_config["assets_directory"] = get_default_config_path(
                "assets")
            retro_config.save()
        else:
            retro_config = RetroConfig(config_file)

        core = self.game_config.get("core")
        info_file = os.path.join(get_default_config_path("info"),
                                 "{}_libretro.info".format(core))
        if system.path_exists(info_file):
            core_config = RetroConfig(info_file)
            try:
                firmware_count = int(core_config["firmware_count"])
            except (ValueError, TypeError):
                firmware_count = 0
            system_path = self.get_system_directory(retro_config)
            notes = core_config["notes"] or ""
            checksums = {}
            if notes.startswith("Suggested md5sums:"):
                parts = notes.split("|")
                for part in parts[1:]:
                    checksum, filename = part.split(" = ")
                    checksums[filename] = checksum
            for index in range(firmware_count):
                firmware_filename = core_config["firmware%d_path" % index]
                firmware_path = os.path.join(system_path, firmware_filename)
                if system.path_exists(firmware_path):
                    if firmware_filename in checksums:
                        checksum = system.get_md5_hash(firmware_path)
                        if checksum == checksums[firmware_filename]:
                            checksum_status = "Checksum good"
                        else:
                            checksum_status = "Checksum failed"
                    else:
                        checksum_status = "No checksum info"
                    logger.info("Firmware '%s' found (%s)", firmware_filename,
                                checksum_status)
                else:
                    logger.warning("Firmware '%s' not found!",
                                   firmware_filename)

                # Before closing issue #431
                # TODO check for firmware*_opt and display an error message if
                # firmware is missing
                # TODO Add dialog for copying the firmware in the correct
                # location

        return True
Esempio n. 41
0
 def game_path(self):
     for apps_path in self.get_steamapps_dirs():
         game_path = get_path_from_appmanifest(apps_path, self.appid)
         if game_path:
             return game_path
     logger.info("Data path for SteamApp %s not found.", self.appid)
Esempio n. 42
0
def notify_mame_xml(*args, **kwargs):
    logger.info("MAME XML written")
Esempio n. 43
0
    def _write_config(self):
        """Write the game configuration in the DB and config file."""
        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.set_platform_from_runner()
        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 = self._get_game_launcher()
        if type(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:
            config['game'].update(self.script['game'])
            config['game'] = self._substitute_config(config['game'])

            # steamless_binary64 can be used to specify 64 bit non-steam binaries
            if system.IS_64BIT and 'steamless_binary64' in config['game']:
                config['game']['steamless_binary'] = config['game'][
                    'steamless_binary64']

        yaml_config = yaml.safe_dump(config, default_flow_style=False)
        with open(config_filename, "w") as config_file:
            config_file.write(yaml_config)
Esempio n. 44
0
def create_prefix(
    prefix,
    wine_path=None,
    arch=WINE_DEFAULT_ARCH,
    overrides={},
    install_gecko=None,
    install_mono=None,
):
    """Create a new Wine prefix."""
    if not prefix:
        raise ValueError("No Wine prefix path given")
    logger.info("Creating a %s prefix in %s", arch, prefix)

    # Follow symlinks, don't delete existing ones as it would break some setups
    if os.path.islink(prefix):
        prefix = os.readlink(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 = import_runner("wine")
        wine_path = wine().get_executable()
    if not wine_path:
        logger.error("Wine not found, can't create prefix")
        return
    wineboot_path = os.path.join(os.path.dirname(wine_path), "wineboot")
    if not system.path_exists(wineboot_path):
        logger.error(
            "No wineboot executable found in %s, "
            "your wine installation is most likely broken",
            wine_path,
        )
        return

    if install_gecko == "False":
        overrides["mshtml"] = "disabled"
    if install_mono == "False":
        overrides["mscoree"] = "disabled"

    wineenv = {
        "WINEARCH": arch,
        "WINEPREFIX": prefix,
        "WINEDLLOVERRIDES": get_overrides_env(overrides),
    }

    system.execute([wineboot_path], env=wineenv)
    for loop_index in range(50):
        time.sleep(0.25)
        if system.path_exists(os.path.join(prefix, "user.reg")):
            break
        if loop_index == 20:
            logger.warning(
                "Wine prefix creation is taking longer than expected...")
    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")
        return
    logger.info("%s Prefix created in %s", arch, prefix)
    prefix_manager = WinePrefixManager(prefix)
    prefix_manager.setup_defaults()
    if 'steamapps/common' in prefix.lower():
        from lutris.runners.winesteam import winesteam
        runner = winesteam()
        logger.info(
            "Transfering Steam information from default prefix to new prefix")
        dest_path = '/tmp/steam.reg'
        default_prefix = runner.get_default_prefix(runner.default_arch)
        wineexec("regedit",
                 args=r"/E '%s' 'HKEY_CURRENT_USER\Software\Valve\Steam'" %
                 dest_path,
                 prefix=default_prefix)
        set_regedit_file(dest_path,
                         wine_path=wine_path,
                         prefix=prefix,
                         arch=arch)
        try:
            os.remove(dest_path)
        except FileNotFoundError:
            logger.error("File %s was already removed", dest_path)
        steam_drive_path = os.path.join(prefix, 'dosdevices', 's:')
        if not system.path_exists(steam_drive_path):
            logger.info("Linking Steam default prefix to drive S:")
            os.symlink(os.path.join(default_prefix, 'drive_c'),
                       steam_drive_path)
Esempio n. 45
0
    def do_command_line(self, command_line):  # noqa: C901  # pylint: disable=arguments-differ
        # pylint: disable=too-many-locals,too-many-return-statements,too-many-branches
        # pylint: disable=too-many-statements
        # TODO: split into multiple methods to reduce complexity (35)
        options = command_line.get_options_dict()

        # Use stdout to output logs, only if no command line argument is
        # provided
        argc = len(sys.argv) - 1
        if "-d" in sys.argv or "--debug" in sys.argv:
            argc -= 1
        if not argc:
            # Switch back the log output to stderr (the default in Python)
            # to avoid messing with any output from command line options.

            # Use when targetting Python 3.7 minimum
            # console_handler.setStream(sys.stderr)

            # Until then...
            logger.removeHandler(log.console_handler)
            log.console_handler = logging.StreamHandler(stream=sys.stdout)
            log.console_handler.setFormatter(log.SIMPLE_FORMATTER)
            logger.addHandler(log.console_handler)

        # Set up logger
        if options.contains("debug"):
            log.console_handler.setFormatter(log.DEBUG_FORMATTER)
            logger.setLevel(logging.DEBUG)

        # Text only commands

        # Print Lutris version and exit
        if options.contains("version"):
            executable_name = os.path.basename(sys.argv[0])
            print(executable_name + "-" + settings.VERSION)
            logger.setLevel(logging.NOTSET)
            return 0

        logger.info("Running Lutris %s", settings.VERSION)
        migrate()
        run_all_checks()
        AsyncCall(init_dxvk_versions, None)

        # List game
        if options.contains("list-games"):
            game_list = pga.get_games()
            if options.contains("installed"):
                game_list = [game for game in game_list if game["installed"]]
            if options.contains("json"):
                self.print_game_json(command_line, game_list)
            else:
                self.print_game_list(command_line, game_list)
            return 0
        # List Steam games
        if options.contains("list-steam-games"):
            self.print_steam_list(command_line)
            return 0
        # List Steam folders
        if options.contains("list-steam-folders"):
            self.print_steam_folders(command_line)
            return 0

        # Execute command in Lutris context
        if options.contains("exec"):
            command = options.lookup_value("exec").get_string()
            self.execute_command(command)
            return 0

        if options.contains("submit-issue"):
            IssueReportWindow(application=self)
            return 0

        try:
            url = options.lookup_value(GLib.OPTION_REMAINING)
            installer_info = self.get_lutris_action(url)
        except ValueError:
            self._print(command_line, "%s is not a valid URI" % url.get_strv())
            return 1
        game_slug = installer_info["game_slug"]
        action = installer_info["action"]
        revision = installer_info["revision"]

        installer_file = None
        if options.contains("install"):
            installer_file = options.lookup_value("install").get_string()
            if installer_file.startswith(("http:", "https:")):
                try:
                    request = Request(installer_file).get()
                except HTTPError:
                    self._print(command_line,
                                "Failed to download %s" % installer_file)
                    return 1
                try:
                    headers = dict(request.response_headers)
                    file_name = headers["Content-Disposition"].split("=",
                                                                     1)[-1]
                except (KeyError, IndexError):
                    file_name = os.path.basename(installer_file)
                file_path = os.path.join(tempfile.gettempdir(), file_name)
                self._print(
                    command_line,
                    "download %s to %s started" % (installer_file, file_path))
                with open(file_path, 'wb') as dest_file:
                    dest_file.write(request.content)
                installer_file = file_path
                action = "install"
            else:
                installer_file = os.path.abspath(installer_file)
                action = "install"

            if not os.path.isfile(installer_file):
                self._print(command_line, "No such file: %s" % installer_file)
                return 1

        db_game = None
        if game_slug:
            if action == "rungameid":
                # Force db_game to use game id
                self.run_in_background = True
                db_game = pga.get_game_by_field(game_slug, "id")
            elif action == "rungame":
                # Force db_game to use game slug
                self.run_in_background = True
                db_game = pga.get_game_by_field(game_slug, "slug")
            elif action == "install":
                # Installers can use game or installer slugs
                self.run_in_background = True
                db_game = pga.get_game_by_field(
                    game_slug, "slug") or pga.get_game_by_field(
                        game_slug, "installer_slug")
            else:
                # Dazed and confused, try anything that might works
                db_game = (pga.get_game_by_field(game_slug, "id")
                           or pga.get_game_by_field(game_slug, "slug") or
                           pga.get_game_by_field(game_slug, "installer_slug"))

        # Graphical commands
        self.activate()
        self.set_tray_icon()

        if not action:
            if db_game and db_game["installed"]:
                # Game found but no action provided, ask what to do
                dlg = InstallOrPlayDialog(db_game["name"])
                if not dlg.action_confirmed:
                    action = None
                elif dlg.action == "play":
                    action = "rungame"
                elif dlg.action == "install":
                    action = "install"
            elif game_slug or installer_file:
                # No game found, default to install if a game_slug or
                # installer_file is provided
                action = "install"

        if action == "install":
            self.show_window(
                InstallerWindow,
                parent=self.window,
                game_slug=game_slug,
                installer_file=installer_file,
                revision=revision,
            )
        elif action in ("rungame", "rungameid"):
            if not db_game or not db_game["id"]:
                logger.warning("No game found in library")
                if not self.window.is_visible():
                    self.do_shutdown()
                return 0
            self.launch(Game(db_game["id"]))
        return 0
Esempio n. 46
0
def notify_mame_xml(result, error):
    if error:
        logger.error("Failed writing MAME XML")
    elif result:
        logger.info("Finished writing MAME XML")
Esempio n. 47
0
def check_vulkan():
    """Reports if Vulkan is enabled on the system"""
    if vkquery.is_vulkan_supported():
        logger.info("Vulkan is supported")
    else:
        logger.info("Vulkan is not available or your system isn't Vulkan capable")
Esempio n. 48
0
 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")
Esempio n. 49
0
    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 = 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)
            self.return_code = self.game_process.returncode
            return False

        return True
Esempio n. 50
0
def create_prefix(  # noqa: C901
    prefix,
    wine_path=None,
    arch=WINE_DEFAULT_ARCH,
    overrides=None,
    install_gecko=None,
    install_mono=None,
):
    """Create a new Wine prefix."""
    # pylint: disable=too-many-locals
    if overrides is None:
        overrides = {}
    if not prefix:
        raise ValueError("No Wine prefix path given")
    logger.info("Creating a %s prefix in %s", arch, prefix)

    # Follow symlinks, don't delete existing ones as it would break some setups
    if os.path.islink(prefix):
        prefix = os.readlink(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 = import_runner("wine")
        wine_path = wine().get_executable()
    if not wine_path:
        logger.error("Wine not found, can't create prefix")
        return
    wineboot_path = os.path.join(os.path.dirname(wine_path), "wineboot")
    if not system.path_exists(wineboot_path):
        logger.error(
            "No wineboot executable found in %s, "
            "your wine installation is most likely broken",
            wine_path,
        )
        return

    if install_gecko == "False":
        overrides["mshtml"] = "disabled"
    if install_mono == "False":
        overrides["mscoree"] = "disabled"

    wineenv = {
        "WINEARCH": arch,
        "WINEPREFIX": prefix,
        "WINEDLLOVERRIDES": get_overrides_env(overrides),
    }

    system.execute([wineboot_path], env=wineenv)
    for loop_index in range(60):
        time.sleep(0.5)
        if system.path_exists(os.path.join(prefix, "user.reg")):
            break
        if loop_index == 30:
            logger.warning(
                "Wine prefix creation is taking longer than expected...")
    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")
        return
    logger.info("%s Prefix created in %s", arch, prefix)
    prefix_manager = WinePrefixManager(prefix)
    prefix_manager.setup_defaults()
Esempio n. 51
0
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
Esempio n. 52
0
    def get_launch_parameters(self, gameplay_info):
        system_config = self.runner.system_config
        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")

        # Mangohud activation
        mangohud = system_config.get("mangohud") or ""
        if mangohud and system.find_executable("mangohud"):
            # This is probably not the way to go. This only work with a few
            # Wine games. It will probably crash it, or do nothing at all.
            # I have never got mangohud to work on anything other than a Wine
            # game.
            dialogs.NoticeDialog("MangoHud support is experimental. Expect the "
                                 "game to crash or the framerate counter not to "
                                 "appear at all.")
            launch_arguments = ["mangohud"] + launch_arguments

        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 = {}
        env.update(self.runner.get_env())

        env.update(gameplay_info.get("env") or {})
        env["game_name"] = self.name

        # 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:
            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])
        return launch_arguments, env
Esempio n. 53
0
    def do_command_line(self, command_line):
        options = command_line.get_options_dict()

        # Use stdout to output logs, only if no command line argument is
        # provided
        argc = len(sys.argv) - 1
        if "-d" in sys.argv or "--debug" in sys.argv:
            argc -= 1
        if not argc:
            # Switch back the log output to stderr (the default in Python)
            # to avoid messing with any output from command line options.

            # Use when targetting Python 3.7 minimum
            # console_handler.setStream(sys.stderr)

            # Until then...
            logger.removeHandler(log.console_handler)
            log.console_handler = logging.StreamHandler(stream=sys.stdout)
            log.console_handler.setFormatter(log.SIMPLE_FORMATTER)
            logger.addHandler(log.console_handler)

        # Set up logger
        if options.contains("debug"):
            log.console_handler.setFormatter(log.DEBUG_FORMATTER)
            logger.setLevel(logging.DEBUG)

        # Text only commands

        # Print Lutris version and exit
        if options.contains("version"):
            executable_name = os.path.basename(sys.argv[0])
            print(executable_name + "-" + settings.VERSION)
            logger.setLevel(logging.NOTSET)
            return 0

        logger.info("Running Lutris %s", settings.VERSION)
        migrate()
        run_all_checks()
        AsyncCall(init_dxvk_versions)

        # List game
        if options.contains("list-games"):
            game_list = pga.get_games()
            if options.contains("installed"):
                game_list = [game for game in game_list if game["installed"]]
            if options.contains("json"):
                self.print_game_json(command_line, game_list)
            else:
                self.print_game_list(command_line, game_list)
            return 0
        # List Steam games
        elif options.contains("list-steam-games"):
            self.print_steam_list(command_line)
            return 0
        # List Steam folders
        elif options.contains("list-steam-folders"):
            self.print_steam_folders(command_line)
            return 0

        # Execute command in Lutris context
        elif options.contains("exec"):
            command = options.lookup_value("exec").get_string()
            self.execute_command(command)
            return 0

        elif options.contains("submit-issue"):
            IssueReportWindow(application=self)
            return 0

        try:
            url = options.lookup_value(GLib.OPTION_REMAINING)
            installer_info = self.get_lutris_action(url)
        except ValueError:
            self._print(command_line, "%s is not a valid URI" % url.get_strv())
            return 1
        game_slug = installer_info["game_slug"]
        action = installer_info["action"]
        revision = installer_info["revision"]

        installer_file = None
        if options.contains("install"):
            installer_file = options.lookup_value("install").get_string()
            installer_file = os.path.abspath(installer_file)
            action = "install"
            if not os.path.isfile(installer_file):
                self._print(command_line, "No such file: %s" % installer_file)
                return 1

        db_game = None
        if game_slug:
            if action == "rungameid":
                # Force db_game to use game id
                self.run_in_background = True
                db_game = pga.get_game_by_field(game_slug, "id")
            elif action == "rungame":
                # Force db_game to use game slug
                self.run_in_background = True
                db_game = pga.get_game_by_field(game_slug, "slug")
            elif action == "install":
                # Installers can use game or installer slugs
                self.run_in_background = True
                db_game = pga.get_game_by_field(
                    game_slug, "slug") or pga.get_game_by_field(
                        game_slug, "installer_slug")
            else:
                # Dazed and confused, try anything that might works
                db_game = (pga.get_game_by_field(game_slug, "id")
                           or pga.get_game_by_field(game_slug, "slug") or
                           pga.get_game_by_field(game_slug, "installer_slug"))

        # Graphical commands
        self.activate()

        if not action:
            if db_game and db_game["installed"]:
                # Game found but no action provided, ask what to do
                dlg = InstallOrPlayDialog(db_game["name"])
                if not dlg.action_confirmed:
                    action = None
                elif dlg.action == "play":
                    action = "rungame"
                elif dlg.action == "install":
                    action = "install"
            elif game_slug or installer_file:
                # No game found, default to install if a game_slug or
                # installer_file is provided
                action = "install"

        if action == "install":
            InstallerWindow(
                game_slug=game_slug,
                installer_file=installer_file,
                revision=revision,
                parent=self.window,
                application=self,
            )
        elif action in ("rungame", "rungameid"):
            if not db_game or not db_game["id"]:
                logger.warning("No game found in library")
                if not self.window.is_visible():
                    self.do_shutdown()
                return 0
            self.launch(Game(db_game["id"]))
        return 0
Esempio n. 54
0
def mark_as_uninstalled(game_info):
    logger.info("Uninstalling %s", game_info['name'])
    return pga.add_or_update(id=game_info['id'], installed=0)
Esempio n. 55
0
 def get_game_details(self, product_id):
     """Return game information for a given game"""
     logger.info("Getting game details for %s", product_id)
     url = "{}/products/{}?expand=downloads".format(self.api_url,
                                                    product_id)
     return self.make_api_request(url)
Esempio n. 56
0
def is_fsync_supported():
    """Checks if the running kernel has Valve's futex patch applied."""
    if FSYNC_SUPPORT_CHECK in ("0", "off"):
        logger.info("futex patch check for fsync was manually disabled")
        return True
    return fsync.is_fsync_supported()
Esempio n. 57
0
    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

        # 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)
Esempio n. 58
0
def is_esync_limit_set():
    """Checks if the number of files open is acceptable for esync usage."""
    if ESYNC_LIMIT_CHECK in ("0", "off"):
        logger.info("fd limit check for esync was manually disabled")
        return True
    return system.LINUX_SYSTEM.has_enough_file_descriptors()
Esempio n. 59
0
    def get_runners_to_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.
        """
        runners_to_install = []
        required_runners = []
        runner = self.get_runner_class(self.installer.runner)
        if runner.depends_on is not None:
            required_runners.append(runner.depends_on())
        required_runners.append(runner())

        for command in self.installer.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)())

        for runner in required_runners:
            params = {}
            if self.installer.runner == "libretro":
                params["core"] = self.installer.script["game"]["core"]
            if self.installer.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.installer.runner not in self.installer.script:
                            self.installer.script[self.installer.runner] = {}
                        version = "{}-{}".format(default_wine["version"],
                                                 default_wine["architecture"])
                        params["version"] = \
                            self.installer.script[self.installer.runner]["version"] = version
                    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", runner)
                runners_to_install.append(runner)

        if self.installer.runner.startswith(
                "wine") and not get_system_wine_version():
            WineNotInstalledWarning(parent=self.parent)
        return runners_to_install
Esempio n. 60
0
    def do_command_line(self, command_line):
        options = command_line.get_options_dict()

        # Set up logger
        if options.contains('debug'):
            logger.setLevel(logging.DEBUG)

        # Text only commands

        # Print Lutris version and exit
        if options.contains('version'):
            executable_name = os.path.basename(sys.argv[0])
            print(executable_name + "-" + VERSION)
            logger.setLevel(logging.NOTSET)
            return 0

        # List game
        if options.contains('list-games'):
            game_list = pga.get_games()
            if options.contains('installed'):
                game_list = [game for game in game_list if game['installed']]
            if options.contains('json'):
                self.print_game_json(command_line, game_list)
            else:
                self.print_game_list(command_line, game_list)
            return 0
        # List Steam games
        elif options.contains('list-steam-games'):
            self.print_steam_list(command_line)
            return 0
        # List Steam folders
        elif options.contains('list-steam-folders'):
            self.print_steam_folders(command_line)
            return 0

        # Execute command in Lutris context
        elif options.contains('exec'):
            command = options.lookup_value('exec').get_string()
            self.execute_command(command)
            return 0

        try:
            url = options.lookup_value(GLib.OPTION_REMAINING)
            installer_info = self.get_lutris_action(url)
        except ValueError:
            self._print(command_line, '%s is not a valid URI' % url.get_strv())
            return 1
        game_slug = installer_info['game_slug']
        action = installer_info['action']
        revision = installer_info['revision']

        installer_file = None
        if options.contains('install'):
            installer_file = options.lookup_value('install').get_string()
            installer_file = os.path.abspath(installer_file)
            action = 'install'
            if not os.path.isfile(installer_file):
                self._print(command_line, "No such file: %s" % installer_file)
                return 1

        # Graphical commands
        self.activate()

        db_game = None
        if game_slug:
            if action == 'rungameid':
                # Force db_game to use game id
                db_game = pga.get_game_by_field(game_slug, 'id')
            elif action == 'rungame':
                # Force db_game to use game slug
                db_game = pga.get_game_by_field(game_slug, 'slug')
            elif action == 'install':
                # Installers can use game or installer slugs
                db_game = (pga.get_game_by_field(game_slug, 'slug') or
                           pga.get_game_by_field(game_slug, 'installer_slug'))

            else:
                # Dazed and confused, try anything that might works
                db_game = (pga.get_game_by_field(game_slug, 'id')
                           or pga.get_game_by_field(game_slug, 'slug') or
                           pga.get_game_by_field(game_slug, 'installer_slug'))

        if not action:
            if db_game and db_game['installed']:
                # Game found but no action provided, ask what to do
                dlg = InstallOrPlayDialog(db_game['name'])
                if not dlg.action_confirmed:
                    action = None
                if dlg.action == 'play':
                    action = 'rungame'
                elif dlg.action == 'install':
                    action = 'install'
            elif game_slug or installer_file:
                # No game found, default to install if a game_slug or
                # installer_file is provided
                action = 'install'

        if action == 'install':
            self.window.present()
            self.window.on_install_clicked(game_slug=game_slug,
                                           installer_file=installer_file,
                                           revision=revision)
        elif action in ('rungame', 'rungameid'):
            if not db_game or not db_game['id']:
                if self.window.is_visible():
                    logger.info("No game found in library")
                else:
                    logger.info("No game found in library, shutting down")
                    self.do_shutdown()
                return 0

            logger.info("Launching %s", db_game['name'])

            # If game is not installed, show the GUI before running. Otherwise leave the GUI closed.
            if not db_game['installed']:
                self.window.present()
            self.window.on_game_run(game_id=db_game['id'])

        else:
            self.window.present()

        return 0