@ -5,7 +5,8 @@
# Authors: Sundar
# Licence: This file is a part of multibootusb package. You can redistribute it or modify
# under the terms of GNU General Public License, v.2 or above
from functools import partial
import io
import os
import platform
import sys
@ -13,7 +14,12 @@ import signal
from PyQt5 import QtCore , QtGui , QtWidgets
import subprocess
import time
import traceback
import webbrowser
if platform . system ( ) == ' Linux ' :
import dbus
from scripts . gui . ui_multibootusb import Ui_MainWindow
from scripts . gui . ui_about import Ui_About
from . import usb
@ -80,6 +86,12 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
# 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 )
@ -98,6 +110,11 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
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 ( )
@ -120,6 +137,7 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
" 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 :
@ -148,18 +166,8 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
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_target_info ( )
# Get the GPT status of the disk and store it on a variable
usb . gpt_device ( config . usb_disk )
@ -174,7 +182,6 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
self . ui . usb_mount . clear ( )
self . ui . usb_type . clear ( )
self . ui . usb_fs . clear ( )
log ( " No USB disk found... " )
def onRefreshClick ( self ) :
@ -187,13 +194,14 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
detected_devices = usb . list_devices ( fixed = True )
else :
detected_devices = usb . list_devices ( )
if not detected_devices :
return
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 )
self . ui . combo_drives . setCurrentIndex ( 0 )
def update_list_box ( self , usb_disk ) :
"""
@ -247,7 +255,7 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
# Detect supported distro
try :
clean_iso_cfg_ext_dir ( # Need to be cleaned.
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 ,
@ -323,21 +331,23 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
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 ) :
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 )
[ c . setEnabled ( False ) for c in self . get_controls ( ) ]
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 )
[ c . setEnabled ( True ) for c in self . get_controls ( ) ]
def update_slider_text ( self ) :
slide_value = self . ui . slider_persistence . value ( ) * 1024 * 1024
@ -356,9 +366,6 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
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. ' )
@ -416,9 +423,6 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
dest_fp )
QtWidgets . QMessageBox . information ( self , ' Install Success... ' ,
' Syslinux installed successfully on ' + config . usb_disk )
elif ret is True and self . ui . check_install_sys_only . isChecked ( ) :
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 )
@ -428,6 +432,38 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
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 .
@ -514,15 +550,21 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
# update_sys_cfg_file(config.uninstall_distro_dir_name)
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 ) :
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 .
@ -530,40 +572,56 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
: 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 \n Insert USB and use Refresh USB button to detect USB. " )
self . ui_enable_controls ( )
elif not config . image_path :
QtWidgets . QMessageBox . information (
self , " No Device... " ,
" No USB device found. \n \n Insert USB and "
" use Refresh USB button to detect USB. " )
return False
if not config . image_path :
log ( " No ISO selected. " )
QtWidgets . QMessageBox . information ( self , " No ISO... " , " No ISO found. \n \n Please select an ISO. " )
self . ui_enable_controls ( )
elif usb . details ( config . usb_disk ) [ ' mount_point ' ] == ' No_Mount ' :
QtWidgets . QMessageBox . information (
self , " No ISO... " ,
" No ISO found. \n \n Please select an ISO. " )
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. " )
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) \n from the drop down list. ' )
self . ui_enable_controls ( )
elif 0 < config . persistence and \
QtWidgets . QMessageBox . information (
self , " No Mount... " ,
" USB disk is not mounted. \n "
" Please mount USB disk and press refresh "
" USB button. " )
return False
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) \n '
' 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 ) )
self . ui_enable_controls ( )
else :
persistence . detect_missing_tools (
config . distro ) )
return False
if not self . check_remount ( ) :
self . update_target_info ( )
return False
if 1 : # Redundant if to keep indentation level.
# 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 )
# 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 )
@ -576,61 +634,65 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
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 :
if config . distro == ' memdisk_iso ' :
reply = QtWidgets . QMessageBox . question ( self , ' Review selection... ' ,
' The ISO sleceted is not supported at the moment. \n '
' You can try booting ISO using memdisk. \n '
' Distro can be uninstalled anytime from main menu. \n \n '
' Proceed with installation? ' ,
QtWidgets . QMessageBox . Yes | QtWidgets . QMessageBox . No ,
QtWidgets . QMessageBox . No )
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 ( )
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 '
' feedback.multibootusb@gmail.com ' )
return False
log ( " MultiBoot Install: Distro type detected: " + config . distro )
if os . path . exists (
os . path . join ( config . usb_mount , " multibootusb " , iso_basename ( config . 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
else :
if config . distro == ' memdisk_iso ' :
reply = QtWidgets . QMessageBox . question ( self , ' Review selection... ' ,
' The ISO sleceted is not supported at the moment. \n '
' You can try booting ISO using memdisk. \n '
' Distro can be uninstalled anytime from main menu. \n \n '
' Proceed with installation? ' ,
QtWidgets . QMessageBox . Yes | QtWidgets . QMessageBox . No ,
QtWidgets . QMessageBox . No )
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 feedback.multibootusb@gmail.com ' )
self . ui_enable_controls ( )
# Added to refresh usb disk remaining size after distro installation
# self.update_gui_usb_info()
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 ( )
return True
elif reply == QtWidgets . QMessageBox . No :
return False
# Added to refresh usb disk remaining size after distro installation
# self.update_gui_usb_info()
def dd_finished ( self ) :
"""
@ -724,6 +786,39 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
"""
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 ) :
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 ' , " " ) )
def closeEvent ( self , event ) :
"""
To capture the main close event .
@ -746,7 +841,6 @@ class AppGui(qemu.Qemu, Imager, QtWidgets.QMainWindow, Ui_MainWindow):
log ( " Close event cancelled. " )
event . ignore ( )
class GuiInstallProgress ( QtCore . QThread ) :
"""
Update GUI thread during install .