def open_uri(uri): """Opens a local or remote URI with the default application""" system.reset_library_preloads() try: Gtk.show_uri(None, uri, Gdk.CURRENT_TIME) except GLib.Error as ex: logger.exception("Failed to open URI %s: %s", uri, ex)
def fetch_icons(lutris_media, window): """Download missing icons from lutris.net""" if not lutris_media: return available_banners, available_icons = lutris_media downloads = [(slug, available_banners[slug], get_icon_path(slug, BANNER)) for slug in available_banners ] + [(slug, available_icons[slug], get_icon_path(slug, ICON)) for slug in available_icons] with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor: future_downloads = { executor.submit(download_media, url, dest_path): slug for slug, url, dest_path in downloads } for future in concurrent.futures.as_completed(future_downloads): slug = future_downloads[future] try: future.result() except Exception as ex: # pylint: disable=broad-except logger.exception('%r generated an exception: %s', slug, ex) else: GLib.idle_add(window.update_image_for_slug, slug, priority=GLib.PRIORITY_LOW) if bool(available_icons): udpate_desktop_icons()
def sync_icons(self): try: resources.fetch_icons([game['slug'] for game in self.game_list], callback=self.on_image_downloaded) except TypeError as ex: logger.exception("Invalid game list:\n%s\nException: %s", self.game_list, ex)
def __init__(self): super().__init__( application_id="net.lutris.Lutris", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, ) init_lutris() gettext.bindtextdomain("lutris", "/usr/share/locale") gettext.textdomain("lutris") GLib.set_application_name(_("Lutris")) self.running_games = Gio.ListStore.new(Game) self.window = None self.tray = None self.css_provider = Gtk.CssProvider.new() self.run_in_background = False if os.geteuid() == 0: ErrorDialog( "Running Lutris as root is not recommended and may cause unexpected issues" ) try: self.css_provider.load_from_path( os.path.join(datapath.get(), "ui", "lutris.css")) except GLib.Error as e: logger.exception(e) if hasattr(self, "add_main_option"): self.add_arguments() else: ErrorDialog( "Your Linux distribution is too old. Lutris won't function properly." )
def __init__(self): super().__init__( application_id="net.lutris.Lutris", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, ) init_lutris() gettext.bindtextdomain("lutris", "/usr/share/locale") gettext.textdomain("lutris") GLib.set_application_name(_("Lutris")) self.running_games = Gio.ListStore.new(Game) self.window = None self.tray = None self.css_provider = Gtk.CssProvider.new() self.run_in_background = False if os.geteuid() == 0: ErrorDialog( "Running Lutris as root is not recommended and may cause unexpected issues" ) try: self.css_provider.load_from_path( os.path.join(datapath.get(), "ui", "lutris.css") ) except GLib.Error as e: logger.exception(e) if hasattr(self, "add_main_option"): self.add_arguments() else: ErrorDialog( "Your Linux distribution is too old. Lutris won't function properly." )
def __init__(self): Gtk.Application.__init__( self, application_id='net.lutris.Lutris', flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) gettext.bindtextdomain("lutris", "/usr/share/locale") gettext.textdomain("lutris") check_config() migrate() update_platforms() GLib.set_application_name(_('Lutris')) self.window = None self.css_provider = Gtk.CssProvider.new() try: self.css_provider.load_from_path( os.path.join(datapath.get(), 'ui', 'lutris.css')) except GLib.Error as e: logger.exception(e) if hasattr(self, 'add_main_option'): self.add_arguments() else: ErrorDialog( "Your Linux distribution is too old, Lutris won't function properly" )
def __init__(self): Gtk.Application.__init__(self, application_id='net.lutris.Lutris', flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) gettext.bindtextdomain("lutris", "/usr/share/locale") gettext.textdomain("lutris") check_config() migrate() update_platforms() GLib.set_application_name(_('Lutris')) self.window = None self.css_provider = Gtk.CssProvider.new() try: self.css_provider.load_from_path(os.path.join(datapath.get(), 'ui', 'lutris.css')) except GLib.Error as e: logger.exception(e) if hasattr(self, 'add_main_option'): self.add_arguments() else: ErrorDialog("Your Linux distribution is too old, Lutris won't function properly")
def __init__(self): super().__init__( application_id="net.lutris.Lutris", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, ) GObject.add_emission_hook(Game, "game-launch", self.on_game_launch) GObject.add_emission_hook(Game, "game-start", self.on_game_start) GObject.add_emission_hook(Game, "game-stop", self.on_game_stop) GObject.add_emission_hook(Game, "game-install", self.on_game_install) GLib.set_application_name(_("Lutris")) self.window = None self.running_games = Gio.ListStore.new(Game) self.app_windows = {} self.tray = None self.css_provider = Gtk.CssProvider.new() self.run_in_background = False if os.geteuid() == 0: ErrorDialog(_("Running Lutris as root is not recommended and may cause unexpected issues")) try: self.css_provider.load_from_path(os.path.join(datapath.get(), "ui", "lutris.css")) except GLib.Error as e: logger.exception(e) if hasattr(self, "add_main_option"): self.add_arguments() else: ErrorDialog(_("Your Linux distribution is too old. Lutris won't function properly."))
def setup_defaults(self): """Sets the defaults for newly created prefixes""" self.override_dll("winemenubuilder.exe", "") try: self.desktop_integration() except OSError as ex: logger.error("Failed to setup desktop integration, the prefix may not be valid.") logger.exception(ex)
def setup_defaults(self): """Sets the defaults for newly created prefixes""" for dll, value in DEFAULT_DLL_OVERRIDES.items(): self.override_dll(dll, value) try: self.desktop_integration() except OSError as ex: logger.error("Failed to setup desktop integration, the prefix may not be valid.") logger.exception(ex)
def setup_defaults(self): """Sets the defaults for newly created prefixes""" self.override_dll("winemenubuilder.exe", "") self.override_dll("steamwebhelper.exe", "") try: self.desktop_integration() except OSError as ex: logger.error("Failed to setup desktop integration, the prefix may not be valid.") logger.exception(ex)
def get_appid(app): """Get the appid for the game""" try: return os.path.splitext(app.get_id())[0] except UnicodeDecodeError: logger.exception( "Failed to read ID for app %s (non UTF-8 encoding). Reverting to executable name.", app, ) return app.get_executable()
def execute_process(self, command, env=None): try: if self.cwd and not system.path_exists(self.cwd): os.makedirs(self.cwd) return subprocess.Popen(command, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.cwd, env=env) except OSError as ex: logger.exception("Failed to execute %s: %s", ' '.join(command), ex) self.error = ex.strerror
def stop(self): """The kill command runs wineserver -k.""" wine_path = self.get_executable() wine_root = os.path.dirname(wine_path) env = self.get_env(full=True) command = [os.path.join(wine_root, "wineserver"), " -k"] logger.debug("Killing all wine processes: %s" % command) try: subprocess.Popen(command, env=env) except OSError: logger.exception('Could not terminate wineserver %s', command)
def _trigger_early_poll(): global _timeout_set try: # prevent changes to size during iteration for command in set(_commands): command.watch_children() except Exception: logger.exception("Signal handler exception") finally: _timeout_set = False return False
def __init__(self, steamapps_paths, callback=None): self.monitors = [] self.callback = callback for steam_path in steamapps_paths: path = Gio.File.new_for_path(steam_path) try: monitor = path.monitor_directory(Gio.FileMonitorFlags.NONE) logger.debug('Watching Steam folder %s', steam_path) monitor.connect('changed', self._on_directory_changed) self.monitors.append(monitor) except GLib.Error as e: logger.exception(e)
def get_raw_registry(reg_filename): """Return an array of the unprocessed contents of a registry file""" if not system.path_exists(reg_filename): return [] with open(reg_filename, "r", encoding='utf-8') as reg_file: try: registry_content = reg_file.readlines() except Exception: # pylint: disable=broad-except logger.exception("Failed to registry read %s", reg_filename) registry_content = [] return registry_content
def sync_icons(self): """Download missing icons""" game_slugs = [game['slug'] for game in self.game_list] if not game_slugs: return logger.debug("Syncing %d icons", len(game_slugs)) try: GLib.idle_add(resources.fetch_icons, game_slugs, self.on_image_downloaded) except TypeError as ex: logger.exception("Invalid game list:\n%s\nException: %s", self.game_list, ex)
def ensure_discord_disconnected(self): """Ensure we are definitely disconnected and fix broken event loop from pypresence""" logger.debug("Ensuring disconnected.") if self.rpc_client is not None: try: self.rpc_client.close() except Exception as e: logger.exception("Unable to close Discord RPC connection: %s", e) if self.rpc_client.sock_writer is not None: try: logger.debug("Forcefully closing sock writer.") self.rpc_client.sock_writer.close() except Exception: logger.exception("Sock writer could not be closed.") try: logger.debug("Forcefully closing event loop.") self.rpc_client.loop.close() except Exception: logger.debug("Could not close event loop.") try: logger.debug("Forcefully replacing event loop.") self.rpc_client.loop = None asyncio.set_event_loop(asyncio.new_event_loop()) except Exception as e: logger.exception("Could not replace event loop: %s", e) try: logger.debug("Forcefully deleting RPC client.") self.rpc_client = None except Exception as ex: logger.exception(ex) self.rpc_client = None self.presence_connected = False
def get_display_manager(): """Return the appropriate display manager instance. Defaults to Mutter if available. This is the only one to support Wayland. """ try: return MutterDisplayManager() except DBusException as ex: logger.debug("Mutter DBus service not reachable: %s", ex) except Exception as ex: # pylint: disable=broad-except logger.exception("Failed to instanciate MutterDisplayConfig. Please report with exception: %s", ex) try: return DisplayManager() except (GLib.Error, NoScreenDetected): return LegacyDisplayManager()
def disconnect(self): """Ensure we are definitely disconnected and fix broken event loop from pypresence That method is a huge mess of non-deterministic bs and should be nuked from orbit. """ logger.debug("Disconnecting from Discord") if self.rpc_client: try: self.rpc_client.close() except Exception as e: logger.exception("Unable to close Discord RPC connection: %s", e) if self.rpc_client.sock_writer is not None: try: logger.debug("Forcefully closing sock writer.") self.rpc_client.sock_writer.close() except Exception: logger.exception("Sock writer could not be closed.") try: logger.debug("Forcefully closing event loop.") self.rpc_client.loop.close() except Exception: logger.debug("Could not close event loop.") try: logger.debug("Forcefully replacing event loop.") self.rpc_client.loop = None asyncio.set_event_loop(asyncio.new_event_loop()) except Exception as e: logger.exception("Could not replace event loop: %s", e) try: logger.debug("Forcefully deleting RPC client.") self.rpc_client = None except Exception as ex: logger.exception(ex) self.rpc_client = None self.presence_connected = False
def get_raw_registry(reg_filename): """Return an array of the unprocessed contents of a registry file""" if not system.path_exists(reg_filename): return [] with open(reg_filename, "r") as reg_file: try: registry_content = reg_file.readlines() except Exception: # pylint: disable=broad-except logger.exception( "Failed to registry read %s, please send attach this file in a bug report", reg_filename) registry_content = [] return registry_content
def get_raw_registry(reg_filename): """Return an array of the unprocessed contents of a registry file""" if not system.path_exists(reg_filename): return [] with open(reg_filename, "r") as reg_file: try: registry_content = reg_file.readlines() except Exception: # pylint: disable=broad-except logger.exception( "Failed to registry read %s, please send attach this file in a bug report", reg_filename ) registry_content = [] return registry_content
def get_installer_files(self, installer, installer_file_id): """Replace the user provided file with download links from Humble Bundle""" try: link = get_humble_download_link(installer.service_appid, installer.runner) except Exception as ex: logger.exception("Failed to get Humble Bundle game: %s", ex) raise UnavailableGame if not link: raise UnavailableGame("No game found on Humble Bundle") filename = link.split("?")[0].split("/")[-1] return [ InstallerFile(installer.game_slug, installer_file_id, { "url": link, "filename": filename }) ]
def ensure_discord_connected(self): """Make sure we are actually connected before trying to send requests""" logger.debug("Ensuring connected.") if self.presence_connected: logger.debug("Already connected!") else: logger.debug("Creating Presence object.") self.rpc_client = PyPresence(self.client_id) try: logger.debug("Attempting to connect.") self.rpc_client.connect() self.presence_connected = True except Exception as ex: logger.exception("Unable to reach Discord. Skipping update: %s", ex) self.ensure_discord_disconnected() return self.presence_connected
def db_insert(db_path, table, fields): columns = ", ".join(list(fields.keys())) placeholders = ("?, " * len(fields))[:-2] field_values = tuple(fields.values()) with db_cursor(db_path) as cursor: try: cursor_execute( cursor, "insert into {0}({1}) values ({2})".format(table, columns, placeholders), field_values, ) except sqlite3.IntegrityError: logger.exception("Uh oh, an integrity error has occurred!") raise inserted_id = cursor.lastrowid return inserted_id
def update_row(self, game_id, game_year, game_playtime): """Update game informations.""" row = self.get_row_by_id(game_id) if row: row[COL_YEAR] = str(game_year) if game_playtime: try: game_playtime = "%.1f hrs" % float(game_playtime) except TypeError: logger.exception("Failed to parse playtime %s", game_playtime) game_playtime = "-" else: game_playtime = "-" row[COL_PLAYTIME_TEXT] = game_playtime self.update_image(game_id, row[COL_INSTALLED])
def ensure_discord_connected(self): """Make sure we are actually connected before trying to send requests""" logger.debug("Ensuring connected.") if self.presence_connected: logger.debug("Already connected!") else: logger.debug("Creating Presence object.") self.rpc_client = PyPresence(self.client_id) try: logger.debug("Attempting to connect.") self.rpc_client.connect() self.presence_connected = True except Exception as ex: logger.exception( "Unable to reach Discord. Skipping update: %s", ex) self.ensure_discord_disconnected() return self.presence_connected
def execute_process(self, command, env=None): """Execute and return a subprocess""" if self.cwd and not system.path_exists(self.cwd): try: os.makedirs(self.cwd) except OSError: logger.error("Failed to create working directory, falling back to %s", self.fallback_cwd) self.cwd = "/tmp" try: return subprocess.Popen( # pylint: disable=consider-using-with command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.cwd, env=env, ) except OSError as ex: logger.exception("Failed to execute %s: %s", " ".join(command), ex) self.error = ex.strerror
def get_display_manager(): """Return the appropriate display manager instance. Defaults to Mutter if available. This is the only one to support Wayland. """ if DBUS_AVAILABLE: try: return MutterDisplayManager() except DBusException as ex: logger.debug("Mutter DBus service not reachable: %s", ex) except Exception as ex: # pylint: disable=broad-except logger.exception("Failed to instanciate MutterDisplayConfig. Please report with exception: %s", ex) else: logger.error("DBus is not available, lutris was not properly installed.") if LIB_GNOME_DESKTOP_AVAILABLE: try: return DisplayManager() except (GLib.Error, NoScreenDetected): pass return LegacyDisplayManager()
def __init__(self): super().__init__( application_id="net.lutris.Lutris", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, ) logger.info("Running Lutris %s", settings.VERSION) set_child_subreaper() gettext.bindtextdomain("lutris", "/usr/share/locale") gettext.textdomain("lutris") check_config() migrate() update_platforms() check_driver() check_libs() check_vulkan() GLib.set_application_name(_("Lutris")) self.running_games = [] self.window = None self.help_overlay = None self.tray = None self.css_provider = Gtk.CssProvider.new() if os.geteuid() == 0: ErrorDialog( "Running Lutris as root is not recommended and may cause unexpected issues" ) try: self.css_provider.load_from_path( os.path.join(datapath.get(), "ui", "lutris.css") ) except GLib.Error as e: logger.exception(e) if hasattr(self, "add_main_option"): self.add_arguments() else: ErrorDialog( "Your Linux distribution is too old, Lutris won't function properly" )
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 migrate(): dest_dir = settings.BANNER_PATH src_dir = os.path.join(settings.DATA_DIR, "banners") try: # init_lutris() creates the new banners directrory if os.path.isdir(src_dir) and os.path.isdir(dest_dir): for filename in os.listdir(src_dir): src_file = os.path.join(src_dir, filename) dest_file = os.path.join(dest_dir, filename) if not os.path.exists(dest_file): os.rename(src_file, dest_file) else: os.unlink(src_file) if not os.listdir(src_dir): os.rmdir(src_dir) except OSError as ex: logger.exception("Failed to migrate banners: %s", ex)
def parse(self, line): """Parse a registry line, populating meta and subkeys""" if len(line) < 4: # Line is too short, nothing to parse return if line.startswith("#"): self.add_meta(line) elif line.startswith('"'): try: key, value = re.split(re.compile(r"(?<![^\\]\\\")="), line, maxsplit=1) except ValueError as ex: logger.error("Unable to parse line %s", line) logger.exception(ex) return key = key[1:-1] self.subkeys[key] = value elif line.startswith("@"): key, value = line.split("=", 1) self.subkeys["default"] = value
def download_icons(self, media_urls, service_media): """Download a list of media files concurrently. Limits the number of simultaneous downloads to avoid API throttling and UI being overloaded with signals. """ with concurrent.futures.ThreadPoolExecutor( max_workers=self.num_workers) as executor: future_downloads = { executor.submit(service_media.download, slug, url): slug for slug, url in media_urls.items() } for future in concurrent.futures.as_completed(future_downloads): slug = future_downloads[future] try: path = future.result() except Exception as ex: # pylint: disable=broad-except logger.exception('%r failed: %s', slug, ex) else: self.emit("icon-loaded", slug, path)