#!/usr/bin/env python3
#
#################################
### A-Team Android Rom Builder ##
#################################
#
# Copyright (C) 2019-Present A-Team Digital Solutions
#

#
# IMPORTS & REQUIREMENTS
#
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Vte", "2.91")
import os
import subprocess
import shutil
import threading
import time
import sys
from gi.repository import (
    Gtk,
    GLib,
    Vte,
    Pango,
    Gdk
)
from builder import (
    get_devices,
    load_project,
    run_script,
    run_script_async,
    TerminalManager,
    build_project_summary,
    get_profiles,
    save_profile,
    load_profile,
    Project,
    create_desktop_launcher
)
from pathlib import Path
from builder.actions import (
    build_command,
    sync_command
)
from builder.resource_monitor import ResourceMonitor
from builder.button_actions import (
    ButtonActions
)
from builder.hosts_manager import (
    load_hosts,
    get_host_config,
    build_ssh_args,
    build_ssh_command
)
from builder.splash import Splash
from builder.splash_manager import (
    get_display_names,
    display_to_filename,
    filename_to_display,
    load_selected_splash,
    save_selected_splash
)
from builder.icon_manager import (
    get_icon_display_names,
    display_to_filename as icon_display_to_filename,
    filename_to_display as icon_filename_to_display,
    load_selected_icon,
    save_selected_icon
)
from builder.version import DISPLAY_VERSION
from builder.updater import check_for_update
from builder.updater_overlay import UpdaterOverlay
BASE = str(
    Path(__file__).resolve().parent
)

#
# UI SIZING
#
HOST_WIDTH        = 185
ANDROID_WIDTH     = 65
LABEL_WIDTH       = 100
ROM_PATH_WIDTH    = 300
DEVICE_WIDTH      = 185
INSTALLER_WIDTH   = 230
GAPPS_WIDTH       = 100
CLEAN_WIDTH       = 75
BUILD_COMMANDS_TOP_BOTTOM_SPACING  = 20
BUILD_SETUP_TOP_SPACING            = 20
PROJECT_PROFILES_TOP_SPACING       = 20
SYNC_SETUP_TOP_SPACING             = 20
SPLASH_TOP_BOTTOM_SPACING          = 20
BUILD_COMMANDS_WIDTH               = 175
SYNC_SETUP_BUTTON_WIDTH            = 150
SYNC_GAPPS_BUTTON_WIDTH            = 150
BUILD_ROM_BUTTON_WIDTH             = 100
LAUNCHER_BUTTON_WIDTH              = 210
CLEAN_BUTTON_WIDTH                 = 100
SPLASH_DROPDOWN_WIDTH              = 185
ICON_DROPDOWN_WIDTH                = 110

# UI Theme
TITLE_COLOR       = "#00FFFF"
# Cyan:           #00FFFF
# Green:          #00FF00
# Orange:         #FF8000
# Red:            #FF0000
# White:          #FFFFFF
TITLE_SIZE        = "12000"
# 10 pt:	      10000
# 12 pt:	      12000
# 14 pt:	      14000
# 16 pt:	      16000
# 18 pt:	      18000
# 20 pt:	      20000
# 24 pt:	      24000
TITLE_BOLD        = True

#
# A-TEAM ANDROID ROM BUILDER APP
#
def set_button_width(button, width):
    button.set_size_request(
        width,
        -1
    )
    button.set_hexpand(False)
    button.set_halign(Gtk.Align.CENTER)
    child = button.get_child()
    if isinstance(child, Gtk.Label):
        child.set_ellipsize(Pango.EllipsizeMode.END)
        child.set_width_chars(1)
        child.set_max_width_chars(1)
        child.set_size_request(
            max(1, width - 24),
            -1
        )


class ROMBuilder(Gtk.Window):
    #
    # INITIATE APP
    #
    def __init__(self):
        super().__init__(
            title=f"A-Team Android ROM Builder  |  Version--> {DISPLAY_VERSION}"
        )
        self.set_default_size(1100, 750)
        self.project = None
        self.loading_profile_list = False
        self.applying_profile = False
        self.notebook = Gtk.Notebook()
        self.overlay = Gtk.Overlay()
        self.overlay.add(self.notebook)
        self.update_overlay = UpdaterOverlay()
        self.overlay.add_overlay(self.update_overlay)
        self.add(self.overlay)
        self.build_setup_tab()    #0
        self.sync_tab()           #1
        self.build_tab()          #2
        self.tools_tab()          #3
        self.terminal_tab()       #4
        # TerminalManager now exists
        self.monitor = ResourceMonitor(self.terminal)
        self.resource_tab()       #5
        self.actions = ButtonActions(
            self.terminal,
            self.notebook
        )
        self.local_rom_path = ""
        GLib.idle_add(self.check_for_updates)
        css = Gtk.CssProvider()
        css.load_from_data(b"""
        #update-overlay {
            background-color: rgba(0,0,0,0.55);
        }
        """)

        Gtk.StyleContext.add_provider_for_screen(
            self.get_screen(),
            css,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
    #
    # UPDATE CHECKER
    #
    def check_for_updates(self):
        update = check_for_update()
        if update:
            self.show_update_dialog(update)
        return False
    def show_update_dialog(self, update):

        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.NONE,
            text="----->   App Update Available   <-----"
        )

        changelog = str(
            update.get(
                "changelog",
                "No Changelog Provided."
            )
        ).strip()

        dialog.format_secondary_text(
            f"""
        Installed Version : {DISPLAY_VERSION}
        Available Version : {update['display']}

        Update Changelog:
        {changelog}

        Would You Like To Install It Now ??
        """
        )
        dialog.add_button(
            "Later",
            Gtk.ResponseType.CANCEL
        )
        dialog.add_button(
            "Proceed",
            Gtk.ResponseType.OK
        )
        response = dialog.run()
        dialog.destroy()
        if response == Gtk.ResponseType.OK:
            self.run_updater(update)
    # Update Run
    def run_updater(self, update):
        updater = Path(BASE) / "scripts" / "updater.sh"
        download_base = (
            "https://ateam.gotadell.com/git/PizzaG/"
            "A-Team-Android_ROM_Builder/raw/branch/releases/"
        )
        archive_url = download_base + update["file"]
        self.update_overlay.set_status("Preparing Update ...")
        self.update_overlay.show_overlay()
        self.update_overlay.start()
        threading.Thread(
            target=self._updater_thread,
            args=(updater, archive_url),
            daemon=True,
        ).start()
    def _updater_thread(self, updater, archive_url):
        process = subprocess.Popen(
            [
                str(updater),
                archive_url,
                str(BASE),
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
        )
        for line in process.stdout:
            line = line.strip()
            print(line)
            if line.startswith("::STATUS::"):
                status = line.replace("::STATUS::", "", 1).strip()
                GLib.idle_add(
                    self.update_overlay.set_status,
                    status,
                )
            elif line.startswith("::PROGRESS::"):
                progress = line.replace("::PROGRESS::", "", 1).strip()
                parts = progress.split("::")
                if len(parts) == 2:
                    try:
                        current = int(parts[0])
                        total = int(parts[1])
                        GLib.idle_add(
                            self.update_overlay.set_progress,
                            current,
                            total,
                        )
                    except ValueError:
                        pass
        process.wait()
        if process.returncode == 0:
            GLib.idle_add(self.finish_update)
        else:
            GLib.idle_add(
                self.update_failed,
                process.returncode,
            )
    def finish_update(self):
        self.update_overlay.stop()
        self.update_overlay.set_status(
            "----->  Update Complete  <-----"
        )
        GLib.timeout_add(
            5000,   # Show "Update Complete" for 5 seconds
            self._show_restarting
        )
        return False
    def _show_restarting(self):
        self.update_overlay.set_status(
            "Restarting A-Team Android ROM Builder ..."
        )
        GLib.timeout_add(
            5000,   # Show "Restarting..." for 5 seconds
            self._restart_after_update
        )
        return False
    def _restart_after_update(self):
        subprocess.Popen([
            sys.executable,
            str(Path(BASE) / "gui.py"),
        ])
        self.destroy()
        return False
    def update_failed(self, code):
        self.update_overlay.stop()
        self.update_overlay.hide_overlay()
        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.ERROR,
            buttons=Gtk.ButtonsType.OK,
            text="Update Failed",
        )
        dialog.format_secondary_text(
            f"The Updater Exited With Code: {code}."
        )
        dialog.run()
        dialog.destroy()
        return False
    #
    # TAB--> 1- BUILD SETUP TAB
    #
    def build_setup_tab(self):
        page = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        page.set_border_width(10)
        grid = Gtk.Grid(
            row_spacing=10,
            column_spacing=10
        )
        grid.set_column_spacing(10)
        grid.set_row_spacing(10)
        grid.set_column_homogeneous(False)
        #
        # BUILD HOST
        #
        label = Gtk.Label(label="Build Host:")
        label.set_margin_top(
            BUILD_SETUP_TOP_SPACING
        )
        label.set_valign(Gtk.Align.CENTER)
        grid.attach(
          label,
          0, 0, 1, 1
        )
        host_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=6
        )
        self.build_host = Gtk.ComboBoxText()
        self.build_host.set_size_request(
            HOST_WIDTH,
            -1
        )
        self.connection_status = Gtk.Label()
        self.connection_status.set_markup(
            '<span foreground="red"><b>● Disconnected</b></span>'
        )
        self.connection_status.set_halign(Gtk.Align.START)
        hosts = load_hosts()
        for host in hosts:
            self.build_host.append_text(host)
        if hosts:
            self.build_host.set_active(0)
        self.build_host.connect(
            "changed",
            self.build_host_changed
        )
        host_box.pack_start(
            self.build_host,
            False,
            False,
            0
        )
        host_box.pack_start(
            self.connection_status,
            False,
            False,
            10
        )
        has_remote_host = False
        for host_name in hosts:
            cfg = get_host_config(host_name)
            if not cfg:
                continue
            if cfg.get(
                "server_address",
                ""
            ).strip():
                has_remote_host = True
                break
        self.has_remote_host = has_remote_host
        # Always create the buttons
        self.connect_btn = Gtk.Button(label="Connect")
        self.disconnect_btn = Gtk.Button(label="Disconnect")
        # Only add them to the UI if a remote host exists
        if self.has_remote_host:
            host_box.pack_start(
                self.connect_btn,
                False,
                False,
                0
            )

            host_box.pack_start(
                self.disconnect_btn,
                False,
                False,
                0
            )
        self.connect_btn.connect(
            "clicked",
            self.connect_host_clicked
        )
        self.disconnect_btn.connect(
            "clicked",
            self.disconnect_host_clicked
        )
        host_box.set_margin_top(
            BUILD_SETUP_TOP_SPACING
        )
        grid.attach(
            host_box,
            1, 0, 1, 1
        )
        self.update_build_host_ui()
        #
        # DEVICE DROPDOWN
        #
        grid.attach(
            Gtk.Label(label="Device:"),
            0, 1, 1, 1
        )
        self.device_combo = Gtk.ComboBoxText()
        self.device_combo.set_size_request(
            DEVICE_WIDTH,
            -1
        )
        self.device_combo.set_halign(
            Gtk.Align.START
        )
        self.devices = get_devices()
        for device in self.devices:
            self.device_combo.append_text(
                device["display"]
            )
        self.device_combo.set_active(0)
        grid.attach(
            self.device_combo,
            1, 1, 1, 1
        )
        #
        # ROM PATH
        #
        grid.attach(
            Gtk.Label(label="ROM Source:"),
            0, 2, 1, 1
        )
        rom_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        self.rom_entry = Gtk.Entry()
        self.rom_entry.set_size_request(
            ROM_PATH_WIDTH,
            -1
        )
        self.rom_entry.set_hexpand(True)
        self.rom_entry.set_halign(
            Gtk.Align.START
        )
        rom_box.pack_start(
            self.rom_entry,
            True,
            True,
            0
        )
        self.browse_btn = Gtk.Button(
            label="Browse"
        )
        self.browse_btn.connect(
            "clicked",
            self.choose_rom
        )
        rom_box.pack_start(
            self.browse_btn,
            False,
            False,
            0
        )
        grid.attach(
            rom_box,
            1, 2, 1, 1
        )
        #
        # ANDROID VERSION
        #
        grid.attach(
            Gtk.Label(label="Android Version:"),
            0, 3, 1, 1
        )
        self.android = Gtk.ComboBoxText()
        self.android.set_size_request(
            ANDROID_WIDTH,
            -1
        )
        self.android.set_halign(
            Gtk.Align.START
        )
        for version in [
            "16",
            "17"
        ]:
            self.android.append_text(
                version
            )
        self.android.set_active(0)
        grid.attach(
            self.android,
            1, 3, 1, 1
        )
        #
        # BUILD VARIANT
        #
        grid.attach(
            Gtk.Label(label="Build Variant:"),
            0, 4, 1, 1
        )
        self.build_variant = Gtk.ComboBoxText()
        self.build_variant.set_size_request(
            120,
            -1
        )
        self.build_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Userdebug",
            "Eng",
            "User"
        ]:
            self.build_variant.append_text(
                variant
            )

        self.build_variant.set_active(0)
        grid.attach(
            self.build_variant,
            1, 4, 1, 1
        )
        #
        # GAPPS VARIANT
        #
        grid.attach(
            Gtk.Label(label="Gapps Variant:"),
            0, 5, 1, 1
        )
        self.gapps_variant = Gtk.ComboBoxText()
        self.gapps_variant.set_size_request(
            GAPPS_WIDTH,
            -1
        )
        self.gapps_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Vanilla",
            "Gapps",
            "Micro-G",
            "Foss"
        ]:
            self.gapps_variant.append_text(
                variant
            )
        self.gapps_variant.set_active(0)
        grid.attach(
            self.gapps_variant,
            1, 5, 1, 1
        )
        #
        # CLEAN VARIANT
        #
        grid.attach(
            Gtk.Label(label="Clean Variant:"),
            0, 6, 1, 1
        )
        self.clean_variant = Gtk.ComboBoxText()
        self.clean_variant.set_size_request(
            CLEAN_WIDTH,
            -1
        )
        self.clean_variant.set_halign(
            Gtk.Align.START
        )
        for variant in [
            "Full",
            "Lite",
            "None"
        ]:
            self.clean_variant.append_text(
                variant
            )
        self.clean_variant.set_active(0)
        grid.attach(
            self.clean_variant,
            1, 6, 1, 1
        )
        #
        # INSTALLER VARIANT
        #
        grid.attach(
            Gtk.Label(label="Installer Variant:"),
            0, 7, 1, 1
        )
        self.installer_variant = Gtk.ComboBoxText()
        self.installer_variant.set_size_request(
            INSTALLER_WIDTH,
            -1
        )
        self.installer_variant.set_halign(
            Gtk.Align.START
        )
        self.installer_variant.append(
            "custom",
            "Custom A-Team ROM Installer"
        )
        self.installer_variant.append(
            "default",
            "Default ROM Installer"
        )
        grid.attach(
            self.installer_variant,
            1, 7, 1, 1
        )
        self.installer_variant.set_active(0)
        #
        # BUILD JOBS
        #
        grid.attach(
            Gtk.Label(label="Build Jobs:"),
            0, 8, 1, 1
        )
        self.jobs = Gtk.SpinButton()
        self.jobs.set_halign(
            Gtk.Align.START
        )
        self.jobs.set_hexpand(False)
        self.jobs.set_range(
            1,
            128
        )
        self.jobs.set_value(
            6
        )
        self.jobs.set_increments(
            1,
            4
        )
        self.jobs.set_numeric(True)
        grid.attach(
            self.jobs,
            1, 8, 1, 1
        )
        #
        # BUILD OPTIONS
        #
        options_frame = Gtk.Frame(
            label="--Build Options--"
        )
        options_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=20,
        )
        options_box.set_margin_top(6)
        options_box.set_margin_bottom(6)
        options_box.set_margin_start(8)
        options_box.set_margin_end(8)
        self.ccache = Gtk.CheckButton(
            label="Ccache"
        )
        self.ccache.set_active(True)
        self.custom_apn = Gtk.CheckButton(
            label="Custom APN"
        )
        self.custom_apn.set_active(False)
        options_box.pack_start(
            self.ccache,
            False,
            False,
            0,
        )
        options_box.pack_start(
            self.custom_apn,
            False,
            False,
            0,
        )
        options_frame.add(options_box)
        grid.attach(
            options_frame,
            0, 9, 2, 1
        )
        #
        # PROFILE MODIFICATION SIGNALS
        #
        self.build_host.connect(
            "changed",
            self.mark_profile_modified
        )
        self.device_combo.connect(
            "changed",
            self.mark_profile_modified
        )
        self.android.connect(
            "changed",
            self.mark_profile_modified
        )
        self.build_variant.connect(
            "changed",
            self.mark_profile_modified
        )
        self.gapps_variant.connect(
            "changed",
            self.mark_profile_modified
        )
        self.clean_variant.connect(
            "changed",
            self.mark_profile_modified
        )
        self.installer_variant.connect(
            "changed",
            self.mark_profile_modified
        )
        self.jobs.connect(
            "value-changed",
            self.mark_profile_modified
        )
        self.ccache.connect(
            "toggled",
            self.mark_profile_modified
        )
        self.custom_apn.connect(
            "toggled",
            self.mark_profile_modified
        )
        #
        #
        # PROJECT PROFILES
        #
        profile_row = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=6
        )
        self.profile_label = Gtk.Label(
            label="Project Profiles:"
        )
        profile_row.pack_start(
            self.profile_label,
            False,
            False,
            0
        )
        #
        # PROFILE DROPDOWN
        #
        self.profile_combo = Gtk.ComboBoxText()
        self.profile_combo.set_size_request(
            ROM_PATH_WIDTH,
            -1
        )
        self.profile_combo.connect(
            "changed",
            self.project_profile_changed
        )
        #
        # SAVE PROFILE
        #
        self.save_profile_btn = Gtk.Button(
            label="Save Profile"
        )
        self.save_profile_btn.connect(
            "clicked",
            self.save_project_profile_clicked
        )
        #
        # Add Save Profile immediately.
        #
        profile_row.pack_start(
            self.save_profile_btn,
            False,
            False,
            0
        )
        self.profile_row = profile_row
        profile_row.set_margin_top(
            PROJECT_PROFILES_TOP_SPACING
        )
        grid.attach(
            profile_row,
            0, 10, 2, 1
        )
        #
        # LOAD PROJECT
        #
        load_btn = Gtk.Button(
            label="Load Project"
        )
        load_btn.connect(
            "clicked",
            self.load_project_clicked
        )
        grid.attach(
            load_btn,
            0, 11, 2, 1
        )
        #
        # Populate profiles after the widgets exist.
        #
        self.refresh_project_profiles()
        #   
        # ADD SETTINGS GRID
        #
        page.pack_start(
            grid,
            False,
            False,
            0
        )
        #
        # Project Summary
        #
        TITLE       = "--  Project Summary  --"
        summary_box = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=4
        )
        title = Gtk.Label()
        markup = (
            f'<span foreground="{TITLE_COLOR}" size="{TITLE_SIZE}">'
            f'{"<b>" if TITLE_BOLD else ""}'
            f'{TITLE}'
            f'{"</b>" if TITLE_BOLD else ""}'
            f'</span>'
        )
        title.set_markup(markup)
        title.set_halign(Gtk.Align.CENTER)
        summary_box.pack_start(
            title,
            False,
            False,
            0
        )
        frame = Gtk.Frame()
        self.info = Gtk.TextView()
        self.info.set_editable(False)
        frame.add(self.info)
        summary_box.pack_start(
            frame,
            True,
            True,
            0
        )
        page.pack_start(
            summary_box,
            True,
            True,
            0
        )
        self.notebook.append_page(
            page,
            Gtk.Label(label="1- Build Setup")
        )
    def build_host_changed(self, combo):
        self.update_build_host_ui()
    # Connect Button Pressed
    def connect_host_clicked(self, button):
        host_name = self.build_host.get_active_text()
        cfg = get_host_config(host_name)
        if not cfg:
            return
        server = cfg["server_address"]
        user = cfg["user"]
        port = cfg.get("port", "22")
        remote_path = cfg.get(
            "remote_rom_source_path",
            ""
        ).strip()
        if not remote_path:
            print("** ERROR **: No remote_rom_source_path Configured In hosts.conf")
            return
        command = build_ssh_command(cfg)
        self.local_rom_path = self.rom_entry.get_text()
        self.connected_host = cfg
        self.ssh_connected = True
        self.terminal.ssh_connected = True
        self.update_build_host_ui()
        self.terminal.remote_cfg = cfg
        self.terminal.remote_path = remote_path
        self.terminal.remote_user = user
        self.terminal.remote_host = server
        self.terminal.remote_port = int(port)
        self.rom_entry.set_text(remote_path)
        self.rom_entry.set_editable(False)
        self.browse_btn.hide()
        print(f"Connecting {host_name} @ {command} ...")
        self.terminal.run(command)
        self.terminal.run(
            f"cd {remote_path}"
        )
        self.monitor.restart()
    # Disconnect Button Pressed
    def disconnect_host_clicked(self, button):
        print("Disconnecting ...")
        self.terminal.run("exit")
        self.connected_host = None
        self.ssh_connected = False
        self.terminal.ssh_connected = False
        self.update_build_host_ui()
        self.terminal.remote_cfg = None
        self.terminal.remote_user = None
        self.terminal.remote_host = None
        self.terminal.remote_port = 22
        self.terminal.remote_path = None
        self.monitor.restart()
        self.rom_entry.set_text(self.local_rom_path)
        self.rom_entry.set_editable(True)
        self.browse_btn.show()
        self.connection_status.set_markup(
            '<span foreground="red"><b>● Disconnected</b></span>'
        )
    def update_build_host_ui(self):
        if not self.has_remote_host:
            self.connection_status.hide()
            return
        host = self.build_host.get_active_text()
        if host == "Local":
            self.connection_status.hide()
            self.connect_btn.hide()
            self.disconnect_btn.hide()
            return
        self.connection_status.show()
        if self.terminal.ssh_connected:
             self.connection_status.set_markup(
                '<span foreground="green"><b>● Connected</b></span>'
             )
             self.connect_btn.hide()
             self.disconnect_btn.show()
        else:
             self.connection_status.set_markup(
                '<span foreground="red"><b>● Disconnected</b></span>'
             )
             self.connect_btn.show()
             self.disconnect_btn.hide()
    #
    # ROM SELECTION BROWSE BUTTON
    #
    def choose_rom(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Select ROM Folder",
            parent=self,
            action=Gtk.FileChooserAction.SELECT_FOLDER
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN,
            Gtk.ResponseType.OK
        )
        if dialog.run() == Gtk.ResponseType.OK:
           self.rom_entry.set_text(
                dialog.get_filename()
            )
        dialog.destroy()
    #
    # REFRESH PROJECT PROFILES
    #
    def refresh_project_profiles(self):
        #
        # Remove old dropdown if currently displayed.
        #
        if hasattr(
            self,
            "profile_combo"
        ):
            parent = (
                self.profile_combo.get_parent()
            )
            if parent:
                parent.remove(
                    self.profile_combo
                )
        self.profile_files = (
            get_profiles()
        )
        #
        # No profiles.
        #
        if not self.profile_files:
            return
        #
        # Reinsert dropdown before Save Profile.
        #
        self.profile_row.pack_start(
            self.profile_combo,
            False,
            False,
            0
        )
        self.profile_row.reorder_child(
            self.profile_combo,
            1
        )
        self.profile_combo.remove_all()
        #
        # Placeholder
        #
        self.profile_combo.append_text(
            "Select Saved Profile..."
        )
        #
        # Populate profiles.
        #
        for profile in self.profile_files:
            self.profile_combo.append_text(
                profile["name"]
            )
        #
        # Do not automatically select a profile.
        #
        self.profile_combo.set_active(0)
        self.profile_row.show_all()
    #
    # PROJECT PROFILE SELECTED
    #
    def project_profile_changed(
        self,
        combo
    ):
        if self.loading_profile_list:
            return
        index = combo.get_active()
        #
        # Index 0 is the placeholder.
        #
        if index <= 0:
            return
        #
        # Convert ComboBox index to profile list index.
        #
        profile_index = index - 1
        if profile_index >= len(
            self.profile_files
        ):
            return
        profile = load_profile(
            self.profile_files[profile_index]["path"]
        )
        self.apply_project_profile(
            profile
        )
    #
    # APPLY PROJECT PROFILE
    #
    def apply_project_profile(
        self,
        profile
    ):
        self.applying_profile = True
        #
        # HOST
        #
        for index, row in enumerate(
            self.build_host.get_model()
        ):
            if row[0] == profile.host_name:
                self.build_host.set_active(
                    index
                )
                break
        #
        # DEVICE
        #
        for index, device in enumerate(
            self.devices
        ):
            if device["name"] == profile.device_name:
                self.device_combo.set_active(
                    index
                )
                break
        #
        # ROM PATH
        #
        self.rom_entry.set_text(
            profile.rom_path
        )
        #
        # ANDROID VERSION
        #
        for index, row in enumerate(
            self.android.get_model()
        ):
            if row[0] == profile.android_version:
                self.android.set_active(
                    index
                )
                break
        #
        # BUILD VARIANT
        #
        for index, row in enumerate(
            self.build_variant.get_model()
        ):
            if row[0].lower() == (
                profile.build_variant.lower()
            ):
                self.build_variant.set_active(
                    index
                )
                break
        #
        # GAPPS VARIANT
        #
        for index, row in enumerate(
            self.gapps_variant.get_model()
        ):
            if row[0].lower() == (
                profile.gapps_variant.lower()
            ):
                self.gapps_variant.set_active(
                    index
                )
                break
        #
        # CLEAN VARIANT
        #
        for index, row in enumerate(
            self.clean_variant.get_model()
        ):
            if row[0].lower() == (
                profile.clean_variant.lower()
            ):
                self.clean_variant.set_active(
                    index
                )
                break
        #
        # INSTALLER VARIANT
        #
        self.installer_variant.set_active_id(
            profile.installer_variant
        )
        #
        # BUILD JOBS
        #
        self.jobs.set_value(
            profile.build_jobs
        )
        #
        # CCACHE
        #
        self.ccache.set_active(
            profile.use_ccache
        )
        #
        # CUSTOM APN
        #
        self.custom_apn.set_active(
            profile.custom_apn
        )
        #
        # Load the actual ROM/device project
        # using the existing project loader.
        #
        self.project = (
            self.actions.load_project(
                profile.rom_path,
                profile.device_name
            )
        )
        #
        # Make sure a valid project was loaded.
        #
        if not self.project:
            self.applying_profile = False
            return
        #
        # Apply profile-specific settings
        # to the loaded Project.
        #
        self.project.profile_name = (
            profile.profile_name
        )
        self.project.profile_modified = False
        self.project.host_name = (
            profile.host_name
        )
        self.project.android_version = (
            profile.android_version
        )
        self.project.build_variant = (
            profile.build_variant
        )
        self.project.build_command = (
            profile.build_command
        )
        self.project.gapps_variant = (
            profile.gapps_variant
        )
        self.project.clean_variant = (
            profile.clean_variant
        )
        self.project.installer_variant = (
            profile.installer_variant
        )
        self.project.build_jobs = (
            profile.build_jobs
        )
        self.project.use_ccache = (
            profile.use_ccache
        )
        self.project.custom_apn = (
            profile.custom_apn
        )
        if hasattr(self, "build_command"):
            self.build_command.set_active_id(
                self.project.build_command
            )
        #
        # Update Project Summary.
        #
        self.update_project_summary()
        self.applying_profile = False
    #
    # SAVE PROJECT PROFILE
    #
    def save_project_profile_clicked(
        self,
        button
    ):
        #
        # Create Project from current GUI settings.
        #
        project = Project()
        #
        # BUILD HOST
        #
        project.host_name = (
            self.build_host.get_active_text()
            or "Local"
        )
        #
        # DEVICE
        #
        index = self.device_combo.get_active()
        if index < 0:
            return
        project.device_name = (
            self.devices[index]["name"]
        )
        #
        # ROM PATH
        #
        project.set_rom_path(
            self.rom_entry.get_text().strip()
        )
        #
        # ANDROID VERSION
        #
        project.android_version = (
            self.android.get_active_text()
        )
        #
        # BUILD VARIANT
        #
        project.build_variant = (
            self.build_variant
            .get_active_text()
            .lower()
        )
        #
        # GAPPS VARIANT
        #
        project.gapps_variant = (
            self.gapps_variant
            .get_active_text()
            .lower()
        )
        #
        # CLEAN VARIANT
        #
        project.clean_variant = (
            self.clean_variant
            .get_active_text()
            .lower()
        )
        #
        # INSTALLER VARIANT
        #
        project.installer_variant = (
            self.installer_variant.get_active_id()
        )
        #
        # BUILD JOBS
        #
        project.build_jobs = (
            self.jobs.get_value_as_int()
        )
        #
        # CCACHE
        #
        project.use_ccache = (
            self.ccache.get_active()
        )
        #
        # CUSTOM APN
        #
        project.custom_apn = (
            self.custom_apn.get_active()
        )
        #
        # PROFILE SAVE LOCATION
        #
        profiles_dir = (
            Path(BASE) / "profiles"
        )
        profiles_dir.mkdir(
            parents=True,
            exist_ok=True
        )
        #
        # Determine whether we currently have
        # an existing loaded profile.
        #
        current_profile_name = ""
        if self.project:
            current_profile_name = (
                self.project.profile_name
                or ""
            ).strip()
        current_profile_file = None
        if current_profile_name:
            candidate = (
                profiles_dir
                / f"{current_profile_name}.json"
            )
            if candidate.exists():
                current_profile_file = candidate
        #
        # Existing profile?
        #
        if current_profile_file:
            dialog = Gtk.MessageDialog(
                parent=self,
                flags=0,
                message_type=Gtk.MessageType.QUESTION,
                buttons=Gtk.ButtonsType.YES_NO,
                text=(
                    f'Update "{current_profile_name}"?'
                )
            )
            dialog.format_secondary_text(
                "Yes will overwrite the existing "
                "profile. No will save as a new profile."
            )
            response = dialog.run()
            dialog.destroy()
            #
            # YES - overwrite existing profile.
            #
            if response == Gtk.ResponseType.YES:
                project.profile_name = (
                    current_profile_name
                )
                project.profile_modified = False
                save_profile(
                    project,
                    current_profile_file
                )
                #
                # Update current project state.
                #
                if self.project:
                    self.project.profile_name = (
                        current_profile_name
                    )
                    self.project.profile_modified = (
                        False
                    )
                #
                # Refresh dropdown.
                #
                self.refresh_project_profiles()
                #
                # Select saved profile.
                #
                for index, profile in enumerate(
                    self.profile_files
                ):
                    if profile["path"] == (
                        current_profile_file
                    ):
                        self.profile_combo.set_active(
                            index + 1
                        )
                        break
                self.update_project_summary()
                return
            #
            # NO - continue to Save As below.
            #
        #
        # SAVE AS
        #
        dialog = Gtk.Dialog(
            title="Save Project Profile",
            parent=self,
            flags=0
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL,
            Gtk.ResponseType.CANCEL,
            Gtk.STOCK_SAVE,
            Gtk.ResponseType.OK
        )
        box = dialog.get_content_area()
        label = Gtk.Label(
            label="Profile Name:"
        )
        entry = Gtk.Entry()
        entry.set_size_request(
            ROM_PATH_WIDTH,
            -1
        )
        box.pack_start(
            label,
            False,
            False,
            5
        )
        box.pack_start(
            entry,
            False,
            False,
            5
        )
        #
        # If saving a modified profile as a new
        # profile, pre-fill the existing name.
        #
        if current_profile_name:
            entry.set_text(
                current_profile_name
            )
        dialog.show_all()
        response = dialog.run()
        if response != Gtk.ResponseType.OK:
            dialog.destroy()
            return
        profile_name = (
            entry.get_text().strip()
        )
        if not profile_name:
            dialog.destroy()
            return
        #
        # Prevent path traversal or directory
        # creation through the profile name.
        #
        profile_name = (
            profile_name
            .replace(
                "/",
                "_"
            )
            .replace(
                "\\",
                "_"
            )
        )
        project.profile_name = (
            profile_name
        )
        project.profile_modified = False
        filename = (
            profiles_dir
            / f"{profile_name}.json"
        )
        #
        # Save the profile.
        #
        save_profile(
            project,
            filename
        )
        dialog.destroy()
        #
        # Update current project profile state.
        #
        if self.project:
            self.project.profile_name = (
                profile_name
            )
            self.project.profile_modified = (
                False
            )
        #
        # Refresh Profile Dropdown.
        #
        self.refresh_project_profiles()
        #
        # Select Newly Saved Profile.
        #
        for index, profile in enumerate(
            self.profile_files
        ):
            if profile["path"] == filename:
                self.profile_combo.set_active(
                    index + 1
                )
                break
        self.update_project_summary()
    #
    # Load Project
    #
    def load_project_clicked(
        self,
        widget
    ):
        index = self.device_combo.get_active()
        if index < 0:
            return
        device = self.devices[index]["name"]
        rom_path = (
            self.rom_entry
            .get_text()
            .strip()
        )
        build_variant = (
            self.build_variant
            .get_active_text()
            .lower()
        )
        self.project = (
            self.actions.load_project(
                rom_path,
                device
            )
        )
        #
        # BUILD HOST
        #
        self.project.host_name = (
            self.build_host.get_active_text()
            or "Local"
        )
        #
        # MANUALLY LOADED PROJECT
        #
        self.project.profile_name = ""
        self.project.profile_modified = False
        gapps_variant = (
            self.gapps_variant
            .get_active_text()
            .lower()
        )       
        clean_variant = (
            self.clean_variant
            .get_active_text()
            .lower()
        )  
        self.project.installer_variant = (
            self.installer_variant.get_active_id()
        )
        self.project.build_variant = build_variant
        self.project.build_command = (
            self.build_command.get_active_id()
            or ""
        )
        self.project.gapps_variant = gapps_variant
        self.project.clean_variant = clean_variant
        self.project.build_jobs = (
            self.jobs.get_value_as_int()
        )
        self.project.use_ccache = (
            self.ccache.get_active()
        )
        self.project.custom_apn = (
            self.custom_apn.get_active()
        )
        self.update_project_summary()
    #
    # MARK PROJECT PROFILE MODIFIED
    #
    def mark_profile_modified(
        self,
        *args
    ):
        #
        # Nothing loaded yet.
        #
        if not self.project:
            return
        #
        # Don't mark the profile modified while
        # a profile is actively being applied.
        #
        if getattr(
            self,
            "applying_profile",
            False
        ):
            return
        #
        # Only profiles that have actually been
        # loaded can become modified.
        #
        if not self.project.profile_name:
            return
        self.project.profile_modified = True
        #
        # The current GUI settings no longer exactly
        # match the saved profile.
        #
        if hasattr(
            self,
            "profile_combo"
        ):
            if self.profile_combo.get_active() != 0:
                self.profile_combo.set_active(
                    0
                )
        self.update_project_summary()
    #
    # Project Summary Info
    #
    def update_project_summary(self):
        buffer = self.info.get_buffer()
        buffer.set_text(
            build_project_summary(
                self.project
            )
        )
    #
    # TAB--> 2- SYNC & SETUP DEVICE
    #
    def sync_tab(self):
        page = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=10
        )
        page.set_border_width(10)
        page.set_halign(Gtk.Align.CENTER)
        sync_btn = Gtk.Button(
            label="Sync & Setup Device"
        )
        set_button_width(
            sync_btn,
            SYNC_SETUP_BUTTON_WIDTH
        )
        sync_btn.connect(
            "clicked",
            self.sync_clicked
        )
        sync_btn.set_margin_top(
            SYNC_SETUP_TOP_SPACING
        )
        page.pack_start(
            sync_btn,
            False,
            False,
            0
        )
        gapps_btn = Gtk.Button(
            label="Sync Gapps Package"
        )
        set_button_width(
            gapps_btn,
            SYNC_GAPPS_BUTTON_WIDTH
        )
        gapps_btn.connect(
            "clicked",
            self.gapps_sync_clicked
        )
        page.pack_start(
            gapps_btn,
            False,
            False,
            0
        )
        self.notebook.append_page(
            page,
            Gtk.Label(label="2- Sync & Setup Device Source")
        )
    #
    # SYNC & SETUP BUTTON PRESSED
    #
    def sync_clicked(self, button):
        self.actions.sync(
            self.project
        )
    #
    # MINDTHEGAPPS SYNC BUTTON PRESSED
    #
    def gapps_sync_clicked(self, button):
        self.actions.gapps_sync(
            self.project
        )
    #
    # TAB--> 3- BUILD
    #
    def build_tab(self):
        page = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=10
        )
        page.set_border_width(10)
        #
        # KNOWN ROM BUILD COMMANDS
        #
        build_command_box = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        build_command_label = Gtk.Label(
            label="Known Rom Build Commands:  "
        )
        build_command_box.pack_start(
            build_command_label,
            False,
            False,
            0
        )
        build_command_box.set_halign(
            Gtk.Align.CENTER
        )
        build_command_box.set_margin_top(
            BUILD_COMMANDS_TOP_BOTTOM_SPACING
        )
        self.build_command = Gtk.ComboBoxText()
        self.build_command.set_size_request(
            BUILD_COMMANDS_WIDTH,
            -1
        )
        self.build_command.set_halign(
            Gtk.Align.START
        )
        build_commands_path = Path(
            BASE
        ) / "configs" / "rom_build_commands.conf"
        if build_commands_path.is_file():
            with open(
                build_commands_path,
                "r",
                encoding="utf-8"
            ) as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    if "-->" in line:
                        display = line
                        command = ""
                    else:
                        display = line
                        command = line.strip().lower()
                    self.build_command.append(
                        command,
                        display
                    )
        if self.build_command.get_model() is not None:
            self.build_command.set_active(0)
        build_command_box.pack_start(
            self.build_command,
            False,
            False,
            0
        )
        page.pack_start(
            build_command_box,
            False,
            False,
            0
        )
        build_btn = Gtk.Button(
            label="Build ROM"
        )
        set_button_width(
            build_btn,
            BUILD_ROM_BUTTON_WIDTH
        )
        build_btn.connect(
            "clicked",
            self.build_clicked
        )
        build_btn.set_margin_top(
            BUILD_COMMANDS_TOP_BOTTOM_SPACING
        )
        page.pack_start(
            build_btn,
            False,
            False,
            0
        )
        self.notebook.append_page(
            page,
            Gtk.Label(label="3- Build")
        )
    #
    # BUILD ROM BUTTON PRESSED
    #
    def build_clicked(self, button):
        self.actions.build(
            self.project,
            self.android.get_active_text(),
            self.jobs.get_value_as_int(),
            self.ccache.get_active()
        )
    #
    # TAB--> TERMINAL
    #
    def terminal_tab(self):
        self.term = Vte.Terminal()
        self.terminal = TerminalManager(
            self.term
        )

        # Explicit terminal clipboard handling.
        # Ctrl+C remains available to the shell for SIGINT; therefore the
        # conventional terminal copy shortcut is Ctrl+Shift+C.
        self.term.connect(
            "key-press-event",
            self.terminal_key_press
        )
        self.term.connect(
            "button-press-event",
            self.terminal_button_press
        )
        self.term.spawn_async(
            Vte.PtyFlags.DEFAULT,
            None,
            ["/bin/bash"],
            [],
            GLib.SpawnFlags.DEFAULT,
            None,
            None,
            -1,
            None,
            None,
            None
        )
        self.notebook.append_page(
            self.term,
            Gtk.Label(label="Terminal")
        )
    #
    # TERMINAL CLIPBOARD / CONTEXT MENU
    #
    def terminal_key_press(self, terminal, event):
        key = event.keyval
        state = event.state

        if (
            state & Gdk.ModifierType.CONTROL_MASK
            and state & Gdk.ModifierType.SHIFT_MASK
        ):
            if key in (Gdk.KEY_c, Gdk.KEY_C):
                terminal.copy_clipboard()
                return True

            if key in (Gdk.KEY_v, Gdk.KEY_V):
                terminal.paste_clipboard()
                return True

        return False

    def terminal_button_press(self, terminal, event):
        if event.button != 3:
            return False

        menu = Gtk.Menu()

        copy_item = Gtk.MenuItem(label="Copy")
        copy_item.set_sensitive(terminal.get_has_selection())
        copy_item.connect(
            "activate",
            lambda item: terminal.copy_clipboard()
        )
        menu.append(copy_item)

        paste_item = Gtk.MenuItem(label="Paste")
        paste_item.connect(
            "activate",
            lambda item: terminal.paste_clipboard()
        )
        menu.append(paste_item)

        menu.append(Gtk.SeparatorMenuItem())

        select_all_item = Gtk.MenuItem(label="Select All")
        select_all_item.connect(
            "activate",
            lambda item: terminal.select_all()
        )
        menu.append(select_all_item)

        menu.show_all()
        menu.popup_at_pointer(event)

        return True

    #
    # TAB--> RESOURCE MONITOR
    #
    def resource_tab(self):
        self.resource_term = Vte.Terminal()
        self.resource_term.set_scroll_on_output(True)
        self.monitor.attach(self.resource_term)
        self.notebook.append_page(
            self.resource_term,
            Gtk.Label(label="Resource Monitor")
        )
        GLib.idle_add(
            self.start_resource_monitor
        )
    #
    # RESOURCE MONITOR START
    #
    def start_resource_monitor(self):
        self.monitor.start()
        return False
    #
    # TAB--> TOOLS
    #
    def tools_tab(self):
        box = Gtk.Box(
            orientation=Gtk.Orientation.VERTICAL,
            spacing=10
        )
        box.set_halign(Gtk.Align.CENTER)
        box.set_border_width(10)
        #
        # Splash Screen Label + Dropdown
        #
        splash_row = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        splash_row.set_halign(Gtk.Align.CENTER)
        splash_row.set_margin_top(
            SPLASH_TOP_BOTTOM_SPACING
        )
        splash_label = Gtk.Label(
            label="App Splash Screen:"
        )
        splash_row.pack_start(
            splash_label,
            False,
            False,
            0
        )
        #
        # Splash Dropdown
        #
        self.splash_combo = Gtk.ComboBoxText()
        self.splash_combo.set_size_request(
            SPLASH_DROPDOWN_WIDTH,
            -1
        )
        self.splash_combo.set_halign(
            Gtk.Align.START
        )
        #
        # Populate dropdown
        #
        #
        displays = get_display_names()
        for name in displays:
            self.splash_combo.append_text(name)
        #
        # Select currently configured splash
        #
        current = filename_to_display(
            load_selected_splash()
        )
        displays = get_display_names()
        if current in displays:
            self.splash_combo.set_active(
                displays.index(current)
            )
        else:
            self.splash_combo.set_active(0)
        #
        # Save immediately when changed
        #
        self.splash_combo.connect(
            "changed",
            self.splash_changed
        )
        splash_row.pack_start(
            self.splash_combo,
            False,
            False,
            0
        )
        box.pack_start(
            splash_row,
            False,
            False,
            0
        )
        #
        # App Launcher Icon Label + Dropdown
        #
        icon_row = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=10
        )
        icon_row.set_halign(Gtk.Align.CENTER)
        icon_label = Gtk.Label(
            label="App Desktop Launcher Icon:"
        )
        icon_row.pack_start(
            icon_label,
            False,
            False,
            0
        )
        #
        # App Launcher Icon Dropdown
        #
        self.icon_combo = (
            Gtk.ComboBoxText()
        )
        self.icon_combo.set_size_request(
            ICON_DROPDOWN_WIDTH,
            -1
        )
        self.icon_combo.set_halign(
            Gtk.Align.START
        )
        #
        # Populate icon dropdown
        #
        icon_displays = (
            get_icon_display_names()
        )
        for name in icon_displays:
            self.icon_combo.append_text(
                name
            )
        #
        # Select currently configured icon
        #
        current_icon = (
            icon_filename_to_display(
                load_selected_icon()
            )
        )
        if (
            current_icon
            in icon_displays
        ):
            self.icon_combo.set_active(
                icon_displays.index(
                    current_icon
                )
            )
        else:
            self.icon_combo.set_active(
                0
            )
        #
        # Save immediately when changed
        #
        self.icon_combo.connect(
            "changed",
            self.icon_changed
        )

        icon_row.pack_start(
            self.icon_combo,
            False,
            False,
            0
        )
        box.pack_start(
            icon_row,
            False,
            False,
            0
        )
        #
        # Desktop Launcher Button
        #
        self.launcher_button = Gtk.Button(
            label="Create App Desktop Launcher"
        )
        set_button_width(
            self.launcher_button,
            LAUNCHER_BUTTON_WIDTH
        )
        self.launcher_button.connect(
            "clicked",
            self.create_launcher_clicked
        )
        self.launcher_button.set_margin_top(
            SPLASH_TOP_BOTTOM_SPACING
        )
        box.pack_start(
            self.launcher_button,
            False,
            False,
            0
        )
        #
        # Clean ROM Button
        #
        self.clean_button = Gtk.Button(
            label="Clean ROM"
        )
        set_button_width(
            self.clean_button,
            CLEAN_BUTTON_WIDTH
        )
        self.clean_button.connect(
            "clicked",
            self.clean_clicked
        )
        box.pack_start(
            self.clean_button,
            False,
            False,
            0
        )
        self.notebook.append_page(
            box,
            Gtk.Label(label="Tools")
        )
    #
    # SPLASH CHANGED
    #
    def splash_changed(self, widget):
        display = widget.get_active_text()
        if display is None:
            return
        filename = display_to_filename(display)
        save_selected_splash(filename)
        print(f"App Splash Screen Changed To: {filename}")
    #
    # ICON CHANGED
    #
    def icon_changed(
        self,
        widget
    ):
        display = (
            widget.get_active_text()
        )
        if display is None:
            return
        filename = (
            icon_display_to_filename(
                display
            )
        )
        save_selected_icon(
            filename
        )
        print(
            f"App Desktop Launcher Icon Changed To: {filename}"
        )
    #
    # CREATE DESKTOP LAUNCHER BUTTON PRESSED
    #
    def create_launcher_clicked(
        self,
        button
    ):
        try:
            launcher_path = (
                create_desktop_launcher()
            )
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="App Dekstop Launcher Created"
            )
            dialog.format_secondary_text(
                f"Launcher created at:\n"
                f"{launcher_path}"
            )
            dialog.run()
            dialog.destroy()
        except Exception as error:
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.ERROR,
                buttons=Gtk.ButtonsType.OK,
                text="App Desktop Launcher Creation Failed"
            )
            dialog.format_secondary_text(
                str(error)
            )
            dialog.run()
            dialog.destroy()
    #
    # CLEAN BUTTON PRESSED
    #
    def clean_clicked(self, button):
        self.actions.clean(
            self.project
        )
#
# App Splash & Startup
#
splash = Splash()
splash.show_all()
def step1():
    splash.set_status("Loading Host Configurations ...")
    GLib.timeout_add(1750, step2)
    return False
def step2():
    splash.set_status("Loading Device Configurations ...")
    GLib.timeout_add(1750, step3)
    return False
def step3():
    splash.set_status("Loading ROM Configurations ...")
    GLib.timeout_add(1750, step4)
    return False
def step4():
    splash.set_status("Initializing Resource Monitor ...")
    GLib.timeout_add(1750, step5)
    return False
def step5():
    splash.set_status("Checking For Updates ...")
    GLib.timeout_add(1750, step6)
    return False
def step6():
    splash.set_status("Initializing A-Team Android ROM Builder App ...")
    GLib.timeout_add(1750, start_builder)
    return False
def start_builder():
    global win
    splash.destroy()
    win = ROMBuilder()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    win.update_build_host_ui()
    return False
# Start the sequence
GLib.timeout_add(500, step1)
Gtk.main()
