From e9434a0b92c175f71c2ef1e5bdbacc9630ad56e7 Mon Sep 17 00:00:00 2001 From: Abhiraj Roy <157954129+iconized@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:43:06 +0530 Subject: [PATCH] chore(comp): change compositions --- functions.py | 12 +- kernel.py | 51 +++ snigdhaos-kernel-manager.py | 2 +- ui/AboutDialog.py | 67 ++++ ui/FlowBox.py | 638 ++++++++++++++++++++++++++++++++ ui/KernelStack.py | 631 +++++++++++++++++++++++++++++++ ui/MenuButton.py | 45 +++ ui/MessageWindow.py | 95 +++++ ui/ProgressWindow.py | 632 +++++++++++++++++++++++++++++++ ui/SettingsWindow.py | 715 ++++++++++++++++++++++++++++++++++++ ui/SplashScreen.py | 30 ++ ui/Stack.py | 30 ++ 12 files changed, 2941 insertions(+), 7 deletions(-) create mode 100644 ui/AboutDialog.py create mode 100644 ui/FlowBox.py create mode 100644 ui/KernelStack.py create mode 100644 ui/MenuButton.py create mode 100644 ui/MessageWindow.py create mode 100644 ui/ProgressWindow.py create mode 100644 ui/SettingsWindow.py create mode 100644 ui/SplashScreen.py create mode 100644 ui/Stack.py diff --git a/functions.py b/functions.py index 65f646d..16a3983 100644 --- a/functions.py +++ b/functions.py @@ -70,17 +70,17 @@ thread_monitor_messages = "thread_monitor_messages" thread_refresh_cache = "thread_refresh_cache" thread_refresh_ui = "thread_refresh_ui" -cache_dir = "%s/.cache/archlinux-kernel-manager" % home +cache_dir = "%s/.cache/snigdhaos-kernel-manager" % home cache_file = "%s/kernels.toml" % cache_dir cache_update = "%s/update" % cache_dir -log_dir = "/var/log/archlinux-kernel-manager" +log_dir = "/var/log/snigdhaos-kernel-manager" event_log_file = "%s/event.log" % log_dir config_file_default = "%s/defaults/config.toml" % base_dir -config_dir = "%s/.config/archlinux-kernel-manager" % home -config_file = "%s/.config/archlinux-kernel-manager/config.toml" % home +config_dir = "%s/.config/snigdhaos-kernel-manager" % home +config_file = "%s/.config/snigdhaos-kernel-manager/config.toml" % home logger = logging.getLogger("logger") @@ -1603,7 +1603,7 @@ def update_bootloader(self): else: if ( "Skipping" - or "same boot loader version in place already." in stdout_lines + or "same boot loader version in place already." in self.stdout_lines ): logger.info("%s update completed" % self.bootloader) @@ -1636,7 +1636,7 @@ def update_bootloader(self): ) logger.error("%s update failed" % self.bootloader) - logger.error(str(stdout_lines)) + logger.error(str(self.stdout_lines)) self.messages_queue.put(event) GLib.idle_add( diff --git a/kernel.py b/kernel.py index e69de29..e8350fc 100644 --- a/kernel.py +++ b/kernel.py @@ -0,0 +1,51 @@ +# Store kernel data taken from +import datetime +from datetime import datetime + + +class Kernel: + def __init__(self, name, headers, version, size, last_modified, file_format): + self.name = name + self.headers = headers + self.version = version + self.size = size + self.last_modified = last_modified + self.file_format = file_format + + def __gt__(self, other): + datetime_value_self = ( + datetime.strptime(self.last_modified, "%d-%b-%Y %H:%M") + .replace(tzinfo=None) + .date() + ) + + datetime_value_other = ( + datetime.strptime(other.last_modified, "%d-%b-%Y %H:%M") + .replace(tzinfo=None) + .date() + ) + + if datetime_value_other > datetime_value_self: + return datetime_value_other + + +class CommunityKernel: + def __init__(self, name, headers, repository, version, build_date, install_size): + self.name = name + self.headers = headers + self.repository = repository + self.version = version + self.build_date = build_date + self.install_size = install_size + + def __gt__(self, other): + if other.name > self.name: + return other + + +class InstalledKernel: + def __init__(self, name, version, date, size): + self.name = name + self.version = version + self.date = date + self.size = size \ No newline at end of file diff --git a/snigdhaos-kernel-manager.py b/snigdhaos-kernel-manager.py index d6c100f..4b72e40 100644 --- a/snigdhaos-kernel-manager.py +++ b/snigdhaos-kernel-manager.py @@ -35,7 +35,7 @@ class Main(Gtk.Application): display = Gtk.Widget.get_display(win) # sourced from /usr/share/icons/hicolor/scalable/apps - win.set_icon_name("archlinux-kernel-manager-tux") + win.set_icon_name("snigdhaos-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) diff --git a/ui/AboutDialog.py b/ui/AboutDialog.py new file mode 100644 index 0000000..49972d8 --- /dev/null +++ b/ui/AboutDialog.py @@ -0,0 +1,67 @@ +# This class stores static information about the app, and is displayed in the about window +import os +import gi +import functions as fn + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio, Gdk + +base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +class AboutDialog(Gtk.AboutDialog): + def __init__(self, manager_gui, **kwargs): + super().__init__(**kwargs) + + website = "http://arcolinux.info/" + authors = ["Erik Dubois", "Fennec"] + program_name = "Arch Linux Kernel Manager" + comments = ( + f"Add/Remove Officially supported Linux kernels on Arch based systems\n" + f"Powered by the Arch Linux Archive (a.k.a ALA)\n" + f"Community based Linux kernels are also supported\n" + f"Developed in Python with GTK 4\n" + ) + + icon_name = "akm-tux" + + self.set_transient_for(manager_gui) + self.set_modal(True) + self.set_authors(authors) + self.set_program_name(program_name) + self.set_comments(comments) + self.set_website(website) + + self.set_logo_icon_name(icon_name) + self.set_version("Version %s" % manager_gui.app_version) + + self.connect("activate-link", self.on_activate_link) + + tux_icon = Gdk.Texture.new_from_file( + file=Gio.File.new_for_path( + os.path.join(base_dir, "images/96x96/akm-tux.png") + ) + ) + + self.set_logo(tux_icon) + + def on_activate_link(self, about_dialog, uri): + try: + cmd = ["sudo", "-u", fn.sudo_username, "xdg-open", uri] + + proc = fn.subprocess.Popen( + cmd, + shell=False, + stdout=fn.subprocess.PIPE, + stderr=fn.subprocess.STDOUT, + universal_newlines=True, + ) + + out, err = proc.communicate(timeout=50) + + fn.logger.warning(out) + + except Exception as e: + fn.logger.error("Exception in activate_link(): %s" % e) + + return True \ No newline at end of file diff --git a/ui/FlowBox.py b/ui/FlowBox.py new file mode 100644 index 0000000..8c44ccf --- /dev/null +++ b/ui/FlowBox.py @@ -0,0 +1,638 @@ +import datetime + +import gi +import os +import functions as fn +from ui.ProgressWindow import ProgressWindow +from ui.MessageWindow import MessageWindow + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio, GLib + +base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +class FlowBox(Gtk.FlowBox): + def __init__( + self, + kernel, + active_kernel, + manager_gui, + source, + ): + super(FlowBox, self).__init__() + + self.manager_gui = manager_gui + + # self.set_row_spacing(1) + # self.set_column_spacing(1) + # self.set_name("hbox_kernel") + # self.set_activate_on_single_click(True) + # self.connect("child-activated", self.on_child_activated) + self.set_valign(Gtk.Align.START) + + self.set_selection_mode(Gtk.SelectionMode.NONE) + + # self.set_homogeneous(True) + + self.set_max_children_per_line(2) + self.set_min_children_per_line(2) + self.kernel_count = 0 + + self.active_kernel_found = False + self.kernels = [] + + self.kernel = kernel + self.source = source + + if self.source == "official": + self.flowbox_official() + + if self.source == "community": + self.flowbox_community() + + def flowbox_community(self): + for community_kernel in self.kernel: + self.kernels.append(community_kernel) + + self.kernel_count += 1 + + if len(self.kernels) > 0: + installed = False + + for cache in self.kernels: + fb_child = Gtk.FlowBoxChild() + fb_child.set_name( + "%s %s %s" % (cache.name, cache.version, cache.repository) + ) + + vbox_kernel_widgets = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=0 + ) + vbox_kernel_widgets.set_name("vbox_kernel_widgets") + vbox_kernel_widgets.set_homogeneous(True) + + switch_kernel = Gtk.Switch() + switch_kernel.set_halign(Gtk.Align.START) + + hbox_kernel_switch = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=0 + ) + + hbox_kernel_switch.append(switch_kernel) + + label_kernel_size = Gtk.Label(xalign=0, yalign=0) + label_kernel_size.set_name("label_kernel_flowbox") + + label_kernel_name = Gtk.Label(xalign=0, yalign=0) + label_kernel_name.set_name("label_kernel_version") + label_kernel_name.set_markup( + "%s %s %s" + % (cache.name, cache.version, cache.repository) + ) + label_kernel_name.set_selectable(True) + + vbox_kernel_widgets.append(label_kernel_name) + + tux_icon = Gtk.Picture.new_for_file( + file=Gio.File.new_for_path( + os.path.join(base_dir, "images/48x48/akm-tux.png") + ) + ) + tux_icon.set_can_shrink(True) + + for installed_kernel in self.manager_gui.installed_kernels: + if "{}-{}".format( + installed_kernel.name, installed_kernel.version + ) == "{}-{}".format(cache.name, cache.version): + installed = True + + if cache.name == installed_kernel.name: + if ( + cache.version > installed_kernel.version + or cache.version != installed_kernel.version + ): + fn.logger.info( + "Kernel upgrade available - %s %s" + % (cache.name, cache.version) + ) + + tux_icon = Gtk.Picture.new_for_file( + file=Gio.File.new_for_path( + os.path.join( + base_dir, "images/48x48/akm-update.png" + ) + ) + ) + tux_icon.set_can_shrink(True) + + label_kernel_name.set_markup( + "*%s %s %s" + % (cache.name, cache.version, cache.repository) + ) + + if installed is True: + switch_kernel.set_state(True) + switch_kernel.set_active(True) + + else: + switch_kernel.set_state(False) + switch_kernel.set_active(False) + + tux_icon.set_content_fit(content_fit=Gtk.ContentFit.SCALE_DOWN) + tux_icon.set_halign(Gtk.Align.START) + + installed = False + switch_kernel.connect("state-set", self.kernel_toggle_state, cache) + + hbox_kernel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + hbox_kernel.set_name("hbox_kernel") + + label_kernel_size.set_text("%sM" % str(cache.install_size)) + + vbox_kernel_widgets.append(label_kernel_size) + + label_kernel_build_date = Gtk.Label(xalign=0, yalign=0) + label_kernel_build_date.set_name("label_kernel_flowbox") + label_kernel_build_date.set_text(cache.build_date) + + vbox_kernel_widgets.append(label_kernel_build_date) + + vbox_kernel_widgets.append(hbox_kernel_switch) + + hbox_kernel.append(tux_icon) + hbox_kernel.append(vbox_kernel_widgets) + + fb_child.set_child(hbox_kernel) + + self.append(fb_child) + + def flowbox_official(self): + for official_kernel in self.manager_gui.official_kernels: + if official_kernel.name == self.kernel: + self.kernels.append(official_kernel) + self.kernel_count += 1 + + if len(self.kernels) > 0: + installed = False + + latest = sorted(self.kernels)[:-1][0] + + for cache in sorted(self.kernels): + fb_child = Gtk.FlowBoxChild() + fb_child.set_name("%s %s" % (cache.name, cache.version)) + if cache == latest: + tux_icon = Gtk.Picture.new_for_file( + file=Gio.File.new_for_path( + os.path.join(base_dir, "images/48x48/akm-new.png") + ) + ) + + else: + tux_icon = Gtk.Picture.new_for_file( + file=Gio.File.new_for_path( + os.path.join(base_dir, "images/48x48/akm-tux.png") + ) + ) + + tux_icon.set_content_fit(content_fit=Gtk.ContentFit.SCALE_DOWN) + tux_icon.set_halign(Gtk.Align.START) + + vbox_kernel_widgets = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=0 + ) + vbox_kernel_widgets.set_homogeneous(True) + + hbox_kernel_switch = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=0 + ) + + switch_kernel = Gtk.Switch() + switch_kernel.set_halign(Gtk.Align.START) + + hbox_kernel_switch.append(switch_kernel) + + label_kernel_version = Gtk.Label(xalign=0, yalign=0) + label_kernel_version.set_name("label_kernel_version") + label_kernel_version.set_selectable(True) + + label_kernel_size = Gtk.Label(xalign=0, yalign=0) + label_kernel_size.set_name("label_kernel_flowbox") + + if self.manager_gui.installed_kernels is None: + self.manager_gui.installed_kernels = fn.get_installed_kernels() + + for installed_kernel in self.manager_gui.installed_kernels: + if ( + "{}-{}".format(installed_kernel.name, installed_kernel.version) + == cache.version + ): + installed = True + + if installed is True: + switch_kernel.set_state(True) + switch_kernel.set_active(True) + + else: + switch_kernel.set_state(False) + switch_kernel.set_active(False) + + installed = False + switch_kernel.connect("state-set", self.kernel_toggle_state, cache) + + label_kernel_version.set_markup("%s" % cache.version) + + hbox_kernel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + hbox_kernel.set_name("hbox_kernel") + + label_kernel_size.set_text(cache.size) + + vbox_kernel_widgets.append(label_kernel_version) + vbox_kernel_widgets.append(label_kernel_size) + + label_kernel_modified = Gtk.Label(xalign=0, yalign=0) + label_kernel_modified.set_name("label_kernel_flowbox") + label_kernel_modified.set_text(cache.last_modified) + + vbox_kernel_widgets.append(label_kernel_modified) + + vbox_kernel_widgets.append(hbox_kernel_switch) + + hbox_kernel.append(tux_icon) + hbox_kernel.append(vbox_kernel_widgets) + + fb_child.set_child(hbox_kernel) + + self.append(fb_child) + + else: + fn.logger.error("Failed to read in kernels.") + + def kernel_toggle_state(self, switch, data, kernel): + fn.logger.debug( + "Switch toggled, kernel selected = %s %s" % (kernel.name, kernel.version) + ) + message = None + title = None + + if fn.check_pacman_lockfile() is False: + # switch widget is currently toggled off + if switch.get_state() is False: # and switch.get_active() is True: + for inst_kernel in fn.get_installed_kernels(): + if inst_kernel.name == kernel.name: + if self.source == "official": + if ( + inst_kernel.version + > kernel.version.split("%s-" % inst_kernel.name)[1] + ): + title = "Downgrading %s kernel" % kernel.name + else: + title = "Upgrading %s kernel" % kernel.name + + break + + if title is None: + title = "Kernel install" + + if self.source == "community": + message = "This will install %s-%s - Is this ok ?" % ( + kernel.name, + kernel.version, + ) + elif self.source == "official": + message = ( + "This will install %s - Is this ok ?" % kernel.version + ) + + message_window = FlowBoxMessageWindow( + title=title, + message=message, + action="install", + kernel=kernel, + transient_for=self.manager_gui, + textview=self.manager_gui.textview, + textbuffer=self.manager_gui.textbuffer, + switch=switch, + source=self.source, + manager_gui=self.manager_gui, + ) + message_window.present() + return True + + # switch widget is currently toggled on + # if widget.get_state() == True and widget.get_active() == False: + if switch.get_state() is True: + # and switch.get_active() is False: + installed_kernels = fn.get_installed_kernels() + + if len(installed_kernels) > 1: + + if self.source == "community": + message = "This will remove %s-%s - Is this ok ?" % ( + kernel.name, + kernel.version, + ) + elif self.source == "official": + message = ( + "This will remove %s - Is this ok ?" % kernel.version + ) + + message_window = FlowBoxMessageWindow( + title="Kernel uninstall", + message=message, + action="uninstall", + kernel=kernel, + transient_for=self.manager_gui, + textview=self.manager_gui.textview, + textbuffer=self.manager_gui.textbuffer, + switch=switch, + source=self.source, + manager_gui=self.manager_gui, + ) + message_window.present() + return True + else: + switch.set_state(True) + # switch.set_active(False) + fn.logger.warn( + "You only have 1 kernel installed, and %s-%s is currently running, uninstall aborted." + % (kernel.name, kernel.version) + ) + msg_win = MessageWindow( + title="Warning: Uninstall aborted", + message=f"You only have 1 kernel installed\n" + f"{kernel.name} {kernel.version} is currently active\n", + image_path="images/48x48/akm-remove.png", + transient_for=self.manager_gui, + detailed_message=False, + ) + msg_win.present() + return True + + else: + fn.logger.error( + "Pacman lockfile found, is another pacman process running ?" + ) + + msg_win = MessageWindow( + title="Warning", + message="Pacman lockfile found, which indicates another pacman process is running", + transient_for=self.manager_gui, + detailed_message=False, + image_path="images/48x48/akm-warning.png", + ) + msg_win.present() + return True + + # while self.manager_gui.default_context.pending(): + # self.manager_gui.default_context.iteration(True) + + +class FlowBoxInstalled(Gtk.FlowBox): + def __init__(self, installed_kernels, manager_gui, **kwargs): + super().__init__(**kwargs) + + self.set_selection_mode(Gtk.SelectionMode.NONE) + + self.set_homogeneous(True) + self.set_max_children_per_line(2) + self.set_min_children_per_line(2) + + self.manager_gui = manager_gui + + for installed_kernel in installed_kernels: + tux_icon = Gtk.Picture.new_for_file( + file=Gio.File.new_for_path( + os.path.join(base_dir, "images/48x48/akm-tux.png") + ) + ) + + fb_child = Gtk.FlowBoxChild() + fb_child.set_name( + "%s %s" % (installed_kernel.name, installed_kernel.version) + ) + + tux_icon.set_content_fit(content_fit=Gtk.ContentFit.SCALE_DOWN) + tux_icon.set_halign(Gtk.Align.START) + + label_installed_kernel_version = Gtk.Label(xalign=0, yalign=0) + label_installed_kernel_version.set_name("label_kernel_version") + label_installed_kernel_version.set_markup( + "%s %s" % (installed_kernel.name, installed_kernel.version) + ) + label_installed_kernel_version.set_selectable(True) + + hbox_installed_version = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=0 + ) + + hbox_installed_version.append(label_installed_kernel_version) + + label_installed_kernel_size = Gtk.Label(xalign=0, yalign=0) + label_installed_kernel_size.set_name("label_kernel_flowbox") + label_installed_kernel_size.set_text("%sM" % str(installed_kernel.size)) + + label_installed_kernel_date = Gtk.Label(xalign=0, yalign=0) + label_installed_kernel_date.set_name("label_kernel_flowbox") + label_installed_kernel_date.set_text("%s" % installed_kernel.date) + + btn_uninstall_kernel = Gtk.Button.new_with_label("Remove") + + btn_context = btn_uninstall_kernel.get_style_context() + btn_context.add_class("destructive-action") + + vbox_uninstall_button = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=0 + ) + vbox_uninstall_button.set_name("box_padding_left") + + btn_uninstall_kernel.set_hexpand(False) + btn_uninstall_kernel.set_halign(Gtk.Align.CENTER) + btn_uninstall_kernel.set_vexpand(False) + btn_uninstall_kernel.set_valign(Gtk.Align.CENTER) + + vbox_uninstall_button.append(btn_uninstall_kernel) + + btn_uninstall_kernel.connect( + "clicked", self.button_uninstall_kernel, installed_kernel + ) + + vbox_kernel_widgets = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=0 + ) + + vbox_kernel_widgets.append(hbox_installed_version) + vbox_kernel_widgets.append(label_installed_kernel_size) + vbox_kernel_widgets.append(label_installed_kernel_date) + vbox_kernel_widgets.append(vbox_uninstall_button) + + hbox_kernel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + hbox_kernel.set_name("hbox_kernel") + + hbox_kernel.append(tux_icon) + hbox_kernel.append(vbox_kernel_widgets) + + fb_child.set_child(hbox_kernel) + + self.append(fb_child) + + def button_uninstall_kernel(self, button, installed_kernel): + installed_kernels = fn.get_installed_kernels() + + if len(installed_kernels) > 1: + fn.logger.info( + "Selected kernel to remove = %s %s" + % (installed_kernel.name, installed_kernel.version) + ) + + message_window = FlowBoxMessageWindow( + title="Kernel uninstall", + message="This will remove %s-%s - Is this ok ?" + % (installed_kernel.name, installed_kernel.version), + action="uninstall", + kernel=installed_kernel, + transient_for=self.manager_gui, + textview=self.manager_gui.textview, + textbuffer=self.manager_gui.textbuffer, + switch=None, + source=None, + manager_gui=self.manager_gui, + ) + message_window.present() + else: + fn.logger.warn( + "You only have 1 kernel installed %s %s, uninstall aborted." + % (installed_kernel.name, installed_kernel.version) + ) + msg_win = MessageWindow( + title="Warning: Uninstall aborted", + message=f"You only have 1 kernel installed\n" + f"{installed_kernel.name} {installed_kernel.version}\n", + image_path="images/48x48/akm-remove.png", + transient_for=self.manager_gui, + detailed_message=False, + ) + msg_win.present() + + +class FlowBoxMessageWindow(Gtk.Window): + def __init__( + self, + title, + message, + action, + kernel, + textview, + textbuffer, + switch, + source, + manager_gui, + **kwargs, + ): + super().__init__(**kwargs) + + self.set_title(title=title) + self.set_modal(modal=True) + self.set_resizable(False) + self.set_icon_name("archlinux-kernel-manager-tux") + + header_bar = Gtk.HeaderBar() + header_bar.set_show_title_buttons(False) + + label_title = Gtk.Label(xalign=0.5, yalign=0.5) + label_title.set_markup("%s" % title) + + self.set_titlebar(header_bar) + + header_bar.set_title_widget(label_title) + + self.textview = textview + self.textbuffer = textbuffer + self.manager_gui = manager_gui + self.kernel = kernel + self.action = action + self.switch = switch + self.source = source + + vbox_flowbox_message = Gtk.Box.new( + orientation=Gtk.Orientation.VERTICAL, spacing=10 + ) + vbox_flowbox_message.set_name("vbox_flowbox_message") + + self.set_child(child=vbox_flowbox_message) + + label_flowbox_message = Gtk.Label(xalign=0, yalign=0) + label_flowbox_message.set_markup("%s" % message) + label_flowbox_message.set_name("label_flowbox_message") + + vbox_flowbox_message.set_halign(Gtk.Align.CENTER) + + # Widgets. + button_yes = Gtk.Button.new_with_label("Yes") + button_yes.set_size_request(100, 30) + button_yes.set_halign(Gtk.Align.END) + button_yes_context = button_yes.get_style_context() + button_yes_context.add_class("destructive-action") + button_yes.connect("clicked", self.on_button_yes_clicked) + + button_no = Gtk.Button.new_with_label("No") + button_no.set_size_request(100, 30) + button_no.set_halign(Gtk.Align.END) + button_no.connect("clicked", self.on_button_no_clicked) + + hbox_buttons = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=15) + hbox_buttons.set_halign(Gtk.Align.CENTER) + hbox_buttons.append(button_yes) + hbox_buttons.append(button_no) + + vbox_flowbox_message.append(label_flowbox_message) + vbox_flowbox_message.append(hbox_buttons) + + def on_button_yes_clicked(self, button): + self.hide() + self.destroy() + progress_window = None + if fn.check_pacman_lockfile() is False: + if self.action == "uninstall": + progress_window = ProgressWindow( + title="Removing kernel", + action="uninstall", + textview=self.textview, + textbuffer=self.textbuffer, + kernel=self.kernel, + switch=self.switch, + source=self.source, + manager_gui=self.manager_gui, + transient_for=self.manager_gui, + ) + + if self.action == "install": + progress_window = ProgressWindow( + title="Installing kernel", + action="install", + textview=self.textview, + textbuffer=self.textbuffer, + kernel=self.kernel, + switch=self.switch, + source=self.source, + manager_gui=self.manager_gui, + transient_for=self.manager_gui, + ) + else: + fn.logger.error( + "Pacman lockfile found, is another pacman process running ?" + ) + + def on_button_no_clicked(self, button): + if self.action == "uninstall": + if self.switch is not None: + self.switch.set_state(True) + + elif self.action == "install": + if self.switch is not None: + self.switch.set_state(False) + + self.hide() + self.destroy() + + return True \ No newline at end of file diff --git a/ui/KernelStack.py b/ui/KernelStack.py new file mode 100644 index 0000000..73b1598 --- /dev/null +++ b/ui/KernelStack.py @@ -0,0 +1,631 @@ +import gi +import os +import functions as fn +from ui.FlowBox import FlowBox, FlowBoxInstalled +from ui.Stack import Stack +from kernel import Kernel, InstalledKernel, CommunityKernel + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio, Gdk + +base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +class KernelStack: + def __init__( + self, + manager_gui, + **kwargs, + ): + super().__init__(**kwargs) + self.manager_gui = manager_gui + self.flowbox_stacks = [] + self.search_entries = [] + + def add_installed_kernels_to_stack(self, reload): + vbox_header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + vbox_header.set_name("vbox_header") + + lbl_heading = Gtk.Label(xalign=0.5, yalign=0.5) + lbl_heading.set_name("label_flowbox_message") + lbl_heading.set_text("%s" % "Installed kernels".upper()) + + lbl_padding = Gtk.Label(xalign=0.0, yalign=0.0) + lbl_padding.set_text(" ") + + grid_banner_img = Gtk.Grid() + + image_settings = Gtk.Image.new_from_file( + os.path.join(base_dir, "images/48x48/akm-install.png") + ) + + image_settings.set_icon_size(Gtk.IconSize.LARGE) + image_settings.set_halign(Gtk.Align.START) + + grid_banner_img.attach(image_settings, 0, 1, 1, 1) + grid_banner_img.attach_next_to( + lbl_padding, + image_settings, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_banner_img.attach_next_to( + lbl_heading, + lbl_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + vbox_header.append(grid_banner_img) + + label_installed_desc = Gtk.Label(xalign=0, yalign=0) + label_installed_desc.set_text("Installed Linux kernel and modules") + label_installed_desc.set_name("label_stack_desc") + + label_installed_count = Gtk.Label(xalign=0, yalign=0) + + label_installed_count.set_name("label_stack_count") + + vbox_search_entry = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + + search_entry_installed = Gtk.SearchEntry() + search_entry_installed.set_name("search_entry_installed") + search_entry_installed.set_placeholder_text("Search installed kernels...") + search_entry_installed.connect("search_changed", self.flowbox_filter_installed) + + vbox_search_entry.append(search_entry_installed) + + if reload is True: + if self.manager_gui.vbox_installed_kernels is not None: + for widget in self.manager_gui.vbox_installed_kernels: + if widget.get_name() == "label_stack_count": + widget.set_markup( + "%s Installed kernels" + % len(self.manager_gui.installed_kernels) + ) + + if widget.get_name() == "scrolled_window_installed": + self.manager_gui.vbox_installed_kernels.remove(widget) + + scrolled_window_installed = Gtk.ScrolledWindow() + scrolled_window_installed.set_name("scrolled_window_installed") + scrolled_window_installed.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + scrolled_window_installed.set_propagate_natural_height(True) + scrolled_window_installed.set_propagate_natural_width(True) + + self.flowbox_installed = FlowBoxInstalled( + installed_kernels=self.manager_gui.installed_kernels, + manager_gui=self.manager_gui, + ) + vbox_installed_flowbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=12 + ) + + # vbox_installed_flowbox.set_halign(align=Gtk.Align.FILL) + + vbox_installed_flowbox.append(self.flowbox_installed) + + scrolled_window_installed.set_child(vbox_installed_flowbox) + + self.manager_gui.vbox_installed_kernels.append(scrolled_window_installed) + + if self.manager_gui.vbox_active_installed_kernel is not None: + self.manager_gui.vbox_installed_kernels.reorder_child_after( + self.manager_gui.vbox_active_installed_kernel, + scrolled_window_installed, + ) + else: + self.manager_gui.vbox_installed_kernels = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + self.manager_gui.vbox_installed_kernels.set_name("vbox_installed_kernels") + + self.manager_gui.vbox_active_installed_kernel = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=5 + ) + self.manager_gui.vbox_active_installed_kernel.set_name("vbox_active_kernel") + + label_active_installed_kernel = Gtk.Label(xalign=0.5, yalign=0.5) + label_active_installed_kernel.set_name("label_active_kernel") + label_active_installed_kernel.set_selectable(True) + + label_active_installed_kernel.set_markup( + "Active kernel: %s" % self.manager_gui.active_kernel + ) + label_active_installed_kernel.set_halign(Gtk.Align.START) + self.manager_gui.vbox_active_installed_kernel.append( + label_active_installed_kernel + ) + + scrolled_window_installed = Gtk.ScrolledWindow() + scrolled_window_installed.set_name("scrolled_window_installed") + scrolled_window_installed.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + scrolled_window_installed.set_propagate_natural_height(True) + scrolled_window_installed.set_propagate_natural_width(True) + + label_installed_count.set_markup( + "%s Installed kernels" % len(self.manager_gui.installed_kernels) + ) + + self.flowbox_installed = FlowBoxInstalled( + installed_kernels=self.manager_gui.installed_kernels, + manager_gui=self.manager_gui, + ) + vbox_installed_flowbox = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=12 + ) + + # vbox_installed_flowbox.set_halign(align=Gtk.Align.FILL) + + vbox_installed_flowbox.append(self.flowbox_installed) + + scrolled_window_installed.set_child(vbox_installed_flowbox) + + # self.manager_gui.vbox_installed_kernels.append(label_installed_title) + self.manager_gui.vbox_installed_kernels.append(vbox_header) + self.manager_gui.vbox_installed_kernels.append(label_installed_desc) + self.manager_gui.vbox_installed_kernels.append(label_installed_count) + self.manager_gui.vbox_installed_kernels.append(vbox_search_entry) + self.manager_gui.vbox_installed_kernels.append(scrolled_window_installed) + self.manager_gui.vbox_installed_kernels.append( + self.manager_gui.vbox_active_installed_kernel + ) + + self.manager_gui.stack.add_titled( + self.manager_gui.vbox_installed_kernels, "Installed", "Installed" + ) + + def add_official_kernels_to_stack(self, reload): + if reload is True: + self.flowbox_stacks.clear() + for kernel in fn.supported_kernels_dict: + vbox_flowbox = None + stack_child = self.manager_gui.stack.get_child_by_name(kernel) + + if stack_child is not None: + for stack_widget in stack_child: + if stack_widget.get_name() == "scrolled_window_official": + scrolled_window_official = stack_widget + vbox_flowbox = ( + scrolled_window_official.get_child().get_child() + ) + + for widget in vbox_flowbox: + widget.remove_all() + + self.flowbox_official_kernel = FlowBox( + kernel, + self.manager_gui.active_kernel, + self.manager_gui, + "official", + ) + self.flowbox_stacks.append(self.flowbox_official_kernel) + + vbox_flowbox.append(self.flowbox_official_kernel) + + # while self.manager_gui.default_context.pending(): + # self.manager_gui.default_context.iteration(True) + else: + for kernel in fn.supported_kernels_dict: + self.manager_gui.vbox_kernels = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + + self.manager_gui.vbox_kernels.set_name("stack_%s" % kernel) + + hbox_sep_kernels = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=10 + ) + + hsep_kernels = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) + + vbox_active_kernel = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=5 + ) + vbox_active_kernel.set_name("vbox_active_kernel") + + label_active_kernel = Gtk.Label(xalign=0.5, yalign=0.5) + label_active_kernel.set_name("label_active_kernel") + label_active_kernel.set_selectable(True) + label_active_kernel.set_markup( + "Active kernel: %s" % self.manager_gui.active_kernel + ) + label_active_kernel.set_halign(Gtk.Align.START) + + label_bottom_padding = Gtk.Label(xalign=0, yalign=0) + label_bottom_padding.set_text(" ") + + hbox_sep_kernels.append(hsep_kernels) + + self.flowbox_official_kernel = FlowBox( + kernel, self.manager_gui.active_kernel, self.manager_gui, "official" + ) + + self.flowbox_stacks.append(self.flowbox_official_kernel) + + vbox_flowbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + vbox_flowbox.set_name("vbox_flowbox_%s" % kernel) + # vbox_flowbox.set_halign(align=Gtk.Align.FILL) + vbox_flowbox.append(self.flowbox_official_kernel) + + scrolled_window_official = Gtk.ScrolledWindow() + scrolled_window_official.set_name("scrolled_window_official") + scrolled_window_official.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + scrolled_window_official.set_propagate_natural_height(True) + scrolled_window_official.set_propagate_natural_width(True) + + label_title = Gtk.Label(xalign=0.5, yalign=0.5) + label_title.set_text(kernel.upper()) + label_title.set_name("label_stack_kernel") + + label_desc = Gtk.Label(xalign=0, yalign=0) + label_desc.set_text(fn.supported_kernels_dict[kernel][0]) + label_desc.set_name("label_stack_desc") + + label_count = Gtk.Label(xalign=0, yalign=0) + label_count.set_markup( + "%s Available kernels" + % self.flowbox_official_kernel.kernel_count + ) + + label_count.set_name("label_stack_count") + + vbox_search_entry = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + + search_entry_official = Gtk.SearchEntry() + search_entry_official.set_name(kernel) + search_entry_official.set_placeholder_text( + "Search %s kernels..." % kernel + ) + search_entry_official.connect( + "search_changed", self.flowbox_filter_official + ) + + self.search_entries.append(search_entry_official) + + vbox_search_entry.append(search_entry_official) + + vbox_active_kernel.append(label_active_kernel) + + vbox_header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + vbox_header.set_name("vbox_header") + + lbl_heading = Gtk.Label(xalign=0.5, yalign=0.5) + lbl_heading.set_name("label_flowbox_message") + lbl_heading.set_text( + "%s - Verified and official kernels" % kernel.upper() + ) + + lbl_padding = Gtk.Label(xalign=0.0, yalign=0.0) + lbl_padding.set_text(" ") + + grid_banner_img = Gtk.Grid() + + image_settings = Gtk.Image.new_from_file( + os.path.join(base_dir, "images/48x48/akm-verified.png") + ) + + image_settings.set_icon_size(Gtk.IconSize.LARGE) + image_settings.set_halign(Gtk.Align.START) + + grid_banner_img.attach(image_settings, 0, 1, 1, 1) + grid_banner_img.attach_next_to( + lbl_padding, + image_settings, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_banner_img.attach_next_to( + lbl_heading, + lbl_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + vbox_header.append(grid_banner_img) + + # vbox_kernels.append(label_title) + self.manager_gui.vbox_kernels.append(vbox_header) + # self.manager_gui.vbox_kernels.append(label_title) + self.manager_gui.vbox_kernels.append(label_desc) + self.manager_gui.vbox_kernels.append(label_count) + self.manager_gui.vbox_kernels.append(vbox_search_entry) + self.manager_gui.vbox_kernels.append(hbox_sep_kernels) + + scrolled_window_official.set_child(vbox_flowbox) + self.manager_gui.vbox_kernels.append(scrolled_window_official) + self.manager_gui.vbox_kernels.append(vbox_active_kernel) + + kernel_sidebar_title = None + + if kernel == "linux": + kernel_sidebar_title = "Linux" + elif kernel == "linux-lts": + kernel_sidebar_title = "Linux-LTS" + elif kernel == "linux-zen": + kernel_sidebar_title = "Linux-ZEN" + elif kernel == "linux-hardened": + kernel_sidebar_title = "Linux-Hardened" + elif kernel == "linux-rt": + kernel_sidebar_title = "Linux-RT" + elif kernel == "linux-rt-lts": + kernel_sidebar_title = "Linux-RT-LTS" + + self.manager_gui.stack.add_titled( + self.manager_gui.vbox_kernels, kernel, kernel_sidebar_title + ) + + def flowbox_filter_official(self, search_entry): + def filter_func(fb_child, text): + if search_entry.get_name() == fb_child.get_name().split(" ")[0]: + if text in fb_child.get_name(): + return True + else: + return False + else: + return True + + text = search_entry.get_text() + + for flowbox in self.flowbox_stacks: + flowbox.set_filter_func(filter_func, text) + + def flowbox_filter_community(self, search_entry): + def filter_func(fb_child, text): + if search_entry.get_name() == "search_entry_community": + if text in fb_child.get_name(): + return True + else: + return False + else: + return True + + text = search_entry.get_text() + + self.flowbox_community.set_filter_func(filter_func, text) + + def flowbox_filter_installed(self, search_entry): + def filter_func(fb_child, text): + if search_entry.get_name() == "search_entry_installed": + if text in fb_child.get_name(): + return True + else: + return False + else: + return True + + text = search_entry.get_text() + + self.flowbox_installed.set_filter_func(filter_func, text) + + def add_community_kernels_to_stack(self, reload): + vbox_active_kernel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + vbox_active_kernel.set_name("vbox_active_kernel") + vbox_kernels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + hbox_sep_kernels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + hsep_kernels = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) + hbox_sep_kernels.append(hsep_kernels) + + label_active_kernel = Gtk.Label(xalign=0.5, yalign=0.5) + label_active_kernel.set_name("label_active_kernel") + label_active_kernel.set_selectable(True) + label_active_kernel.set_markup( + "Active kernel: %s" % self.manager_gui.active_kernel + ) + label_active_kernel.set_halign(Gtk.Align.START) + + label_count = Gtk.Label(xalign=0, yalign=0) + label_count.set_name("label_stack_count") + + vbox_search_entry = None + + search_entry_community = Gtk.SearchEntry() + search_entry_community.set_name("search_entry_community") + search_entry_community.set_placeholder_text( + "Search %s kernels..." % "community based" + ) + search_entry_community.connect("search_changed", self.flowbox_filter_community) + + hbox_warning_message = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=5 + ) + hbox_warning_message.set_name("hbox_warning_message") + + label_pacman_warning = Gtk.Label(xalign=0, yalign=0) + label_pacman_warning.set_name("label_community_warning") + + image_warning = Gtk.Image.new_from_file( + os.path.join(base_dir, "images/48x48/akm-warning.png") + ) + image_warning.set_name("image_warning") + + image_warning.set_icon_size(Gtk.IconSize.LARGE) + image_warning.set_halign(Gtk.Align.CENTER) + + hbox_warning = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + hbox_warning.set_name("hbox_warning") + + hbox_warning.append(image_warning) + # hbox_warning.append(label_pacman_warning) + + label_warning = Gtk.Label(xalign=0, yalign=0) + label_warning.set_name("label_community_warning") + label_warning.set_markup( + f"These are user produced content\n" + f"Any use of the provided files is at your own risk" + ) + + hbox_warning.append(label_warning) + + if reload is True: + vbox_flowbox = None + stack_child = self.manager_gui.stack.get_child_by_name("Community Kernels") + + if stack_child is not None: + for stack_widget in stack_child: + if stack_widget.get_name() == "label_stack_count": + stack_widget.set_markup( + "%s Available kernels" + % len(self.manager_gui.community_kernels) + ) + if stack_widget.get_name() == "vbox_search_entry": + if len(self.manager_gui.community_kernels) == 0: + for search_entry in stack_widget: + search_entry.set_visible(False) + else: + for search_entry in stack_widget: + search_entry.set_visible(True) + + if stack_widget.get_name() == "scrolled_window_community": + scrolled_window_community = stack_widget + vbox_flowbox = scrolled_window_community.get_child().get_child() + + for widget in vbox_flowbox: + if widget.get_name() != "vbox_no_community": + widget.remove_all() + else: + if len(self.manager_gui.community_kernels) > 0: + # widget.hide() + for box_widget in widget: + box_widget.hide() + + vbox_search_entry = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + + vbox_search_entry.append(search_entry_community) + # widget.append(hbox_warning) + widget.append(vbox_search_entry) + + self.flowbox_community = FlowBox( + self.manager_gui.community_kernels, + self.manager_gui.active_kernel, + self.manager_gui, + "community", + ) + vbox_flowbox.append(self.flowbox_community) + + while self.manager_gui.default_context.pending(): + # fn.time.sleep(0.1) + self.manager_gui.default_context.iteration(True) + else: + self.flowbox_community = None + + vbox_flowbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + # vbox_flowbox.set_halign(align=Gtk.Align.FILL) + + if len(self.manager_gui.community_kernels) == 0: + label_count.set_markup("%s Available kernels" % 0) + else: + self.flowbox_community = FlowBox( + self.manager_gui.community_kernels, + self.manager_gui.active_kernel, + self.manager_gui, + "community", + ) + + vbox_flowbox.append(self.flowbox_community) + + label_count.set_markup( + "%s Available kernels" % self.flowbox_community.kernel_count + ) + + vbox_search_entry = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + + vbox_search_entry.set_name("vbox_search_entry") + + vbox_search_entry.append(search_entry_community) + + if reload is False: + scrolled_window_community = Gtk.ScrolledWindow() + scrolled_window_community.set_name("scrolled_window_community") + scrolled_window_community.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + scrolled_window_community.set_propagate_natural_height(True) + scrolled_window_community.set_propagate_natural_width(True) + + label_title = Gtk.Label(xalign=0.5, yalign=0.5) + label_title.set_text("Community Kernels") + label_title.set_name("label_stack_kernel") + + label_desc = Gtk.Label(xalign=0, yalign=0) + label_desc.set_text("Community Linux kernel and modules") + label_desc.set_name("label_stack_desc") + + vbox_active_kernel.append(label_active_kernel) + + vbox_header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + vbox_header.set_name("vbox_header") + + lbl_heading = Gtk.Label(xalign=0.5, yalign=0.5) + lbl_heading.set_name("label_flowbox_message") + + lbl_heading.set_text( + "%s - Unofficial kernels" % "Community based".upper() + ) + + lbl_padding = Gtk.Label(xalign=0.0, yalign=0.0) + lbl_padding.set_text(" ") + + grid_banner_img = Gtk.Grid() + + image_settings = Gtk.Image.new_from_file( + os.path.join(base_dir, "images/48x48/akm-community.png") + ) + + image_settings.set_icon_size(Gtk.IconSize.LARGE) + image_settings.set_halign(Gtk.Align.START) + + grid_banner_img.attach(image_settings, 0, 1, 1, 1) + grid_banner_img.attach_next_to( + lbl_padding, + image_settings, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_banner_img.attach_next_to( + lbl_heading, + lbl_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + vbox_header.append(grid_banner_img) + + vbox_kernels.append(vbox_header) + vbox_kernels.append(label_desc) + vbox_kernels.append(hbox_warning) + vbox_kernels.append(label_count) + + if vbox_search_entry is not None: + vbox_kernels.append(vbox_search_entry) + vbox_kernels.append(hbox_sep_kernels) + + scrolled_window_community.set_child(vbox_flowbox) + + vbox_kernels.append(scrolled_window_community) + vbox_kernels.append(vbox_active_kernel) + + self.manager_gui.stack.add_titled( + vbox_kernels, "Community Kernels", "Community" + ) \ No newline at end of file diff --git a/ui/MenuButton.py b/ui/MenuButton.py new file mode 100644 index 0000000..3d14c58 --- /dev/null +++ b/ui/MenuButton.py @@ -0,0 +1,45 @@ +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk + +# Gtk.Builder xml for the application menu +APP_MENU = """ + + + +
+ + _About + win.about + + + _Settings + win.settings + + + _Refresh + win.refresh + + + _Quit + win.quit + +
+
+
+""" + + +class MenuButton(Gtk.MenuButton): + """ + Wrapper class for at Gtk.Menubutton with a menu defined + in a Gtk.Builder xml string + """ + + def __init__(self, icon_name="open-menu-symbolic"): + super(MenuButton, self).__init__() + builder = Gtk.Builder.new_from_string(APP_MENU, -1) + menu = builder.get_object("app-menu") + self.set_menu_model(menu) + self.set_icon_name(icon_name) \ No newline at end of file diff --git a/ui/MessageWindow.py b/ui/MessageWindow.py new file mode 100644 index 0000000..5c57885 --- /dev/null +++ b/ui/MessageWindow.py @@ -0,0 +1,95 @@ +import gi +import os +import functions as fn + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio, GLib + +base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +class MessageWindow(Gtk.Window): + def __init__(self, title, message, image_path, detailed_message, **kwargs): + super().__init__(**kwargs) + + # self.set_title(title=title) + self.set_modal(modal=True) + self.set_resizable(False) + icon_name = "akm-tux" + self.set_icon_name(icon_name) + + header_bar = Gtk.HeaderBar() + header_bar.set_show_title_buttons(True) + + hbox_title = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + + label_title = Gtk.Label(xalign=0.5, yalign=0.5) + label_title.set_markup("%s" % title) + + hbox_title.append(label_title) + header_bar.set_title_widget(hbox_title) + + self.set_titlebar(header_bar) + + vbox_message = Gtk.Box.new(orientation=Gtk.Orientation.VERTICAL, spacing=10) + vbox_message.set_name("vbox_flowbox_message") + + image = Gtk.Picture.new_for_filename(os.path.join(base_dir, image_path)) + + image.set_content_fit(content_fit=Gtk.ContentFit.SCALE_DOWN) + image.set_halign(Gtk.Align.START) + + hbox_image = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + + # hbox_image.append(image) + + self.set_child(child=vbox_message) + + if detailed_message is True: + scrolled_window = Gtk.ScrolledWindow() + + textview = Gtk.TextView() + textview.set_property("editable", False) + textview.set_property("monospace", True) + + textview.set_vexpand(True) + textview.set_hexpand(True) + + msg_buffer = textview.get_buffer() + msg_buffer.insert( + msg_buffer.get_end_iter(), + "Event timestamp = %s\n" + % fn.datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ) + msg_buffer.insert(msg_buffer.get_end_iter(), "%s\n" % message) + + scrolled_window.set_child(textview) + + hbox_image.append(scrolled_window) + + self.set_size_request(700, 500) + self.set_resizable(True) + else: + label_message = Gtk.Label(xalign=0, yalign=0) + label_message.set_markup("%s" % message) + label_message.set_name("label_flowbox_message") + + hbox_image.append(image) + hbox_image.append(label_message) + + vbox_message.append(hbox_image) + + button_ok = Gtk.Button.new_with_label("OK") + button_ok.set_size_request(100, 30) + button_ok.set_halign(Gtk.Align.END) + button_ok.connect("clicked", self.on_button_ok_clicked) + + hbox_buttons = Gtk.Box.new(orientation=Gtk.Orientation.HORIZONTAL, spacing=20) + hbox_buttons.set_halign(Gtk.Align.END) + hbox_buttons.append(button_ok) + + vbox_message.append(hbox_buttons) + + def on_button_ok_clicked(self, button): + self.hide() + self.destroy() \ No newline at end of file diff --git a/ui/ProgressWindow.py b/ui/ProgressWindow.py new file mode 100644 index 0000000..315d64d --- /dev/null +++ b/ui/ProgressWindow.py @@ -0,0 +1,632 @@ +import sys +import gi +import os +import 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, 400) + self.connect("close-request", self.on_close) + + self.textview = textview + self.textbuffer = textbuffer + + self.kernel_state_queue = fn.Queue() + self.messages_queue = fn.Queue() + self.kernel = kernel + self.timeout_id = None + self.errors_found = False + + 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 + + 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) + ) + + # get kernel version from pacman + self.installed_kernel_version = fn.get_kernel_version(self.kernel.name) + + if self.installed_kernel_version is not None: + fn.logger.debug( + "Installed kernel version = %s" % self.installed_kernel_version + ) + else: + fn.logger.debug("Nothing to remove .. previous kernel not installed") + + 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.label_spinner_progress) + self.hbox_spinner.append(self.spinner) + + vbox_padding = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20) + 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 + + if ( + self.source == "official" + and action == "install" + or action == "uninstall" + and self.source == "official" + ): + 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 + + if self.source == "official": + self.restore_kernel = None + + 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: + 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") + + 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): + mw = MessageWindow( + title="Pacman process running", + message="Pacman is busy processing a transaction .. please wait", + image_path="images/48x48/akm-progress.png", + transient_for=self, + detailed_message=False, + ) + + mw.present() + else: + self.destroy() + + def on_close(self, data): + if fn.check_pacman_process(self): + mw = MessageWindow( + title="Pacman process running", + message="Pacman is busy processing a transaction .. please wait", + image_path="images/48x48/akm-progress.png", + transient_for=self, + detailed_message=False, + ) + + mw.present() + + return True + return False + + def check_kernel_state(self): + returncode = None + kernel = None + while True: + items = self.kernel_state_queue.get() + + try: + if items is not None: + returncode, action, kernel = items + + if returncode == 0: + self.label_notify_revealer.set_text( + "Kernel %s completed" % action + ) + self.reveal_notify() + + fn.logger.info("Kernel %s completed" % action) + + if returncode == 1: + self.errors_found = True + + self.label_notify_revealer.set_text("Kernel %s failed" % action) + self.reveal_notify() + + fn.logger.error("Kernel %s failed" % action) + + event = "%s [ERROR]: Kernel %s failed\n" % ( + fn.datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"), + action, + ) + self.messages_queue.put(event) + + self.label_status.set_markup( + "Kernel %s failed - see logs above" + % action + ) + + # undo action here if action == install + + event = ( + "%s [INFO]: Attempting to undo previous Linux package changes\n" + % ( + fn.datetime.datetime.now().strftime( + "%Y-%m-%d-%H-%M-%S" + ), + ) + ) + + self.messages_queue.put(event) + + if action == "install" and self.restore_kernel is not None: + self.restore = True + fn.logger.info( + "Installation failed, attempting removal of previous Linux package changes" + ) + self.set_title("Kernel installation failed") + + self.label_spinner_progress.set_markup( + "Please wait restoring kernel %s" + % self.restore_kernel.version + ) + + fn.uninstall(self) + + fn.logger.info( + "Restoring previously installed kernel %s" + % self.restore_kernel.version + ) + + 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 + fn.install_archive_kernel(self) + self.set_title("Kernel installation failed") + self.label_status.set_markup( + f"Kernel %s failed - see logs above\n" + % action + ) + + # self.spinner.set_spinning(False) + # self.hbox_spinner.hide() + # + # self.label_progress_window_desc.set_markup( + # f"This window can be now closed\n" + # f"A reboot is recommended when Linux packages have changed" + # ) + + # break + else: + if ( + returncode == 0 + and "-headers" in kernel + or action == "uninstall" + or action == "install" + and self.errors_found is False + ): + + fn.update_bootloader(self) + self.update_installed_list() + self.update_official_list() + + if len(self.manager_gui.community_kernels) > 0: + self.update_community_list() + + if self.restore == False: + self.label_title.set_markup( + "Kernel %s completed" % action + ) + + self.label_status.set_markup( + "Kernel %s completed" + % action + ) + + self.spinner.set_spinning(False) + self.hbox_spinner.hide() + + self.label_progress_window_desc.set_markup( + f"This window can be now closed\n" + f"A reboot is recommended when Linux packages have changed" + ) + else: + self.label_title.set_markup( + "Kernel %s failed" % action + ) + + self.label_status.set_markup( + "Kernel %s failed" + % action + ) + + self.spinner.set_spinning(False) + self.hbox_spinner.hide() + + self.label_progress_window_desc.set_markup( + f"This window can be now closed\n" + f"Previous kernel restored due to failure\n" + f"A reboot is recommended when Linux packages have changed" + ) + + # # else: + # self.spinner.set_spinning(False) + # self.hbox_spinner.hide() + # + # self.label_progress_window_desc.set_markup( + # f"This window can be now closed\n" + # f"A reboot is recommended when Linux packages have changed" + # ) + + break + except Exception as e: + fn.logger.error("Exception in check_kernel_state(): %s" % e) + + finally: + self.kernel_state_queue.task_done() + + 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) \ No newline at end of file diff --git a/ui/SettingsWindow.py b/ui/SettingsWindow.py new file mode 100644 index 0000000..de6ecc9 --- /dev/null +++ b/ui/SettingsWindow.py @@ -0,0 +1,715 @@ +import gi +import os +from ui.Stack import Stack +from ui.MessageWindow import MessageWindow +import functions as fn + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio, GLib, GObject + +base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +class SettingsWindow(Gtk.Window): + def __init__(self, fn, manager_gui, **kwargs): + super().__init__(**kwargs) + + self.set_title("Arch Linux Kernel Manager - Settings") + self.set_resizable(False) + self.set_size_request(600, 600) + stack = Stack(transition_type="CROSSFADE") + + self.set_icon_name("akm-tux") + self.manager_gui = manager_gui + self.set_modal(True) + self.set_transient_for(self.manager_gui) + + self.queue_kernels = self.manager_gui.queue_kernels + + header_bar = Gtk.HeaderBar() + + header_bar.set_show_title_buttons(True) + + self.set_titlebar(header_bar) + + hbox_main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + hbox_main.set_name("box") + self.set_child(child=hbox_main) + + vbox_header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + vbox_header.set_name("vbox_header") + + lbl_heading = Gtk.Label(xalign=0.5, yalign=0.5) + lbl_heading.set_name("label_flowbox_message") + lbl_heading.set_text("Preferences") + + lbl_padding = Gtk.Label(xalign=0.0, yalign=0.0) + lbl_padding.set_text(" ") + + grid_banner_img = Gtk.Grid() + + image_settings = Gtk.Image.new_from_file( + os.path.join(base_dir, "images/48x48/akm-settings.png") + ) + + image_settings.set_icon_size(Gtk.IconSize.LARGE) + image_settings.set_halign(Gtk.Align.START) + + grid_banner_img.attach(image_settings, 0, 1, 1, 1) + grid_banner_img.attach_next_to( + lbl_padding, + image_settings, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_banner_img.attach_next_to( + lbl_heading, + lbl_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + vbox_header.append(grid_banner_img) + + hbox_main.append(vbox_header) + + stack_switcher = Gtk.StackSwitcher() + stack_switcher.set_orientation(Gtk.Orientation.HORIZONTAL) + stack_switcher.set_stack(stack) + + button_close = Gtk.Button(label="Close") + button_close.connect("clicked", self.on_close_clicked) + + hbox_stack_sidebar = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + hbox_stack_sidebar.set_name("box") + + hbox_stack_sidebar.append(stack_switcher) + hbox_stack_sidebar.append(stack) + + hbox_main.append(hbox_stack_sidebar) + + vbox_button = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + vbox_button.set_halign(Gtk.Align.END) + vbox_button.set_name("box") + + vbox_button.append(button_close) + + hbox_stack_sidebar.append(vbox_button) + + vbox_settings = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + vbox_settings.set_name("box") + + label_official_kernels = Gtk.Label(xalign=0, yalign=0) + label_official_kernels.set_markup( + "Latest Official kernels (%s)" % len(fn.supported_kernels_dict) + ) + + label_community_kernels = Gtk.Label(xalign=0, yalign=0) + label_community_kernels.set_markup( + "Latest Community based kernels (%s)" + % len(self.manager_gui.community_kernels) + ) + + vbox_settings_listbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + + self.listbox_official_kernels = Gtk.ListBox() + self.listbox_official_kernels.set_selection_mode(Gtk.SelectionMode.NONE) + + self.label_loading_kernels = Gtk.Label(xalign=0, yalign=0) + self.label_loading_kernels.set_text("Loading ...") + + self.listbox_official_kernels.append(self.label_loading_kernels) + + listbox_community_kernels = Gtk.ListBox() + listbox_community_kernels.set_selection_mode(Gtk.SelectionMode.NONE) + + scrolled_window_community = Gtk.ScrolledWindow() + scrolled_window_official = Gtk.ScrolledWindow() + + scrolled_window_community.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + + scrolled_window_official.set_policy( + Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC + ) + + scrolled_window_community.set_size_request(0, 150) + scrolled_window_official.set_size_request(0, 150) + + scrolled_window_official.set_child(self.listbox_official_kernels) + vbox_community_warning = None + + self.kernel_versions_queue = fn.Queue() + fn.Thread( + target=fn.get_latest_versions, + args=(self,), + daemon=True, + ).start() + + fn.Thread(target=self.check_official_version_queue, daemon=True).start() + + if len(self.manager_gui.community_kernels) > 0: + for community_kernel in self.manager_gui.community_kernels: + row_community_kernel = Gtk.ListBoxRow() + hbox_community_kernel = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5 + ) + hbox_community_kernel.set_name("box_row") + + hbox_row_official_kernel_row = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=10 + ) + + label_community_kernel = Gtk.Label(xalign=0, yalign=0) + label_community_kernel.set_text("%s" % community_kernel.name) + + label_community_kernel_version = Gtk.Label(xalign=0, yalign=0) + label_community_kernel_version.set_text("%s" % community_kernel.version) + + hbox_row_official_kernel_row.append(label_community_kernel) + hbox_row_official_kernel_row.append(label_community_kernel_version) + + hbox_community_kernel.append(hbox_row_official_kernel_row) + + row_community_kernel.set_child(hbox_community_kernel) + listbox_community_kernels.append(row_community_kernel) + scrolled_window_community.set_child(listbox_community_kernels) + else: + vbox_community_warning = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=10 + ) + vbox_community_warning.set_name("box") + + 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_warning = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + hbox_warning.set_name("box") + + hbox_warning.append(image_warning) + + label_pacman_no_community = Gtk.Label(xalign=0, yalign=0) + label_pacman_no_community.set_markup( + f"Cannot find any supported unofficial pacman repository's\n" + f"Add unofficial pacman repository's to use community based kernels" + ) + + hbox_warning.append(label_pacman_no_community) + + vbox_community_warning.append(hbox_warning) + + vbox_settings_listbox.append(label_official_kernels) + vbox_settings_listbox.append(scrolled_window_official) + vbox_settings_listbox.append(label_community_kernels) + + if len(self.manager_gui.community_kernels) > 0: + vbox_settings_listbox.append(scrolled_window_community) + else: + vbox_settings_listbox.append(vbox_community_warning) + + vbox_settings.append(vbox_settings_listbox) + + vbox_settings_adv = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + + vbox_settings_adv.set_name("box") + + self.listbox_settings_adv = Gtk.ListBox() + self.listbox_settings_adv.set_selection_mode(Gtk.SelectionMode.NONE) + + row_settings_adv = Gtk.ListBoxRow() + self.listbox_settings_adv.append(row_settings_adv) + + hbox_bootloader_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + hbox_bootloader_row.set_name("box_row") + hbox_bootloader_row.set_halign(Gtk.Align.START) + + self.hbox_bootloader_grub_row = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=5 + ) + self.hbox_bootloader_grub_row.set_name("box_row") + self.hbox_bootloader_grub_row.set_halign(Gtk.Align.START) + + self.text_entry_bootloader_file = Gtk.Entry() + + hbox_switch_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + hbox_switch_row.set_name("box_row") + hbox_switch_row.set_halign(Gtk.Align.START) + + hbox_log_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + hbox_log_row.set_name("box_row") + hbox_log_row.set_halign(Gtk.Align.START) + + label_bootloader = Gtk.Label(xalign=0, yalign=0) + label_bootloader.set_markup("Bootloader") + + hbox_warning = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + hbox_warning.set_name("hbox_warning") + + label_bootloader_warning = Gtk.Label(xalign=0, yalign=0) + label_bootloader_warning.set_markup( + f"Only change this setting if you know what you are doing\n" + f"The selected Grub/Systemd-boot bootloader entry will be updated\n" + f"This may break your system" + ) + + hbox_warning.append(label_bootloader_warning) + + label_settings_bootloader_title = Gtk.Label(xalign=0.5, yalign=0.5) + label_settings_bootloader_title.set_markup("Current Bootloader") + + self.label_settings_bootloader_file = Gtk.Label(xalign=0.5, yalign=0.5) + self.label_settings_bootloader_file.set_text("GRUB config file") + + self.button_override_bootloader = Gtk.Button( + label="Override bootloader settings" + ) + self.button_override_bootloader.connect("clicked", self.on_override_clicked) + self.hbox_bootloader_override_row = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=20 + ) + self.hbox_bootloader_override_row.set_name("box_row") + self.hbox_bootloader_override_row.append(self.button_override_bootloader) + + boot_loaders = {0: "grub", 1: "systemd-boot"} + + # Set up the factory + factory = Gtk.SignalListItemFactory() + factory.connect("setup", self._on_factory_setup) + factory.connect("bind", self._on_factory_bind) + + self.model = Gio.ListStore(item_type=Bootloader) + for bootloader_id in boot_loaders.keys(): + self.model.append( + Bootloader( + id=bootloader_id, + name=boot_loaders[bootloader_id], + ) + ) + + self.dropdown_bootloader = Gtk.DropDown( + model=self.model, factory=factory, hexpand=True + ) + + self.dropdown_bootloader.set_sensitive(False) + + self.selected_bootloader = None + + self._bootloader_grub_config = "/boot/grub/grub.cfg" + + row_settings_override_grub = Gtk.ListBoxRow() + row_settings_grub = Gtk.ListBoxRow() + self.listbox_settings_adv.append(row_settings_grub) + + self.listbox_settings_adv.append(row_settings_override_grub) + + self.text_entry_bootloader_file.connect("changed", self.on_entry_changed) + self.text_entry_bootloader_file.props.editable = False + text_entry_buffer_file = Gtk.EntryBuffer() + + if self.manager_gui.bootloader_grub_cfg is not None: + text_entry_buffer_file.set_text( + self.manager_gui.bootloader_grub_cfg, + len(self.manager_gui.bootloader_grub_cfg), + ) + else: + text_entry_buffer_file.set_text( + self._bootloader_grub_config, + len(self._bootloader_grub_config), + ) + + self.text_entry_bootloader_file.set_buffer(text_entry_buffer_file) + self.text_entry_bootloader_file.set_halign(Gtk.Align.END) + self.text_entry_bootloader_file.set_sensitive(False) + + label_grub_file_path = Gtk.Label(xalign=0.5, yalign=0.5) + label_grub_file_path.set_markup("Grub file path") + + self.hbox_bootloader_grub_row.append(label_grub_file_path) + self.hbox_bootloader_grub_row.append(self.text_entry_bootloader_file) + + row_settings_grub.set_child(self.hbox_bootloader_grub_row) + + if manager_gui.bootloader == "grub": + self.dropdown_bootloader.set_selected(0) + self.selected_bootloader = 0 + self.hbox_bootloader_grub_row.set_visible(True) + + row_settings_override_grub.set_child(self.hbox_bootloader_override_row) + + if manager_gui.bootloader == "systemd-boot": + + self.selected_bootloader = 1 + + self.dropdown_bootloader.set_selected(1) + row_settings_override_systemd = Gtk.ListBoxRow() + self.listbox_settings_adv.append(row_settings_override_systemd) + row_settings_override_systemd.set_child(self.hbox_bootloader_override_row) + + self.hbox_bootloader_grub_row.set_visible(False) + + self.dropdown_bootloader.connect( + "notify::selected-item", self._on_selected_item_notify + ) + + hbox_bootloader_row.append(label_settings_bootloader_title) + hbox_bootloader_row.append(self.dropdown_bootloader) + + row_settings_adv.set_child(hbox_bootloader_row) + + vbox_settings_adv.append(label_bootloader) + vbox_settings_adv.append(hbox_warning) + vbox_settings_adv.append(self.listbox_settings_adv) + + listbox_settings_cache = Gtk.ListBox() + listbox_settings_cache.set_selection_mode(Gtk.SelectionMode.NONE) + + row_settings_cache = Gtk.ListBoxRow() + listbox_settings_cache.append(row_settings_cache) + + label_cache = Gtk.Label(xalign=0, yalign=0) + label_cache.set_markup("Refresh data from Arch Linux Archive") + + label_cache_update = Gtk.Label(xalign=0.5, yalign=0.5) + label_cache_update.set_text("Update (this will take some time)") + + self.label_cache_update_status = Gtk.Label(xalign=0.5, yalign=0.5) + + switch_refresh_cache = Gtk.Switch() + switch_refresh_cache.connect("state-set", self.refresh_toggle) + + label_cache_file = Gtk.Label(xalign=0, yalign=0) + label_cache_file.set_text(fn.cache_file) + label_cache_file.set_selectable(True) + + self.label_cache_lastmodified = Gtk.Label(xalign=0, yalign=0) + self.label_cache_lastmodified.set_markup( + "Last modified date: %s" % fn.get_cache_last_modified() + ) + + hbox_switch_row.append(label_cache_update) + hbox_switch_row.append(switch_refresh_cache) + hbox_switch_row.append(self.label_cache_update_status) + + row_settings_cache.set_child(hbox_switch_row) + + label_logfile = Gtk.Label(xalign=0, yalign=0) + label_logfile.set_markup("Log file") + + button_logfile = Gtk.Button(label="Open event log file") + button_logfile.connect("clicked", self.on_button_logfile_clicked) + + label_logfile_location = Gtk.Label(xalign=0.5, yalign=0.5) + label_logfile_location.set_text(fn.event_log_file) + label_logfile_location.set_selectable(True) + hbox_log_row.append(button_logfile) + hbox_log_row.append(label_logfile_location) + + listbox_settings_log = Gtk.ListBox() + listbox_settings_log.set_selection_mode(Gtk.SelectionMode.NONE) + + row_settings_log = Gtk.ListBoxRow() + listbox_settings_log.append(row_settings_log) + + row_settings_log.set_child(hbox_log_row) + + vbox_settings_adv.append(label_cache) + vbox_settings_adv.append(self.label_cache_lastmodified) + vbox_settings_adv.append(label_cache_file) + vbox_settings_adv.append(listbox_settings_cache) + vbox_settings_adv.append(label_logfile) + vbox_settings_adv.append(listbox_settings_log) + + stack.add_titled(vbox_settings_adv, "Advanced Settings", "Advanced") + stack.add_titled(vbox_settings, "Kernels", "Kernel versions") + + def populate_official_kernels(self): + self.label_loading_kernels.hide() + for official_kernel in fn.supported_kernels_dict: + row_official_kernel = Gtk.ListBoxRow() + hbox_row_official = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + + hbox_row_official_kernel_row = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, spacing=10 + ) + + hbox_row_official.set_name("box_row") + + label_kernel = Gtk.Label(xalign=0, yalign=0) + label_kernel.set_text("%s" % official_kernel) + + label_kernel_version = Gtk.Label(xalign=0, yalign=0) + label_kernel_version.set_text("%s" % self.kernel_versions[official_kernel]) + + hbox_row_official_kernel_row.append(label_kernel) + hbox_row_official_kernel_row.append(label_kernel_version) + + hbox_row_official.append(hbox_row_official_kernel_row) + + row_official_kernel.set_child(hbox_row_official) + + self.listbox_official_kernels.append(row_official_kernel) + + def check_official_version_queue(self): + while True: + self.kernel_versions = self.kernel_versions_queue.get() + + if self.kernel_versions is not None: + break + + self.kernel_versions_queue.task_done() + + GLib.idle_add(self.populate_official_kernels, priority=GLib.PRIORITY_DEFAULT) + + def on_entry_changed(self, entry): + if ( + len(entry.get_text()) > 0 + and entry.get_text() != self.manager_gui.bootloader_grub_cfg + ): + self.button_override_bootloader.get_child().set_text("Apply changes") + + def _on_factory_setup(self, factory, list_item): + label = Gtk.Label() + list_item.set_child(label) + + def _on_factory_bind(self, factory, list_item): + label = list_item.get_child() + bootloader = list_item.get_item() + label.set_text(bootloader.name) + + def on_override_clicked(self, widget): + if self.button_override_bootloader.get_child().get_text() == "Apply changes": + # validate bootloader + if self.dropdown_bootloader.get_selected() == 1: + 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="Cannot select systemd-boot, UEFI boot mode is not available", + image_path="images/48x48/akm-warning.png", + transient_for=self, + detailed_message=False, + ) + + mw.present() + self.dropdown_bootloader.set_selected(0) + return + + config_data = fn.read_config(self) + + if config_data is not None: + # grub + + if ( + self.dropdown_bootloader.get_selected() == 0 + and len( + self.text_entry_bootloader_file.get_buffer().get_text().strip() + ) + > 0 + ): + if fn.os.path.exists( + self.text_entry_bootloader_file.get_buffer().get_text().strip() + ): + if "bootloader" in config_data.keys(): + config_data.remove("bootloader") + + bootloader = fn.tomlkit.table(True) + bootloader.update({"name": "grub"}) + bootloader.update( + { + "grub_config": self.text_entry_bootloader_file.get_buffer() + .get_text() + .strip() + } + ) + + config_data.append("bootloader", bootloader) + + if fn.update_config(config_data, "grub") is True: + self.manager_gui.bootloader = "grub" + self.manager_gui.bootloader_grub_cfg = ( + self.text_entry_bootloader_file.get_buffer() + .get_text() + .strip() + ) + else: + mw = MessageWindow( + title="Grub config file", + message="The specified Grub config file %s does not exist" + % self.text_entry_bootloader_file.get_buffer() + .get_text() + .strip(), + image_path="images/48x48/akm-warning.png", + transient_for=self, + detailed_message=False, + ) + + mw.present() + self.button_override_bootloader.get_child().set_text( + "Override bootloader settings" + ) + + elif ( + self.dropdown_bootloader.get_selected() == 1 + and self.selected_bootloader + != self.dropdown_bootloader.get_selected() + ): + if "bootloader" in config_data.keys(): + config_data.remove("bootloader") + + self.hbox_bootloader_grub_row.set_visible(True) + + bootloader = fn.tomlkit.table(True) + bootloader.update({"name": "systemd-boot"}) + + config_data.append("bootloader", bootloader) + + if fn.update_config(config_data, "systemd-boot") is True: + self.manager_gui.bootloader = "systemd-boot" + + else: + self.dropdown_bootloader.set_sensitive(True) + + if self.dropdown_bootloader.get_selected() == 0: + self.hbox_bootloader_grub_row.set_visible(True) + self.text_entry_bootloader_file.set_sensitive(True) + self.text_entry_bootloader_file.props.editable = True + elif self.dropdown_bootloader.get_selected() == 1: + self.hbox_bootloader_grub_row.set_visible(False) + + def _on_selected_item_notify(self, dd, _): + if self.dropdown_bootloader.get_selected() != self.selected_bootloader: + self.button_override_bootloader.get_child().set_text("Apply changes") + else: + self.button_override_bootloader.get_child().set_text( + "Override bootloader settings" + ) + if dd.get_selected() == 1: + if self.text_entry_bootloader_file is not None: + self.hbox_bootloader_grub_row.set_visible(False) + elif dd.get_selected() == 0: + if self.text_entry_bootloader_file is not None: + self.hbox_bootloader_grub_row.set_visible(True) + self.text_entry_bootloader_file.set_sensitive(True) + self.text_entry_bootloader_file.props.editable = True + + def monitor_kernels_queue(self, switch): + while True: + if len(fn.fetched_kernels_dict) > 0: + self.manager_gui.official_kernels = self.queue_kernels.get() + self.queue_kernels.task_done() + self.refreshed = True + if self.manager_gui.official_kernels is not None: + switch.set_sensitive(False) + self.update_official_list() + self.update_community_list() + self.update_timestamp() + self.label_cache_update_status.set_markup( + "Cache refresh completed" + ) + else: + self.label_cache_update_status.set_markup( + "Cache refresh failed" + ) + self.refreshed = False + self.update_timestamp() + break + else: + self.label_cache_update_status.set_markup( + "Cache refresh in progress" + ) + # fn.time.sleep(0.3) + + def refresh_toggle(self, switch, data): + if switch.get_active() is True: + # refresh cache + fn.logger.info("Refreshing cache file %s" % fn.cache_file) + switch.set_sensitive(False) + + try: + th_refresh_cache = fn.Thread( + name=fn.thread_refresh_cache, + target=fn.refresh_cache, + args=(self,), + daemon=True, + ) + + th_refresh_cache.start() + + # monitor queue + fn.Thread( + target=self.monitor_kernels_queue, daemon=True, args=(switch,) + ).start() + + except Exception as e: + fn.logger.error("Exception in refresh_toggle(): %s" % e) + self.label_cache_update_status.set_markup("Cache refresh failed") + + def update_timestamp(self): + if self.refreshed is True: + self.label_cache_lastmodified.set_markup( + "Last modified date: %s" + % fn.get_cache_last_modified() + ) + else: + self.label_cache_lastmodified.set_markup( + "Last modified date: %s" + % "Refresh failed" + ) + + 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 on_close_clicked(self, widget): + self.destroy() + + def on_button_logfile_clicked(self, widget): + try: + cmd = ["sudo", "-u", fn.sudo_username, "xdg-open", fn.event_log_file] + fn.subprocess.Popen( + cmd, + shell=False, + stdout=fn.subprocess.PIPE, + stderr=fn.subprocess.STDOUT, + ) + + except Exception as e: + fn.logger.error("Exception in on_button_logfile_clicked(): %s" % e) + + +class Bootloader(GObject.Object): + __gtype_name__ = "Bootloader" + + def __init__(self, id, name): + super().__init__() + + self.id = id + self.name = name + + @GObject.Property + def bootloader_id(self): + return self.id + + @GObject.Property + def bootloader_name(self): + return self.name \ No newline at end of file diff --git a/ui/SplashScreen.py b/ui/SplashScreen.py new file mode 100644 index 0000000..73639b7 --- /dev/null +++ b/ui/SplashScreen.py @@ -0,0 +1,30 @@ +import gi +import functions as fn + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, Gio + +base_dir = fn.os.path.abspath(fn.os.path.join(fn.os.path.dirname(__file__), "..")) + + +class SplashScreen(Gtk.Window): + def __init__(self, app_name, **kwargs): + super().__init__(**kwargs) + self.set_decorated(False) + self.set_resizable(False) + self.set_default_size(600, 400) + + self.set_modal(True) + self.set_title(app_name) + self.set_icon_name("archlinux-kernel-manager-tux") + + tux_icon = Gtk.Picture.new_for_file( + file=Gio.File.new_for_path( + fn.os.path.join(base_dir, "images/600x400/akm-tux-splash.png") + ) + ) + + tux_icon.set_content_fit(content_fit=Gtk.ContentFit.FILL) + + self.set_child(child=tux_icon) + self.present() \ No newline at end of file diff --git a/ui/Stack.py b/ui/Stack.py new file mode 100644 index 0000000..a5f5850 --- /dev/null +++ b/ui/Stack.py @@ -0,0 +1,30 @@ +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk + + +class Stack(Gtk.Stack): + def __init__(self, transition_type): + super(Stack, self).__init__() + + # self.set_transition_type(Gtk.StackTransitionType.ROTATE_LEFT) + if transition_type == "ROTATE_LEFT": + transition_type = Gtk.StackTransitionType.ROTATE_LEFT + if transition_type == "ROTATE_RIGHT": + transition_type = Gtk.StackTransitionType.ROTATE_RIGHT + if transition_type == "CROSSFADE": + transition_type = Gtk.StackTransitionType.CROSSFADE + if transition_type == "SLIDE_UP": + transition_type = Gtk.StackTransitionType.SLIDE_UP + if transition_type == "SLIDE_DOWN": + transition_type = Gtk.StackTransitionType.SLIDE_DOWN + if transition_type == "OVER_DOWN": + transition_type = Gtk.StackTransitionType.OVER_DOWN + + self.set_transition_type(transition_type) + self.set_hexpand(True) + self.set_vexpand(True) + self.set_transition_duration(250) + self.set_hhomogeneous(False) + self.set_vhomogeneous(False) \ No newline at end of file