from gettext import gettext as _ from typing import Dict, Iterable, List from gi.repository import Gdk, Gtk from lutris.gui.config.base_config_box import BaseConfigBox from lutris.gui.widgets.log_text_view import LogTextView from lutris.util import linux, system from lutris.util.linux import gather_system_info_dict from lutris.util.log import get_log_contents from lutris.util.strings import gtk_safe from lutris.util.wine.wine import is_esync_limit_set, is_fsync_supported, is_installed_systemwide class SystemBox(BaseConfigBox): features_definitions = [ { "name": _("Vulkan support"), "callable": linux.LINUX_SYSTEM.is_vulkan_supported, }, { "name": _("Esync support"), "callable": is_esync_limit_set, }, { "name": _("Fsync support"), "callable": is_fsync_supported, }, { "name": _("Wine installed"), "callable": is_installed_systemwide, }, {"name": _("Gamescope"), "callable": system.can_find_executable, "args": ("gamescope",)}, {"name": _("Mangohud"), "callable": system.can_find_executable, "args": ("mangohud",)}, {"name": _("Gamemode"), "callable": linux.LINUX_SYSTEM.gamemode_available}, {"name": _("Steam"), "callable": linux.LINUX_SYSTEM.has_steam}, {"name": _("In Flatpak"), "callable": linux.LINUX_SYSTEM.is_flatpak}, ] def __init__(self): super().__init__() self.pack_start(self.get_section_label(_("System information")), False, False, 0) self.scrolled_window = Gtk.ScrolledWindow(visible=True) self.scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sysinfo_frame = Gtk.Frame(visible=True) sysinfo_frame.get_style_context().add_class("info-frame") sysinfo_frame.add(self.scrolled_window) self.pack_start(sysinfo_frame, True, True, 0) button_copy = Gtk.Button(_("Copy system info to Clipboard"), halign=Gtk.Align.START, visible=True) button_copy.connect("clicked", self.on_copy_clicked) self.pack_start(button_copy, False, False, 0) self.pack_start(self.get_section_label(_("Lutris logs")), False, False, 0) self.log_scrolled_window = Gtk.ScrolledWindow(visible=True) self.log_scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) log_frame = Gtk.Frame(visible=True) log_frame.get_style_context().add_class("info-frame") log_frame.add(self.log_scrolled_window) self.pack_start(log_frame, True, True, 0) button_log_copy = Gtk.Button(_("Copy logs to Clipboard"), halign=Gtk.Align.START, visible=True) button_log_copy.connect("clicked", self.on_copy_log_clicked) self.pack_start(button_log_copy, False, False, 0) def populate(self): items = self.get_items() self.scrolled_window.add(self.get_grid(items)) self.log_scrolled_window.add(self.get_log_view()) def get_log_view(self): log_buffer = Gtk.TextBuffer() log_buffer.set_text(get_log_contents()) return LogTextView(log_buffer) def get_items(self) -> list: """Assembles a list of items to display; most items are name-value tuples giving various bits of information, section headers appear also, as plain strings.""" features = self.get_features() items = [(f["name"], f["available_text"]) for f in features] system_info_readable = gather_system_info_dict() for section, dictionary in system_info_readable.items(): items.append(section) items.extend(dictionary.items()) return items @staticmethod def get_grid(items: Iterable) -> Gtk.Grid: """Constructs a Gtk.Grid containing labels for each item given; each item may be a name-value tuple, producing two labels, or just a string, giving one that covers two columns; this later is used for section headers.""" grid = Gtk.Grid(visible=True, row_spacing=6, margin=16) row = 0 for item in items: if isinstance(item, str): header_label = Gtk.Label(visible=True, xalign=0, yalign=0, margin_top=16) header_label.set_markup("[%s]" % gtk_safe(item)) if row == 0: grid.set_margin_top(0) grid.attach(header_label, 0, row, 2, 1) else: name, text = item name_label = Gtk.Label(name + ":", visible=True, xalign=0, yalign=0, margin_right=30) grid.attach(name_label, 0, row, 1, 1) markup_label = Gtk.Label(visible=True, xalign=0, selectable=True) markup_label.set_markup("%s" % gtk_safe(text)) grid.attach(markup_label, 1, row, 1, 1) row += 1 return grid @staticmethod def get_text(items: Iterable) -> str: """Constructs text for the clipboard, given the same items as get_grid() takess""" lines = [] for item in items: if isinstance(item, str): lines.append(f"[{item}]") else: name, text = item lines.append(f"{name}: {text}") return "\n".join(lines) def get_features(self) -> List[Dict[str, str]]: """Provides a list of features that may be present in your system; each is given as a dict, which hase 'name' and 'available_text' keys.""" yes = _("YES") no = _("NO") def eval_feature(feature): result = feature.copy() func = feature["callable"] args = feature.get("args", ()) result["availability"] = bool(func(*args)) result["available_text"] = yes if result["availability"] else no return result return [eval_feature(f) for f in self.features_definitions] def on_copy_clicked(self, _widget) -> None: items = self.get_items() text = self.get_text(items) clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard.set_text(text.strip(), -1) def on_copy_log_clicked(self, _widget) -> None: text = get_log_contents() clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard.set_text(text.strip(), -1)