OSDN Git Service

Initial commit
[rebornos/cnchi-gnome-osdn.git] / Cnchi / features.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 #  features.py
5 #
6 #  Copyright © 2013-2019 RebornOS
7 #
8 #  This file is part of Cnchi.
9 #
10 #  Cnchi is free software; you can redistribute it and/or modify
11 #  it under the terms of the GNU General Public License as published by
12 #  the Free Software Foundation; either version 3 of the License, or
13 #  (at your option) any later version.
14 #
15 #  Cnchi is distributed in the hope that it will be useful,
16 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 #  GNU General Public License for more details.
19 #
20 #  The following additional terms are in effect as per Section 7 of the license:
21 #
22 #  The preservation of all legal notices and author attributions in
23 #  the material or in the Appropriate Legal Notices displayed
24 #  by works containing it is required.
25 #
26 #  You should have received a copy of the GNU General Public License
27 #  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
28
29
30 """ Features screen """
31 import subprocess
32 import logging
33
34 import gi
35 gi.require_version('Gtk', '3.0')
36 from gi.repository import Gtk
37
38 import desktop_info
39 import features_info
40
41 import misc.extra as misc
42
43 from pages.gtkbasebox import GtkBaseBox
44
45 from lembrame.dialog import LembrameDialog
46
47 from hardware.modules.nvidia import Nvidia
48 from hardware.modules.nvidia_390xx import Nvidia390xx
49 from hardware.modules.nvidia_340xx import Nvidia340xx
50 from hardware.modules.nvidia_304xx import Nvidia304xx
51 from hardware.modules.catalyst import Catalyst
52 from hardware.modules.amdgpu import AMDGpu
53 from hardware.modules.amdgpu_exp import AMDGpuExp
54 from hardware.modules.i915 import Intel915
55
56 class Graphics():
57     """ Gets graphic device info using the hardware module """
58     """ Removed from nvidia detection: 340xx and 304xx. See below: """
59     """ Nvidia340xx().detect() or Nvidia304xx().detect() """
60
61     @staticmethod
62     def nvidia():
63         """ Returns true if an nVidia card is detected """
64         if (Nvidia().detect() or Nvidia390xx().detect()):
65             return True
66         return False
67
68     @staticmethod
69     def amd():
70         """ Returns true if an AMD card is detected """
71         if (Catalyst().detect() or AMDGpu().detect() or
72             AMDGpuExp().detect()):
73             return True
74         return False
75
76     @staticmethod
77     def i915():
78         """ Returns if an Intel card is detected """
79         return Intel915().detect()
80
81     def bumblebee(self):
82         """ Returns true if an nVidia and an Intel card are detected """
83         return self.nvidia() and self.i915()
84
85
86 class Features(GtkBaseBox):
87     """ Features screen class """
88
89     COL_ICON = 0
90     COL_TITLE = 1
91     COL_DESCRIPTION = 2
92     COL_SWITCH = 3
93
94     def __init__(self, params, prev_page="desktop", next_page="cache"):
95         """ Initializes features ui """
96         # This is initialized each time this screen is shown in prepare()
97         self.features = None
98
99         super().__init__(self, params, "features", prev_page, next_page)
100
101         self.graphics = Graphics()
102
103         self.listbox_rows = {}
104
105         self.a11y = params['a11y']
106
107         self.show_advanced = False
108         self.advanced_checkbutton = self.gui.get_object("advanced_checkbutton")
109         self.advanced_checkbutton.set_active(False)
110
111         # Set up list box
112         self.listbox = self.gui.get_object("listbox")
113         self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
114         self.listbox.set_sort_func(self.listbox_sort_by_name, None)
115
116         # Only show ufw rules and aur disclaimer info once
117         self.info_already_shown = {"ufw": False, "aur": False}
118
119         # Only load defaults for each DE the first time this screen is shown
120         self.defaults_loaded = False
121
122         # Store which features where active when lembrame was selected
123         # (when lembrame is selected, all other features are deactivated)
124         self.features_lembrame = []
125
126     def show_advanced_toggled(self, _widget):
127         """ Display or hide advanced features """
128         self.show_advanced = self.advanced_checkbutton.get_active()
129         self.update_advanced_features()
130
131     @staticmethod
132     def on_listbox_row_selected(_listbox, listbox_row):
133         """ Someone selected a different row of the listbox
134             WARNING: IF LIST LAYOUT IS CHANGED THEN THIS SHOULD BE CHANGED ACCORDINGLY. """
135         if listbox_row is not None:
136             for vbox in listbox_row:
137                 switch = vbox.get_children()[2]
138                 if switch:
139                     switch.set_active(not switch.get_active())
140
141     def add_feature_icon(self, feature, box):
142         """ Adds feature icon to listbox row box """
143         if feature in features_info.ICON_NAMES:
144             icon_name = features_info.ICON_NAMES[feature]
145         else:
146             logging.debug("No icon found for feature %s", feature)
147             icon_name = "missing"
148
149         image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DND)
150         object_name = "image_" + feature
151         image.set_name(object_name)
152         image.set_property('margin_start', 10)
153         self.listbox_rows[feature].append(image)
154         box.pack_start(image, False, False, 0)
155
156     def add_feature_label(self, feature, box):
157         """ Adds feature title and label to listbox row box """
158         text_box = Gtk.VBox()
159
160         object_name = "label_title_" + feature
161         label_title = Gtk.Label.new()
162         label_title.set_halign(Gtk.Align.START)
163         label_title.set_justify(Gtk.Justification.LEFT)
164         label_title.set_name(object_name)
165         self.listbox_rows[feature].append(label_title)
166         text_box.pack_start(label_title, False, True, 0)
167
168         object_name = "label_" + feature
169         label = Gtk.Label.new()
170         label.set_halign(Gtk.Align.START)
171         label.set_justify(Gtk.Justification.LEFT)
172         label.set_name(object_name)
173         self.listbox_rows[feature].append(label)
174         text_box.pack_start(label, False, False, 0)
175         box.pack_start(text_box, False, False, 0)
176
177     def on_switch_activated(self, switch, _gparam):
178         """ Feature has been activated or deactivated """
179         for feature in self.features:
180             row = self.listbox_rows[feature]
181             if row[Features.COL_SWITCH] == switch:
182                 is_active = switch.get_active()
183                 self.settings.set("feature_" + feature, is_active)
184                 # Extra actions on this switch trigger
185                 self.extra_switch_actions(feature, is_active)
186
187     def extra_switch_actions(self, feature, is_active):
188         """ Lembrame feature disables all others, any other disables lembrame """
189         if is_active:
190             if feature == 'lembrame':
191                 # Disable all switches if Lembrame is selected
192                 logging.debug("Activating Lembrame. Deactivating the rest of the switches")
193                 self.features_lembrame = []
194                 for row_feature in self.listbox_rows:
195                     if row_feature != 'lembrame':
196                         switch = self.listbox_rows[row_feature][Features.COL_SWITCH]
197                         if switch.get_active():
198                             self.features_lembrame.append(row_feature)
199                             switch.set_active(False)
200             else:
201                 # Disable lembrame if any other option is activated
202                 self.features_lembrame = []
203                 try:
204                     switch = self.listbox_rows['lembrame'][Features.COL_SWITCH]
205                     if switch and switch.get_active():
206                         msg = "Activating something else besides Lembrame. Deactivating Lembrame."
207                         logging.debug(msg)
208                         switch.set_active(False)
209                 except KeyError as err:
210                     pass
211         elif feature == 'lembrame':
212             # Activate previous deactivated features
213             for row_feature in self.features_lembrame:
214                 switch = self.listbox_rows[row_feature][Features.COL_SWITCH]
215                 switch.set_active(True)
216             self.features_lembrame = []
217
218     def add_feature_switch(self, feature, box):
219         """ Add a switch so the user can activate/deactivate the feature """
220         object_name = "switch_" + feature
221         switch = Gtk.Switch.new()
222         switch.set_name(object_name)
223         switch.set_property('margin_top', 10)
224         switch.set_property('margin_bottom', 10)
225         switch.set_property('margin_end', 10)
226         switch.connect("notify::active", self.on_switch_activated)
227         self.listbox_rows[feature].append(switch)
228         box.pack_end(switch, False, False, 0)
229
230     def fill_listbox(self):
231         """ Fills listbox with all the features and switches """
232         for listbox_row in self.listbox.get_children():
233             listbox_row.destroy()
234
235         self.listbox_rows = {}
236
237         # Only add graphic-driver feature if an AMD or Nvidia is detected
238         if "graphic_drivers" in self.features:
239             allow = False
240             if self.graphics.amd():
241                 allow = True
242             if self.graphics.nvidia() and not self.graphics.bumblebee():
243                 allow = True
244             if not allow:
245                 logging.debug("Neither AMD nor Nvidia cards have been detected. "
246                               "Removing proprietary graphic drivers feature.")
247                 self.features.remove("graphic_drivers")
248
249         for feature in self.features:
250             box = Gtk.Box(spacing=20)
251             box.set_name(feature + "-row")
252
253             self.listbox_rows[feature] = []
254
255             self.add_feature_icon(feature, box)
256             self.add_feature_label(feature, box)
257             self.add_feature_switch(feature, box)
258             # Add row to our gtklist
259             self.listbox.add(box)
260
261         self.listbox.show_all()
262
263     def update_advanced_features(self):
264         """ Shows or hides advanced features """
265         try:
266             if self.features:
267                 for feature in self.features:
268                     row = self.listbox_rows[feature]
269                     box = row[Features.COL_ICON].get_parent()
270                     listboxrow = box.get_parent()
271                     if feature in features_info.ADVANCED and not self.show_advanced:
272                         listboxrow.hide()
273                     else:
274                         listboxrow.show()
275         except AttributeError as msg:
276             logging.debug(msg)
277
278     @staticmethod
279     def listbox_sort_by_name(row1, row2, _user_data):
280         """ Sort function for listbox
281             Returns : < 0 if row1 should be before row2, 0 if they are equal and > 0 otherwise
282             WARNING: IF LAYOUT IS CHANGED IN fill_listbox THEN THIS SHOULD BE
283             CHANGED ACCORDINGLY. """
284         box1 = row1.get_child()
285         txt_box1 = box1.get_children()[1]
286         label1 = txt_box1.get_children()[0]
287
288         box2 = row2.get_child()
289         txt_box2 = box2.get_children()[1]
290         label2 = txt_box2.get_children()[0]
291
292         text = [label1.get_text(), label2.get_text()]
293         # sorted_text = misc.sort_list(text, self.settings.get("locale"))
294         sorted_text = misc.sort_list(text)
295
296         # If strings are already well sorted return < 0
297         if text[0] == sorted_text[0]:
298             return -1
299
300         # Strings must be swaped, return > 0
301         return 1
302
303     def set_row_text(self, feature, title, desc, tooltip):
304         """ Set translated text to our listbox feature row """
305         if feature in self.listbox_rows:
306             title = "<span weight='bold' size='large'>{0}</span>".format(title)
307             desc = "<span size='small'>{0}</span>".format(desc)
308             row = self.listbox_rows[feature]
309             row[Features.COL_TITLE].set_markup(title)
310             row[Features.COL_DESCRIPTION].set_markup(desc)
311             for widget in row:
312                 widget.set_tooltip_markup(tooltip)
313
314     def translate_ui(self):
315         """ Translates all ui elements """
316
317         self.advanced_checkbutton.set_label(_("Show advanced features"))
318
319         desktop = self.settings.get('desktop')
320         txt = desktop_info.NAMES[desktop] + " - " + _("Feature Selection")
321         self.header.set_subtitle(txt)
322
323         for feature in self.features:
324             title = _(features_info.TITLES[feature])
325             desc = _(features_info.DESCRIPTIONS[feature])
326             tooltip = _(features_info.TOOLTIPS[feature])
327             self.set_row_text(feature, title, desc, tooltip)
328
329         # Sort listbox items
330         self.listbox.invalidate_sort()
331
332     def switch_defaults_on(self):
333         """ Enable some features by default """
334
335         if 'bluetooth' in self.features:
336             try:
337                 process1 = subprocess.Popen(["/usr/bin/lsusb"], stdout=subprocess.PIPE)
338                 process2 = subprocess.Popen(
339                     ["grep", "-i", "bluetooth"],
340                     stdin=process1.stdout,
341                     stdout=subprocess.PIPE)
342                 process1.stdout.close()
343                 out, _process_error = process2.communicate()
344                 if out.decode():
345                     row = self.listbox_rows['bluetooth']
346                     row[Features.COL_SWITCH].set_active(True)
347             except subprocess.CalledProcessError as err:
348                 logging.warning(
349                     "Error checking bluetooth presence. Command %s failed: %s",
350                     err.cmd,
351                     err.output)
352
353         if 'cups' in self.features:
354             row = self.listbox_rows['cups']
355             row[Features.COL_SWITCH].set_active(True)
356
357         if 'visual' in self.features:
358             row = self.listbox_rows['visual']
359             row[Features.COL_SWITCH].set_active(True)
360
361         if 'chromium' in self.features:
362             row = self.listbox_rows['firefox']
363             row[Features.COL_SWITCH].set_active(True)
364
365         if 'aur' in self.features:
366             row = self.listbox_rows['aur']
367             row[Features.COL_SWITCH].set_active(True)
368
369         if 'lts' in self.features:
370             row = self.listbox_rows['lts']
371             row[Features.COL_SWITCH].set_active(True)
372
373         if 'a11y' in self.features and self.a11y:
374             row = self.listbox_rows['a11y']
375             row[Features.COL_SWITCH].set_active(True)
376
377     def show_disclaimer_messages(self):
378         """ Show ufw and AUR warning messages if necessary """
379         # Show ufw info message if ufw is selected (show it only once)
380         if self.settings.get("feature_firewall") and not self.info_already_shown["ufw"]:
381             self.show_info_dialog("ufw")
382             self.info_already_shown["ufw"] = True
383
384         # Show AUR disclaimer if AUR is selected (show it only once)
385         if self.settings.get("feature_aur") and not self.info_already_shown["aur"]:
386             self.show_info_dialog("aur")
387             self.info_already_shown["aur"] = True
388
389     def show_info_dialog(self, feature):
390         """ Some features show an information dialog when this screen is accepted """
391         if feature == "aur":
392             # Aur disclaimer
393             txt1 = _("Arch User Repository - Disclaimer")
394             txt2 = _("The Arch User Repository is a collection of user-submitted PKGBUILDs\n"
395                      "that supplement software available from the official repositories.\n\n"
396                      "The AUR is community driven and NOT supported by Arch or RebornOS.\n")
397         elif feature == "ufw":
398             # Ufw rules info
399             txt1 = _("Uncomplicated Firewall will be installed with these rules:")
400             toallow = misc.get_network()
401             txt2 = _("ufw default deny\nufw allow from {0}\nufw allow Transmission\n"
402                      "ufw allow SSH").format(toallow)
403         else:
404             # No message
405             return
406
407         txt1 = "<big>{0}</big>".format(txt1)
408         txt2 = "<i>{0}</i>".format(txt2)
409
410         info = Gtk.MessageDialog(
411             transient_for=self.get_main_window(),
412             modal=True,
413             destroy_with_parent=True,
414             message_type=Gtk.MessageType.INFO,
415             buttons=Gtk.ButtonsType.CLOSE)
416         info.set_markup(txt1)
417         info.format_secondary_markup(txt2)
418         info.run()
419         info.destroy()
420
421     def ask_nginx(self):
422         """ LAMP: Ask user if he wants Apache or Nginx """
423         if self.settings.get("feature_lamp"):
424             info = Gtk.MessageDialog(
425                 transient_for=self.get_main_window(),
426                 modal=True,
427                 destroy_with_parent=True,
428                 message_type=Gtk.MessageType.INFO,
429                 buttons=Gtk.ButtonsType.YES_NO)
430             info.set_markup("LAMP / LEMP")
431             msg = _(
432                 "Do you want to install the Nginx server instead of the Apache server?")
433             info.format_secondary_markup(msg)
434             response = info.run()
435             info.destroy()
436             if response == Gtk.ResponseType.YES:
437                 self.settings.set("feature_lemp", True)
438             else:
439                 self.settings.set("feature_lemp", False)
440
441     def ask_lembrame(self):
442         """ Asks user for lembrame credentials """
443         if self.settings.get("feature_lembrame"):
444             dlg = LembrameDialog(
445                 self.get_main_window(),
446                 self.gui_dir)
447
448             response = dlg.run()
449
450             if response == Gtk.ResponseType.APPLY:
451                 logging.debug("Saving Lembrame credentials")
452                 self.settings.set(
453                     'lembrame_credentials',
454                     dlg.get_credentials())
455
456             dlg.destroy()
457
458     def store_switches(self):
459         """ Store current feature selections """
460         for feature in self.features:
461             row = self.listbox_rows[feature]
462             is_active = row[Features.COL_SWITCH].get_active()
463             self.settings.set("feature_" + feature, is_active)
464             if is_active:
465                 logging.debug("Feature '%s' has been selected", feature)
466
467     def store_values(self):
468         """ Go to next screen, but first save changes """
469
470         self.store_switches()
471         self.show_disclaimer_messages()
472         self.ask_nginx()
473         self.ask_lembrame()
474
475         self.listbox_rows = {}
476
477         return True
478
479     def prepare(self, direction):
480         """ Prepare features screen to get ready to show itself """
481         # Each desktop has its own features
482         desktop = self.settings.get('desktop')
483         self.features = list(
484             set(desktop_info.ALL_FEATURES) -
485             set(desktop_info.EXCLUDED_FEATURES[desktop]))
486         self.fill_listbox()
487         self.translate_ui()
488         self.show_all()
489         if not self.defaults_loaded:
490             self.switch_defaults_on()
491             # Only load defaults once
492             self.defaults_loaded = True
493         else:
494             # Load values user has chosen when this screen is shown again
495             self.load_values()
496         self.update_advanced_features()
497
498     def load_values(self):
499         """ Get previous selected switches values """
500         for feature in self.features:
501             row = self.listbox_rows[feature]
502             is_active = self.settings.get("feature_" + feature)
503             if row[Features.COL_SWITCH] is not None and is_active is not None:
504                 row[Features.COL_SWITCH].set_active(is_active)
505
506
507 # When testing, no _() is available
508 try:
509     _("")
510 except NameError as err:
511     def _(message):
512         return message