Repository: lutris/lutris

Issue:
Add 'System Information" to GUI
It's nice to have information about graphics drivers printed in terminal, but it's not ideal and I would prefer if it could be accessible through GUI too.
We could implement it in a way Steam does (a window that gets brought up from "Help" menu) , but leave only useful information like graphics devices, driver type, version, Desktop Environment, kernel version, distro (version of it), session type (Wayland/Xorg) and maybe some other stuff too.
![image](https://user-images.githubusercontent.com/10602045/52991833-78649900-341f-11e9-811d-026ef462590d.png)



Discussion:

Resolving patch:
diff --git a/lutris/gui/config/common.py b/lutris/gui/config/common.py
--- a/lutris/gui/config/common.py
+++ b/lutris/gui/config/common.py
@@ -1,7 +1,7 @@
 """Shared config dialog stuff"""
 # pylint: disable=no-member,not-an-iterable
 import os
-from gi.repository import Gtk, Pango, GLib
+from gi.repository import Gtk, Gdk, Pango, GLib
 from lutris.game import Game
 from lutris.config import LutrisConfig, make_game_config_id
 from lutris.util.log import logger
@@ -19,6 +19,7 @@
 )
 from lutris.util.strings import slugify
 from lutris.util import resources
+from lutris.util.linux import gather_system_info_str
 
 
 # pylint: disable=too-many-instance-attributes
@@ -48,6 +49,8 @@ def __init__(self):
         self.runner_name = None
         self.runner_index = None
         self.lutris_config = None
+        self.clipboard = None
+        self._clipboard_buffer = None
 
     @staticmethod
     def build_scrolled_window(widget):
@@ -70,6 +73,7 @@ def build_tabs(self, config_level):
             self._build_runner_tab(config_level)
         if config_level == "system":
             self._build_prefs_tab()
+            self._build_sysinfo_tab()
         self._build_system_tab(config_level)
 
     def _build_info_tab(self):
@@ -107,6 +111,29 @@ def _build_prefs_tab(self):
         info_sw = self.build_scrolled_window(prefs_box)
         self._add_notebook_tab(info_sw, "Lutris preferences")
 
+    def _build_sysinfo_tab(self):
+        sysinfo_grid = Gtk.Grid()
+        sysinfo_view = Gtk.TextView()
+        sysinfo_view.set_editable(False)
+        sysinfo_view.set_cursor_visible(False)
+        sysinfo_str = gather_system_info_str()
+
+        text_buffer = sysinfo_view.get_buffer()
+        text_buffer.set_text(sysinfo_str)
+        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
+        self._clipboard_buffer = sysinfo_str
+
+        button_copy = Gtk.Button("Copy System Info")
+        button_copy.connect("clicked", self._copy_text)
+
+        sysinfo_grid.add(sysinfo_view)
+        sysinfo_grid.attach_next_to(button_copy, sysinfo_view, Gtk.PositionType.BOTTOM, 1, 1)
+        info_sw = self.build_scrolled_window(sysinfo_grid)
+        self._add_notebook_tab(info_sw, "System Information")
+
+    def _copy_text(self, widget):
+        self.clipboard.set_text(self._clipboard_buffer, -1)
+
     def _get_game_cache_box(self):
         box = Gtk.Box(spacing=12, margin_right=12, margin_left=12)
         label = Label("Cache path")
diff --git a/lutris/util/linux.py b/lutris/util/linux.py
--- a/lutris/util/linux.py
+++ b/lutris/util/linux.py
@@ -11,6 +11,7 @@
 from lutris.vendor.distro import linux_distribution
 from lutris.util.graphics import drivers
 from lutris.util.graphics import glxinfo
+from lutris.util.graphics import vkquery
 from lutris.util.log import logger
 from lutris.util.disks import get_drive_for_path
 
@@ -173,17 +174,13 @@ def get_drives():
 
     @staticmethod
     def get_ram_info():
-        """Return RAM information"""
-        try:
-            output = subprocess.check_output(["free"]).decode().split("\n")
-        except subprocess.CalledProcessError as ex:
-            logger.error("Failed to get RAM information: %s", ex)
-            return None
-        columns = output[0].split()
-        meminfo = {}
-        for parts in [line.split() for line in output[1:] if line]:
-            meminfo[parts[0].strip(":").lower()] = dict(zip(columns, parts[1:]))
-        return meminfo
+        """Parse the output of /proc/meminfo and return RAM information in kB"""
+        mem = {}
+        with open("/proc/meminfo") as meminfo:
+            for line in meminfo.readlines():
+                key, value = line.split(":", 1)
+                mem[key.strip()] = value.strip('kB \n')
+        return mem
 
     @staticmethod
     def get_dist_info():
