2 # -*- coding: utf-8 -*-
6 # Copyright © 2013-2019 RebornOS
8 # This file is part of Cnchi.
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.
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.
20 # The following additional terms are in effect as per Section 7 of the license:
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.
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/>.
30 """ Features screen """
35 gi.require_version('Gtk', '3.0')
36 from gi.repository import Gtk
41 import misc.extra as misc
43 from pages.gtkbasebox import GtkBaseBox
45 from lembrame.dialog import LembrameDialog
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
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() """
63 """ Returns true if an nVidia card is detected """
64 if (Nvidia().detect() or Nvidia390xx().detect()):
70 """ Returns true if an AMD card is detected """
71 if (Catalyst().detect() or AMDGpu().detect() or
72 AMDGpuExp().detect()):
78 """ Returns if an Intel card is detected """
79 return Intel915().detect()
82 """ Returns true if an nVidia and an Intel card are detected """
83 return self.nvidia() and self.i915()
86 class Features(GtkBaseBox):
87 """ Features screen class """
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()
99 super().__init__(self, params, "features", prev_page, next_page)
101 self.graphics = Graphics()
103 self.listbox_rows = {}
105 self.a11y = params['a11y']
107 self.show_advanced = False
108 self.advanced_checkbutton = self.gui.get_object("advanced_checkbutton")
109 self.advanced_checkbutton.set_active(False)
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)
116 # Only show ufw rules and aur disclaimer info once
117 self.info_already_shown = {"ufw": False, "aur": False}
119 # Only load defaults for each DE the first time this screen is shown
120 self.defaults_loaded = False
122 # Store which features where active when lembrame was selected
123 # (when lembrame is selected, all other features are deactivated)
124 self.features_lembrame = []
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()
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]
139 switch.set_active(not switch.get_active())
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]
146 logging.debug("No icon found for feature %s", feature)
147 icon_name = "missing"
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)
156 def add_feature_label(self, feature, box):
157 """ Adds feature title and label to listbox row box """
158 text_box = Gtk.VBox()
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)
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)
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)
187 def extra_switch_actions(self, feature, is_active):
188 """ Lembrame feature disables all others, any other disables lembrame """
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)
201 # Disable lembrame if any other option is activated
202 self.features_lembrame = []
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."
208 switch.set_active(False)
209 except KeyError as err:
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 = []
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)
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()
235 self.listbox_rows = {}
237 # Only add graphic-driver feature if an AMD or Nvidia is detected
238 if "graphic_drivers" in self.features:
240 if self.graphics.amd():
242 if self.graphics.nvidia() and not self.graphics.bumblebee():
245 logging.debug("Neither AMD nor Nvidia cards have been detected. "
246 "Removing proprietary graphic drivers feature.")
247 self.features.remove("graphic_drivers")
249 for feature in self.features:
250 box = Gtk.Box(spacing=20)
251 box.set_name(feature + "-row")
253 self.listbox_rows[feature] = []
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)
261 self.listbox.show_all()
263 def update_advanced_features(self):
264 """ Shows or hides advanced 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:
275 except AttributeError as msg:
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]
288 box2 = row2.get_child()
289 txt_box2 = box2.get_children()[1]
290 label2 = txt_box2.get_children()[0]
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)
296 # If strings are already well sorted return < 0
297 if text[0] == sorted_text[0]:
300 # Strings must be swaped, return > 0
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)
312 widget.set_tooltip_markup(tooltip)
314 def translate_ui(self):
315 """ Translates all ui elements """
317 self.advanced_checkbutton.set_label(_("Show advanced features"))
319 desktop = self.settings.get('desktop')
320 txt = desktop_info.NAMES[desktop] + " - " + _("Feature Selection")
321 self.header.set_subtitle(txt)
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)
330 self.listbox.invalidate_sort()
332 def switch_defaults_on(self):
333 """ Enable some features by default """
335 if 'bluetooth' in self.features:
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()
345 row = self.listbox_rows['bluetooth']
346 row[Features.COL_SWITCH].set_active(True)
347 except subprocess.CalledProcessError as err:
349 "Error checking bluetooth presence. Command %s failed: %s",
353 if 'cups' in self.features:
354 row = self.listbox_rows['cups']
355 row[Features.COL_SWITCH].set_active(True)
357 if 'visual' in self.features:
358 row = self.listbox_rows['visual']
359 row[Features.COL_SWITCH].set_active(True)
361 if 'chromium' in self.features:
362 row = self.listbox_rows['firefox']
363 row[Features.COL_SWITCH].set_active(True)
365 if 'aur' in self.features:
366 row = self.listbox_rows['aur']
367 row[Features.COL_SWITCH].set_active(True)
369 if 'lts' in self.features:
370 row = self.listbox_rows['lts']
371 row[Features.COL_SWITCH].set_active(True)
373 if 'a11y' in self.features and self.a11y:
374 row = self.listbox_rows['a11y']
375 row[Features.COL_SWITCH].set_active(True)
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
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
389 def show_info_dialog(self, feature):
390 """ Some features show an information dialog when this screen is accepted """
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":
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)
407 txt1 = "<big>{0}</big>".format(txt1)
408 txt2 = "<i>{0}</i>".format(txt2)
410 info = Gtk.MessageDialog(
411 transient_for=self.get_main_window(),
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)
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(),
427 destroy_with_parent=True,
428 message_type=Gtk.MessageType.INFO,
429 buttons=Gtk.ButtonsType.YES_NO)
430 info.set_markup("LAMP / LEMP")
432 "Do you want to install the Nginx server instead of the Apache server?")
433 info.format_secondary_markup(msg)
434 response = info.run()
436 if response == Gtk.ResponseType.YES:
437 self.settings.set("feature_lemp", True)
439 self.settings.set("feature_lemp", False)
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(),
450 if response == Gtk.ResponseType.APPLY:
451 logging.debug("Saving Lembrame credentials")
453 'lembrame_credentials',
454 dlg.get_credentials())
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)
465 logging.debug("Feature '%s' has been selected", feature)
467 def store_values(self):
468 """ Go to next screen, but first save changes """
470 self.store_switches()
471 self.show_disclaimer_messages()
475 self.listbox_rows = {}
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]))
489 if not self.defaults_loaded:
490 self.switch_defaults_on()
491 # Only load defaults once
492 self.defaults_loaded = True
494 # Load values user has chosen when this screen is shown again
496 self.update_advanced_features()
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)
507 # When testing, no _() is available
510 except NameError as err: