From 621bc393e6e7aabd2c34ce03b333bd579a7d0fbf Mon Sep 17 00:00:00 2001
From: Abhiraj Roy <157954129+iconized@users.noreply.github.com>
Date: Fri, 31 May 2024 18:24:07 +0530
Subject: [PATCH] perf(extend): extends the functionalities
---
functions.py | 0
push.sh | 0
snigdhaos-kernel-manager.py | 114 +++++++-
ui/ManagerGUI.py | 516 ++++++++++++++++++++++++++++++++++++
4 files changed, 629 insertions(+), 1 deletion(-)
create mode 100644 functions.py
mode change 100644 => 100755 push.sh
create mode 100644 ui/ManagerGUI.py
diff --git a/functions.py b/functions.py
new file mode 100644
index 0000000..e69de29
diff --git a/push.sh b/push.sh
old mode 100644
new mode 100755
diff --git a/snigdhaos-kernel-manager.py b/snigdhaos-kernel-manager.py
index 84c350e..808f3d7 100644
--- a/snigdhaos-kernel-manager.py
+++ b/snigdhaos-kernel-manager.py
@@ -1,3 +1,115 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
+
+import os
+import gi
+import signal
+import functions as fn
+
+from ui.ManagerGUI import ManagerGUI
+
+gi.require_version("Gtk", "4.0")
+
+from gi.repository import Gtk, Gio, GLib, Gdk
+
+base_dir = fn.os.path.dirname(fn.os.path.realpath(__file__))
+app_name = "Snigdha OS Kernel Manager"
+app_version = "${app_version}"
+app_name_dir = "snigdhaos-kernel-manager"
+app_id = "org.snigdhaos.kernelmanager"
+lock_file = "/tmp/sokm.lock"
+pid_file = "/tmp/sokm.pid"
+
+
+class Main(Gtk.Application):
+ def __init__(self):
+ super().__init__(application_id=app_id, flags=Gio.ApplicationFlags.FLAGS_NONE)
+
+ def do_activate(self):
+ default_context = GLib.MainContext.default()
+
+ win = self.props.active_window
+
+ if not win:
+ win = ManagerGUI(
+ application=self,
+ app_name=app_name,
+ default_context=default_context,
+ app_version=app_version,
+ )
+
+ display = Gtk.Widget.get_display(win)
+
+ # sourced from /usr/share/icons/hicolor/scalable/apps
+ win.set_icon_name("archlinux-kernel-manager-tux")
+ provider = Gtk.CssProvider.new()
+ css_file = Gio.file_new_for_path(base_dir + "/snigdhaos-kernel-manager.css")
+ provider.load_from_file(css_file)
+ Gtk.StyleContext.add_provider_for_display(
+ display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
+ )
+
+ win.present()
+
+ def do_startup(self):
+ Gtk.Application.do_startup(self)
+
+ def do_shutdown(self):
+ Gtk.Application.do_shutdown(self)
+ if os.path.exists(lock_file):
+ os.remove(lock_file)
+ if os.path.exists(pid_file):
+ os.remove(pid_file)
+
+
+def signal_handler(sig, frame):
+ Gtk.main_quit(0)
+
+
+# These should be kept as it ensures that multiple installation instances can't be run concurrently.
+if __name__ == "__main__":
+ try:
+ # signal.signal(signal.SIGINT, signal_handler)
+
+ if not fn.os.path.isfile(lock_file):
+ with open(pid_file, "w") as f:
+ f.write(str(fn.os.getpid()))
+
+ # splash = SplashScreen()
+
+ app = Main()
+ app.run(None)
+ else:
+ md = Gtk.MessageDialog(
+ parent=Main(),
+ flags=0,
+ message_type=Gtk.MessageType.INFO,
+ buttons=Gtk.ButtonsType.YES_NO,
+ text="%s Lock File Found" % app_name,
+ )
+ md.format_secondary_markup(
+ "A %s lock file has been found. This indicates there is already an instance of %s running.\n\
+ Click 'Yes' to remove the lock file and try running again"
+ % (lock_file, app_name)
+ ) # noqa
+
+ result = md.run()
+ md.destroy()
+
+ if result in (Gtk.ResponseType.OK, Gtk.ResponseType.YES):
+ pid = ""
+ if fn.os.path.exists(pid_file):
+ with open(pid_file, "r") as f:
+ line = f.read()
+ pid = line.rstrip().lstrip()
+
+ else:
+ # in the rare event that the lock file is present, but the pid isn't
+ fn.os.unlink(lock_file)
+ fn.sys.exit(1)
+ else:
+ fn.sys.exit(1)
+ except Exception as e:
+ # fn.logger.error("Exception in __main__: %s" % e)
+ print("Exception in __main__: %s" % e)
\ No newline at end of file
diff --git a/ui/ManagerGUI.py b/ui/ManagerGUI.py
new file mode 100644
index 0000000..94bc9d0
--- /dev/null
+++ b/ui/ManagerGUI.py
@@ -0,0 +1,516 @@
+import gi
+import os
+from ui.MenuButton import MenuButton
+from ui.Stack import Stack
+from ui.KernelStack import KernelStack
+from ui.FlowBox import FlowBox, FlowBoxInstalled
+from ui.AboutDialog import AboutDialog
+from ui.SplashScreen import SplashScreen
+from ui.MessageWindow import MessageWindow
+from ui.SettingsWindow import SettingsWindow
+import libs.functions as fn
+
+gi.require_version("Gtk", "4.0")
+from gi.repository import Gtk, Gio, Gdk, GLib
+
+
+base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+
+
+class ManagerGUI(Gtk.ApplicationWindow):
+ def __init__(self, app_name, default_context, app_version, **kwargs):
+ super().__init__(**kwargs)
+
+ self.default_context = default_context
+
+ self.app_version = app_version
+
+ if self.app_version == "${app_version}":
+ self.app_version = "dev"
+
+ fn.logger.info("Version = %s" % self.app_version)
+
+ self.set_title(app_name)
+ self.set_resizable(True)
+ self.set_default_size(950, 650)
+
+ # get list of kernels from the arch archive website, aur, then cache
+ self.official_kernels = []
+ self.community_kernels = []
+
+ # splashscreen queue for threading
+ self.queue_load_progress = fn.Queue()
+
+ # official kernels queue for threading
+ self.queue_kernels = fn.Queue()
+
+ # community kernels queue for threading
+ self.queue_community_kernels = fn.Queue()
+
+ 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)
+
+ self.splash_screen = SplashScreen(app_name)
+
+ try:
+ fn.Thread(
+ target=self.wait_for_gui_load,
+ daemon=True,
+ ).start()
+ except Exception as e:
+ fn.logger.error(e)
+
+ while self.default_context.pending():
+ fn.time.sleep(0.1)
+ self.default_context.iteration(True)
+
+ self.bootloader = None
+ self.bootloader_grub_cfg = None
+
+ # self.bootloader = fn.get_boot_loader()
+
+ config_data = fn.setup_config(self)
+
+ if "bootloader" in config_data.keys():
+ if config_data["bootloader"]["name"] is not None:
+ self.bootloader = config_data["bootloader"]["name"].lower()
+
+ if self.bootloader == "grub":
+ if config_data["bootloader"]["grub_config"] is not None:
+ self.bootloader_grub_cfg = config_data["bootloader"][
+ "grub_config"
+ ]
+ elif self.bootloader != "systemd-boot" or self.bootloader != "grub":
+ fn.logger.warning(
+ "Invalid bootloader config found it should only be systemd-boot or grub"
+ )
+
+ fn.logger.warning("Using bootctl to determine current bootloader")
+ self.bootloader = None
+
+ if self.bootloader is not None or self.bootloader_grub_cfg is not None:
+ fn.logger.info("User provided bootloader options read from config file")
+ fn.logger.info("User bootloader option = %s " % self.bootloader)
+ if self.bootloader_grub_cfg is not None:
+ fn.logger.info(
+ "User bootloader Grub config = %s " % self.bootloader_grub_cfg
+ )
+ else:
+ # no config setting found for bootloader use default method
+ self.bootloader = fn.get_boot_loader()
+ if self.bootloader == "grub":
+ self.bootloader_grub_cfg = "/boot/grub/grub.cfg"
+
+ if self.bootloader is not None:
+ fn.create_cache_dir()
+ fn.create_log_dir()
+ fn.get_pacman_repos()
+
+ self.stack = Stack(transition_type="OVER_DOWN")
+ self.kernel_stack = KernelStack(self)
+
+ header_bar = Gtk.HeaderBar()
+
+ label_title = Gtk.Label(xalign=0.5, yalign=0.5)
+ label_title.set_markup("%s" % app_name)
+
+ header_bar.set_title_widget(label_title)
+ header_bar.set_show_title_buttons(True)
+
+ self.set_titlebar(header_bar)
+
+ menu_outerbox = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
+ header_bar.pack_end(menu_outerbox)
+
+ menu_outerbox.show()
+
+ menubutton = MenuButton()
+
+ menu_outerbox.append(menubutton)
+
+ menubutton.show()
+
+ action_about = Gio.SimpleAction(name="about")
+ action_about.connect("activate", self.on_about)
+
+ action_settings = Gio.SimpleAction(name="settings")
+ action_settings.connect("activate", self.on_settings, fn)
+
+ self.add_action(action_settings)
+
+ self.add_action(action_about)
+
+ action_refresh = Gio.SimpleAction(name="refresh")
+ action_refresh.connect("activate", self.on_refresh)
+
+ self.add_action(action_refresh)
+
+ action_quit = Gio.SimpleAction(name="quit")
+ action_quit.connect("activate", self.on_quit)
+
+ self.add_action(action_quit)
+
+ # add shortcut keys
+
+ event_controller_key = Gtk.EventControllerKey.new()
+ event_controller_key.connect("key-pressed", self.key_pressed)
+
+ self.add_controller(event_controller_key)
+
+ # overlay = Gtk.Overlay()
+ # self.set_child(child=overlay)
+
+ self.vbox = Gtk.Box.new(orientation=Gtk.Orientation.VERTICAL, spacing=10)
+ self.vbox.set_name("main")
+
+ self.set_child(child=self.vbox)
+
+ self.vbox.append(self.notify_revealer)
+
+ self.installed_kernels = fn.get_installed_kernels()
+
+ self.active_kernel = fn.get_active_kernel()
+
+ fn.logger.info("Installed kernels = %s" % len(self.installed_kernels))
+
+ self.refresh_cache = False
+
+ self.refresh_cache = fn.get_latest_kernel_updates(self)
+
+ self.start_get_kernels_threads()
+
+ self.load_kernels_gui()
+
+ # validate bootloader
+ if self.bootloader_grub_cfg and not os.path.exists(
+ self.bootloader_grub_cfg
+ ):
+ mw = MessageWindow(
+ title="Grub config file not found",
+ message=f"The specified Grub config file: {self.bootloader_grub_cfg} does not exist\n"
+ f"This will cause an issue when updating the bootloader\n"
+ f"Update the configuration file/use the Advanced Settings to change this\n",
+ image_path="images/48x48/akm-error.png",
+ detailed_message=False,
+ transient_for=self,
+ )
+
+ mw.present()
+ if self.bootloader == "systemd-boot":
+ if not os.path.exists(
+ "/sys/firmware/efi/fw_platform_size"
+ ) or not os.path.exists("/sys/firmware/efi/efivars"):
+ mw = MessageWindow(
+ title="Legacy boot detected",
+ message=f"Cannot select systemd-boot, UEFI boot mode is not available\n"
+ f"Update the configuration file\n"
+ f"Or use the Advanced Settings to change this\n",
+ image_path="images/48x48/akm-warning.png",
+ detailed_message=False,
+ transient_for=self,
+ )
+
+ mw.present()
+
+ else:
+ fn.logger.error("Failed to set bootloader, application closing")
+ fn.sys.exit(1)
+
+ def key_pressed(self, keyval, keycode, state, userdata):
+ shortcut = Gtk.accelerator_get_label(
+ keycode, keyval.get_current_event().get_modifier_state()
+ )
+
+ # quit application
+ if shortcut in ("Ctrl+Q", "Ctrl+Mod2+Q"):
+ self.destroy()
+
+ def open_settings(self, fn):
+ settings_win = SettingsWindow(fn, self)
+ settings_win.present()
+
+ 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 start_get_kernels_threads(self):
+ if self.refresh_cache is False:
+ fn.logger.info("Starting get official Linux kernels thread")
+ try:
+ fn.Thread(
+ name=fn.thread_get_kernels,
+ target=fn.get_official_kernels,
+ daemon=True,
+ args=(self,),
+ ).start()
+
+ except Exception as e:
+ fn.logger.error("Exception in thread fn.get_official_kernels(): %s" % e)
+ finally:
+ self.official_kernels = self.queue_kernels.get()
+ self.queue_kernels.task_done()
+
+ else:
+ self.official_kernels = self.queue_kernels.get()
+ self.queue_kernels.task_done()
+
+ fn.logger.info("Starting pacman db synchronization thread")
+ self.queue_load_progress.put("Starting pacman db synchronization")
+
+ self.pacman_db_sync()
+
+ fn.logger.info("Starting get community kernels thread")
+ self.queue_load_progress.put("Getting community based Linux kernels")
+
+ try:
+ thread_get_community_kernels = fn.Thread(
+ name=fn.thread_get_community_kernels,
+ target=fn.get_community_kernels,
+ daemon=True,
+ args=(self,),
+ )
+
+ thread_get_community_kernels.start()
+
+ except Exception as e:
+ fn.logger.error("Exception in thread_get_community_kernels: %s" % e)
+ finally:
+ self.community_kernels = self.queue_community_kernels.get()
+ self.queue_community_kernels.task_done()
+
+ # =====================================================
+ # PACMAN DB SYNC
+ # =====================================================
+
+ def pacman_db_sync(self):
+ sync_err = fn.sync_package_db()
+
+ if sync_err is not None:
+ fn.logger.error("Pacman db synchronization failed")
+
+ print(
+ "---------------------------------------------------------------------------"
+ )
+
+ GLib.idle_add(
+ self.show_sync_db_message_dialog,
+ sync_err,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ else:
+ fn.logger.info("Pacman DB synchronization completed")
+
+ def show_sync_db_message_dialog(self, sync_err):
+ mw = MessageWindow(
+ title="Error - Pacman db synchronization",
+ message=f"Pacman db synchronization failed\n"
+ f"Failed to run 'pacman -Syu'\n"
+ f"{sync_err}\n",
+ image_path="images/48x48/akm-warning.png",
+ transient_for=self,
+ detailed_message=True,
+ )
+
+ mw.present()
+
+ # keep splash screen open, until main gui is loaded
+ def wait_for_gui_load(self):
+ while True:
+ fn.time.sleep(0.2)
+ status = self.queue_load_progress.get()
+ if status == 1:
+ GLib.idle_add(
+ self.splash_screen.destroy,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+ break
+
+ def on_settings(self, action, param, fn):
+ self.open_settings(fn)
+
+ def on_about(self, action, param):
+ about_dialog = AboutDialog(self)
+ about_dialog.present()
+
+ def on_refresh(self, action, param):
+ if not fn.is_thread_alive(fn.thread_refresh_ui):
+ fn.Thread(
+ name=fn.thread_refresh_ui,
+ target=self.refresh_ui,
+ daemon=True,
+ ).start()
+
+ def refresh_ui(self):
+ fn.logger.debug("Refreshing UI")
+
+ self.label_notify_revealer.set_text("Refreshing UI started")
+ GLib.idle_add(
+ self.reveal_notify,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+ fn.pacman_repos_list = []
+ fn.get_pacman_repos()
+
+ fn.cached_kernels_list = []
+ fn.community_kernels_list = []
+
+ self.official_kernels = None
+
+ self.community_kernels = None
+
+ self.installed_kernels = None
+
+ self.start_get_kernels_threads()
+
+ self.installed_kernels = fn.get_installed_kernels()
+
+ self.label_notify_revealer.set_text("Refreshing official kernels")
+ GLib.idle_add(
+ self.reveal_notify,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ GLib.idle_add(
+ self.kernel_stack.add_official_kernels_to_stack,
+ True,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ self.label_notify_revealer.set_text("Refreshing community kernels")
+ GLib.idle_add(
+ self.reveal_notify,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ GLib.idle_add(
+ self.kernel_stack.add_community_kernels_to_stack,
+ True,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ self.label_notify_revealer.set_text("Refreshing installed kernels")
+ GLib.idle_add(
+ self.reveal_notify,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ GLib.idle_add(
+ self.kernel_stack.add_installed_kernels_to_stack,
+ True,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ while self.default_context.pending():
+ fn.time.sleep(0.3)
+ self.default_context.iteration(False)
+
+ # fn.time.sleep(0.5)
+
+ fn.logger.debug("Refresh UI completed")
+
+ self.label_notify_revealer.set_text("Refreshing UI completed")
+ GLib.idle_add(
+ self.reveal_notify,
+ priority=GLib.PRIORITY_DEFAULT,
+ )
+
+ def on_quit(self, action, param):
+ self.destroy()
+ fn.logger.info("Application quit")
+
+ def on_button_quit_response(self, widget):
+ self.destroy()
+ fn.logger.info("Application quit")
+
+ def load_kernels_gui(self):
+ hbox_sep = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
+ hsep = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL)
+ hbox_sep.append(hsep)
+
+ # handle error here with message
+ if self.official_kernels is None:
+ fn.logger.error("Failed to retrieve kernel list")
+
+ stack_sidebar = Gtk.StackSidebar()
+ stack_sidebar.set_name("stack_sidebar")
+ stack_sidebar.set_stack(self.stack)
+
+ hbox_stack_sidebar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
+ hbox_stack_sidebar.set_name("hbox_stack_sidebar")
+ hbox_stack_sidebar.append(stack_sidebar)
+ hbox_stack_sidebar.append(self.stack)
+
+ self.vbox.append(hbox_stack_sidebar)
+
+ button_quit = Gtk.Button.new_with_label("Quit")
+ # button_quit.set_size_request(100, 30)
+ button_quit.connect(
+ "clicked",
+ self.on_button_quit_response,
+ )
+
+ btn_context = button_quit.get_style_context()
+ btn_context.add_class("destructive-action")
+
+ grid_bottom_panel = Gtk.Grid()
+ grid_bottom_panel.set_halign(Gtk.Align.END)
+ grid_bottom_panel.set_row_homogeneous(True)
+
+ grid_bottom_panel.attach(button_quit, 0, 1, 1, 1)
+
+ self.vbox.append(grid_bottom_panel)
+
+ self.textbuffer = Gtk.TextBuffer()
+
+ 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)
+
+ fn.logger.info("Creating kernel UI")
+
+ # add official kernel flowbox
+
+ fn.logger.debug("Adding official kernels to UI")
+ self.kernel_stack.add_official_kernels_to_stack(reload=False)
+
+ fn.logger.debug("Adding community kernels to UI")
+ self.kernel_stack.add_community_kernels_to_stack(reload=False)
+
+ fn.logger.debug("Adding installed kernels to UI")
+ self.kernel_stack.add_installed_kernels_to_stack(reload=False)
+
+ while self.default_context.pending():
+ self.default_context.iteration(True)
+
+ fn.time.sleep(0.3)
+
+ self.queue_load_progress.put(1)
+ fn.logger.info("Kernel manager UI loaded")
\ No newline at end of file