@@ -204,6 +201,13 @@ def get_arch():
             return "armv7"
         logger.warning("Unsupported architecture %s", machine)
 
+    @staticmethod
+    def get_kernel_version():
+        """Get kernel info from /proc/version"""
+        with open("/proc/version") as kernel_info:
+            info = kernel_info.readlines()[0]
+            version = info.split(" ")[2]
+        return version
     @property
     def runtime_architectures(self):
         """Return the architectures supported on this machine"""
@@ -390,5 +394,62 @@ def gather_system_info():
     system_info["drives"] = LINUX_SYSTEM.get_drives()
     system_info["ram"] = LINUX_SYSTEM.get_ram_info()
     system_info["dist"] = LINUX_SYSTEM.get_dist_info()
+    system_info["arch"] = LINUX_SYSTEM.get_arch()
+    system_info["kernel"] = LINUX_SYSTEM.get_kernel_version()
     system_info["glxinfo"] = glxinfo.GlxInfo().as_dict()
     return system_info
+
+def gather_system_info_str():
+    """Get all relevant system information already formatted as a string"""
+    system_info = gather_system_info()
+    system_info_readable = {}
+    #Add system information
+    system_dict = {}
+    system_dict["OS"] = ' '.join(system_info["dist"])
+    system_dict["Arch"] = system_info["arch"]
+    system_dict["Kernel"] = system_info["kernel"]
+    system_dict["Desktop"] = system_info["env"].get("XDG_CURRENT_DESKTOP", "Not found")
+    system_dict["Display Server"] = system_info["env"].get("XDG_SESSION_TYPE", "Not found")
+    system_info_readable["System"] = system_dict
+    #Add CPU information
+    cpu_dict = {}
+    cpu_dict["Vendor"] = system_info["cpus"][0]["vendor_id"]
+    cpu_dict["Model"] = system_info["cpus"][0]["model name"]
+    cpu_dict["Physical cores"] = system_info["cpus"][0]["cpu cores"]
+    cpu_dict["Logical cores"] = system_info["cpus"][0]["siblings"]
+    system_info_readable["CPU"] = cpu_dict
+    #Add memory information
+    ram_dict = {}
+    ram_dict["RAM"] = system_info["ram"]["MemTotal"] + " kB"
+    ram_dict["Swap"] = system_info["ram"]["SwapTotal"] + " kB"
+    system_info_readable["Memory"] = ram_dict
+    #Add graphics information
+    graphics_dict = {}
+    if LINUX_SYSTEM.glxinfo:
+        graphics_dict["Vendor"] = system_info["glxinfo"]["opengl_vendor"]
+        graphics_dict["OpenGL Renderer"] = system_info["glxinfo"]["opengl_renderer"]
+        graphics_dict["OpenGL Version"] = system_info["glxinfo"]["opengl_version"]
+        graphics_dict["OpenGL Core"] = system_info["glxinfo"]["opengl_core_profile_version"]
+        graphics_dict["OpenGL ES"] = system_info["glxinfo"]["opengl_es_profile_version"]
+    else:
+        graphics_dict["Vendor"] = "Unable to obtain glxinfo"
+    #check Vulkan support
+    if vkquery.is_vulkan_supported():
+        graphics_dict["Vulkan"] = "Supported"
+    else:
+        graphics_dict["Vulkan"] = "Not Supported"
+    system_info_readable["Graphics"] = graphics_dict
+    #format output
+    def number_of_tab(string, maxt=5):
+        if int(len(string)/4) > maxt:
+            return 0
+        return maxt-int(len(string)/4)
+    output = ''
+    for section in system_info_readable:
+        output += '{}:\n'.format(section)
+        dictionary = system_info_readable[section]
+        for key in dictionary:
+            tabs = "\t"*number_of_tab(key+":")
+            output += '{}{}{}\n'.format(key + ":", tabs, dictionary[key])
+        output += '\n'
+    return output


Test patch: