1#!/usr/local/bin/python3.8
2
3import platform
4import subprocess
5import shlex
6import os
7import re
8import threading
9from json import loads
10
11from SettingsWidgets import SidePage
12from xapp.GSettingsWidgets import *
13
14
15def killProcess(process):
16    process.kill()
17
18
19def getProcessOut(command):
20    timeout = 2.0  # Timeout for any subprocess before aborting it
21
22    lines = []
23    p = subprocess.Popen(command, stdout=subprocess.PIPE)
24    timer = threading.Timer(timeout, killProcess, [p])
25    timer.start()
26    while True:
27        line = p.stdout.readline()
28        if not line:
29            break
30        if line != '':
31            lines.append(line.decode('utf-8'))
32    timer.cancel()
33    return lines
34
35
36def getGraphicsInfos():
37    cards = {}
38    count = 0
39    envpath = os.environ["PATH"]
40    os.environ["PATH"] = envpath + ":/usr/local/sbin:/usr/sbin:/sbin"
41    for card in getProcessOut(("lspci")):
42        if not "VGA" in card:
43            continue
44        cardId = card.split()[0]
45        cardName = None
46        for line in getProcessOut(("lspci", "-v", "-s", cardId)):
47            if line.startswith(cardId):
48                cardName = (line.split(":")[2].split("(rev")[0].strip())
49
50        if cardName:
51            cards[count] = (cardName)
52            count += 1
53    os.environ["PATH"] = envpath
54    return cards
55
56
57def getDiskSize():
58    disksize = 0
59    try:
60        out = getProcessOut(("lsblk", "--json", "--output", "size", "--bytes", "--nodeps"))
61        jsonobj = loads(''.join(out))
62    except Exception:
63        return _("Unknown size"), False
64
65    for blk in jsonobj['blockdevices']:
66        disksize += int(blk['size'])
67
68    return disksize, (len(jsonobj['blockdevices']) > 1)
69
70
71def getProcInfos():
72    # For some platforms, 'model name' will no longer take effect.
73    # We can try our best to detect it, but if all attempts failed just leave it to be "Unknown".
74    # Source: https://github.com/dylanaraps/neofetch/blob/6dd85d67fc0d4ede9248f2df31b2cd554cca6c2f/neofetch#L2163
75    cpudetect = ("model name", "Hardware", "Processor", "cpu model", "chip type", "cpu type")
76    infos = [
77        ("/proc/cpuinfo", [("cpu_name", cpudetect), ("cpu_siblings", ("siblings",)), ("cpu_cores", ("cpu cores",))]),
78        ("/proc/meminfo", [("mem_total", ("MemTotal",))])
79    ]
80
81    result = {}
82    for (proc, pairs) in infos:
83        for line in getProcessOut(("cat", proc)):
84            for (key, start) in pairs:
85                for item in start:
86                    if line.startswith(item):
87                        result[key] = line.split(':', 1)[1].strip()
88                        break
89    if "cpu_name" not in result:
90        result["cpu_name"] = _("Unknown CPU")
91    if "mem_total" not in result:
92        result["mem_total"] = _("Unknown size")
93    return result
94
95
96def createSystemInfos():
97    procInfos = getProcInfos()
98    infos = []
99    arch = platform.machine().replace("_", "-")
100    try:
101        (memsize, memunit) = procInfos['mem_total'].split(" ")
102        memsize = float(memsize)
103    except ValueError:
104        memsize = procInfos['mem_total']
105        memunit = ""
106    processorName = procInfos['cpu_name'].replace("(R)", "\u00A9").replace("(TM)", "\u2122")
107    if 'cpu_cores' in procInfos:
108        processorName = processorName + " \u00D7 " + procInfos['cpu_cores']
109
110    if os.path.exists("/etc/linuxmint/info"):
111        args = shlex.split("awk -F \"=\" '/GRUB_TITLE/ {print $2}' /etc/linuxmint/info")
112        title = subprocess.check_output(args).decode('utf-8').rstrip("\n")
113        infos.append((_("Operating System"), title))
114    elif os.path.exists("/etc/arch-release"):
115        contents = open("/etc/arch-release", 'r').readline().split()
116        title = ' '.join(contents[:2]) or "Arch Linux"
117        infos.append((_("Operating System"), title))
118    elif os.path.exists("/etc/manjaro-release"):
119        contents = open("/etc/manjaro-release", 'r').readline().split()
120        title = ' '.join(contents[:2]) or "Manjaro Linux"
121        infos.append((_("Operating System"), title))
122    else:
123        import distro
124        s = '%s (%s)' % (' '.join(distro.linux_distribution()), arch)
125        # Normalize spacing in distribution name
126        s = re.sub(r'\s{2,}', ' ', s)
127        infos.append((_("Operating System"), s))
128    if 'CINNAMON_VERSION' in os.environ:
129        infos.append((_("Cinnamon Version"), os.environ['CINNAMON_VERSION']))
130    infos.append((_("Linux Kernel"), platform.release()))
131    infos.append((_("Processor"), processorName))
132    if memunit == "kB":
133        infos.append((_("Memory"), '%.1f %s' % ((float(memsize)/(1024*1024)), _("GiB"))))
134    else:
135        infos.append((_("Memory"), procInfos['mem_total']))
136
137    diskSize, multipleDisks = getDiskSize()
138    if (multipleDisks):
139        diskText = _("Hard Drives")
140    else:
141        diskText = _("Hard Drive")
142    try:
143        infos.append((diskText, '%.1f %s' % ((diskSize / (1000*1000*1000)), _("GB"))))
144    except:
145        infos.append((diskText, diskSize))
146    cards = getGraphicsInfos()
147    for card in cards:
148        infos.append((_("Graphics Card"), cards[card]))
149
150    return infos
151
152
153class Module:
154    name = "info"
155    category = "hardware"
156    comment = _("Display system information")
157
158    def __init__(self, content_box):
159        keywords = _("system, information, details, graphic, sound, kernel, version")
160        sidePage = SidePage(_("System Info"), "cs-details", keywords, content_box, module=self)
161        self.sidePage = sidePage
162
163    def on_module_selected(self):
164        if not self.loaded:
165            print("Loading Info module")
166
167            infos = createSystemInfos()
168
169            page = SettingsPage()
170            self.sidePage.add_widget(page)
171
172            settings = page.add_section(_("System info"))
173
174            for (key, value) in infos:
175                widget = SettingsWidget()
176                widget.set_spacing(40)
177                labelKey = Gtk.Label.new(key)
178                widget.pack_start(labelKey, False, False, 0)
179                labelKey.get_style_context().add_class("dim-label")
180                labelValue = Gtk.Label.new(value)
181                labelValue.set_selectable(True)
182                labelValue.set_line_wrap(True)
183                widget.pack_end(labelValue, False, False, 0)
184                settings.add_row(widget)
185
186            if os.path.exists("/usr/local/bin/upload-system-info"):
187                widget = SettingsWidget()
188
189                spinner = Gtk.Spinner(visible=True)
190                button = Gtk.Button(label=_("Upload system information"),
191                                    tooltip_text=_("No personal information included"),
192                                    always_show_image=True,
193                                    image=spinner)
194                button.connect("clicked", self.on_button_clicked, spinner)
195                widget.pack_start(button, True, True, 0)
196                settings.add_row(widget)
197
198    def on_button_clicked(self, button, spinner):
199
200        try:
201            subproc = Gio.Subprocess.new(["upload-system-info"], Gio.SubprocessFlags.NONE)
202            subproc.wait_check_async(None, self.on_subprocess_complete, spinner)
203            spinner.start()
204        except GLib.Error as e:
205            print("upload-system-info failed to run: %s" % e.message)
206
207    def on_subprocess_complete(self, subproc, result, spinner):
208        spinner.stop()
209
210        try:
211            success = subproc.wait_check_finish(result)
212        except GLib.Error as e:
213            print("upload-system-info failed: %s" % e.message)
214