# -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*- # Copyright (C) 2013 Canonical Ltd. # Written by Evan Dandrea # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import glob import os import subprocess import sys import syslog from ubiquity import i18n, misc, osextras, plugin, upower from ubiquity.install_misc import (archdetect, is_secure_boot, minimal_install_rlist_path) NAME = 'prepare' AFTER = 'wireless' WEIGHT = 11 OEM = False # TODO: This cannot be a non-debconf plugin after all as OEMs may want to # preseed the 'install updates' and 'install non-free software' options. So? # Just db_get them. No need for any other overhead, surely. Actually, you # need the dbfilter for that get. class PreparePageBase(plugin.PluginUI): plugin_title = 'ubiquity/text/prepare_heading_label' download_updates = True download_updates_enabled = True def __init__(self, *args, **kwargs): plugin.PluginUI.__init__(self) def plugin_set_online_state(self, state): # if we're disabling, remember what it was before so we can re-enable # properly if network comes back if self.prepare_network_connection.get_state() and not state: self.download_updates = self.get_download_updates() self.prepare_network_connection.set_state(state) self.enable_download_updates(state) self.download_updates_enabled = state if not state: self.set_download_updates(False) else: self.set_download_updates(self.download_updates) def set_sufficient_space(self, state, required, free): if not state: # There's either no drives present, or not enough free space. # Either way, we cannot continue. self.show_insufficient_space_page(required, free) self.controller.allow_go_forward(False) self.prepare_sufficient_space.set_state(state) def plugin_translate(self, lang): self.rst_title_text = i18n.get_string('rst_header', lang) return def show_ubuntu_drivers_spinner(self): return def hide_ubuntu_drivers_spinner(self): return class PageGtk(PreparePageBase): restricted_package_name = 'ubuntu-restricted-addons' def __init__(self, controller, *args, **kwargs): self.controller = controller from ubiquity.gtkwidgets import Builder builder = Builder() self.controller.add_builder(builder) builder.add_from_file(os.path.join( os.environ['UBIQUITY_GLADE'], 'stepPrepare.ui')) builder.connect_signals(self) # Get all objects + add internal child(s) all_widgets = builder.get_object_ids() for wdg in all_widgets: setattr(self, wdg, builder.get_object(wdg)) self.password_strength_pages = { 'empty': 0, 'too_short': 1, 'good': 2, } self.password_match_pages = { 'empty': 0, 'mismatch': 1, 'ok': 2, } if upower.has_battery(): upower.setup_power_watch(self.prepare_power_source) else: self.prepare_power_source.hide() self.prepare_network_connection = builder.get_object( 'prepare_network_connection') self.using_secureboot = False self.secureboot_box.set_sensitive(False) self.password_grid.set_sensitive(False) self.minimal_install_vbox.set_visible( os.path.exists(minimal_install_rlist_path)) self.prepare_page = builder.get_object('stepPrepare') self.insufficient_space_page = builder.get_object('stepNoSpace') self.rst_page = builder.get_object('stepRST') self.rst_label = builder.get_object('label_using_rst') self.rst_label.connect('activate-link', self.on_link_clicked) self.current_page = self.prepare_page self.plugin_widgets = self.prepare_page self.plugin_optional_widgets = [self.insufficient_space_page, self.rst_page] def on_link_clicked(self, widget, uri): misc.launch_uri(uri) def plugin_get_current_page(self): return self.current_page def show_insufficient_space_page(self, required, free): self.current_page = self.insufficient_space_page self.label_required_space.set_label(required) self.label_free_space.set_label(free) self.controller.go_to_page(self.current_page) def show_rst_page(self): for page in self.controller._wizard.pages: if page.module.NAME == NAME: page.title = 'ubiquity/text/rst_header' break self.current_page = self.rst_page self.controller.go_to_page(self.current_page) return True def show_ubuntu_drivers_spinner(self): frontend = self.controller._wizard frontend.status_spinner.show() frontend.status_spinner.start() frontend.status_label.show() def hide_ubuntu_drivers_spinner(self): frontend = self.controller._wizard frontend.status_spinner.hide() frontend.status_spinner.stop() frontend.status_label.hide() def plugin_on_next_clicked(self): if self.current_page != self.rst_page: if self.get_use_nonfree(): self.show_ubuntu_drivers_spinner() return self.controller._wizard.do_reboot() return True def set_using_secureboot(self, secureboot): self.using_secureboot = secureboot self.secureboot_box.set_visible(secureboot) self.disable_secureboot.set_active(True) self.on_nonfree_toggled(None) self.info_loop(None) def enable_download_updates(self, val): if (val): template = 'ubiquity/text/label_download_updates' else: template = 'ubiquity/text/label_download_updates_na' self.label_download_updates.set_label( self.controller.get_string(template)) self.prepare_download_updates.set_sensitive(val) def set_download_updates(self, val): self.prepare_download_updates.set_active(val) def get_download_updates(self): return self.prepare_download_updates.get_active() def set_minimal_install(self, val): self.prepare_minimal_install.set_active(val) def get_minimal_install(self): return self.prepare_minimal_install.get_active() def set_allow_nonfree(self, allow): if not allow: self.prepare_nonfree_software.set_active(False) self.nonfree_vbox.set_property('visible', False) def set_use_nonfree(self, val): if osextras.find_on_path('ubuntu-drivers'): self.prepare_nonfree_software.set_active(val) else: self.debug('Could not find ubuntu-drivers on the executable path.') self.set_allow_nonfree(False) def get_use_nonfree(self): return self.prepare_nonfree_software.get_active() def get_disable_secureboot(self): return self.disable_secureboot.get_active() def plugin_translate(self, lang): PreparePageBase.plugin_translate(self, lang) frontend = self.controller._wizard frontend.status_label.set_text(self.controller.get_string( 'ubiquity/text/preparing_ud_label', lang)) release = misc.get_release() from gi.repository import Gtk for widget in [self.prepare_download_updates, self.label_required_space, self.label_free_space]: text = i18n.get_string(Gtk.Buildable.get_name(widget), lang) text = text.replace('${RELEASE}', release.name) widget.set_label(text) def on_nonfree_toggled(self, widget): enabled = self.get_use_nonfree() self.secureboot_box.set_sensitive(enabled) self.info_loop(None) def on_secureboot_toggled(self, widget): enabled = self.get_disable_secureboot() self.password_grid.set_sensitive(enabled) self.info_loop(None) def info_loop(self, unused_widget): if not self.get_use_nonfree() \ or not self.password_grid.get_sensitive(): self.controller.allow_go_forward(True) return True complete = False passw = self.password.get_text() vpassw = self.verified_password.get_text() if len(passw) == 0: self.password_strength.set_current_page( self.password_strength_pages['empty']) elif len(passw) >= 8: self.password_strength.set_current_page( self.password_strength_pages['good']) else: self.password_strength.set_current_page( self.password_strength_pages['too_short']) if len(passw) == 0 or len(vpassw) == 0: self.password_match.set_current_page( self.password_match_pages['empty']) elif passw != vpassw or (passw and len(passw) < 8): self.password_match.set_current_page( self.password_match_pages['empty']) if len(passw) >= 8 and (not passw.startswith(vpassw) or len(vpassw) / len(passw) > 0.6): self.password_match.set_current_page( self.password_match_pages['mismatch']) else: complete = True self.password_match.set_current_page( self.password_match_pages['ok']) self.controller.allow_go_forward(complete) return complete def get_secureboot_key(self): return self.password.get_text() def show_learn_more(self, unused): from gi.repository import Gtk sb_title_template = 'ubiquity/text/efi_secureboot' sb_info_template = 'ubiquity/text/efi_secureboot_info' secureboot_title = self.controller.get_string(sb_title_template) secureboot_msg = self.controller.get_string(sb_info_template) dialog = Gtk.MessageDialog( self.current_page.get_toplevel(), Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, None) dialog.set_title(secureboot_title) dialog.set_markup(secureboot_msg) dialog.run() dialog.destroy() class PageKde(PreparePageBase): plugin_breadcrumb = 'ubiquity/text/breadcrumb_prepare' restricted_package_name = 'kubuntu-restricted-addons' def __init__(self, controller, *args, **kwargs): from ubiquity.qtwidgets import StateBox self.controller = controller try: from PyQt5 import uic from PyQt5 import QtGui, QtWidgets # No worries, this has nothing to do with NM, we just want the # generic progress indicator from there from ubiquity.frontend.kde_components.nmwidgets import ProgressIndicator self.page = uic.loadUi('/usr/share/ubiquity/qt/stepPrepare.ui') self.prepare_minimal_install = self.page.prepare_minimal_install self.qt_label_minimal_install = self.page.qt_label_minimal_install self.prepare_download_updates = self.page.prepare_download_updates self.prepare_nonfree_software = self.page.prepare_nonfree_software self.prepare_foss_disclaimer = self.page.prepare_foss_disclaimer self.prepare_sufficient_space = StateBox(self.page) self.secureboot_label = self.page.secureboot_label self.disable_secureboot = self.page.disable_secureboot self.password = self.page.password self.verified_password = self.page.verified_password self.password_extra_label = self.page.password_extra_label self.badPassword = self.page.badPassword self.badPassword.setPixmap(QtGui.QPixmap( "/usr/share/icons/oxygen/16x16/status/dialog-warning.png")) self.progress_indicator = ProgressIndicator() self.progress_indicator.hide() layout = QtWidgets.QHBoxLayout(self.page.progress_container) layout.addWidget(self.progress_indicator) # TODO we should set these up and tear them down while on this # page. try: self.prepare_power_source = StateBox(self.page) if upower.has_battery(): upower.setup_power_watch(self.prepare_power_source) else: self.prepare_power_source.hide() except Exception as e: # TODO use an inconsistent state? print('unable to set up power source watch:', e) if not os.path.exists(minimal_install_rlist_path): self.qt_label_minimal_install.hide() self.prepare_minimal_install.hide() try: self.prepare_network_connection = StateBox(self.page) except Exception as e: print('unable to set up network connection watch:', e) except Exception as e: print("Could not create prepare page:", str(e), file=sys.stderr) self.debug('Could not create prepare page: %s', e) self.page = None self.set_using_secureboot(False) self.plugin_widgets = self.page def show_rst_page(self): return False def show_ubuntu_drivers_spinner(self): self.progress_indicator.show() self.progress_indicator.setSpinnerVisible(True) def hide_ubuntu_drivers_spinner(self): self.progress_indicator.setSpinnerVisible(False) self.progress_indicator.hide() def plugin_on_next_clicked(self): if self.get_use_nonfree(): self.show_ubuntu_drivers_spinner() def show_insufficient_space_page(self, required, free): from PyQt5 import QtWidgets QtWidgets.QMessageBox.critical(self.page, free, required) sys.exit(1) return def set_using_secureboot(self, secureboot): self.using_secureboot = secureboot self.secureboot_label.setVisible(secureboot) self.disable_secureboot.setVisible(secureboot) self.password.setVisible(secureboot) self.verified_password.setVisible(secureboot) self.password_extra_label.setVisible(secureboot) self.badPassword.hide() if (secureboot): self.password.textChanged.connect(self.verify_password) self.verified_password.textChanged.connect(self.verify_password) # show warning if passwords do not match def verify_password(self): complete = False if self.password.text() == self.verified_password.text(): self.badPassword.hide() complete = True else: self.badPassword.show() if not self.password.text(): complete = False self.controller.allow_go_forward(complete) def get_secureboot_key(self): return str(self.page.password.text()) def enable_download_updates(self, val): self.prepare_download_updates.setEnabled(val) def set_download_updates(self, val): self.prepare_download_updates.setChecked(val) def get_download_updates(self): from PyQt5.QtCore import Qt return self.prepare_download_updates.checkState() == Qt.Checked def set_minimal_install(self, val): self.prepare_minimal_install.setChecked(val) def get_minimal_install(self): if self.prepare_minimal_install.isChecked(): return True return False def set_allow_nonfree(self, allow): if not allow: self.prepare_nonfree_software.setChecked(False) self.prepare_nonfree_software.setVisible(False) self.prepare_foss_disclaimer.setVisible(False) def set_use_nonfree(self, val): if osextras.find_on_path('ubuntu-drivers'): self.prepare_nonfree_software.setChecked(val) else: self.debug('Could not find ubuntu-drivers on the executable path.') self.set_allow_nonfree(False) def get_use_nonfree(self): from PyQt5.QtCore import Qt return self.prepare_nonfree_software.checkState() == Qt.Checked def plugin_translate(self, lang): PreparePageBase.plugin_translate(self, lang) # Translate the progress label self.progress_indicator.setText(self.controller.get_string( 'ubiquity/text/preparing_ud_label', lang)) # gtk does the ${RELEASE} replace for the title in gtk_ui but we do # it per plugin because our title widget is per plugin release = misc.get_release() widgets = ( self.page.prepare_heading_label, self.page.prepare_download_updates, ) for widget in widgets: text = widget.text() text = text.replace('${RELEASE}', release.name) text = text.replace('Ubuntu', 'Kubuntu') widget.setText(text) class Page(plugin.Plugin): def prepare(self): if (self.db.get('apt-setup/restricted') == 'false' or self.db.get('apt-setup/multiverse') == 'false'): self.ui.set_allow_nonfree(False) else: use_nonfree = self.db.get('ubiquity/use_nonfree') == 'true' self.ui.set_use_nonfree(use_nonfree) arch, subarch = archdetect() if 'efi' in subarch: if is_secure_boot(): self.ui.set_using_secureboot(True) self.ui.download_updates = self.db.get('ubiquity/download_updates') == 'true' if self.ui.download_updates_enabled: self.ui.set_download_updates(self.ui.download_updates) minimal_install = self.db.get('ubiquity/minimal_install') == 'true' self.ui.set_minimal_install(minimal_install) self.apply_debconf_branding() # wait for it to finish if self.frontend.ubuntu_drivers: self.frontend.ubuntu_drivers.communicate() self.frontend.ubuntu_drivers = None # output whether there are OEM packages for this system try: with open('/run/ubuntu-drivers-oem.autoinstall', 'r') as f: syslog.syslog(F'ubuntu-drivers list-oem finished with: "{" ".join(f.read().splitlines())}"') except FileNotFoundError: syslog.syslog("ubuntu-drivers list-oem finished with no available packages. Maybe we need to apt update? " "Doing that and trying again.") # We only do this when we really have to since it could be slow: apt update & re-run of ubuntu-drivers self.frontend.save_oem_metapackages_list(wait_finished=True) try: with open('/run/ubuntu-drivers-oem.autoinstall', 'r') as f: syslog.syslog(F'ubuntu-drivers list-oem finished with: "{" ".join(f.read().splitlines())}"') except FileNotFoundError: syslog.syslog("No, we didn't find any OEM packages again.") if self.should_show_rst_page(): if not self.ui.show_rst_page(): self.setup_sufficient_space() else: self.ui.plugin_is_restart = True else: self.setup_sufficient_space() command = ['/usr/share/ubiquity/simple-plugins', 'prepare'] questions = ['ubiquity/use_nonfree'] return command, questions def apply_debconf_branding(self): release = misc.get_release() for template in ['ubiquity/text/required_space', 'ubiquity/text/free_space']: self.db.subst(template, 'RELEASE', release.name) def should_show_rst_page(self): search = '/sys/module/ahci/drivers/pci:ahci/*/remapped_nvme' for remapped_nvme in glob.glob(search): with open(remapped_nvme, 'r') as f: if int(f.read()) > 0: return True return os.environ.get('SHOW_RST_UI', '0') == '1' def setup_sufficient_space(self): # TODO move into prepare. size = misc.install_size() self.db.subst( 'ubiquity/text/required_space', 'SIZE', misc.format_size(size)) free = self.free_space() self.db.subst( 'ubiquity/text/free_space', 'SIZE', misc.format_size(free)) required_text = self.description('ubiquity/text/required_space') free_text = self.description('ubiquity/text/free_space') self.ui.set_sufficient_space(size < free, required_text, free_text) def free_space(self): biggest = 0 with misc.raised_privileges(): proc = subprocess.Popen( ['parted_devices'], stdout=subprocess.PIPE, universal_newlines=True) devices = proc.communicate()[0].rstrip('\n').split('\n') for device in devices: if device and int(device.split('\t')[1]) > biggest: biggest = int(device.split('\t')[1]) return biggest def ok_handler(self): download_updates = self.ui.get_download_updates() minimal_install = self.ui.get_minimal_install() use_nonfree = self.ui.get_use_nonfree() secureboot_key = self.ui.get_secureboot_key() self.preseed_bool('ubiquity/use_nonfree', use_nonfree) self.preseed_bool('ubiquity/download_updates', download_updates) self.preseed_bool('ubiquity/minimal_install', minimal_install) if self.ui.using_secureboot and secureboot_key: self.preseed('ubiquity/secureboot_key', secureboot_key, seen=True) if use_nonfree: with misc.raised_privileges(): # Install ubuntu-restricted-addons. self.preseed_bool('apt-setup/universe', True) self.preseed_bool('apt-setup/multiverse', True) if self.db.fget('ubiquity/nonfree_package', 'seen') != 'true': self.preseed( 'ubiquity/nonfree_package', self.ui.restricted_package_name) plugin.Plugin.ok_handler(self) def cleanup(self): self.ui.hide_ubuntu_drivers_spinner() plugin.Plugin.cleanup(self)