diff --git a/ui/ProgressWindow.py b/ui/ProgressWindow.py
new file mode 100644
index 0000000..0fa11e7
--- /dev/null
+++ b/ui/ProgressWindow.py
@@ -0,0 +1,658 @@
+import random
+import shutil
+import sys
+import gi
+import os
+import libs.functions as fn
+from ui.MessageWindow import MessageWindow
+from gi.repository import Gtk, Gio, GLib
+
+base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+
+
+class ProgressWindow(Gtk.Window):
+ def __init__(
+ self,
+ title,
+ action,
+ textview,
+ textbuffer,
+ kernel,
+ switch,
+ source,
+ manager_gui,
+ **kwargs,
+ ):
+ super().__init__(**kwargs)
+
+ self.set_title(title=title)
+ self.set_modal(modal=True)
+ self.set_resizable(True)
+ self.set_size_request(700, 250)
+ self.connect("close-request", self.on_close)
+
+ self.textview = textview
+ self.textbuffer = textbuffer
+
+ self.kernel_state_queue = fn.Queue()
+ self.messages_queue = fn.Queue()
+
+ # create temp file to lock the close button
+ self.lockfile = "/tmp/.akm-progress.lock"
+ if os.path.exists(self.lockfile):
+ os.unlink(self.lockfile)
+
+ with open(self.lockfile, "w") as f:
+ f.write("")
+
+ self.kernel = kernel
+ self.timeout_id = None
+ self.errors_found = False
+ self.restore_kernel = None
+
+ self.action = action
+ self.switch = switch
+
+ self.restore = False
+
+ self.source = source
+ self.manager_gui = manager_gui
+
+ self.bootloader = self.manager_gui.bootloader
+ self.bootloader_grub_cfg = self.manager_gui.bootloader_grub_cfg
+
+ vbox_progress = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
+ vbox_progress.set_name("main")
+
+ self.set_child(child=vbox_progress)
+
+ header_bar = Gtk.HeaderBar()
+ self.label_title = Gtk.Label(xalign=0.5, yalign=0.5)
+
+ header_bar.set_show_title_buttons(True)
+
+ self.set_titlebar(header_bar)
+
+ self.label_title.set_markup("%s" % title)
+ header_bar.set_title_widget(self.label_title)
+
+ vbox_icon_settings = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
+ vbox_icon_settings.set_name("vbox_icon_settings")
+
+ lbl_heading = Gtk.Label(xalign=0.5, yalign=0.5)
+ lbl_heading.set_name("label_flowbox_message")
+
+ lbl_padding = Gtk.Label(xalign=0.0, yalign=0.0)
+ lbl_padding.set_text(" ")
+
+ grid_banner_img = Gtk.Grid()
+
+ image_settings = None
+
+ self.local_modules_version = None
+
+ if action == "install":
+ image_settings = Gtk.Image.new_from_file(
+ os.path.join(base_dir, "images/48x48/akm-install.png")
+ )
+ lbl_heading.set_markup(
+ "Installing %s version %s "
+ % (self.kernel.name, self.kernel.version)
+ )
+ if action == "uninstall":
+ image_settings = Gtk.Image.new_from_file(
+ os.path.join(base_dir, "images/48x48/akm-remove.png")
+ )
+ lbl_heading.set_markup(
+ "Removing %s version %s "
+ % (self.kernel.name, self.kernel.version)
+ )
+
+ image_settings.set_halign(Gtk.Align.START)
+ image_settings.set_icon_size(Gtk.IconSize.LARGE)
+
+ hbox_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
+ hbox_header.set_name("vbox_header")
+
+ hbox_header.append(image_settings)
+ hbox_header.append(lbl_heading)
+
+ vbox_progress.append(hbox_header)
+
+ self.spinner = Gtk.Spinner()
+ self.spinner.set_spinning(True)
+
+ image_warning = Gtk.Image.new_from_file(
+ os.path.join(base_dir, "images/48x48/akm-warning.png")
+ )
+
+ image_warning.set_icon_size(Gtk.IconSize.LARGE)
+ image_warning.set_halign(Gtk.Align.START)
+
+ hbox_progress_warning = Gtk.Box(
+ orientation=Gtk.Orientation.HORIZONTAL, spacing=5
+ )
+ hbox_progress_warning.set_name("hbox_warning")
+
+ hbox_progress_warning.append(image_warning)
+
+ self.label_progress_window_desc = Gtk.Label(xalign=0, yalign=0)
+
+ self.label_progress_window_desc.set_markup(
+ f"Do not close this window while a kernel {self.action} activity is in progress\n"
+ f"Progress can be monitored in the log below\n"
+ f"A reboot is recommended when Linux packages have changed"
+ )
+
+ hbox_progress_warning.append(self.label_progress_window_desc)
+
+ self.label_status = Gtk.Label(xalign=0, yalign=0)
+
+ button_close = Gtk.Button.new_with_label("Close")
+ button_close.set_size_request(100, 30)
+ button_close.set_halign(Gtk.Align.END)
+
+ button_close.connect(
+ "clicked",
+ self.on_button_close_response,
+ )
+
+ self.label_spinner_progress = Gtk.Label(xalign=0, yalign=0)
+ if self.action == "install":
+ self.label_spinner_progress.set_markup(
+ "Please wait kernel %s is in progress" % "installation"
+ )
+ elif self.action == "uninstall":
+ self.label_spinner_progress.set_markup(
+ "Please wait kernel %s is in progress" % "removal"
+ )
+
+ self.hbox_spinner = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
+ self.hbox_spinner.append(self.spinner)
+ self.hbox_spinner.append(self.label_spinner_progress)
+
+ vbox_padding = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
+ vbox_padding.set_valign(Gtk.Align.END)
+
+ label_padding = Gtk.Label(xalign=0, yalign=0)
+ label_padding.set_valign(Gtk.Align.END)
+ vbox_padding.append(label_padding)
+
+ hbox_button_close = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
+
+ hbox_button_close.append(button_close)
+ hbox_button_close.set_halign(Gtk.Align.END)
+
+ self.scrolled_window = Gtk.ScrolledWindow()
+ self.scrolled_window.set_propagate_natural_height(True)
+ self.scrolled_window.set_propagate_natural_width(True)
+ self.scrolled_window.set_policy(
+ Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC
+ )
+
+ hbox_notify_revealer = Gtk.Box(
+ orientation=Gtk.Orientation.HORIZONTAL, spacing=20
+ )
+
+ hbox_notify_revealer.set_name("hbox_notify_revealer")
+ hbox_notify_revealer.set_halign(Gtk.Align.CENTER)
+
+ self.notify_revealer = Gtk.Revealer()
+ self.notify_revealer.set_reveal_child(False)
+ self.label_notify_revealer = Gtk.Label(xalign=0, yalign=0)
+ self.label_notify_revealer.set_name("label_notify_revealer")
+
+ self.notify_revealer.set_child(hbox_notify_revealer)
+
+ hbox_notify_revealer.append(self.label_notify_revealer)
+
+ if self.textview.get_buffer() is not None:
+ self.textview = Gtk.TextView()
+ self.textview.set_property("editable", False)
+ self.textview.set_property("monospace", True)
+
+ self.textview.set_vexpand(True)
+ self.textview.set_hexpand(True)
+
+ self.textview.set_buffer(self.textbuffer)
+ self.scrolled_window.set_child(self.textview)
+
+ self.scrolled_window.set_size_request(300, 300)
+
+ vbox_progress.append(hbox_progress_warning)
+ vbox_progress.append(self.notify_revealer)
+ vbox_progress.append(self.scrolled_window)
+ vbox_progress.append(self.hbox_spinner)
+ vbox_progress.append(self.label_status)
+ # vbox_progress.append(vbox_padding)
+ vbox_progress.append(hbox_button_close)
+
+ self.present()
+
+ self.linux_headers = None
+ self.restore_kernel = None
+
+ if (
+ self.source == "official"
+ and action == "install"
+ or action == "uninstall"
+ and self.source == "official"
+ ):
+ fn.logger.info("Official kernel selected")
+ if kernel.name == "linux":
+ self.linux_headers = "linux-headers"
+ if kernel.name == "linux-rt":
+ self.linux_headers = "linux-rt-headers"
+ if kernel.name == "linux-rt-lts":
+ self.linux_headers = "linux-rt-lts-headers"
+ if kernel.name == "linux-hardened":
+ self.linux_headers = "linux-hardened-headers"
+ if kernel.name == "linux-zen":
+ self.linux_headers = "linux-zen-headers"
+ if kernel.name == "linux-lts":
+ self.linux_headers = "linux-lts-headers"
+
+ self.official_kernels = [
+ "%s/packages/l/%s/%s-x86_64%s"
+ % (
+ fn.archlinux_mirror_archive_url,
+ kernel.name,
+ kernel.version,
+ kernel.file_format,
+ ),
+ "%s/packages/l/%s/%s-x86_64%s"
+ % (
+ fn.archlinux_mirror_archive_url,
+ self.linux_headers,
+ kernel.headers,
+ kernel.file_format,
+ ),
+ ]
+ # in the event an install goes wrong, fallback and reinstall previous kernel
+
+ for inst_kernel in fn.get_installed_kernels():
+ if inst_kernel.name == self.kernel.name:
+ self.restore_kernel = inst_kernel
+ break
+
+ if self.restore_kernel:
+ self.local_modules_version = fn.get_kernel_modules_version(
+ self.restore_kernel.name, "local"
+ )
+ fn.logger.info("Restore kernel = %s" % self.restore_kernel.name)
+ fn.logger.info(
+ "Restore kernel version = %s" % self.restore_kernel.version
+ )
+ else:
+ fn.logger.info("No previous %s kernel installed" % self.kernel.name)
+ else:
+ fn.logger.info("Community kernel, no kernel restore available")
+ self.local_modules_version = fn.get_kernel_modules_version(
+ self.kernel.name, "local"
+ )
+
+ if fn.check_pacman_lockfile() is False:
+ th_monitor_messages_queue = fn.threading.Thread(
+ name=fn.thread_monitor_messages,
+ target=fn.monitor_messages_queue,
+ daemon=True,
+ args=(self,),
+ )
+
+ th_monitor_messages_queue.start()
+
+ if fn.is_thread_alive(fn.thread_monitor_messages):
+ self.textbuffer.delete(
+ self.textbuffer.get_start_iter(), self.textbuffer.get_end_iter()
+ )
+
+ if not fn.is_thread_alive(fn.thread_check_kernel_state):
+ th_check_kernel_state = fn.threading.Thread(
+ name=fn.thread_check_kernel_state,
+ target=self.check_kernel_state,
+ daemon=True,
+ )
+ th_check_kernel_state.start()
+
+ if action == "install" and self.source == "community":
+ self.label_notify_revealer.set_text(
+ "Installing from %s" % kernel.repository
+ )
+ self.reveal_notify()
+ event = (
+ "%s [INFO]: Installing kernel from repository %s, kernel = %s-%s\n"
+ % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ self.kernel.repository,
+ self.kernel.name,
+ self.kernel.version,
+ )
+ )
+ self.messages_queue.put(event)
+
+ if not fn.is_thread_alive(fn.thread_install_community_kernel):
+ th_install_ch = fn.threading.Thread(
+ name=fn.thread_install_community_kernel,
+ target=fn.install_community_kernel,
+ args=(self,),
+ daemon=True,
+ )
+
+ th_install_ch.start()
+
+ if action == "install" and self.source == "official":
+ self.label_notify_revealer.set_text("Installing kernel packages ...")
+
+ self.reveal_notify()
+
+ event = "%s [INFO]: Installing kernel = %s | version = %s\n" % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ self.kernel.name,
+ self.kernel.version,
+ )
+ self.messages_queue.put(event)
+
+ if not fn.is_thread_alive(fn.thread_install_archive_kernel):
+ th_install = fn.threading.Thread(
+ name=fn.thread_install_archive_kernel,
+ target=fn.install_archive_kernel,
+ args=(self,),
+ daemon=True,
+ )
+
+ th_install.start()
+
+ if action == "uninstall":
+ if fn.check_pacman_lockfile() is False:
+ self.label_notify_revealer.set_text("Removing kernel packages ...")
+ self.reveal_notify()
+
+ event = "%s [INFO]: Uninstalling kernel %s %s\n" % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ self.kernel.name,
+ self.kernel.version,
+ )
+ self.messages_queue.put(event)
+
+ if not fn.is_thread_alive(fn.thread_uninstall_kernel):
+ th_uninstall_kernel = fn.threading.Thread(
+ name=fn.thread_uninstall_kernel,
+ target=self.uninstall_kernel,
+ daemon=True,
+ )
+
+ th_uninstall_kernel.start()
+ else:
+ self.label_notify_revealer.set_text(
+ "Pacman lockfile found cannot continue ..."
+ )
+
+ self.reveal_notify()
+
+ fn.logger.error(
+ "Pacman lockfile found, is another pacman process running ?"
+ )
+
+ def timeout(self):
+ self.hide_notify()
+
+ def hide_notify(self):
+ self.notify_revealer.set_reveal_child(False)
+ if self.timeout_id is not None:
+ GLib.source_remove(self.timeout_id)
+ self.timeout_id = None
+
+ def reveal_notify(self):
+ # reveal = self.notify_revealer.get_reveal_child()
+ self.notify_revealer.set_reveal_child(True)
+ self.timeout_id = GLib.timeout_add(3000, self.timeout)
+
+ def on_button_close_response(self, widget):
+ if fn.check_pacman_process(self) or os.path.exists(self.lockfile):
+ mw = MessageWindow(
+ title="Pacman process running",
+ message="Pacman is busy processing a transaction .. please wait",
+ transient_for=self,
+ detailed_message=False,
+ )
+
+ mw.present()
+ else:
+ self.destroy()
+
+ def on_close(self, data):
+ if fn.check_pacman_process(self) or os.path.exists(self.lockfile):
+ mw = MessageWindow(
+ title="Pacman process running",
+ message="Pacman is busy processing a transaction .. please wait",
+ transient_for=self,
+ detailed_message=False,
+ )
+
+ mw.present()
+
+ return True
+ return False
+
+ def check_kernel_state(self):
+ returncode = None
+ action = None
+ while True:
+ items = self.kernel_state_queue.get()
+
+ if items is not None:
+ returncode, action = items
+
+ try:
+ if returncode == 0:
+ self.errors_found = False
+
+ fn.logger.info("Kernel %s completed" % action)
+
+ self.label_status.set_markup(
+ "Kernel %s completed"
+ % self.action
+ )
+ self.label_title.set_markup("Kernel %s completed" % action)
+
+ if fn.kernel_initrd(self) == 1:
+ self.errors_found = True
+ self.kernel_fail(action)
+ else:
+
+ fn.update_bootloader(self)
+
+ self.label_notify_revealer.set_text(
+ "Kernel %s completed" % action
+ )
+ self.reveal_notify()
+
+ fn.logger.info("Kernel %s completed" % action)
+
+ self.spinner.set_spinning(False)
+ self.hbox_spinner.hide()
+
+ self.label_status.set_markup(
+ "Kernel %s completed"
+ % self.action
+ )
+ self.label_title.set_markup(
+ "Kernel %s completed" % action
+ )
+
+ break
+
+ elif returncode == 1:
+ self.errors_found = True
+ self.kernel_fail(action)
+ else:
+ self.restore = None
+
+ fn.kernel_initrd(self)
+ fn.update_bootloader(self)
+
+ self.spinner.set_spinning(False)
+ self.hbox_spinner.hide()
+
+ if self.errors_found is True:
+
+ self.label_status.set_markup(
+ f"Kernel %s failed - see logs above\n"
+ % action
+ )
+
+ break
+ #
+ # else:
+ # break
+
+ except Exception as e:
+ fn.logger.error("Exception in check_kernel_state(): %s" % e)
+ finally:
+ self.kernel_state_queue.task_done()
+
+ if os.path.exists(self.lockfile):
+ os.unlink(self.lockfile)
+
+ self.update_installed_list()
+ self.update_official_list()
+
+ if len(self.manager_gui.community_kernels) > 0:
+ self.update_community_list()
+
+ while self.manager_gui.default_context.pending():
+ self.manager_gui.default_context.iteration(True)
+ fn.time.sleep(0.3)
+
+ self.spinner.set_spinning(False)
+ self.hbox_spinner.hide()
+
+ if self.errors_found is True:
+ event = (
+ "%s [ERROR]: Problems encountered with the last transaction, see logs"
+ % (fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),)
+ )
+ self.messages_queue.put(event)
+
+ else:
+ event = "%s [INFO]: A reboot is recommended" % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ )
+ self.messages_queue.put(event)
+
+ if os.path.exists("/usr/lib/modules/build"):
+ shutil.rmtree("/usr/lib/modules/build", ignore_errors=True)
+
+ break
+
+ def kernel_fail(self, action):
+ self.errors_found = True
+ self.label_notify_revealer.set_text("Kernel %s failed" % action)
+ self.reveal_notify()
+
+ fn.logger.error("Kernel %s failed" % action)
+ self.label_title.set_markup("Kernel %s failed" % action)
+
+ self.label_status.set_markup(
+ "Kernel %s failed - see logs above"
+ % action
+ )
+ # self.action = "uninstall"
+ fn.logger.info(
+ "Installation failed, attempting removal of previous Linux package changes"
+ )
+ event = "%s [INFO]: Reverting package changes made\n" % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ )
+
+ self.label_spinner_progress.set_markup(
+ "Please wait reverting package changes"
+ )
+
+ self.messages_queue.put(event)
+
+ self.label_title.set_markup("Kernel install failed")
+ self.action = "uninstall"
+ fn.uninstall(self)
+
+ if self.restore_kernel is not None and self.source == "official":
+ self.restore = True
+
+ self.label_spinner_progress.set_markup(
+ "Please wait restoring kernel %s" % self.restore_kernel.version
+ )
+
+ fn.logger.info(
+ "Restoring previously installed kernel %s" % self.restore_kernel.version
+ )
+
+ event = "%s [INFO]: Restoring previously installed kernel %s\n" % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ self.restore_kernel.version,
+ )
+
+ self.messages_queue.put(event)
+
+ self.official_kernels = [
+ "%s/packages/l/%s/%s-%s-x86_64%s"
+ % (
+ fn.archlinux_mirror_archive_url,
+ self.restore_kernel.name,
+ self.restore_kernel.name,
+ self.restore_kernel.version,
+ ".pkg.tar.zst",
+ ),
+ "%s/packages/l/%s/%s-%s-x86_64%s"
+ % (
+ fn.archlinux_mirror_archive_url,
+ self.linux_headers,
+ self.linux_headers,
+ self.restore_kernel.version,
+ ".pkg.tar.zst",
+ ),
+ ]
+ self.errors_found = False
+ self.action = "install"
+ fn.install_archive_kernel(self)
+
+ self.label_title.set_markup("Kernel restored due to failure")
+ # elif self.source == "community":
+ # GLib.idle_add(
+ # fn.show_mw,
+ # self,
+ # "System changes",
+ # f"Kernel {self.action} failed\n"
+ # f"There have been errors, please review the logs\n",
+ # "images/48x48/akm-warning.png",
+ # priority=GLib.PRIORITY_DEFAULT,
+ # )
+
+ def update_installed_list(self):
+ self.manager_gui.installed_kernels = fn.get_installed_kernels()
+ GLib.idle_add(
+ self.manager_gui.kernel_stack.add_installed_kernels_to_stack, True
+ )
+
+ def update_official_list(self):
+ self.manager_gui.installed_kernels = fn.get_installed_kernels()
+ GLib.idle_add(
+ self.manager_gui.kernel_stack.add_official_kernels_to_stack,
+ True,
+ )
+
+ def update_community_list(self):
+ self.manager_gui.installed_kernels = fn.get_installed_kernels()
+ GLib.idle_add(
+ self.manager_gui.kernel_stack.add_community_kernels_to_stack,
+ True,
+ )
+
+ def uninstall_kernel(self):
+ event = "%s [INFO]: Uninstalling kernel %s\n" % (
+ fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
+ self.kernel.version,
+ )
+
+ self.messages_queue.put(event)
+
+ fn.uninstall(self)