#!/usr/bin/env python3 import gi gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, GLib, Gio import subprocess import re import sys import os class BootEntry: def __init__(self, num, name, active=False): self.num = num self.name = name self.active = active class AdvancedRebootWindow(Adw.ApplicationWindow): def __init__(self, **kwargs): super().__init__(**kwargs) self.set_default_size(600, 500) self.set_title("Advanced Reboot Options") main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) main_box.set_spacing(0) header = Adw.HeaderBar() main_box.append(header) title_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) title_box.set_margin_top(20) title_box.set_margin_bottom(20) title_box.set_margin_start(20) title_box.set_margin_end(20) title_label = Gtk.Label(label="Choose Boot Option") title_label.add_css_class("title-1") title_box.append(title_label) main_box.append(title_box) separator1 = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) main_box.append(separator1) scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolled.set_vexpand(True) scrolled.set_margin_top(12) scrolled.set_margin_bottom(12) scrolled.set_margin_start(12) scrolled.set_margin_end(12) self.listbox = Gtk.ListBox() self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE) self.listbox.add_css_class("boxed-list") self.boot_entries = self.get_boot_entries() self.selected_entry = None if not self.boot_entries: error_label = Gtk.Label(label="No EFI boot entries found!") error_label.add_css_class("dim-label") error_label.set_margin_top(40) error_label.set_margin_bottom(40) scrolled.set_child(error_label) else: for entry in self.boot_entries: row = self.create_boot_entry_row(entry) self.listbox.append(row) self.listbox.connect("row-selected", self.on_row_selected) self.listbox.connect("row-activated", self.on_row_activated) scrolled.set_child(self.listbox) main_box.append(scrolled) separator2 = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) main_box.append(separator2) button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) button_box.set_margin_top(12) button_box.set_margin_bottom(12) button_box.set_margin_start(12) button_box.set_margin_end(12) button_box.set_halign(Gtk.Align.END) cancel_btn = Gtk.Button(label="Cancel") cancel_btn.connect("clicked", lambda x: self.close()) button_box.append(cancel_btn) self.reboot_to_btn = Gtk.Button(label="Reboot to Selected") self.reboot_to_btn.add_css_class("destructive-action") self.reboot_to_btn.set_sensitive(False) self.reboot_to_btn.connect("clicked", self.on_reboot) button_box.append(self.reboot_to_btn) main_box.append(button_box) self.set_content(main_box) def create_boot_entry_row(self, entry): row = Adw.ActionRow() clean_name = self.clean_boot_name(entry.name) row.set_title(clean_name) subtitle = f"Boot entry: {entry.num}" if entry.active: subtitle += " • Currently active" row.set_subtitle(subtitle) if entry.active: checkmark = Gtk.Image.new_from_icon_name("object-select-symbolic") row.add_suffix(checkmark) row.entry = entry return row def clean_boot_name(self, name): for separator in ['HD(', 'PciRoot(', 'File(', '\t']: if separator in name: name = name.split(separator)[0] return name.strip() def get_boot_entries(self): try: result = subprocess.run(['efibootmgr'], capture_output=True, text=True, check=True) entries = [] current_match = re.search(r'BootCurrent: ([0-9A-F]+)', result.stdout) current = current_match.group(1) if current_match else None for line in result.stdout.split('\n'): match = re.match(r'Boot([0-9A-F]+)\*?\s+(.+)', line) if match: num = match.group(1) name = match.group(2) active = (num == current) entries.append(BootEntry(num, name, active)) return entries except subprocess.CalledProcessError: return [] except FileNotFoundError: return [] except Exception as e: print(f"Error getting boot entries: {e}", file=sys.stderr) return [] def on_row_selected(self, listbox, row): if row is None: self.selected_entry = None self.reboot_to_btn.set_sensitive(False) self.reboot_to_btn.set_label("Reboot to Selected") return self.selected_entry = row.entry self.reboot_to_btn.set_sensitive(True) clean_name = self.clean_boot_name(row.entry.name) self.reboot_to_btn.set_label(f"Reboot to {clean_name}") def on_row_activated(self, listbox, row): self.selected_entry = row.entry self.reboot_to_btn.set_sensitive(True) self.on_reboot(None) def on_reboot(self, button): if not self.selected_entry: return clean_name = self.clean_boot_name(self.selected_entry.name) dialog = Adw.MessageDialog.new(self) dialog.set_heading("Confirm Reboot") dialog.set_body(f"Selected: {clean_name}") dialog.set_body_use_markup(True) dialog.add_response("cancel", "Cancel") dialog.add_response("reboot", "Reboot Now") dialog.set_response_appearance("reboot", Adw.ResponseAppearance.DESTRUCTIVE) dialog.set_default_response("cancel") dialog.set_close_response("cancel") dialog.connect("response", self.on_reboot_response) dialog.present() def on_reboot_response(self, dialog, response): if response == "reboot": try: subprocess.run(['efibootmgr', '--bootnext', self.selected_entry.num], check=True) subprocess.run(['systemctl', 'reboot'], check=True) except subprocess.CalledProcessError as e: self.show_error("Failed to set boot entry", f"Command failed with exit code {e.returncode}") except Exception as e: self.show_error("Error", str(e)) def show_error(self, heading, body): dialog = Adw.MessageDialog.new(self) dialog.set_heading(heading) dialog.set_body(body) dialog.add_response("ok", "OK") dialog.set_default_response("ok") dialog.present() class AdvancedRebootApp(Adw.Application): def __init__(self): super().__init__(application_id='com.example.AdvancedReboot', flags=Gio.ApplicationFlags.FLAGS_NONE) def do_activate(self): win = AdvancedRebootWindow(application=self) win.present() def get_user_theme_preference(): try: original_user = os.environ.get('SUDO_USER') or os.environ.get('PKEXEC_UID') if original_user: if original_user.isdigit(): import pwd original_user = pwd.getpwuid(int(original_user)).pw_name import pwd user_home = pwd.getpwnam(original_user).pw_dir user_runtime_dir = f"/run/user/{pwd.getpwnam(original_user).pw_uid}" result = subprocess.run( ['sudo', '-u', original_user, 'gsettings', 'get', 'org.gnome.desktop.interface', 'color-scheme'], capture_output=True, text=True, env={ 'DBUS_SESSION_BUS_ADDRESS': f'unix:path={user_runtime_dir}/bus', 'HOME': user_home } ) if result.returncode == 0: theme = result.stdout.strip().strip("'") return theme except Exception as e: print(f"Could not get user theme: {e}", file=sys.stderr) return None if __name__ == '__main__': if os.geteuid() != 0: print("Error: This program must be run as root", file=sys.stderr) print(f"Use: pkexec {sys.argv[0]}", file=sys.stderr) sys.exit(1) user_theme = get_user_theme_preference() app = AdvancedRebootApp() if user_theme: style_manager = Adw.StyleManager.get_default() if 'dark' in user_theme.lower(): style_manager.set_color_scheme(Adw.ColorScheme.FORCE_DARK) elif 'light' in user_theme.lower(): style_manager.set_color_scheme(Adw.ColorScheme.FORCE_LIGHT) else: style_manager.set_color_scheme(Adw.ColorScheme.DEFAULT) app.run(None)