Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ubuntu 24.04 #7

Merged
merged 13 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ __pycache__
deb_dist
*.tar.gz
*.egg-info
tmp
tmp

grub*.cfg
*.deb
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@
Rebuilt package for Ubuntu 22.04 Jammy Jellyfish

* 0.0.8: Build for Ubuntu 23.04
Rebuilt package for Ubuntu 23.04 Lunar Lobster
Rebuilt package for Ubuntu 23.04 Lunar Lobster

* 0.0.9: Parsing and menu hover bugfix
Bugfix, some menu items weren't parsed correctly.
Bugfix, menu items weren't nesting correctly.
Bugfix, menu items were being activated on hover.
Rebuilt package for Ubuntu 24.04 Noble Numbat.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ Clone this repo, then run the python script.

```
cd src
./grub-reboot-picker.py
sudo ./grub-reboot-picker.py
```


Sudo is required here because grub.cfg may not be readable (0600 permission)

## Building a distributable

Expand All @@ -80,12 +80,16 @@ sudo apt install python3-stdeb fakeroot python3-all dh-python lintian devscripts
Then to build:

```
# Set the version
# Set the version and suite (noble, jammy, etc)
nano version.sh
# Update the changelog, carefully
nano CHANGELOG.md
# Read the version
source version.sh
# Clean everything
rm -rf deb_dist dist *.tar.gz *.egg* build tmp
# Create the source and deb
python3 setup.py --command-packages=stdeb.command bdist_deb
python3 setup.py --command-packages=stdeb.command sdist_dsc --suite $suite bdist_deb
# Run a lint against this deb
lintian deb_dist/grub-reboot-picker_$version-1_all.deb
# Look at information about this deb
Expand Down
2 changes: 1 addition & 1 deletion com.mendhak.grubrebootpicker.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Type=Application
Name=Grub Reboot Picker
GenericName=Grub Reboot Picker
Comment=Pick an OS to reboot into
Exec=/usr/sbin/grub-reboot-picker
Exec=pkexec /usr/sbin/grub-reboot-picker
Terminal=false
Icon=un-reboot
Keywords=reboot;grub;restart;OS;windows;ubuntu;
Expand Down
12 changes: 12 additions & 0 deletions com.mendhak.grubrebootpicker.policy
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,16 @@
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/shutdown</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">false</annotate>
</action>
<action id="org.freedesktop.policykit.pkexec.grub-reboot-picker">
<description>Run grub-reboot-picker</description>
<message>Run grub-reboot-picker so it can read grub.cfg</message>
<icon_name>un-reboot</icon_name>
<defaults>
<allow_any>yes</allow_any>
<allow_inactive>yes</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/grub-reboot-picker</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">false</annotate>
</action>
</policyconfig>
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
ignore-install-requires: True
depends3: python3-gi, python3-gi-cairo, gir1.2-gtk-3.0, gir1.2-appindicator3-0.1
package3: grub-reboot-picker
suite: lunar
section: utils
copyright-file: LICENSE.md
build-depends: python3-stdeb, fakeroot, python3-all, dh-python, python3-setuptools
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
into different OSes based on the grub menu.
Basically, a wrapper around grub-reboot command.
The icon sits in the tray area in gnome.
Tested on Ubuntu 20.04, 22.04, 23.04.
Tested on Ubuntu 20.04, 22.04, 23.04, 24.04
""",
long_description_content_type="text/plain",
url="https://github.com/mendhak/grub-reboot-picker",
Expand Down
227 changes: 71 additions & 156 deletions src/grub-reboot-picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,135 +9,91 @@

SHOW_GRUB_MENU_SUB_MENUS = True
DEVELOPMENT_MODE = False
GRUB_CONFIG_PATH = "/boot/grub/grub.cfg"
if DEVELOPMENT_MODE:
GRUB_CONFIG_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "grub.test3.cfg")
GRUB_CONFIG_PATH = "/boot/grub/grub.cfg"

icon_name = "un-reboot"


def get_grub_entries():
def get_all_grub_entries(file_path, include_submenus=True):
"""
Builds JSON from grub menu entries, just the top level ones, like this:
Build a dictionary of Grub menu items with sub menu items if applicable.
Simply if it has child items it's a 'submenu' else it's just a top level menu.
{
"menuitems": [
{
"name": "Ubuntu"
},
{
"name": "Windows Boot Manager (on /dev/nvme0n1p2)"
},
{
"name": "UEFI Firmware Settings"
}
]
'Ubuntu': [],
'Advanced options for Ubuntu': [
'Ubuntu, with Linux 6.8.0-39-generic',
'Ubuntu, with Linux 6.8.0-39-generic (recovery mode)'
],
'Memory test (memtest86+x64.bin)': [],
'Memory test (memtest86+x64.bin, serial console)': [],
'UEFI Firmware Settings': []
}
"""
pattern = re.compile("^menuentry '([^']*)'")
grub_entries = {}
grub_entries.clear()
grub_entries['menuitems'] = []

for i, line in enumerate(open('/boot/grub/grub.cfg')):
for match in re.finditer(pattern, line):
grub_entry = {}
grub_entry['name'] = match.group(1)
grub_entries['menuitems'].append(grub_entry)
# grub_entries.append(match.group(1))
return grub_entries


def get_grub_entries_with_submenus():
"""
Builds JSON from grub menu entries, with sub menu items like this:
{
"menuitems": [
{
"name": "Ubuntu",
"submenuitems": [
{
"name": "Ubuntu, with Linux 5.4.0-31-generic"
},
{
"name": "Ubuntu, with Linux 5.4.0-31-generic (recovery mode)"
},
{
"name": "Ubuntu, with Linux 5.4.0-29-generic"
},
{
"name": "Ubuntu, with Linux 5.4.0-29-generic (recovery mode)"
}
]
},
{
"name": "Windows Boot Manager (on /dev/nvme0n1p2)",
"submenuitems": []
},
{
"name": "UEFI Firmware Settings",
"submenuitems": []
}
]
}

"""
menu_pattern = re.compile("^menuentry '([^']*)'")
submenu_pattern = re.compile("^submenu '([^']*)'")
submenu_entry_pattern = re.compile("^\\s+menuentry '([^']*)'")

grub_entries = {}
grub_entries.clear()
grub_entries['menuitems'] = []
menu_entry_match = None
current_submenu = None
submenu_entry_match = None

for i, line in enumerate(open('/boot/grub/grub.cfg')):
menu_entry_match = re.match(menu_pattern, line)
if menu_entry_match:
grub_entry = {}
grub_entry['name'] = menu_entry_match.group(1)
grub_entries['menuitems'].append(grub_entry)
continue

submenu_entry_match = re.match(submenu_pattern, line)
if submenu_entry_match:
grub_entry = {}
grub_entry['name'] = submenu_entry_match.group(1)
grub_entries['menuitems'].append(grub_entry)
# print(submenu_entry_match.group(1))
current_submenu = grub_entry
current_submenu['submenuitems'] = []
continue

if current_submenu:
submenu_entry_match = re.match(submenu_entry_pattern, line)
if submenu_entry_match:
# print(submenu_entry_match.group(1))
grub_entry = {}
grub_entry['name'] = submenu_entry_match.group(1)
current_submenu['submenuitems'].append(grub_entry)
return grub_entries
with open(file_path, 'r') as file:
lines = file.readlines()

menu_pattern = re.compile("^\\s*menuentry ['\"]([^'\"]*)['\"]")
submenu_pattern = re.compile("^\\s*submenu ['\"]([^']*)['\"]")
closing_brace_pattern = re.compile("^\\s*}")

menu_entries = {}

processing_submenu = False
submenu_item_added = False

for line in lines:
submenu_match = submenu_pattern.match(line)
menu_match = menu_pattern.match(line)
closing_brace_match = closing_brace_pattern.match(line)

if submenu_match:
submenu_title = submenu_match.group(1)
menu_entries[submenu_title] = []
processing_submenu = True
elif menu_match:
menu_title = menu_match.group(1)
if processing_submenu:
menu_entries[submenu_title].append(menu_title)
submenu_item_added = True
else:
menu_entries[menu_title] = []
elif closing_brace_match:
# submenu_item_added would match for the first nested closing brace,
# then processing_submenu for the top level closing brace.
if submenu_item_added:
submenu_item_added = False
elif processing_submenu:
processing_submenu = False

if not include_submenus:
for k, v in list(menu_entries.items()):
if len(v) > 0:
del menu_entries[k]

return menu_entries


def build_menu():
menu = Gtk.Menu()

if SHOW_GRUB_MENU_SUB_MENUS:
grub_entries = get_grub_entries_with_submenus()
else:
grub_entries = get_grub_entries()
grub_entries = get_all_grub_entries(GRUB_CONFIG_PATH, SHOW_GRUB_MENU_SUB_MENUS)

print(grub_entries)

for grub_entry in grub_entries['menuitems']:
menuitem = Gtk.MenuItem(label=grub_entry['name'])
if len(grub_entry.get('submenuitems', [])) == 0:
for grub_entry, grub_children in grub_entries.items():
menuitem = Gtk.MenuItem(label=grub_entry)
if len(grub_children) == 0:
menuitem.connect('activate', do_grub_reboot, grub_entry)
submenu = Gtk.Menu()
for grub_entry_submenuitem in grub_entry.get('submenuitems', []):
# print(grub_entry_submenuitem)
submenu_item = Gtk.MenuItem(label=grub_entry_submenuitem['name'])
submenu_item.connect('activate', do_grub_reboot, grub_entry_submenuitem,
grub_entry)
submenu.append(submenu_item)
menuitem.set_submenu(submenu)
else:
submenu = Gtk.Menu()
for grub_child in grub_children:
submenu_item = Gtk.MenuItem(label=grub_child)
submenu_item.connect('activate', do_grub_reboot, grub_child, grub_entry)
submenu.append(submenu_item)
menuitem.set_submenu(submenu)

menu.append(menuitem)

Expand All @@ -155,9 +111,9 @@ def build_menu():

def do_grub_reboot(menuitem, grub_entry, parent_grub_entry=None):
if parent_grub_entry is not None:
grub_reboot_value = "{}>{}".format(parent_grub_entry['name'], grub_entry['name'])
grub_reboot_value = "{}>{}".format(parent_grub_entry, grub_entry)
else:
grub_reboot_value = "{}".format(grub_entry['name'])
grub_reboot_value = "{}".format(grub_entry)

if DEVELOPMENT_MODE:
print("pkexec grub-reboot '{}' && sleep 1 && pkexec reboot".format(grub_reboot_value))
Expand All @@ -175,37 +131,6 @@ def do_shutdown(_):
def quit(_):
Gtk.main_quit()

# win = Gtk.Window()
# win.connect("destroy", Gtk.main_quit)
# #win.set_icon_from_file("logo.svg")
# win.set_icon_name(icon_name)
# win.set_position(Gtk.WindowPosition.CENTER_ALWAYS)

# grid = Gtk.Grid()
# win.add(grid)

# button = Gtk.Button(label="Message")
# button.connect("clicked", on_button_clicked)
# button.set_size_request(300,100)
# #win.add(button)
# grid.add(button)

# label = Gtk.Label()
# label.set_label("Hello World")
# label.set_angle(25)
# label.set_halign(Gtk.Align.END)
# grid.attach(label, 1, 2, 2, 1)
# # win.add(label)

# statusicon = Gtk.StatusIcon()
# statusicon.set_from_file("logo.svg")
# statusicon.set_visible(True)
# statusicon.set_has_tooltip(True)

# indicator = AppIndicator3.Indicator.new("customtray",
# os.path.abspath("logo.svg"),
# AppIndicator3.IndicatorCategory.APPLICATION_STATUS)


indicator = AppIndicator3.Indicator.new(
"customtray", icon_name,
Expand All @@ -214,14 +139,4 @@ def quit(_):
indicator.set_menu(build_menu())


# def reset_menu():
# indicator.set_menu(build_menu())
# return True


# GLib.timeout_add(5000, reset_menu)

# win.set_default_size(500,500)
# win.show_all()

Gtk.main()
3 changes: 2 additions & 1 deletion version.sh
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export version=0.0.8
export suite=focal
export version=0.0.9+$suite
Loading