示例#1
0
class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
    """
    Main multibootusb GUI manipulation class.
    """

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.tabWidget.setCurrentIndex(0)
        #       self.qemu = Qemu()

        self.ui.label_persistence_value.setVisible(False)
        self.ui.label_persistence.setVisible(False)
        self.ui.slider_persistence.setVisible(False)

        config.usb_disk = None
        config.image_path = None

        #  Main Tab
        self.ui.checkbox_all_drives.clicked.connect(self.onAllDrivesClicked)
        self.ui.button_detect_drives.clicked.connect(self.onRefreshClick)
        self.ui.action_Quit.triggered.connect(self.on_close_Click)
        self.ui.action_About.triggered.connect(self.onAboutClick)
        self.ui.button_browse_image.clicked.connect(self.browse_iso)
        #         self.ui.combo_drives.activated[str].connect(self.onComboChange)
        self.ui.combo_drives.currentIndexChanged.connect(self.onComboChange)
        self.ui.button_install_distro.clicked.connect(self.onCreateClick)
        self.ui.button_uninstall_distro.clicked.connect(self.OnUninstallClick)
        self.ui.slider_persistence.valueChanged.connect(self.update_slider_text)
        #         self.ui.slider_persistence.sliderReleased.connect(self.ui_update_persistence)

        # ISO Imager Tab
        self.ui.button_write_image_to_disk.clicked.connect(self.dd_write)

        #  Syslinux Tab
        self.ui.button_install_syslinux.clicked.connect(self.onInstall_syslinuxClick)
        self.ui.button_edit_syslinux.clicked.connect(self.onedit_syslinux)

        # QEMU Tab
        self.ui.boot_iso_qemu.clicked.connect(self.on_Qemu_Boot_iso_Click)
        self.ui.boot_usb_qemu.clicked.connect(self.on_Qemu_Boot_usb_Click)
        #         self.ui.combo_iso_boot_ram.activated[str].connect(self.qemu_iso_ram)
        #         self.ui.combo_usb_boot_ram.activated[str].connect(self.qemu_usb_ram)
        #         self.ui.boot_usb_qemu.clicked.connect(lambda: self.on_Qemu_Boot_usb_Click(str(self.ui.combo_drives.currentText())))
        #  Update progressbar and status  (Main ISO install)
        self.progress_thread_install = GuiInstallProgress()
        self.progress_thread_install.finished.connect(self.install_syslinux)
        self.progress_thread_install.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_install.status.connect(self.ui.statusbar.showMessage)

        #  Update progressbar and status  (Uninstall from previous install)
        self.progress_thread_uninstall = GuiUninstallProgress()
        self.progress_thread_uninstall.finished.connect(self.uninstall_sys_file_update)
        self.progress_thread_uninstall.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_uninstall.status.connect(self.ui.statusbar.showMessage)

        #  Update progressbar and status  (dd ISO)
        self.progress_thread_dd = DD_Progress()
        self.progress_thread_dd.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_dd.finished.connect(self.dd_finished)
        self.progress_thread_dd.status.connect(self.ui.statusbar.showMessage)

        prepare_mbusb_host_dir()
        self.onRefreshClick()

    def onAllDrivesClicked(self):
        """
        Include fixed drives to available USB devices.
        :return:
        """
        if self.ui.checkbox_all_drives.isChecked() is False:
            self.onRefreshClick()
            return

        reply = QtWidgets.QMessageBox.warning(self, "WARNING!",
                                              "This option enables working with fixed drives\n\
and is potentially VERY DANGEROUS\n\n\
Are you SURE you want to enable it?",
                                              QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                              QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.No:
            self.ui.checkbox_all_drives.setChecked(False)
        elif reply == QtWidgets.QMessageBox.Yes:
            self.ui.checkbox_all_drives.setChecked(True)
            self.onRefreshClick()

    def onAboutClick(self):
        about = QtWidgets.QDialog()
        about.ui = Ui_About()
        about.ui.setupUi(about)
        about.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        about.setWindowTitle("About MultiBootUSB - " + mbusb_version())
        about.setWindowIcon(QtGui.QIcon(resource_path(os.path.join("data", "tools", "multibootusb.png"))))
        about.ui.button_close.clicked.connect(about.close)

        about.exec_()

    def onComboChange(self):
        """
        Detects and updates GUI with populated USB device details.
        :return:
        """
        self.ui.installed_distros.clear()
        config.usb_disk = str(self.ui.combo_drives.currentText())
        config.imager_usb_disk = str(self.ui.combo_drives.currentText())

        if config.usb_disk:
            log("Selected device " + config.usb_disk)

            config.usb_details = usb.details(config.usb_disk)
            config.persistence_max_size = persistence.max_disk_persistence(config.usb_disk)
            config.usb_mount = config.usb_details.get('mount_point', "")
            self.ui.usb_dev.setText(config.usb_disk)

            self.ui.usb_vendor.setText(config.usb_details.get('vendor', ""))
            self.ui.usb_model.setText(config.usb_details.get('model', ""))
            self.ui.usb_size.setText(str(usb.bytes2human(config.usb_details.get('size_total', ""))))
            self.ui.usb_mount.setText(config.usb_details.get('mount_point', ""))
            self.ui.usb_type.setText(config.usb_details.get('devtype', ""))
            self.ui.usb_fs.setText(config.usb_details.get('file_system', ""))

            self.update_list_box(config.usb_disk)
            self.ui_update_persistence()
        else:
            self.ui.usb_dev.clear()
            self.ui.usb_vendor.clear()
            self.ui.usb_model.clear()
            self.ui.usb_size.clear()
            self.ui.usb_mount.clear()
            self.ui.usb_type.clear()
            self.ui.usb_fs.clear()

            log("No USB disk found...")

    def onRefreshClick(self):
        """
        Calls function to detect USB devices.
        :return:
        """
        self.ui.combo_drives.clear()
        if self.ui.checkbox_all_drives.isChecked():
            detected_devices = usb.list_devices(fixed=True)
        else:
            detected_devices = usb.list_devices()

        if detected_devices:
            for device in detected_devices:
                self.ui.combo_drives.addItem(str(device))
            self.ui.combo_drives.setCurrentIndex(0)

    def update_list_box(self, usb_disk):
        """
        Updates listbox with installed distros on selected USB disk.
        :param usb_mount: Selected USB disk from combobox.
        :return:
        """
        distro_list = install_distro_list()
        if distro_list is not None:
            self.ui.installed_distros.clear()
            for name in distro_list:
                self.ui.installed_distros.addItem(name)
        else:
            if not config.usb_mount:
                log("USB disk is not mounted and can't update list widget...")

    def browse_iso(self):
        if str(self.ui.image_path.text()):
            self.ui.image_path.clear()
        preference_file_path = os.path.join(multibootusb_host_dir(), "preference", "iso_dir.txt")
        dir_path = ''
        if os.path.exists(preference_file_path):
            dir_path = open(preference_file_path, 'r').read()

        config.image_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Select an iso...', dir_path, 'ISO Files (*.iso);; Zip Files(*.zip)')[0]

        if config.image_path:
            default_dir_path = os.path.dirname(config.image_path)
            gen.write_to_file(preference_file_path, default_dir_path)

            if platform.system() == "Windows":
                if "/" in config.image_path:
                    config.image_path = config.image_path.strip().replace("/", "\\")

            self.ui.image_path.insert(str(config.image_path))
            self.ui.label_image_size_value.setText(str(bytes2human(iso_size(config.image_path))))
            self.ui.label_image_size_value.setVisible(True)
            self.ui.label_image_bootable_value.setText(str(is_bootable(config.image_path)))
            self.ui.label_image_bootable_value.setVisible(True)

            if os.path.exists(config.image_path):
                clean_iso_cfg_ext_dir(os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir"))  # Need to be cleaned.
                extract_cfg_file(config.image_path)
                config.distro = distro(iso_cfg_ext_dir(), config.image_path)  # Detect supported distro
                self.ui.label_image_type_value.setText(str(config.distro))
                self.ui.label_image_type_value.setVisible(True)
                if config.distro:
                    per_availability = persistence.persistence_distro(config.distro, config.image_path)
                    if per_availability is not None:
                        config.persistence_available = True
                        if config.usb_disk:
                            per_max_size = persistence.max_disk_persistence(config.usb_disk)
                            config.persistence_max_size = per_max_size
                            log('Persistence Max Size: ' + str(bytes2human(per_max_size)))
                    else:
                        config.persistence_available = False
                        log('Persistence is not available for ' + iso_name(config.image_path))

                    self.ui_update_persistence()
        else:
            log("File not selected...")

    def ui_update_persistence(self):
        #         log("===== config.persistence_available = " + str(config.persistence_available))
        #         log("===== config.persistence_max_size = " + str(config.persistence_max_size))
        #         log("===== config.persistence = " + str(config.persistence))
        if config.persistence_available and config.persistence_max_size:
            self.ui.label_persistence_value.setVisible(True)
            self.ui.label_persistence.setVisible(True)
            self.ui.slider_persistence.setVisible(True)
            self.ui.label_persistence_value.setEnabled(True)
            self.ui.label_persistence.setEnabled(True)
            self.ui.slider_persistence.setEnabled(True)
            self.ui.slider_persistence.setTickInterval(10)
            self.ui.slider_persistence.setSingleStep(10)
            self.ui.slider_persistence.setMaximum(config.persistence_max_size / 1024 / 1024)
        #             log("===== getMaximum = " + self.ui.slider_persistence.getMaximum()
        else:
            self.ui.label_persistence_value.setEnabled(False)
            self.ui.label_persistence.setEnabled(False)
            self.ui.slider_persistence.setEnabled(False)
            self.ui.label_persistence_value.setVisible(False)
            self.ui.label_persistence.setVisible(False)
            self.ui.slider_persistence.setVisible(False)

    def ui_disable_persistence(self):
        self.ui.label_persistence_value.setEnabled(False)
        self.ui.label_persistence.setEnabled(False)
        self.ui.slider_persistence.setEnabled(False)
        self.ui.label_persistence_value.setVisible(False)
        self.ui.label_persistence.setVisible(False)
        self.ui.slider_persistence.setVisible(False)

    def ui_disable_controls(self):
        self.ui.combo_drives.setEnabled(False)
        self.ui.checkbox_all_drives.setEnabled(False)
        self.ui.button_detect_drives.setEnabled(False)
        self.ui.button_browse_image.setEnabled(False)
        self.ui.image_path.setEnabled(False)
        self.ui.tabWidget.setEnabled(False)

    def ui_enable_controls(self):
        self.ui.combo_drives.setEnabled(True)
        self.ui.checkbox_all_drives.setEnabled(True)
        self.ui.button_detect_drives.setEnabled(True)
        self.ui.button_browse_image.setEnabled(True)
        self.ui.image_path.setEnabled(True)
        self.ui.tabWidget.setEnabled(True)

    def update_slider_text(self):
        slide_value = self.ui.slider_persistence.value() * 1024 * 1024
        self.ui.label_persistence_value.setText(bytes2human(slide_value))
        config.persistence = slide_value

    def install_syslinux(self):
        """
        Function to install syslinux on distro directory and on selected USB disks.
        :return:
        """
        self.ui.statusbar.showMessage(str("Status: Installing Syslinux..."))
        syslinux_distro_dir(config.usb_disk, config.image_path, config.distro)
        syslinux_default(config.usb_disk)
        update_distro_cfg_files(config.image_path, config.usb_disk, config.distro, config.persistence)
        self.update_list_box(config.usb_disk)
        if sys.platform.startswith("linux"):
            self.ui.statusbar.showMessage("Status: Sync is in progress...")
            os.sync()
        self.ui.statusbar.showMessage("Status: Idle")
        self.ui_disable_persistence()
        log(iso_name(config.image_path) + ' has been successfully installed.')
        QtWidgets.QMessageBox.information(self, 'Finished...',
                                          iso_name(config.image_path) + ' has been successfully installed.')
        config.process_exist = None
        self.ui_enable_controls()

    def onInstall_syslinuxClick(self):
        """
        Function to install syslinux/extlinux on selected USB disk.
        :return:
        """

        self.ui_disable_controls()
        if platform.system() == "Linux" or platform.system() == "Windows":
            if self.ui.check_install_sys_all.isChecked() or self.ui.check_install_sys_only.isChecked():
                if platform.system() == 'Linux' and config.usb_disk[-1].isdigit() is False:
                    gen.log('Selected USB is a disk. Please select a disk partition from the drop down list')
                    QtWidgets.QMessageBox.information(self, 'No Partition...!',
                                                      'USB disk selected doesn\'t contain a partition.\n'
                                                      'Please select the partition (ending '
                                                      'with a digit eg. /dev/sdb1)\nfrom the drop down list.')

                else:
                    log("Installing default syslinux on " + config.usb_disk)
                    ret = syslinux_default(config.usb_disk)
                    if ret is True:
                        if self.ui.check_install_sys_all.isChecked():
                            log("Copying multibootusb directory to " + config.usb_mount)
                            for dirpath, dirnames, filenames in os.walk(
                                    resource_path(os.path.join("tools", "multibootusb"))):
                                for f in filenames:
                                    log("Copying " + f)
                                    shutil.copy(resource_path(os.path.join(dirpath, f)),
                                                os.path.join(self.usb.get_usb(config.usb_disk).mount, "multibootusb"))
                        QtWidgets.QMessageBox.information(self, 'Install Success...',
                                                          'Syslinux installed successfully on ' + config.usb_disk)
                    elif ret is False:
                        QtWidgets.QMessageBox.information(self, 'Install error...',
                                                          'Sorry. Syslinux failed to install on ' + config.usb_disk)
            else:
                QtWidgets.QMessageBox.information(self, 'No selection...',
                                                  'Please select one of the option from above.')

        self.ui_enable_controls()

    def onedit_syslinux(self):
        """
        Function to edit main syslinux.cfg file.
        :return:
        """
        # Function to edit syslinux.cfg file on editors like gedit, notepad etc.
        # Suggest me more editor which can be included in to this function.
        sys_cfg_file = os.path.join(config.usb_mount, "multibootusb", "syslinux.cfg")
        log("Locating " + sys_cfg_file)
        editor = ''
        if not os.path.exists(sys_cfg_file):
            log("syslinux.cfg file not found...")
            QtWidgets.QMessageBox.information(self, 'File not found...', 'Sorry. Unable to locate syslinux.cfg file.\n'
                                                                         'You can only edit syslinux.cfg file generated by multibootusb.')
        else:
            if platform.system() == "Linux":
                for e in config.editors_linux:
                    if subprocess.call('which ' + e, shell=True) == 0:
                        log("Editor found is " + e)
                        editor = e
                        break
            elif platform.system() == "Windows":
                for e in config.editors_win:
                    if not shutil.which(e) is None:
                        log("Editor found is " + e)
                        editor = e
                        break
            if not editor:
                QtWidgets.QMessageBox.information(self, 'Editor not found...',
                                                  'Sorry. Installed editor is not supported by multibootusb\n'
                                                  'Edit ' + sys_cfg_file + ' manually.\n')
            else:
                try:
                    subprocess.Popen(editor + " '" + sys_cfg_file + "'", shell=True).pid
                except OSError:
                    QtWidgets.QMessageBox.warning(self, 'Error...',
                                                  'Failed to open syslinux.cfg file.\n'
                                                  'Edit syslinux.cfg file manually.\n')

    def OnUninstallClick(self):
        """
        Triggers a function to uninstall a selected distro.
        :return:
        """

        self.ui_disable_controls()

        if self.ui.installed_distros.currentItem() is None:
            log("Please select a distro from the list.")
            QtWidgets.QMessageBox.information(self, 'No selection.', 'Please select a distro from the list.')
            self.ui_enable_controls()
        else:
            config.uninstall_distro_dir_name = str(self.ui.installed_distros.currentItem().text()).strip()
            reply = QtWidgets.QMessageBox.question(self, "Review selection...",
                                                   "Are you sure to uninstall " + config.uninstall_distro_dir_name,
                                                   QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                                   QtWidgets.QMessageBox.No)

            if reply == QtWidgets.QMessageBox.Yes:
                if not os.path.exists(os.path.join(config.usb_mount, 'multibootusb', config.uninstall_distro_dir_name)):
                    log("Distro install directory not found. Just updating syslinux.cfg file.")
                    update_sys_cfg_file()
                    # self.uninstall.update_sys_cfg_file()
                    self.ui_enable_controls()
                else:
                    self.progress_thread_uninstall.start()
            else:
                self.ui_enable_controls()

    def uninstall_sys_file_update(self):
        """
        Function to remove and update uninstall distro text.
        :return:
        """
        update_sys_cfg_file()
        self.update_list_box(config.usb_mount)
        if sys.platform.startswith("linux"):
            self.ui.statusbar.showMessage("Status: Sync in progress...")
            os.sync()
        self.ui.statusbar.showMessage("Status: Idle")
        QtWidgets.QMessageBox.information(self, 'Uninstall Complete...',
                                          config.uninstall_distro_dir_name + ' has been successfully removed.')
        self.ui_enable_controls()

    def onCreateClick(self):
        """
        Main function to create bootable USB disk.
        :param usb_disk: ComboBox text as detected USB disk.
        :param iso_link: LineEdit text as selected ISO link.
        :return:
        """

        self.ui_disable_controls()

        if not config.usb_disk:
            log("ERROR: No USB device found.")
            QtWidgets.QMessageBox.information(self, "No Device...",
                                              "No USB device found.\n\nInsert USB and use Refresh USB button to detect USB.")
            self.ui_enable_controls()
        elif not config.image_path:
            log("No ISO selected.")
            QtWidgets.QMessageBox.information(self, "No ISO...", "No ISO found.\n\nPlease select an ISO.")
            self.ui_enable_controls()
        elif usb.details(config.usb_disk)['mount_point'] == 'No_Mount':
            log("ERROR: USB disk is not mounted.")
            QtWidgets.QMessageBox.information(self, "No Mount...", "USB disk is not mounted.\n"
                                                                   "Please mount USB disk and press refresh USB button.")
            self.ui_enable_controls()
        elif platform.system() == 'Linux' and config.usb_disk[-1].isdigit() is False:
            gen.log('Selected USB is a disk. Please select a disk partition from the drop down list')
            QtWidgets.QMessageBox.information(self, 'No Partition...!',
                                              'USB disk selected doesn\'t contain a partition.\n'
                                              'Please select the partition (ending '
                                              'with a digit eg. /dev/sdb1)\nfrom the drop down list.')
            self.ui_enable_controls()
        else:
            # clean_iso_cfg_ext_dir(os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir"))  # Need to be cleaned.
            # extract_cfg_file(config.image_path)  # Extract files from ISO
            # config.distro = distro(iso_cfg_ext_dir(), config.image_path)  # Detect supported distro
            usb_details = usb.details(config.usb_disk)
            log("MultiBoot Install: USB Disk: " + config.usb_disk)
            log("MultiBoot Install: USB Label: " + config.usb_label)
            log("MultiBoot Install: USB UUID: " + config.usb_uuid)
            log("MultiBoot Install: USB mount path: " + config.usb_mount)
            log("MultiBoot Install: Disk total size: " + str(usb.bytes2human(usb_details['size_total'])))
            log("MultiBoot Install: Disk used size: " + str(usb.bytes2human(usb_details['size_used'])))
            log("MultiBoot Install: Disk free size: " + str(usb.bytes2human(usb_details['size_free'])))
            log("MultiBoot Install: Filesystem: " + usb_details['file_system'])
            log("MultiBoot Install: Disk vendor: " + usb_details['vendor'])
            log("MultiBoot Install: Disk model: " + usb_details['model'])
            log("MultiBoot Install: ISO file: " + iso_name(config.image_path))

            if os.path.exists(config.image_path):
                # self.ui.image_path.clear()
                if config.distro:
                    log("MultiBoot Install: Distro type detected: " + config.distro)
                    if not os.path.exists(
                            os.path.join(config.usb_mount, "multibootusb", iso_basename(config.image_path))):
                        config.persistence = self.ui.slider_persistence.value() * 1024 * 1024
                        log("Persistence chosen is " + str(bytes2human(config.persistence)))
                        install_size = iso_size(config.image_path) + config.persistence
                        if install_size >= disk_usage(config.usb_mount).free:
                            log("ERROR: Not enough space available on " + config.usb_disk)
                            QtWidgets.QMessageBox.information(self, "No Space.",
                                                              "No space available on " + config.usb_disk)
                            self.ui_enable_controls()
                        else:
                            reply = QtWidgets.QMessageBox.question(self, 'Review selection...',
                                                                   'Selected USB disk: %s\n' % config.usb_disk +
                                                                   'USB mount point: %s\n' % config.usb_mount +
                                                                   'Selected distro: %s\n\n' % iso_name(
                                                                       config.image_path) +
                                                                   'Proceed with installation?',
                                                                   QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                                                   QtWidgets.QMessageBox.No)

                            if reply == QtWidgets.QMessageBox.Yes:
                                self.ui.slider_persistence.setEnabled(False)
                                copy_mbusb_dir_usb(config.usb_disk)
                                config.process_exist = True
                                self.progress_thread_install.start()
                            elif reply == QtWidgets.QMessageBox.No:
                                self.ui_enable_controls()

                    else:
                        QtWidgets.QMessageBox.information(self, 'Already exists...',
                                                          os.path.basename(
                                                              config.image_path) + ' is already installed.')
                        self.ui_enable_controls()
                else:
                    QtWidgets.QMessageBox.information(self, 'No support...',
                                                      'Sorry.\n' + os.path.basename(config.image_path) +
                                                      ' is not supported at the moment.\n'
                                                      'Please email this issue to [email protected]')
                    self.ui_enable_controls()

                    # Added to refresh usb disk remaining size after distro installation
                    # self.update_gui_usb_info()

    def dd_finished(self):
        """
        Re-enable the blocked widgets for newer use.
        :return:
        """
        self.ui.progressbar.setValue(0)
        self.ui.statusbar.showMessage("Status: Idle")
        config.process_exist = None

        msgBox = QtWidgets.QMessageBox()
        msgBox.setText("Image succesfully written to USB disk.")
        msgBox.setInformativeText("Reboot to boot from USB or test it from <b>Boot ISO/USB</b> tab.");
        msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
        msgBox.setIcon(QtWidgets.QMessageBox.Information)
        msgBox.exec()

        self.ui_enable_controls()

    def dd_start(self):
        """
        Function to block the widgets under ISO Imager tab...
        :return:
        """
        self.ui.progressbar.setValue(0)
        self.ui.statusbar.showMessage("Status: Idle")
        # FIXME        self.ui.lineEdit_3.clear()
        self.ui.button_browse_image.setEnabled(False)
        self.ui.combo_drives.setEnabled(False)
        # FIXME self.ui.pushbtn_imager_refreshusb.setEnabled(False)
        status_text = ("Status: Writing " + os.path.basename(config.image_path) + " to " + config.usb_disk)
        self.ui.statusbar.showMessage(status_text)

    def dd_quit(self):
        self.ui.progressbar.setValue(0)
        self.ui.statusbar.showMessage("Status: Idle")
        self.ui.combo_drives.setEnabled(True)
        self.ui.button_browse_image.setEnabled(True)
        QtWidgets.QMessageBox.information(self, 'Failed!', 'Failed writing image.')

    def dd_write(self):
        self.ui_disable_controls()

        if not config.usb_disk:
            QtWidgets.QMessageBox.information(self, 'No USB disk selected',
                                              'Please insert USB disk and click "Detect Drives".')
            self.ui_enable_controls()
        elif not config.image_path:
            QtWidgets.QMessageBox.information(self, 'No ISO selected', 'Please select an ISO.')
            self.ui_enable_controls()
        else:
            imager = Imager()
            if platform.system() == 'Linux' and config.usb_details['devtype'] == "partition":
                gen.log('Selected device is a partition. Please select a disk from the drop down list')
                QtWidgets.QMessageBox.information(self, 'Incompatible device', 'Selected device (%s) is a partition!\n'
                                                                               'ISO must be written to a whole disk.'
                                                                               '\n\nPlease select a disk from the drop down list.' % config.usb_disk)
                self.ui_enable_controls()
            else:
                usb_disk_size = int(imager.imager_usb_detail(config.usb_disk, partition=0).total_size)
                self.iso_size = os.path.getsize(config.image_path)
                if self.iso_size >= usb_disk_size:
                    QtWidgets.QMessageBox.information(self, "No enough space on disk.",
                                                      os.path.basename(config.image_path) +
                                                      " size is larger than the size of " + config.usb_disk)
                    self.ui_enable_controls()
                # elif gen.process_exist('explorer.exe') is not False:
                #    # Check if windows explorer is running and inform user to close it.
                #    QtWidgets.QMessageBox.information(self, "Windows Explorer", "Windows Explorer is running\n"
                #                                                                "You need to close it before writing ISO "
                #                                                                "image to disk...")
                else:
                    reply = QtWidgets.QMessageBox.question \
                        (self, 'Review selection',
                         'Selected disk: %s\n' % config.usb_disk +
                         'Selected image: %s\n\n' % os.path.basename(config.image_path) +
                         'Proceed with writing image to disk?',
                         QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)

                    if reply == QtWidgets.QMessageBox.Yes:
                        self.dd_start()
                        config.process_exist = True
                        self.progress_thread_dd.start()
                    elif reply == QtWidgets.QMessageBox.No:
                        self.ui_enable_controls()

    def on_close_Click(self):
        """
        Closes main GUI.
        :return:
        """
        self.close()

    def closeEvent(self, event):
        """
        To capture the main close event.
        :param event: Close event.
        :return:
        """
        if config.process_exist == None:
            event.accept()
        else:
            reply = QtWidgets.QMessageBox.question(self, 'Exit MultiBootUSB...',
                                                   "A process is still running.\n"
                                                   "Do you really want to quit multibootusb?",
                                                   QtWidgets.QMessageBox.Yes,
                                                   QtWidgets.QMessageBox.No)
            if reply == QtWidgets.QMessageBox.Yes:
                log("Closing multibootusb...")
                event.accept()
                sys.exit(0)
            else:
                log("Close event cancelled.")
                event.ignore()
示例#2
0
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.tabWidget.setCurrentIndex(0)
        #       self.qemu = Qemu()

        self.ui.label_persistence_value.setVisible(False)
        self.ui.label_persistence.setVisible(False)
        self.ui.slider_persistence.setVisible(False)

        config.usb_disk = None
        config.image_path = None

        #  Main Tab
        self.ui.checkbox_all_drives.clicked.connect(self.onAllDrivesClicked)
        self.ui.button_detect_drives.clicked.connect(self.onRefreshClick)
        self.ui.action_Quit.triggered.connect(self.on_close_Click)
        self.ui.action_About.triggered.connect(self.onAboutClick)
        self.ui.button_browse_image.clicked.connect(self.browse_iso)
        #         self.ui.combo_drives.activated[str].connect(self.onComboChange)
        self.ui.combo_drives.currentIndexChanged.connect(self.onComboChange)
        self.ui.button_install_distro.clicked.connect(self.onCreateClick)
        self.ui.button_uninstall_distro.clicked.connect(self.OnUninstallClick)
        self.ui.slider_persistence.valueChanged.connect(self.update_slider_text)
        #         self.ui.slider_persistence.sliderReleased.connect(self.ui_update_persistence)

        # ISO Imager Tab
        self.ui.button_write_image_to_disk.clicked.connect(self.dd_write)

        #  Syslinux Tab
        self.ui.button_install_syslinux.clicked.connect(self.onInstall_syslinuxClick)
        self.ui.button_edit_syslinux.clicked.connect(self.onedit_syslinux)

        # QEMU Tab
        self.ui.boot_iso_qemu.clicked.connect(self.on_Qemu_Boot_iso_Click)
        self.ui.boot_usb_qemu.clicked.connect(self.on_Qemu_Boot_usb_Click)
        #         self.ui.combo_iso_boot_ram.activated[str].connect(self.qemu_iso_ram)
        #         self.ui.combo_usb_boot_ram.activated[str].connect(self.qemu_usb_ram)
        #         self.ui.boot_usb_qemu.clicked.connect(lambda: self.on_Qemu_Boot_usb_Click(str(self.ui.combo_drives.currentText())))
        #  Update progressbar and status  (Main ISO install)
        self.progress_thread_install = GuiInstallProgress()
        self.progress_thread_install.finished.connect(self.install_syslinux)
        self.progress_thread_install.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_install.status.connect(self.ui.statusbar.showMessage)

        #  Update progressbar and status  (Uninstall from previous install)
        self.progress_thread_uninstall = GuiUninstallProgress()
        self.progress_thread_uninstall.finished.connect(self.uninstall_sys_file_update)
        self.progress_thread_uninstall.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_uninstall.status.connect(self.ui.statusbar.showMessage)

        #  Update progressbar and status  (dd ISO)
        self.progress_thread_dd = DD_Progress()
        self.progress_thread_dd.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_dd.finished.connect(self.dd_finished)
        self.progress_thread_dd.status.connect(self.ui.statusbar.showMessage)

        prepare_mbusb_host_dir()
        self.onRefreshClick()
示例#3
0
class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
    """
    Main multibootusb GUI manipulation class.
    """
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.tabWidget.setCurrentIndex(0)
        #       self.qemu = Qemu()

        self.ui.label_persistence_value.setVisible(False)
        self.ui.label_persistence.setVisible(False)
        self.ui.slider_persistence.setVisible(False)

        #  Main Tab
        self.ui.checkbox_all_drives.clicked.connect(self.onAllDrivesClicked)
        self.ui.button_detect_drives.clicked.connect(self.onRefreshClick)
        self.ui.action_Quit.triggered.connect(self.on_close_Click)
        self.ui.action_About.triggered.connect(self.onAboutClick)
        self.ui.button_browse_image.clicked.connect(self.browse_iso)
        #         self.ui.combo_drives.activated[str].connect(self.onComboChange)
        self.ui.combo_drives.currentIndexChanged.connect(self.onComboChange)
        self.ui.button_install_distro.clicked.connect(self.onCreateClick)
        self.ui.button_uninstall_distro.clicked.connect(self.OnUninstallClick)
        self.ui.slider_persistence.valueChanged.connect(
            self.update_slider_text)
        #         self.ui.slider_persistence.sliderReleased.connect(self.ui_update_persistence)

        # ISO Imager Tab
        self.ui.button_write_image_to_disk.clicked.connect(self.dd_write)

        #  Syslinux Tab
        self.ui.button_install_syslinux.clicked.connect(
            self.onInstall_syslinuxClick)
        self.ui.button_edit_syslinux.clicked.connect(self.onedit_syslinux)

        # QEMU Tab
        self.ui.boot_iso_qemu.clicked.connect(self.on_Qemu_Boot_iso_Click)
        self.ui.boot_usb_qemu.clicked.connect(self.on_Qemu_Boot_usb_Click)
        #         self.ui.combo_iso_boot_ram.activated[str].connect(self.qemu_iso_ram)
        #         self.ui.combo_usb_boot_ram.activated[str].connect(self.qemu_usb_ram)
        #         self.ui.boot_usb_qemu.clicked.connect(lambda: self.on_Qemu_Boot_usb_Click(str(self.ui.combo_drives.currentText())))

        self.ui.run_fsck_repair.clicked.connect(
            partial(self.onFsckClick, usb.repair_vfat_filesystem))
        self.ui.run_fsck_check.clicked.connect(
            partial(self.onFsckClick, usb.check_vfat_filesystem))

        #  Update progressbar and status  (Main ISO install)
        self.progress_thread_install = GuiInstallProgress()
        self.progress_thread_install.finished.connect(self.install_syslinux)
        self.progress_thread_install.update.connect(
            self.ui.progressbar.setValue)
        self.progress_thread_install.status.connect(
            self.ui.statusbar.showMessage)

        #  Update progressbar and status  (Uninstall from previous install)
        self.progress_thread_uninstall = GuiUninstallProgress()
        self.progress_thread_uninstall.finished.connect(
            self.uninstall_sys_file_update)
        self.progress_thread_uninstall.update.connect(
            self.ui.progressbar.setValue)
        self.progress_thread_uninstall.status.connect(
            self.ui.statusbar.showMessage)

        #  Update progressbar and status  (dd ISO)
        self.progress_thread_dd = DD_Progress()
        self.progress_thread_dd.update.connect(self.ui.progressbar.setValue)
        self.progress_thread_dd.finished.connect(self.dd_finished)
        self.progress_thread_dd.status.connect(self.ui.statusbar.showMessage)

        if platform.system() == 'Windows' or os.system('which fsck.vfat') != 0:
            i = self.ui.tabWidget.indexOf(self.ui.tab_fsck)
            if 0 <= i:
                self.ui.tabWidget.removeTab(i)

        prepare_mbusb_host_dir()
        self.onRefreshClick()

    def onAllDrivesClicked(self):
        """
        Include fixed drives to available USB devices.
        :return:
        """
        if self.ui.checkbox_all_drives.isChecked() is False:
            self.onRefreshClick()
            return

        if getattr(config, 'protected_drives', []):
            reply = QtWidgets.QMessageBox.Yes
        else:
            reply = QtWidgets.QMessageBox.warning(
                self, "WARNING!",
                "This option enables working with fixed drives\n"
                "and is potentially VERY DANGEROUS\n\n"
                "Are you SURE you want to enable it?",
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.No:
            self.ui.checkbox_all_drives.setChecked(False)
        elif reply == QtWidgets.QMessageBox.Yes:
            self.ui.checkbox_all_drives.setChecked(True)
            self.onRefreshClick()

    def onAboutClick(self):
        about = QtWidgets.QDialog()
        about.ui = Ui_About()
        about.ui.setupUi(about)
        about.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        about.setWindowTitle("About MultiBootUSB - " + mbusb_version())
        about.setWindowIcon(
            QtGui.QIcon(
                resource_path(os.path.join("data", "tools",
                                           "multibootusb.png"))))
        about.ui.button_close.clicked.connect(about.close)
        about.ui.label_6.linkActivated.connect(webbrowser.open_new_tab)
        about.exec_()

    def onComboChange(self):
        """
        Detects and updates GUI with populated USB device details.
        :return:
        """
        self.ui.installed_distros.clear()
        config.usb_disk = osdriver.listbox_entry_to_device(
            self.ui.combo_drives.currentText())
        if config.usb_disk == 0 or config.usb_disk:
            # Get the GPT status of the disk and store it on a variable
            try:
                usb.gpt_device(config.usb_disk)
                config.imager_usb_disk \
                    = self.ui.combo_drives.currentText()
                config.usb_details \
                    = usb.details(config.usb_disk)
            except Exception as e:
                o = io.StringIO()
                traceback.print_exc(None, o)
                log(o.getvalue())
                QtWidgets.QMessageBox.critical(
                    self, "The disk/partition is not usable.", str(e))
                self.ui.combo_drives.setCurrentIndex(0)
                # Above statement triggers call to this method.
                return
            log("Selected device " + osdriver.usb_disk_desc(config.usb_disk))
            self.update_target_info()
            self.update_list_box(config.usb_disk)
            self.ui_update_persistence()
        else:
            self.ui.usb_dev.clear()
            self.ui.usb_vendor.clear()
            self.ui.usb_model.clear()
            self.ui.usb_size.clear()
            self.ui.usb_mount.clear()
            self.ui.usb_type.clear()
            self.ui.usb_fs.clear()
            log("No USB disk found...")

    def onRefreshClick(self):
        """
        Calls function to detect USB devices.
        :return:
        """
        self.ui.combo_drives.clear()
        detected_devices = usb.list_devices(
            fixed=self.ui.checkbox_all_drives.isChecked())
        if not detected_devices:
            return
        protected_drives = getattr(config, 'protected_drives', [])
        for device in detected_devices:
            if all(not device.startswith(d) for d in protected_drives):
                self.ui.combo_drives.addItem(str(device))
        self.ui.combo_drives.setCurrentIndex(0)

    def update_list_box(self, usb_disk):
        """
        Updates listbox with installed distros on selected USB disk.
        :param usb_mount: Selected USB disk from combobox.
        :return:
        """
        distro_list = uninstall_distro.install_distro_list()
        if distro_list is not None:
            self.ui.installed_distros.clear()
            for name in distro_list:
                self.ui.installed_distros.addItem(name)
        else:
            if not config.usb_mount:
                log("USB disk is not mounted and can't update list widget...")

    def browse_iso(self):
        if str(self.ui.image_path.text()):
            self.ui.image_path.clear()
        preference_file_path = os.path.join(multibootusb_host_dir(),
                                            "preference", "iso_dir.txt")
        dir_path = ''
        if os.path.exists(preference_file_path):
            dir_path = open(preference_file_path, 'r').read()

        config.image_path = QtWidgets.QFileDialog.getOpenFileName(
            self, 'Select an iso...', dir_path,
            'ISO Files (*.iso);; Zip Files(*.zip);; '
            'Img Files(*.img);; All Files(*.*)')[0]

        if config.image_path:
            # sanity checks
            if not is_readable(config.image_path):
                QtWidgets.QMessageBox.critical(
                    self, "ISO Not readable",
                    "Sorry, the file \"{0}\" is not readable.".format(
                        config.image_path))
                return
            if iso_size(config.image_path) == 0:
                QtWidgets.QMessageBox.critical(
                    self, "ISO is an empty file",
                    "Sorry, the file \"{0}\" contains no data.".format(
                        config.image_path))
                return
            default_dir_path = os.path.dirname(config.image_path)
            gen.write_to_file(preference_file_path, default_dir_path)

            # Detect supported distro
            try:
                clean_iso_cfg_ext_dir(  # Need to be cleaned.
                    os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir"))
                extract_cfg_file(config.image_path)
                config.distro = distro(iso_cfg_ext_dir(),
                                       config.image_path,
                                       expose_exception=True)
            except Exception as exc:
                QtWidgets.QMessageBox.critical(
                    self, "Failure to detect distro type",
                    'Sorry, failed in examining "{0}" to detect distro type '
                    'due to the following reason.\n\n"{1}".'.format(
                        config.image_path, exc))
                return

            if platform.system() == "Windows":
                if "/" in config.image_path:
                    config.image_path = config.image_path.strip().replace(
                        "/", "\\")

            self.ui.image_path.insert(str(config.image_path))
            self.ui.label_image_size_value.setText(
                str(bytes2human(iso_size(config.image_path))))
            self.ui.label_image_size_value.setVisible(True)
            self.ui.label_image_bootable_value.setText(
                str(is_bootable(config.image_path)))
            self.ui.label_image_bootable_value.setVisible(True)

            if os.path.exists(config.image_path):
                self.ui.label_image_type_value.setText(str(config.distro))
                self.ui.label_image_type_value.setVisible(True)
                if config.distro:
                    per_availability = persistence.persistence_distro(
                        config.distro, config.image_path)
                    if per_availability is not None:
                        config.persistence_available = True
                        if config.usb_disk:
                            per_max_size = persistence.max_disk_persistence(
                                config.usb_disk)
                            config.persistence_max_size = per_max_size
                            log('Persistence Max Size: ' +
                                str(bytes2human(per_max_size)))
                    else:
                        config.persistence_available = False
                        log('Persistence support is not available for ' +
                            iso_name(config.image_path))

                    self.ui_update_persistence()
        else:
            log("File not selected...")

    def ui_update_persistence(self):
        #         log("===== config.persistence_available = " + str(config.persistence_available))
        #         log("===== config.persistence_max_size = " + str(config.persistence_max_size))
        #         log("===== config.persistence = " + str(config.persistence))
        if config.persistence_available and config.persistence_max_size:
            self.ui.label_persistence_value.setVisible(True)
            self.ui.label_persistence.setVisible(True)
            self.ui.slider_persistence.setVisible(True)
            self.ui.label_persistence_value.setEnabled(True)
            self.ui.label_persistence.setEnabled(True)
            self.ui.slider_persistence.setEnabled(True)
            self.ui.slider_persistence.setTickInterval(10)
            self.ui.slider_persistence.setSingleStep(10)
            self.ui.slider_persistence.setMaximum(config.persistence_max_size /
                                                  1024 / 1024)
        #             log("===== getMaximum = " + self.ui.slider_persistence.getMaximum()
        else:
            self.ui.label_persistence_value.setEnabled(False)
            self.ui.label_persistence.setEnabled(False)
            self.ui.slider_persistence.setEnabled(False)
            self.ui.label_persistence_value.setVisible(False)
            self.ui.label_persistence.setVisible(False)
            self.ui.slider_persistence.setVisible(False)

    def ui_disable_persistence(self):
        self.ui.label_persistence_value.setEnabled(False)
        self.ui.label_persistence.setEnabled(False)
        self.ui.slider_persistence.setEnabled(False)
        self.ui.label_persistence_value.setVisible(False)
        self.ui.label_persistence.setVisible(False)
        self.ui.slider_persistence.setVisible(False)

    def get_controls(self):
        return [
            self.ui.combo_drives,
            self.ui.checkbox_all_drives,
            self.ui.button_detect_drives,
            self.ui.button_browse_image,
            self.ui.image_path,
            self.ui.tabWidget,
            self.ui.button_install_distro,
            self.ui.button_uninstall_distro,
        ]

    def ui_disable_controls(self):
        [c.setEnabled(False) for c in self.get_controls()]

    def ui_enable_controls(self):
        [c.setEnabled(True) for c in self.get_controls()]

    def update_slider_text(self):
        slide_value = self.ui.slider_persistence.value() * 1024 * 1024
        self.ui.label_persistence_value.setText(bytes2human(slide_value))
        config.persistence = slide_value

    def install_syslinux(self):
        try:
            try:
                self.install_syslinux_impl()
            finally:
                config.process_exist = None
                self.ui_enable_controls()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            uninstall_distro.do_uninstall_distro(
                config.distro, iso_basename(config.image_path))
            o = io.StringIO()
            traceback.print_exc(None, o)
            QtWidgets.QMessageBox.information(self,
                                              'install_syslinux() failed',
                                              o.getvalue())
            log("install_syslinux() failed.")
            log(o.getvalue())

    def install_syslinux_impl(self):
        """
        Function to install syslinux on distro directory and on selected USB disks.
        :return:
        """
        self.ui.statusbar.showMessage(str("Status: Installing Syslinux..."))
        syslinux_distro_dir(config.usb_disk, config.image_path, config.distro)
        syslinux_default(config.usb_disk)
        replace_grub_binary()
        update_distro_cfg_files(config.image_path, config.usb_disk,
                                config.distro, config.persistence)
        self.update_list_box(config.usb_disk)
        self.ui.statusbar.showMessage("Status: Idle")
        self.ui_disable_persistence()
        log(iso_name(config.image_path) + ' has been successfully installed.')
        QtWidgets.QMessageBox.information(
            self, 'Finished...',
            iso_name(config.image_path) + ' has been successfully installed.')

    def onInstall_syslinuxClick(self):
        """
        Function to install syslinux/extlinux on selected USB disk, except extlinux.cfg and syslinux.cfg.
        :return:
        """

        self.ui_disable_controls()
        if not config.usb_disk:
            log("ERROR Syslinux Install :  No USB device found.")
            QtWidgets.QMessageBox.information(
                self, "No Device...",
                "No USB device found.\n\nInsert USB and use Refresh USB button to detect USB."
            )
        elif platform.system() == "Linux" or platform.system() == "Windows":
            if self.ui.check_install_sys_all.isChecked(
            ) or self.ui.check_install_sys_only.isChecked():
                if platform.system(
                ) == 'Linux' and config.usb_disk[-1].isdigit() is False:
                    gen.log(
                        'Selected USB is a disk. Please select a disk partition from the drop down list'
                    )
                    QtWidgets.QMessageBox.information(
                        self, 'No Partition...!',
                        'USB disk selected doesn\'t contain a partition.\n'
                        'Please select the partition (ending '
                        'with a digit eg. /dev/sdb1)\nfrom the drop down list.'
                    )

                else:
                    log("Installing default syslinux on " + config.usb_disk)
                    ret = syslinux_default(config.usb_disk)
                    if ret is True and \
                       self.ui.check_install_sys_all.isChecked():
                        log("Copying multibootusb directory to " +
                            config.usb_mount)
                        src_root = resource_path(
                            os.path.join("data", "multibootusb"))
                        cutoff = len(src_root) + 1
                        dst_root = os.path.join(config.usb_mount,
                                                "multibootusb")
                        if not os.path.exists(dst_root):
                            os.makedirs(dst_root)
                        excludes = ['extlinux.cfg', 'syslinux.cfg']
                        for dirpath, dirnames, filenames in os.walk(src_root):
                            subdir_part = dirpath[cutoff:]
                            dest_dir = os.path.join(dst_root, subdir_part)
                            if not os.path.exists(dest_dir):
                                os.makedirs(dest_dir)
                            for f in filenames:
                                dest_fp = os.path.join(dest_dir, f)
                                if f in excludes and os.path.exists(dest_fp):
                                    continue
                                # log("Copying " + f)
                                shutil.copy(os.path.join(dirpath, f), dest_fp)
                        QtWidgets.QMessageBox.information(
                            self, 'Install Success...',
                            'Syslinux installed successfully on ' +
                            config.usb_disk)
                    elif ret is False:
                        QtWidgets.QMessageBox.information(
                            self, 'Install error...',
                            'Sorry. Syslinux failed to install on ' +
                            config.usb_disk)
            else:
                QtWidgets.QMessageBox.information(
                    self, 'No selection...',
                    'Please select one of the option from above.')

        self.ui_enable_controls()

    def onFsckClick(self, fsck_func):
        try:
            self.onFsckClick_impl(fsck_func)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            o = io.StringIO()
            traceback.print_exc(None, o)
            QtWidgets.QMessageBox.information(self, 'Failed to run fsck',
                                              o.getvalue())

    def onFsckClick_impl(self, fsck_func):
        if not config.usb_disk:
            QtWidgets.QMessageBox.information(
                self, 'No partition is selected',
                'Please select the partition to check.')
            return
        if not config.usb_disk[-1:].isdigit():
            QtWidgets.QMessageBox.information(
                self, 'Selected device is not partition',
                'Please select a partition not a disk.')
            return
        output = []
        with usb.UnmountedContext(config.usb_disk, self.update_usb_mount):
            fsck_func(config.usb_disk, output)
        for resultcode, msgout, cmd in output:
            QtWidgets.QMessageBox.information(
                self, 'Integrity Check',
                cmd + ' said:\n' + str(msgout[0], 'utf-8'))

    def onedit_syslinux(self):
        """
        Function to edit main syslinux.cfg file.
        :return:
        """
        # Function to edit syslinux.cfg file on editors like gedit, notepad etc.
        # Suggest me more editor which can be included in to this function.
        sys_cfg_file = os.path.join(config.usb_mount, "multibootusb",
                                    "syslinux.cfg")
        log("Locating " + sys_cfg_file)
        editor = ''
        if not os.path.exists(sys_cfg_file):
            log("syslinux.cfg file not found...")
            QtWidgets.QMessageBox.information(
                self, 'File not found...',
                'Sorry. Unable to locate syslinux.cfg file.\n'
                'You can only edit syslinux.cfg file generated by multibootusb.'
            )
        else:
            if platform.system() == "Linux":
                for e in config.editors_linux:
                    if subprocess.call('which ' + e, shell=True) == 0:
                        log("Editor found is " + e)
                        editor = e
                        break
            elif platform.system() == "Windows":
                for e in config.editors_win:
                    if not shutil.which(e) is None:
                        log("Editor found is " + e)
                        editor = e
                        break
            if not editor:
                QtWidgets.QMessageBox.information(
                    self, 'Editor not found...',
                    'Sorry. Installed editor is not supported by multibootusb\n'
                    'Edit ' + sys_cfg_file + ' manually.\n')
            else:
                try:
                    subprocess.Popen(editor + " '" + sys_cfg_file + "'",
                                     shell=True).pid
                except OSError:
                    QtWidgets.QMessageBox.warning(
                        self, 'Error...', 'Failed to open syslinux.cfg file.\n'
                        'Edit syslinux.cfg file manually.\n')

    def OnUninstallClick(self):
        """
        Triggers a function to uninstall a selected distro.
        :return:
        """

        self.ui_disable_controls()

        if self.ui.installed_distros.currentItem() is None:
            log("Please select a distro from the list.")
            QtWidgets.QMessageBox.information(
                self, 'No selection.', 'Please select a distro from the list.')
            self.ui_enable_controls()
        else:
            config.uninstall_distro_dir_name = str(
                self.ui.installed_distros.currentItem().text()).strip()
            reply = QtWidgets.QMessageBox.question(
                self, "Review selection...", "Are you sure to uninstall " +
                config.uninstall_distro_dir_name,
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                QtWidgets.QMessageBox.No)

            if reply == QtWidgets.QMessageBox.Yes:
                if not os.path.exists(
                        os.path.join(config.usb_mount, 'multibootusb',
                                     config.uninstall_distro_dir_name)):
                    log("Distro install directory not found. "
                        "Just updating syslinux.cfg and grub.cfg.")
                    uninstall_distro.update_sys_cfg_file(
                        config.uninstall_distro_dir_name)
                    uninstall_distro.update_grub_cfg_file(
                        config.uninstall_distro_dir_name)
                    self.uninstall_sys_file_update()
                    # self.uninstall.update_sys_cfg_file()
                    self.ui_enable_controls()
                else:
                    self.progress_thread_uninstall.start()
            else:
                self.ui_enable_controls()

    def uninstall_sys_file_update(self):
        """
        Function to remove and update uninstall distro text.
        :return:
        """

        # This function is already called from 'do_uninstall_distro()'
        # update_sys_cfg_file(config.uninstall_distro_dir_name)

        self.update_list_box(config.usb_mount)
        self.ui.statusbar.showMessage("Status: Idle")
        QtWidgets.QMessageBox.information(
            self, 'Uninstall Complete...', config.uninstall_distro_dir_name +
            ' has been successfully removed.')
        self.ui_enable_controls()

    def onCreateClick(self):
        installing = False
        self.ui_disable_controls()
        try:
            installing = self.onCreateClick_impl()
        finally:
            if not installing:
                self.ui_enable_controls()

    def onCreateClick_impl(self):
        """
        Main function to create bootable USB disk.
        :param usb_disk: ComboBox text as detected USB disk.
        :param iso_link: LineEdit text as selected ISO link.
        :return:
        """
        for cond, log_msg, dialog_title, dialog_msg in [
            (lambda: config.usb_disk is None, 'ERROR: No USB device found.',
             'No Device...', 'No USB device found.\n\nInsert USB and '
             'use Refresh USB button to detect USB.'),
            (lambda: not config.image_path, 'No ISO selected.', 'No ISO...',
             'No ISO found.\n\nPlease select an ISO.'),
            (lambda: ' ' in os.path.basename(config.image_path),
             'Spaces in iso-file name is not allowed.', 'Bad ISO filename...',
             'Filename that contains space(s) is not '
             'supported.')
        ]:
            if cond():
                QtWidgets.QMessageBox.information(self, dialog_title,
                                                  dialog_msg)
                return False

        usb_details = config.usb_details
        if usb_details['mount_point'] == 'No_Mount':
            log("ERROR: USB disk is not mounted.")
            QtWidgets.QMessageBox.information(
                self, "No Mount...", "USB disk is not mounted.\n"
                "Please mount USB disk and press refresh "
                "USB button.")
            return False
        if config.usb_details['devtype'] == 'disk':
            gen.log('Selected USB is a physical disk. '
                    'Please select '
                    'a partition or volume from the drop down list')
            QtWidgets.QMessageBox.information(
                self, 'No Partition...!', 'Selected USB is a physical disk. '
                'Please select a partition (e.g. /dev/sdc1) '
                'or a volume (e.g. G:) '
                'from the drop down list.')
            return False
        if 0 < config.persistence and \
             persistence.detect_missing_tools(config.distro):
            QtWidgets.QMessageBox.information(
                self, 'Missing tools...!',
                persistence.detect_missing_tools(config.distro))
            return False
        if not self.check_remount():
            self.update_target_info()
            return False

            # clean_iso_cfg_ext_dir(os.path.join(multibootusb_host_dir(), "iso_cfg_ext_dir"))  # Need to be cleaned.
            # extract_cfg_file(config.image_path)  # Extract files from ISO
            # config.distro = distro(iso_cfg_ext_dir(), config.image_path)    # Detect supported distro
        log("MultiBoot Install: USB Disk: " + config.usb_disk)
        log("MultiBoot Install: USB Label: " + config.usb_label)
        log("MultiBoot Install: USB UUID: " + config.usb_uuid)
        log("MultiBoot Install: USB mount path: " + config.usb_mount)
        log("MultiBoot Install: Disk total size: " +
            str(usb.bytes2human(usb_details['size_total'])))
        log("MultiBoot Install: Disk used size: " +
            str(usb.bytes2human(usb_details['size_used'])))
        log("MultiBoot Install: Disk free size: " +
            str(usb.bytes2human(usb_details['size_free'])))
        log("MultiBoot Install: Filesystem: " + usb_details['file_system'])
        log("MultiBoot Install: Disk vendor: " + usb_details['vendor'])
        log("MultiBoot Install: Disk model: " + usb_details['model'])
        log("MultiBoot Install: ISO file: " + iso_name(config.image_path))

        if not os.path.exists(config.image_path):
            return False

        # self.ui.image_path.clear()
        if not config.distro:
            QtWidgets.QMessageBox.information(
                self, 'No support...',
                'Sorry.\n' + os.path.basename(config.image_path) +
                ' is not supported at the moment.\n'
                'Please email this issue to '
                '*****@*****.**')
            return False

        log("MultiBoot Install: Distro type detected: " + config.distro)
        full_image_path = os.path.join(config.usb_mount, "multibootusb",
                                       iso_basename(config.image_path))
        if os.path.exists(full_image_path):
            QtWidgets.QMessageBox.information(
                self, 'Already exists...',
                os.path.basename(config.image_path) + ' is already installed.')
            return False

        config.persistence = self.ui.slider_persistence.value() \
                     * 1024 * 1024
        log("Persistence chosen is " + str(bytes2human(config.persistence)))
        install_size = iso_size(config.image_path) + config.persistence
        if install_size >= disk_usage(config.usb_mount).free:
            log("ERROR: Not enough space available on " + config.usb_disk)
            QtWidgets.QMessageBox.information(
                self, "No Space.", "No space available on " + config.usb_disk)
            return False

        msg = '''
The ISO sleceted is not supported at the moment.
You can try booting ISO using memdisk.
Distro can be uninstalled anytime from main menu.

Proceed with installation?'''.lstrip() if config.distro == 'memdisk_iso' else \
        '''
Selected USB disk: %s
USB mount point: %s
Selected distro: %s

Log location: %s

Proceed with installation?'''.lstrip() % \
    (config.usb_disk, config.usb_mount, iso_name(config.image_path),
        osdriver.mbusb_log_file())
        reply = QtWidgets.QMessageBox.question(self, 'Review selection...',
                                               msg)
        if reply == QtWidgets.QMessageBox.Yes:
            self.ui.slider_persistence.setEnabled(False)
            copy_mbusb_dir_usb(config.usb_disk)
            config.process_exist = True
            self.progress_thread_install.start()
            return True

        return False

    def dd_finished(self):
        """
        Re-enable the blocked widgets for newer use.
        :return:
        """
        self.ui.progressbar.setValue(0)
        self.ui.statusbar.showMessage("Status: Idle")
        config.process_exist = None

        msgBox = QtWidgets.QMessageBox()
        if self.progress_thread_dd.error:
            title = "Failed to write the iso image to the USB disk."
            msg = self.progress_thread_dd.error
        else:
            title = "Image succesfully written to USB disk."
            msg = "Reboot to boot from USB or test it from " \
              "<b>Boot ISO/USB</b> tab."
        msgBox.setText(title)
        msgBox.setInformativeText(msg)
        msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
        msgBox.setIcon(QtWidgets.QMessageBox.Information)
        msgBox.exec_()

        self.ui_enable_controls()

    def dd_start(self):
        """
        Function to block the widgets under ISO Imager tab...
        :return:
        """
        self.ui.progressbar.setValue(0)
        self.ui.statusbar.showMessage("Status: Idle")
        # FIXME        self.ui.lineEdit_3.clear()
        self.ui.button_browse_image.setEnabled(False)
        self.ui.combo_drives.setEnabled(False)
        # FIXME self.ui.pushbtn_imager_refreshusb.setEnabled(False)
        status_text = ("Status: Writing " +
                       os.path.basename(config.image_path) + " to " +
                       osdriver.usb_disk_desc(config.usb_disk))
        self.ui.statusbar.showMessage(status_text)

    def dd_quit(self):
        self.ui.progressbar.setValue(0)
        self.ui.statusbar.showMessage("Status: Idle")
        self.ui.combo_drives.setEnabled(True)
        self.ui.button_browse_image.setEnabled(True)
        QtWidgets.QMessageBox.information(self, 'Failed!',
                                          'Failed writing image.')

    def dd_write(self):
        self.ui_disable_controls()

        if not config.usb_disk:
            QtWidgets.QMessageBox.information(
                self, 'No USB disk selected',
                'Please insert USB disk and click "Detect Drives".')
            self.ui_enable_controls()
        elif not config.image_path:
            QtWidgets.QMessageBox.information(self, 'No ISO selected',
                                              'Please select an ISO.')
            self.ui_enable_controls()
        else:
            imager = Imager()
            if config.usb_details['devtype'] == "partition":
                gen.log(
                    'Selected device is a partition. Please select a disk from the drop down list'
                )
                QtWidgets.QMessageBox.information(
                    self, 'Incompatible device',
                    'Selected device (%s) is a partition!\n'
                    'ISO must be written to a whole disk.'
                    '\n\nPlease select a disk from the drop down list.' %
                    config.usb_disk)
                self.ui_enable_controls()
            else:
                usb_disk_size = int(
                    imager.imager_usb_detail(config.usb_disk).total_size)
                self.iso_size = os.path.getsize(config.image_path)
                if self.iso_size >= usb_disk_size:
                    QtWidgets.QMessageBox.information(
                        self, "No enough space on disk.",
                        os.path.basename(config.image_path) +
                        " size is larger than the size of " +
                        osdriver.usb_disk_desc(config.usb_disk))
                    self.ui_enable_controls()
                # elif gen.process_exist('explorer.exe') is not False:
                #    # Check if windows explorer is running and inform user to close it.
                #    QtWidgets.QMessageBox.information(self, "Windows Explorer", "Windows Explorer is running\n"
                #                                                                "You need to close it before writing ISO "
                #                                                                "image to disk...")
                else:
                    reply = QtWidgets.QMessageBox.question \
                        (self, 'Review selection',
                         'Selected disk: %s\n' % osdriver.usb_disk_desc(config.usb_disk) +
                         'Selected image: %s\n\n' % os.path.basename(config.image_path) +
                         'Proceed with writing image to disk?',
                         QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)

                    if reply == QtWidgets.QMessageBox.Yes:
                        self.dd_start()
                        config.process_exist = True
                        self.progress_thread_dd.start()
                    elif reply == QtWidgets.QMessageBox.No:
                        self.ui_enable_controls()

    def on_close_Click(self):
        """
        Closes main GUI.
        :return:
        """
        self.close()

    def update_usb_mount(self, new_usb_details):
        config.update_usb_mount(new_usb_details)
        self.ui.usb_mount.setText(config.usb_mount)

    def check_remount(self):
        if config.usb_details['file_system'] != 'vfat':
            return True
        try:
            with UnmountedContext(config.usb_disk, self.update_usb_mount) as m:
                pass
            return True
        except usb.RemountError:
            QtWidgets.QMessageBox.critical(
                self, "Remount failed.", "Could not remount '{0}'. "
                "Please make sure no process has open "
                "handle(s) to previously mounted filesystem.".format(
                    config.usb_disk))
            return False

    def update_target_info(self):

        usb_total_size = str(
            usb.bytes2human(config.usb_details.get('size_total', "")))
        usb_free_size = str(
            usb.bytes2human(config.usb_details.get('size_free', "")))
        config.persistence_max_size = persistence.max_disk_persistence(
            config.usb_disk)
        config.usb_mount = config.usb_details.get('mount_point', "")
        self.ui.usb_dev.setText(osdriver.usb_disk_desc(config.usb_disk))

        self.ui.usb_vendor.setText(config.usb_details.get('vendor', ""))
        self.ui.usb_model.setText(config.usb_details.get('model', ""))
        self.ui.usb_size.setText('Free :: ' + usb_free_size + ' / Total :: ' +
                                 usb_total_size)
        self.ui.usb_mount.setText(config.usb_details.get('mount_point', ""))
        self.ui.usb_type.setText(config.usb_details.get('devtype', ""))
        self.ui.usb_fs.setText(config.usb_details.get('file_system', ""))

    def closeEvent(self, event):
        """
        To capture the main close event.
        :param event: Close event.
        :return:
        """
        if config.process_exist == None:
            event.accept()
        else:
            reply = QtWidgets.QMessageBox.question(
                self, 'Exit MultiBootUSB...', "A process is still running.\n"
                "Do you really want to quit multibootusb?",
                QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
            if reply == QtWidgets.QMessageBox.Yes:
                log("Closing multibootusb...")
                event.accept()
                sys.exit(0)
            else:
                log("Close event cancelled.")
                event.ignore()