From 42b3bc1f2edccd7af7ad3c10f887fad19f34ee4a Mon Sep 17 00:00:00 2001
From: Chris Rios <31458662+robotman40@users.noreply.github.com>
Date: Sat, 15 Nov 2025 16:57:40 -0800
Subject: [PATCH 1/6] Add files via upload
---
usr/lib/linuxmint/mintdrivers/mintdrivers.py | 25 ++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/usr/lib/linuxmint/mintdrivers/mintdrivers.py b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
index 3d256e0..dac8b94 100755
--- a/usr/lib/linuxmint/mintdrivers/mintdrivers.py
+++ b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
@@ -62,6 +62,9 @@ def __init__(self):
else:
print("can not get name for object '%s'" % o)
+ if self.is_secure_boot_enabled():
+ self.show_secure_boot_warning()
+
self.window_main.show()
self.window_main.set_title(_("Driver Manager"))
@@ -117,6 +120,28 @@ def show_page(self, page):
self.builder.get_object("spinner").stop()
self.builder.get_object("stack").set_visible_child_name(page)
+ def show_secure_boot_warning(self):
+ dialog = Gtk.MessageDialog(
+ title=_("Secure Boot Warning"),
+ flags=Gtk.DialogFlags.MODAL,
+ type=Gtk.MessageType.WARNING,
+ buttons=Gtk.ButtonsType.OK,
+ message_format=_("Warning: Secure Boot is enabled on your computer.\n\n"
+ "Some of the drivers you are about to install may not work until Secure Boot is disabled.\n\n"
+ "Please refer to your computer's documentation for instructions on how to disable Secure Boot."),
+ )
+ dialog.run()
+ dialog.destroy()
+
+ def is_secure_boot_enabled(self):
+ try:
+ result = subprocess.run(['mokutil', '--sb-state'], capture_output=True, text=True)
+ output = result.stdout.lower()
+ return 'secureboot enabled' in output
+ except Exception as e:
+ print(f"Error checking Secure Boot status: {e}")
+ return False
+
def on_error_button(self, button):
self.show_page("drivers_page")
From fd68890351fc4f686b2ed3aa173197e4504aff59 Mon Sep 17 00:00:00 2001
From: Chris Rios <31458662+robotman40@users.noreply.github.com>
Date: Sat, 15 Nov 2025 16:59:06 -0800
Subject: [PATCH 2/6] Added secure boot notice when enabled
Some Linux Mint users report being unable to boot into LM or having missing functionality when installing third-party drivers. This is often solved by disabling Secure Boot in UEFI, so I wanted to add a notice for this.
---
mintdrivers.py | 795 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 795 insertions(+)
create mode 100644 mintdrivers.py
diff --git a/mintdrivers.py b/mintdrivers.py
new file mode 100644
index 0000000..dac8b94
--- /dev/null
+++ b/mintdrivers.py
@@ -0,0 +1,795 @@
+#! /usr/bin/python3
+# -*- coding=utf-8 -*-
+
+import gettext
+import locale
+import os
+import sys
+import apt
+import subprocess
+import gi
+gi.require_version("GdkPixbuf", "2.0")
+gi.require_version("Gtk", "3.0")
+gi.require_version("XApp", "1.0")
+gi.require_version("PackageKitGlib", "1.0")
+from gi.repository import GdkPixbuf, Gtk, XApp, Gio, GLib
+from gi.repository import PackageKitGlib as packagekit
+from UbuntuDrivers import detect
+import psutil
+import re
+import urllib
+import threading
+
+# Used as a decorator to run things in the background
+def _async(func):
+ def wrapper(*args, **kwargs):
+ thread = threading.Thread(target=func, args=args, kwargs=kwargs)
+ thread.daemon = True
+ thread.start()
+ return thread
+ return wrapper
+
+# Used as a decorator to run things in the main loop, from another thread
+def idle(func):
+ def wrapper(*args):
+ GLib.idle_add(func, *args)
+ return wrapper
+
+APP = 'mintdrivers'
+LOCALE_DIR = "/usr/share/locale"
+locale.bindtextdomain(APP, LOCALE_DIR)
+gettext.bindtextdomain(APP, LOCALE_DIR)
+gettext.textdomain(APP)
+_ = gettext.gettext
+
+class Application:
+
+ def __init__(self):
+
+ self.test_mode = False
+ if len(sys.argv) > 1 and sys.argv[1] == "test":
+ self.test_mode = True
+ print("Test mode detected, adding a dummy device.")
+
+ self.builder = Gtk.Builder()
+ self.builder.set_translation_domain(APP)
+ self.builder.add_from_file("/usr/share/linuxmint/mintdrivers/main.ui")
+ self.builder.connect_signals(self)
+ for o in self.builder.get_objects():
+ if issubclass(type(o), Gtk.Buildable):
+ name = Gtk.Buildable.get_name(o)
+ setattr(self, name, o)
+ else:
+ print("can not get name for object '%s'" % o)
+
+ if self.is_secure_boot_enabled():
+ self.show_secure_boot_warning()
+
+ self.window_main.show()
+
+ self.window_main.set_title(_("Driver Manager"))
+
+ self.window_main.connect("delete_event", self.quit_application)
+
+ self.button_driver_revert = Gtk.Button(label=_("Re_vert"), use_underline=True)
+ self.button_driver_revert.connect("clicked", self.on_driver_changes_revert)
+ self.button_driver_apply = Gtk.Button(label=_("_Apply Changes"), use_underline=True)
+ self.button_driver_apply.connect("clicked", self.on_driver_changes_apply)
+ self.button_driver_cancel = Gtk.Button(label=_("_Cancel"), use_underline=True)
+ self.button_driver_cancel.connect("clicked", self.on_driver_changes_cancel)
+ self.button_driver_restart = Gtk.Button(label=_("_Restart..."), use_underline=True)
+ self.button_driver_restart.connect("clicked", self.on_driver_restart_clicked)
+ self.button_driver_revert.set_sensitive(False)
+ self.button_driver_revert.set_visible(True)
+ self.button_driver_apply.set_sensitive(False)
+ self.button_driver_apply.set_visible(True)
+ self.button_driver_cancel.set_visible(False)
+ self.button_driver_restart.set_visible(False)
+ self.box_driver_action.pack_end(self.button_driver_apply, False, False, 0)
+ self.box_driver_action.pack_end(self.button_driver_revert, False, False, 0)
+ self.box_driver_action.pack_end(self.button_driver_restart, False, False, 0)
+ self.box_driver_action.pack_end(self.button_driver_cancel, False, False, 0)
+
+ self.builder.get_object("error_button").connect("clicked", self.on_error_button)
+ self.builder.get_object("button_mount_media").connect("clicked", self.on_mount_media_button)
+ self.builder.get_object("button_offline").connect("clicked", self.check_internet_or_live_media)
+
+ self.progress_bar = Gtk.ProgressBar(valign=Gtk.Align.CENTER)
+ self.box_driver_action.pack_end(self.progress_bar, True, True, 0)
+ self.progress_bar.set_visible(False)
+
+ self.needs_restart = False
+ self.needs_broadcom_reload = False
+ self.live_mode = False
+
+ self.show_page("refresh_page")
+
+ with open('/proc/cmdline') as f:
+ cmdline = f.read()
+ if ("boot=casper" in cmdline) or ("boot=live" in cmdline):
+ print ("Live mode detected")
+ self.live_mode = True
+ self.update_cache()
+ else:
+ self.check_internet_or_live_media()
+
+ def show_page(self, page):
+ if page == "refresh_page":
+ self.builder.get_object("spinner").start()
+ else:
+ self.builder.get_object("spinner").stop()
+ self.builder.get_object("stack").set_visible_child_name(page)
+
+ def show_secure_boot_warning(self):
+ dialog = Gtk.MessageDialog(
+ title=_("Secure Boot Warning"),
+ flags=Gtk.DialogFlags.MODAL,
+ type=Gtk.MessageType.WARNING,
+ buttons=Gtk.ButtonsType.OK,
+ message_format=_("Warning: Secure Boot is enabled on your computer.\n\n"
+ "Some of the drivers you are about to install may not work until Secure Boot is disabled.\n\n"
+ "Please refer to your computer's documentation for instructions on how to disable Secure Boot."),
+ )
+ dialog.run()
+ dialog.destroy()
+
+ def is_secure_boot_enabled(self):
+ try:
+ result = subprocess.run(['mokutil', '--sb-state'], capture_output=True, text=True)
+ output = result.stdout.lower()
+ return 'secureboot enabled' in output
+ except Exception as e:
+ print(f"Error checking Secure Boot status: {e}")
+ return False
+
+ def on_error_button(self, button):
+ self.show_page("drivers_page")
+
+ def update_cache(self):
+ print("Updating cache")
+ self.show_page("refresh_page")
+ task = packagekit.Task()
+ task.refresh_cache_async(True, Gio.Cancellable(), self.on_cache_update_progress, (None, ), self.on_cache_update_finished, (None, ))
+
+ def on_error(self, error):
+ # Returns False if the error was from cancelling or failing to authenticate.
+ # This will bring the ui back to pre-apply state. Returning True will reset
+ # entirely.
+
+ # it thinks it's a PkClientError, but it's really PkErrorEnum
+ # the GError code is set to 0xFF + code
+ if error.code >= 0xFF:
+ real_code = error.code - 0xFF
+
+ if real_code == packagekit.ErrorEnum.NOT_AUTHORIZED:
+ # Silently ignore auth failures or cancellation.
+ return False
+
+ self.show_page("error_page")
+ self.builder.get_object("error_label").set_label(error.message)
+ return True
+
+ def on_cache_update_progress(self, progress, ptype, data=None):
+ pass
+
+ def on_cache_update_finished(self, source, result, data=None):
+ print("Cache updated")
+ XApp.set_window_progress(self.window_main, 0)
+ self.get_drivers_async()
+
+ def quit_application(self, widget=None, event=None):
+ self.cleanup_live_media()
+ Gtk.main_quit()
+
+ def cleanup_live_media(self):
+ subprocess.call(["sudo", "mintdrivers-remove-live-media"])
+
+ def check_connectivity(self, reference):
+ try:
+ urllib.request.urlopen(reference, timeout=10)
+ return True
+ except:
+ return False
+
+ @idle
+ def check_internet_or_live_media(self, widget=None):
+ self.show_page("refresh_page")
+ print ("Checking Internet connectivity...")
+ try:
+ urllib.request.urlopen("https://archive.ubuntu.com", timeout=10)
+ # We're online
+ print (" --> Computer is online")
+ self.update_cache()
+ return
+ except:
+ print (" --> Computer is offline")
+
+ # We're offline, let's look for a live media
+ print ("Checking for a live media...")
+ mount_point = None
+ partitions = psutil.disk_partitions()
+ for p in partitions:
+ if p.fstype == "iso9660":
+ mount_point = p.mountpoint
+ print (" --> Found: %s at %s" % (p.device, p.mountpoint))
+ break
+
+ if mount_point is None:
+ # Offline and no live media, show the offline page
+ print (" --> None found.")
+ self.show_page("offline_page")
+ return
+
+ # We're offline but an ISO was detected
+ # Let's make sure it's mounted as a repository
+ if os.path.exists("/media/mintdrivers/.disk/info"):
+ print (" --> Mounted in /media/mintdrivers")
+ self.update_cache()
+ else:
+ print (" --> Not mounted in /media/mintdrivers")
+ self.show_page("media_page")
+
+ def on_mount_media_button(self, button):
+ print("Mounting live media")
+ self.show_page("refresh_page")
+ self.mount_live_media()
+
+ @_async
+ def mount_live_media(self):
+ subprocess.call(["/usr/bin/pkexec", "mintdrivers-add-live-media"])
+ self.check_internet_or_live_media()
+
+ def on_driver_changes_progress(self, progress, ptype, data=None):
+ self.button_driver_revert.set_visible(False)
+ self.button_driver_apply.set_visible(False)
+ self.button_driver_restart.set_visible(False)
+ self.button_driver_cancel.set_visible(True)
+ self.progress_bar.set_visible(True)
+ self.progress_bar.set_visible(True)
+
+ if progress.get_status() == packagekit.StatusEnum.DOWNLOAD:
+ self.label_driver_action.set_label(_("Downloading drivers..."))
+ elif progress.get_status() == packagekit.StatusEnum.INSTALL:
+ self.label_driver_action.set_label(_("Installing drivers..."))
+ elif progress.get_status() == packagekit.StatusEnum.REMOVE:
+ self.label_driver_action.set_label(_("Removing drivers..."))
+ elif progress.get_status() == packagekit.StatusEnum.CANCEL:
+ self.label_driver_action.set_label(_("Cancelling..."))
+ elif progress.get_status() == packagekit.StatusEnum.LOADING_CACHE:
+ self.label_driver_action.set_label(_("Loading cache..."))
+ else:
+ self.label_driver_action.set_label("")
+ if ptype == packagekit.ProgressType.PERCENTAGE:
+ prog_value = progress.get_property('percentage')
+ self.progress_bar.set_fraction(prog_value / 100.0)
+ XApp.set_window_progress(self.window_main, prog_value)
+
+ def on_driver_changes_finish(self, source, result, installs):
+ errors = False
+ try:
+ self.pk_task.generic_finish(result)
+ except GLib.Error as e:
+ errors = True
+ if self.on_error(e):
+ # real failure
+ self.on_driver_changes_revert()
+ self.clear_changes()
+ else:
+ self.button_driver_revert.set_sensitive(bool(self.driver_changes))
+ self.button_driver_apply.set_sensitive(bool(self.driver_changes))
+
+ if installs is None or len(installs) == 0 or errors:
+ if self.needs_broadcom_reload:
+ print("Reloading Broadcom modules")
+ subprocess.call(["sudo", "mintdrivers-load-broadcom-modules"])
+ self.needs_restart = (not errors)
+ self.progress_bar.set_visible(False)
+ self.apt_cache = apt.Cache()
+ self.set_driver_action_status()
+ self.update_label_and_icons_from_status()
+ self.button_driver_revert.set_visible(True)
+ self.button_driver_apply.set_visible(True)
+ self.button_driver_cancel.set_visible(False)
+ self.scrolled_window_drivers.set_sensitive(True)
+ XApp.set_window_progress(self.window_main, 0)
+ else:
+ print("Installing", installs)
+ self.pk_task.install_packages_async(installs,
+ self.cancellable, # cancellable
+ self.on_driver_changes_progress,
+ (None, ), # progress data
+ self.on_driver_changes_finish, # GAsyncReadyCallback
+ None # callback data
+ )
+
+ def on_driver_changes_apply(self, button):
+ self.pk_task = packagekit.Task()
+ installs = []
+ removals = []
+
+ for pkg in self.driver_changes:
+ if pkg.is_installed:
+ removals.append(self.get_package_id(pkg.installed))
+ # The main NVIDIA package is only a metapackage.
+ # We need to collect its dependencies, so that
+ # we can uninstall the driver properly.
+ if 'nvidia' in pkg.shortname:
+ for dep in self.get_dependencies(self.apt_cache, pkg.shortname, 'nvidia'):
+ dep_pkg = self.apt_cache[dep]
+ if dep_pkg.is_installed:
+ removals.append(self.get_package_id(dep_pkg.installed))
+ else:
+ installs.append(self.get_package_id(pkg.candidate))
+ if pkg.shortname == "broadcom-sta-dkms":
+ self.needs_broadcom_reload = True
+
+ self.cancellable = Gio.Cancellable()
+ try:
+ if len(removals) > 0:
+ try:
+ # Try to purge (Mint specific version of packagekit)
+ print("Purging", removals)
+ self.pk_task.purge_packages_async(removals,
+ False, # allow deps
+ True, # autoremove
+ self.cancellable, # cancellable
+ self.on_driver_changes_progress,
+ (None, ), # progress data
+ self.on_driver_changes_finish, # callback ready
+ installs # callback data
+ )
+ except:
+ # If purging isn't supported, just remove
+ print("Couldn't purge! Removing", removals)
+ self.pk_task.remove_packages_async(removals,
+ False, # allow deps
+ True, # autoremove
+ self.cancellable, # cancellable
+ self.on_driver_changes_progress,
+ (None, ), # progress data
+ self.on_driver_changes_finish, # callback ready
+ installs # callback data
+ )
+ elif len(installs) > 0:
+ print("Installing", installs)
+ self.pk_task.install_packages_async(installs,
+ self.cancellable, # cancellable
+ self.on_driver_changes_progress,
+ (None, ), # progress data
+ self.on_driver_changes_finish, # GAsyncReadyCallback
+ None # callback data
+ )
+
+ self.button_driver_revert.set_sensitive(False)
+ self.button_driver_apply.set_sensitive(False)
+ self.scrolled_window_drivers.set_sensitive(False)
+ except Exception as e:
+ print("Warning: install not completed successfully: {}".format(e))
+
+ def on_driver_changes_revert(self, button_revert=None):
+
+ # HACK: set all the "Do not use" first; then go through the list of the
+ # actually selected drivers.
+ for button in self.no_drv:
+ button.set_active(True)
+
+ for alias in self.orig_selection:
+ button = self.orig_selection[alias]
+ button.set_active(True)
+
+ self.clear_changes()
+
+ self.button_driver_revert.set_sensitive(False)
+ self.button_driver_apply.set_sensitive(False)
+
+ def on_driver_changes_cancel(self, button_cancel):
+ self.cancellable.cancel()
+ self.clear_changes()
+
+ def on_driver_restart_clicked(self, button_restart):
+ self.cleanup_live_media()
+ subprocess.call(['systemctl', 'reboot'])
+
+ def clear_changes(self):
+ self.orig_selection = {}
+ self.driver_changes = []
+
+ def on_driver_selection_changed(self, button, modalias, pkg_name=None):
+ if self.ui_building:
+ return
+
+ pkg = None
+ try:
+ if pkg_name:
+ pkg = self.apt_cache[pkg_name]
+ except KeyError:
+ pass
+
+ if button.get_active():
+ if pkg in self.driver_changes:
+ self.driver_changes.remove(pkg)
+
+ if (pkg is not None
+ and modalias in self.orig_selection
+ and button is not self.orig_selection[modalias]):
+ self.driver_changes.append(pkg)
+ else:
+ if pkg in self.driver_changes:
+ self.driver_changes.remove(pkg)
+
+ # for revert; to re-activate the original radio buttons.
+ if modalias not in self.orig_selection:
+ self.orig_selection[modalias] = button
+
+ if (pkg is not None
+ and pkg not in self.driver_changes
+ and pkg.is_installed):
+ self.driver_changes.append(pkg)
+
+ self.button_driver_revert.set_sensitive(bool(self.driver_changes))
+ self.button_driver_apply.set_sensitive(bool(self.driver_changes))
+
+
+ def get_package_id(self, ver):
+ """ Return the PackageKit package id """
+ assert isinstance(ver, apt.package.Version)
+ return "%s;%s;%s;" % (ver.package.shortname, ver.version, ver.package.architecture())
+
+ @staticmethod
+ def get_dependencies(apt_cache, package_name, pattern=None):
+ """ Get the package dependencies, which can be filtered out by a pattern """
+ dependencies = []
+ for or_group in apt_cache[package_name].candidate.dependencies:
+ for dep in or_group:
+ if dep.rawtype in ["Depends", "PreDepends"]:
+ dependencies.append(dep.name)
+ if pattern:
+ dependencies = [ x for x in dependencies if x.find(pattern) != -1 ]
+ return dependencies
+
+ def gather_device_data(self, device):
+ """Get various device data used to build the GUI.
+
+ return a tuple of (overall_status string, icon, drivers dict).
+ the drivers dict is using this form:
+ {"recommended/alternative": {pkg_name: {
+ 'selected': True/False
+ 'description': 'description'
+ 'builtin': True/False,
+ 'free': True/False
+ }
+ }}
+ "manually_installed": {"manual": {'selected': True, 'description': description_string}}
+ "no_driver": {"no_driver": {'selected': True/False, 'description': description_string}}
+
+ Please note that either manually_installed and no_driver are set to None if not applicable
+ (no_driver isn't present if there are builtins)
+ """
+
+ possible_overall_status = {
+ 'recommended': (_("This device is using the recommended driver."), "recommended-driver"),
+ 'alternative': (_("This device is using an alternative driver."), "other-driver"),
+ 'manually_installed': (_("This device is using a manually-installed driver."), "other-driver"),
+ 'no_driver': (_("This device is not working."), "disable-device")
+ }
+
+ returned_drivers = {'recommended': {}, 'alternative': {}, 'manually_installed': {}, 'no_driver': {}}
+ have_builtin = False
+ one_selected = False
+ try:
+ if device['manual_install']:
+ returned_drivers['manually_installed'] = {True: {'selected': True,
+ 'description': _("Continue using a manually installed driver")}}
+ except KeyError:
+ pass
+
+ """
+ - Never show server drivers.
+ - Pre 560: closed source drivers are recommended. Override any recommended -open drivers with the closed
+ source equivalent.
+ - Post 560 (02/13/2025: open-source drivers are recommended by their devs, and closed source drivers may
+ not be available for a given version.
+ """
+ ignored = []
+
+ for pkg_driver_name in device['drivers']:
+ if not pkg_driver_name.startswith("nvidia-"):
+ continue
+ if pkg_driver_name in ignored:
+ print("Skipping ignored NVIDIA driver '%s'" % pkg_driver_name)
+ continue
+ if pkg_driver_name.endswith(("-server", "-server-open")):
+ print("Ignoring server NVIDIA driver '%s'" % pkg_driver_name)
+ ignored.append(pkg_driver_name)
+ continue
+
+ try:
+ version = int(re.search(r"nvidia-driver-([0-9]{3}).*", pkg_driver_name).groups()[0])
+ open_preferred = version >= 560
+ except:
+ open_preferred = False
+
+ is_open = pkg_driver_name.endswith("-open")
+ current_driver = device['drivers'][pkg_driver_name]
+ recommended = current_driver.get("recommended", False) and current_driver.get("from_distro", False)
+
+ if is_open:
+ closed_name = pkg_driver_name.replace("-open", "")
+ has_closed = closed_name in device['drivers']
+ if has_closed:
+ if open_preferred:
+ print("Ignoring closed NVIDIA driver '%s' as the open one is preferred." % closed_name)
+ ignored.append(closed_name)
+ else:
+ print("Ignoring open NVIDIA driver '%s' as a closed one exists and is preferred." % pkg_driver_name)
+ if recommended:
+ device['drivers'][closed_name]["recommended"] = True
+ ignored.append(pkg_driver_name)
+
+ for pkg_driver_name in device['drivers']:
+ if pkg_driver_name in ignored:
+ continue
+ current_driver = device['drivers'][pkg_driver_name]
+
+ # get general status
+ driver_status = 'alternative'
+ try:
+ if (current_driver['recommended'] and current_driver['from_distro']):
+ driver_status = 'recommended'
+ except KeyError:
+ pass
+
+ builtin = False
+ try:
+ if current_driver['builtin']:
+ builtin = True
+ have_builtin = True
+ except KeyError:
+ pass
+
+ try:
+ pkg = self.apt_cache[pkg_driver_name]
+ installed = pkg.is_installed
+ if installed:
+ version = pkg.installed.version
+ summary = pkg.installed.summary
+ else:
+ version = pkg.candidate.version
+ summary = pkg.candidate.summary
+ description_line1 = "%s" % pkg.shortname
+ description_line2 = "%s %s" % (_("Version"), version)
+ description_line3 = "%s" % summary
+ if driver_status == 'recommended':
+ description_line1 = "%s (%s)" % (description_line1, _("recommended"))
+ if current_driver['free'] and pkg.shortname != "broadcom-sta-dkms" and (not pkg.shortname.startswith("nvidia-")):
+ description_line1 = "%s (%s)" % (description_line1, _("open-source"))
+ if pkg.shortname.startswith("firmware-b43"):
+ # B43 requires a connection to the Internet
+ description_line1 = "%s (%s)" % (description_line1, _("requires a connection to the Internet"))
+ description = "%s\n%s\n%s" % (description_line1, description_line2, description_line3)
+ except KeyError:
+ print("WARNING: a driver ({}) doesn't have any available package associated: {}".format(pkg_driver_name, current_driver))
+ continue
+
+ selected = False
+ if not builtin and not returned_drivers['manually_installed']:
+ selected = installed
+ if installed:
+ selected = True
+ one_selected = True
+
+ returned_drivers[driver_status].setdefault(pkg_driver_name, {'selected': selected,
+ 'description': description,
+ 'builtin': builtin,
+ 'free': current_driver['free']})
+
+ # adjust making the needed addition
+ if not have_builtin:
+ selected = False
+ if not one_selected:
+ selected = True
+ returned_drivers["no_driver"] = {True: {'selected': selected,
+ 'description': _("Do not use the device")}}
+ else:
+ # we have a builtin and no selection: builtin is the selected one then
+ if not one_selected:
+ for section in ('recommended', 'alternative'):
+ for pkg_name in returned_drivers[section]:
+ if returned_drivers[section][pkg_name]['builtin']:
+ returned_drivers[section][pkg_name]['selected'] = True
+
+ # compute overall status
+ for section in returned_drivers:
+ for keys in returned_drivers[section]:
+ if returned_drivers[section][keys]['selected']:
+ (overall_status, icon) = possible_overall_status[section]
+
+ return overall_status, icon, returned_drivers
+
+ def get_device_icon(self, device):
+ vendor = device.get('vendor', _('Unknown'))
+ model = device.get('model', _('Unknown'))
+ icon = "generic"
+ if "nvidia" in vendor.lower():
+ icon = "nvidia"
+ elif "radeon" in vendor.lower() or "radeon" in model.lower() or "Advanced Micro Devices" in vendor or "AMD" in vendor or "ATI" in vendor:
+ icon = "ati"
+ elif "broadcom" in vendor.lower():
+ icon = "broadcom"
+ elif "virtualbox" in vendor.lower() or "virtualbox" in model.lower():
+ icon = "virtualbox"
+
+ if "intel-microcode" in device['drivers']:
+ icon = "intel"
+ elif "amd64-microcode" in device['drivers']:
+ icon = "amd"
+
+ return GdkPixbuf.Pixbuf.new_from_file_at_size("/usr/share/linuxmint/mintdrivers/icons/%s.svg" % icon, 48, -1)
+
+ def get_cpu_name(self):
+ with open("/proc/cpuinfo") as cpuinfo:
+ for line in cpuinfo:
+ if "model name" in line:
+ return re.sub( ".*model name.*:", "", line, 1).strip()
+ return _("Processor")
+
+ @_async
+ def get_drivers_async(self):
+ self.apt_cache = apt.Cache()
+ self.devices = detect.system_device_drivers()
+ if self.test_mode:
+ dummy_device = {
+ 'modalias': '',
+ 'vendor': 'Linux Mint', 'model': 'Dummy Test Device',
+ 'drivers': {
+ 'mint-dev-pkg': {'free': False, 'from_distro': True, 'recommended': True},
+ 'mint-dev-pkg-debconf': {'free': False, 'from_distro': True, 'recommended': False},
+ 'linux-generic': {'free': True, 'builtin': True, 'from_distro': True, 'recommended': False}}
+ }
+ self.devices['dummy'] = dummy_device
+ self.show_drivers()
+
+ @idle
+ def show_drivers(self):
+ self.driver_changes = []
+ self.orig_selection = {}
+ # HACK: the case where the selection is actually "Do not use"; is a little
+ # tricky to implement because you can't check for whether a package is
+ # installed or any such thing. So let's keep a list of all the
+ # "Do not use" radios, set those active first, then iterate through
+ # orig_selection when doing a Reset.
+ self.no_drv = []
+ self.nonfree_drivers = 0
+ self.ui_building = True
+ self.dynamic_device_status = {}
+ drivers_found = False
+ if len(self.devices) != 0:
+ for device in sorted(self.devices.keys()):
+ (overall_status, icon, drivers) = self.gather_device_data(self.devices[device])
+ is_cpu = False
+ if "intel-microcode" in self.devices[device]['drivers'] or "amd64-microcode" in self.devices[device]['drivers']:
+ is_cpu = True
+ overall_status = _("Processor microcode")
+ brand_icon = Gtk.Image()
+ brand_icon.set_valign(Gtk.Align.START)
+ brand_icon.set_halign(Gtk.Align.CENTER)
+ brand_icon.set_from_pixbuf(self.get_device_icon(self.devices[device]))
+ driver_status = Gtk.Image()
+ driver_status.set_valign(Gtk.Align.START)
+ driver_status.set_halign(Gtk.Align.CENTER)
+ driver_status.set_from_icon_name(icon, Gtk.IconSize.MENU)
+ device_box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.HORIZONTAL)
+ device_box.pack_start(brand_icon, False, False, 6)
+ device_detail = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
+ device_box.pack_start(device_detail, True, True, 0)
+ model_name = self.devices[device].get('model', None)
+ vendor_name = self.devices[device].get('vendor', None)
+ if is_cpu:
+ device_name = self.get_cpu_name()
+ elif vendor_name is None and model_name is None:
+ device_name = _("Unknown")
+ elif vendor_name is None:
+ device_name = model_name
+ elif model_name is None:
+ device_name = vendor_name
+ else:
+ device_name = "%s: %s" % (vendor_name, model_name)
+ if "vmware" in device_name.lower() or "virtualbox" in device_name.lower():
+ print ("Ignoring device %s" % device_name)
+ continue
+ if drivers["manually_installed"]:
+ print("Ignoring device: %s (manually_installed)" % device_name)
+ continue
+ drivers_found = True
+ widget = Gtk.Label(label=device_name)
+ widget.set_halign(Gtk.Align.START)
+ device_detail.pack_start(widget, True, False, 0)
+ widget = Gtk.Label(label="{}".format(overall_status))
+ widget.set_halign(Gtk.Align.START)
+ widget.set_use_markup(True)
+ device_detail.pack_start(widget, True, False, 0)
+ self.dynamic_device_status[device] = (driver_status, widget)
+
+ option_group = None
+ # define the order of introspection
+ for section in ('recommended', 'alternative', 'manually_installed', 'no_driver'):
+ for driver in sorted(drivers[section], key=lambda x: self.sort_string(drivers[section], x), reverse=True):
+ radio_button = Gtk.RadioButton.new(None)
+ label = Gtk.Label()
+ label.set_markup(drivers[section][driver]['description'])
+ radio_button.add(label)
+ if option_group:
+ radio_button.join_group(option_group)
+ else:
+ option_group = radio_button
+ device_detail.pack_start(radio_button, True, False, 0)
+ radio_button.set_active(drivers[section][driver]['selected'])
+
+ if section == 'no_driver':
+ self.no_drv.append(radio_button)
+ if is_cpu:
+ label.set_markup(_("Do not update the CPU microcode"))
+ if section in ('manually_install', 'no_driver') or ('builtin' in drivers[section][driver] and drivers[section][driver]['builtin']):
+ radio_button.connect("toggled", self.on_driver_selection_changed, device)
+ else:
+ radio_button.connect("toggled", self.on_driver_selection_changed, device, driver)
+ if drivers['manually_installed'] and section != 'manually_installed' and "firmware" not in str(driver):
+ radio_button.set_sensitive(False)
+
+ self.box_driver_detail.pack_start(device_box, False, False, 6)
+
+ if drivers_found:
+ self.show_page("drivers_page")
+ else:
+ self.show_page("no_drivers_page")
+ print("Your computer does not need any additional drivers")
+
+ self.ui_building = False
+ self.box_driver_detail.show_all()
+ self.set_driver_action_status()
+
+ def sort_string(self, drivers, x):
+ value = x
+ try:
+ value = "%s %s" % (not drivers[x]['free'], value)
+ except:
+ pass #best effort (some driver options don't have a 'free' flag, and that's alright)
+ return value
+
+ def update_label_and_icons_from_status(self):
+ """Update the current label and icon, computing the new device status"""
+
+ for device in self.devices:
+ if device in self.dynamic_device_status.keys():
+ (overall_status, icon, drivers) = self.gather_device_data(self.devices[device])
+ (driver_status, widget) = self.dynamic_device_status[device]
+ driver_status.set_from_icon_name(icon, Gtk.IconSize.MENU)
+ widget.set_label("{}".format(overall_status))
+
+ def set_driver_action_status(self):
+ # Update the label in case we end up having some kind of proprietary driver in use.
+ if (not self.live_mode) and (os.path.exists('/var/run/reboot-required') or self.needs_restart):
+ self.label_driver_action.set_label(_("You need to restart the computer to complete the driver changes."))
+ self.button_driver_restart.set_visible(True)
+ self.window_main.set_urgency_hint(True)
+ return
+
+ self.nonfree_drivers = 0
+ for device in self.devices:
+ for pkg_name in self.devices[device]['drivers']:
+ pkg = self.apt_cache[pkg_name]
+ if (not self.devices[device]['drivers'][pkg_name]['free'] or pkg_name == "broadcom-sta-dkms") and pkg.is_installed:
+ self.nonfree_drivers = self.nonfree_drivers + 1
+
+ if self.nonfree_drivers > 0:
+ self.label_driver_action.set_label(gettext.ngettext(
+ "%(count)d proprietary driver in use.",
+ "%(count)d proprietary drivers in use.",
+ self.nonfree_drivers)
+ % {'count': self.nonfree_drivers})
+ else:
+ self.label_driver_action.set_label(_("No proprietary drivers are in use."))
+
+if __name__ == "__main__":
+ Application()
+ Gtk.main()
From 30cec87ddc93fd5fc9695ef065eeb1c4de56e595 Mon Sep 17 00:00:00 2001
From: Chris Rios
Date: Sat, 15 Nov 2025 16:54:49 -0800
Subject: [PATCH 3/6] Add warning for secure boot enabled systems
---
usr/lib/linuxmint/mintdrivers/mintdrivers.py | 25 ++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/usr/lib/linuxmint/mintdrivers/mintdrivers.py b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
index 3d256e0..dac8b94 100755
--- a/usr/lib/linuxmint/mintdrivers/mintdrivers.py
+++ b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
@@ -62,6 +62,9 @@ def __init__(self):
else:
print("can not get name for object '%s'" % o)
+ if self.is_secure_boot_enabled():
+ self.show_secure_boot_warning()
+
self.window_main.show()
self.window_main.set_title(_("Driver Manager"))
@@ -117,6 +120,28 @@ def show_page(self, page):
self.builder.get_object("spinner").stop()
self.builder.get_object("stack").set_visible_child_name(page)
+ def show_secure_boot_warning(self):
+ dialog = Gtk.MessageDialog(
+ title=_("Secure Boot Warning"),
+ flags=Gtk.DialogFlags.MODAL,
+ type=Gtk.MessageType.WARNING,
+ buttons=Gtk.ButtonsType.OK,
+ message_format=_("Warning: Secure Boot is enabled on your computer.\n\n"
+ "Some of the drivers you are about to install may not work until Secure Boot is disabled.\n\n"
+ "Please refer to your computer's documentation for instructions on how to disable Secure Boot."),
+ )
+ dialog.run()
+ dialog.destroy()
+
+ def is_secure_boot_enabled(self):
+ try:
+ result = subprocess.run(['mokutil', '--sb-state'], capture_output=True, text=True)
+ output = result.stdout.lower()
+ return 'secureboot enabled' in output
+ except Exception as e:
+ print(f"Error checking Secure Boot status: {e}")
+ return False
+
def on_error_button(self, button):
self.show_page("drivers_page")
From 4338c03786068485f078fce2ffa7d5a139792c7d Mon Sep 17 00:00:00 2001
From: Chris Rios <31458662+robotman40@users.noreply.github.com>
Date: Sat, 15 Nov 2025 17:01:21 -0800
Subject: [PATCH 4/6] Delete mintdrivers.py
---
mintdrivers.py | 795 -------------------------------------------------
1 file changed, 795 deletions(-)
delete mode 100644 mintdrivers.py
diff --git a/mintdrivers.py b/mintdrivers.py
deleted file mode 100644
index dac8b94..0000000
--- a/mintdrivers.py
+++ /dev/null
@@ -1,795 +0,0 @@
-#! /usr/bin/python3
-# -*- coding=utf-8 -*-
-
-import gettext
-import locale
-import os
-import sys
-import apt
-import subprocess
-import gi
-gi.require_version("GdkPixbuf", "2.0")
-gi.require_version("Gtk", "3.0")
-gi.require_version("XApp", "1.0")
-gi.require_version("PackageKitGlib", "1.0")
-from gi.repository import GdkPixbuf, Gtk, XApp, Gio, GLib
-from gi.repository import PackageKitGlib as packagekit
-from UbuntuDrivers import detect
-import psutil
-import re
-import urllib
-import threading
-
-# Used as a decorator to run things in the background
-def _async(func):
- def wrapper(*args, **kwargs):
- thread = threading.Thread(target=func, args=args, kwargs=kwargs)
- thread.daemon = True
- thread.start()
- return thread
- return wrapper
-
-# Used as a decorator to run things in the main loop, from another thread
-def idle(func):
- def wrapper(*args):
- GLib.idle_add(func, *args)
- return wrapper
-
-APP = 'mintdrivers'
-LOCALE_DIR = "/usr/share/locale"
-locale.bindtextdomain(APP, LOCALE_DIR)
-gettext.bindtextdomain(APP, LOCALE_DIR)
-gettext.textdomain(APP)
-_ = gettext.gettext
-
-class Application:
-
- def __init__(self):
-
- self.test_mode = False
- if len(sys.argv) > 1 and sys.argv[1] == "test":
- self.test_mode = True
- print("Test mode detected, adding a dummy device.")
-
- self.builder = Gtk.Builder()
- self.builder.set_translation_domain(APP)
- self.builder.add_from_file("/usr/share/linuxmint/mintdrivers/main.ui")
- self.builder.connect_signals(self)
- for o in self.builder.get_objects():
- if issubclass(type(o), Gtk.Buildable):
- name = Gtk.Buildable.get_name(o)
- setattr(self, name, o)
- else:
- print("can not get name for object '%s'" % o)
-
- if self.is_secure_boot_enabled():
- self.show_secure_boot_warning()
-
- self.window_main.show()
-
- self.window_main.set_title(_("Driver Manager"))
-
- self.window_main.connect("delete_event", self.quit_application)
-
- self.button_driver_revert = Gtk.Button(label=_("Re_vert"), use_underline=True)
- self.button_driver_revert.connect("clicked", self.on_driver_changes_revert)
- self.button_driver_apply = Gtk.Button(label=_("_Apply Changes"), use_underline=True)
- self.button_driver_apply.connect("clicked", self.on_driver_changes_apply)
- self.button_driver_cancel = Gtk.Button(label=_("_Cancel"), use_underline=True)
- self.button_driver_cancel.connect("clicked", self.on_driver_changes_cancel)
- self.button_driver_restart = Gtk.Button(label=_("_Restart..."), use_underline=True)
- self.button_driver_restart.connect("clicked", self.on_driver_restart_clicked)
- self.button_driver_revert.set_sensitive(False)
- self.button_driver_revert.set_visible(True)
- self.button_driver_apply.set_sensitive(False)
- self.button_driver_apply.set_visible(True)
- self.button_driver_cancel.set_visible(False)
- self.button_driver_restart.set_visible(False)
- self.box_driver_action.pack_end(self.button_driver_apply, False, False, 0)
- self.box_driver_action.pack_end(self.button_driver_revert, False, False, 0)
- self.box_driver_action.pack_end(self.button_driver_restart, False, False, 0)
- self.box_driver_action.pack_end(self.button_driver_cancel, False, False, 0)
-
- self.builder.get_object("error_button").connect("clicked", self.on_error_button)
- self.builder.get_object("button_mount_media").connect("clicked", self.on_mount_media_button)
- self.builder.get_object("button_offline").connect("clicked", self.check_internet_or_live_media)
-
- self.progress_bar = Gtk.ProgressBar(valign=Gtk.Align.CENTER)
- self.box_driver_action.pack_end(self.progress_bar, True, True, 0)
- self.progress_bar.set_visible(False)
-
- self.needs_restart = False
- self.needs_broadcom_reload = False
- self.live_mode = False
-
- self.show_page("refresh_page")
-
- with open('/proc/cmdline') as f:
- cmdline = f.read()
- if ("boot=casper" in cmdline) or ("boot=live" in cmdline):
- print ("Live mode detected")
- self.live_mode = True
- self.update_cache()
- else:
- self.check_internet_or_live_media()
-
- def show_page(self, page):
- if page == "refresh_page":
- self.builder.get_object("spinner").start()
- else:
- self.builder.get_object("spinner").stop()
- self.builder.get_object("stack").set_visible_child_name(page)
-
- def show_secure_boot_warning(self):
- dialog = Gtk.MessageDialog(
- title=_("Secure Boot Warning"),
- flags=Gtk.DialogFlags.MODAL,
- type=Gtk.MessageType.WARNING,
- buttons=Gtk.ButtonsType.OK,
- message_format=_("Warning: Secure Boot is enabled on your computer.\n\n"
- "Some of the drivers you are about to install may not work until Secure Boot is disabled.\n\n"
- "Please refer to your computer's documentation for instructions on how to disable Secure Boot."),
- )
- dialog.run()
- dialog.destroy()
-
- def is_secure_boot_enabled(self):
- try:
- result = subprocess.run(['mokutil', '--sb-state'], capture_output=True, text=True)
- output = result.stdout.lower()
- return 'secureboot enabled' in output
- except Exception as e:
- print(f"Error checking Secure Boot status: {e}")
- return False
-
- def on_error_button(self, button):
- self.show_page("drivers_page")
-
- def update_cache(self):
- print("Updating cache")
- self.show_page("refresh_page")
- task = packagekit.Task()
- task.refresh_cache_async(True, Gio.Cancellable(), self.on_cache_update_progress, (None, ), self.on_cache_update_finished, (None, ))
-
- def on_error(self, error):
- # Returns False if the error was from cancelling or failing to authenticate.
- # This will bring the ui back to pre-apply state. Returning True will reset
- # entirely.
-
- # it thinks it's a PkClientError, but it's really PkErrorEnum
- # the GError code is set to 0xFF + code
- if error.code >= 0xFF:
- real_code = error.code - 0xFF
-
- if real_code == packagekit.ErrorEnum.NOT_AUTHORIZED:
- # Silently ignore auth failures or cancellation.
- return False
-
- self.show_page("error_page")
- self.builder.get_object("error_label").set_label(error.message)
- return True
-
- def on_cache_update_progress(self, progress, ptype, data=None):
- pass
-
- def on_cache_update_finished(self, source, result, data=None):
- print("Cache updated")
- XApp.set_window_progress(self.window_main, 0)
- self.get_drivers_async()
-
- def quit_application(self, widget=None, event=None):
- self.cleanup_live_media()
- Gtk.main_quit()
-
- def cleanup_live_media(self):
- subprocess.call(["sudo", "mintdrivers-remove-live-media"])
-
- def check_connectivity(self, reference):
- try:
- urllib.request.urlopen(reference, timeout=10)
- return True
- except:
- return False
-
- @idle
- def check_internet_or_live_media(self, widget=None):
- self.show_page("refresh_page")
- print ("Checking Internet connectivity...")
- try:
- urllib.request.urlopen("https://archive.ubuntu.com", timeout=10)
- # We're online
- print (" --> Computer is online")
- self.update_cache()
- return
- except:
- print (" --> Computer is offline")
-
- # We're offline, let's look for a live media
- print ("Checking for a live media...")
- mount_point = None
- partitions = psutil.disk_partitions()
- for p in partitions:
- if p.fstype == "iso9660":
- mount_point = p.mountpoint
- print (" --> Found: %s at %s" % (p.device, p.mountpoint))
- break
-
- if mount_point is None:
- # Offline and no live media, show the offline page
- print (" --> None found.")
- self.show_page("offline_page")
- return
-
- # We're offline but an ISO was detected
- # Let's make sure it's mounted as a repository
- if os.path.exists("/media/mintdrivers/.disk/info"):
- print (" --> Mounted in /media/mintdrivers")
- self.update_cache()
- else:
- print (" --> Not mounted in /media/mintdrivers")
- self.show_page("media_page")
-
- def on_mount_media_button(self, button):
- print("Mounting live media")
- self.show_page("refresh_page")
- self.mount_live_media()
-
- @_async
- def mount_live_media(self):
- subprocess.call(["/usr/bin/pkexec", "mintdrivers-add-live-media"])
- self.check_internet_or_live_media()
-
- def on_driver_changes_progress(self, progress, ptype, data=None):
- self.button_driver_revert.set_visible(False)
- self.button_driver_apply.set_visible(False)
- self.button_driver_restart.set_visible(False)
- self.button_driver_cancel.set_visible(True)
- self.progress_bar.set_visible(True)
- self.progress_bar.set_visible(True)
-
- if progress.get_status() == packagekit.StatusEnum.DOWNLOAD:
- self.label_driver_action.set_label(_("Downloading drivers..."))
- elif progress.get_status() == packagekit.StatusEnum.INSTALL:
- self.label_driver_action.set_label(_("Installing drivers..."))
- elif progress.get_status() == packagekit.StatusEnum.REMOVE:
- self.label_driver_action.set_label(_("Removing drivers..."))
- elif progress.get_status() == packagekit.StatusEnum.CANCEL:
- self.label_driver_action.set_label(_("Cancelling..."))
- elif progress.get_status() == packagekit.StatusEnum.LOADING_CACHE:
- self.label_driver_action.set_label(_("Loading cache..."))
- else:
- self.label_driver_action.set_label("")
- if ptype == packagekit.ProgressType.PERCENTAGE:
- prog_value = progress.get_property('percentage')
- self.progress_bar.set_fraction(prog_value / 100.0)
- XApp.set_window_progress(self.window_main, prog_value)
-
- def on_driver_changes_finish(self, source, result, installs):
- errors = False
- try:
- self.pk_task.generic_finish(result)
- except GLib.Error as e:
- errors = True
- if self.on_error(e):
- # real failure
- self.on_driver_changes_revert()
- self.clear_changes()
- else:
- self.button_driver_revert.set_sensitive(bool(self.driver_changes))
- self.button_driver_apply.set_sensitive(bool(self.driver_changes))
-
- if installs is None or len(installs) == 0 or errors:
- if self.needs_broadcom_reload:
- print("Reloading Broadcom modules")
- subprocess.call(["sudo", "mintdrivers-load-broadcom-modules"])
- self.needs_restart = (not errors)
- self.progress_bar.set_visible(False)
- self.apt_cache = apt.Cache()
- self.set_driver_action_status()
- self.update_label_and_icons_from_status()
- self.button_driver_revert.set_visible(True)
- self.button_driver_apply.set_visible(True)
- self.button_driver_cancel.set_visible(False)
- self.scrolled_window_drivers.set_sensitive(True)
- XApp.set_window_progress(self.window_main, 0)
- else:
- print("Installing", installs)
- self.pk_task.install_packages_async(installs,
- self.cancellable, # cancellable
- self.on_driver_changes_progress,
- (None, ), # progress data
- self.on_driver_changes_finish, # GAsyncReadyCallback
- None # callback data
- )
-
- def on_driver_changes_apply(self, button):
- self.pk_task = packagekit.Task()
- installs = []
- removals = []
-
- for pkg in self.driver_changes:
- if pkg.is_installed:
- removals.append(self.get_package_id(pkg.installed))
- # The main NVIDIA package is only a metapackage.
- # We need to collect its dependencies, so that
- # we can uninstall the driver properly.
- if 'nvidia' in pkg.shortname:
- for dep in self.get_dependencies(self.apt_cache, pkg.shortname, 'nvidia'):
- dep_pkg = self.apt_cache[dep]
- if dep_pkg.is_installed:
- removals.append(self.get_package_id(dep_pkg.installed))
- else:
- installs.append(self.get_package_id(pkg.candidate))
- if pkg.shortname == "broadcom-sta-dkms":
- self.needs_broadcom_reload = True
-
- self.cancellable = Gio.Cancellable()
- try:
- if len(removals) > 0:
- try:
- # Try to purge (Mint specific version of packagekit)
- print("Purging", removals)
- self.pk_task.purge_packages_async(removals,
- False, # allow deps
- True, # autoremove
- self.cancellable, # cancellable
- self.on_driver_changes_progress,
- (None, ), # progress data
- self.on_driver_changes_finish, # callback ready
- installs # callback data
- )
- except:
- # If purging isn't supported, just remove
- print("Couldn't purge! Removing", removals)
- self.pk_task.remove_packages_async(removals,
- False, # allow deps
- True, # autoremove
- self.cancellable, # cancellable
- self.on_driver_changes_progress,
- (None, ), # progress data
- self.on_driver_changes_finish, # callback ready
- installs # callback data
- )
- elif len(installs) > 0:
- print("Installing", installs)
- self.pk_task.install_packages_async(installs,
- self.cancellable, # cancellable
- self.on_driver_changes_progress,
- (None, ), # progress data
- self.on_driver_changes_finish, # GAsyncReadyCallback
- None # callback data
- )
-
- self.button_driver_revert.set_sensitive(False)
- self.button_driver_apply.set_sensitive(False)
- self.scrolled_window_drivers.set_sensitive(False)
- except Exception as e:
- print("Warning: install not completed successfully: {}".format(e))
-
- def on_driver_changes_revert(self, button_revert=None):
-
- # HACK: set all the "Do not use" first; then go through the list of the
- # actually selected drivers.
- for button in self.no_drv:
- button.set_active(True)
-
- for alias in self.orig_selection:
- button = self.orig_selection[alias]
- button.set_active(True)
-
- self.clear_changes()
-
- self.button_driver_revert.set_sensitive(False)
- self.button_driver_apply.set_sensitive(False)
-
- def on_driver_changes_cancel(self, button_cancel):
- self.cancellable.cancel()
- self.clear_changes()
-
- def on_driver_restart_clicked(self, button_restart):
- self.cleanup_live_media()
- subprocess.call(['systemctl', 'reboot'])
-
- def clear_changes(self):
- self.orig_selection = {}
- self.driver_changes = []
-
- def on_driver_selection_changed(self, button, modalias, pkg_name=None):
- if self.ui_building:
- return
-
- pkg = None
- try:
- if pkg_name:
- pkg = self.apt_cache[pkg_name]
- except KeyError:
- pass
-
- if button.get_active():
- if pkg in self.driver_changes:
- self.driver_changes.remove(pkg)
-
- if (pkg is not None
- and modalias in self.orig_selection
- and button is not self.orig_selection[modalias]):
- self.driver_changes.append(pkg)
- else:
- if pkg in self.driver_changes:
- self.driver_changes.remove(pkg)
-
- # for revert; to re-activate the original radio buttons.
- if modalias not in self.orig_selection:
- self.orig_selection[modalias] = button
-
- if (pkg is not None
- and pkg not in self.driver_changes
- and pkg.is_installed):
- self.driver_changes.append(pkg)
-
- self.button_driver_revert.set_sensitive(bool(self.driver_changes))
- self.button_driver_apply.set_sensitive(bool(self.driver_changes))
-
-
- def get_package_id(self, ver):
- """ Return the PackageKit package id """
- assert isinstance(ver, apt.package.Version)
- return "%s;%s;%s;" % (ver.package.shortname, ver.version, ver.package.architecture())
-
- @staticmethod
- def get_dependencies(apt_cache, package_name, pattern=None):
- """ Get the package dependencies, which can be filtered out by a pattern """
- dependencies = []
- for or_group in apt_cache[package_name].candidate.dependencies:
- for dep in or_group:
- if dep.rawtype in ["Depends", "PreDepends"]:
- dependencies.append(dep.name)
- if pattern:
- dependencies = [ x for x in dependencies if x.find(pattern) != -1 ]
- return dependencies
-
- def gather_device_data(self, device):
- """Get various device data used to build the GUI.
-
- return a tuple of (overall_status string, icon, drivers dict).
- the drivers dict is using this form:
- {"recommended/alternative": {pkg_name: {
- 'selected': True/False
- 'description': 'description'
- 'builtin': True/False,
- 'free': True/False
- }
- }}
- "manually_installed": {"manual": {'selected': True, 'description': description_string}}
- "no_driver": {"no_driver": {'selected': True/False, 'description': description_string}}
-
- Please note that either manually_installed and no_driver are set to None if not applicable
- (no_driver isn't present if there are builtins)
- """
-
- possible_overall_status = {
- 'recommended': (_("This device is using the recommended driver."), "recommended-driver"),
- 'alternative': (_("This device is using an alternative driver."), "other-driver"),
- 'manually_installed': (_("This device is using a manually-installed driver."), "other-driver"),
- 'no_driver': (_("This device is not working."), "disable-device")
- }
-
- returned_drivers = {'recommended': {}, 'alternative': {}, 'manually_installed': {}, 'no_driver': {}}
- have_builtin = False
- one_selected = False
- try:
- if device['manual_install']:
- returned_drivers['manually_installed'] = {True: {'selected': True,
- 'description': _("Continue using a manually installed driver")}}
- except KeyError:
- pass
-
- """
- - Never show server drivers.
- - Pre 560: closed source drivers are recommended. Override any recommended -open drivers with the closed
- source equivalent.
- - Post 560 (02/13/2025: open-source drivers are recommended by their devs, and closed source drivers may
- not be available for a given version.
- """
- ignored = []
-
- for pkg_driver_name in device['drivers']:
- if not pkg_driver_name.startswith("nvidia-"):
- continue
- if pkg_driver_name in ignored:
- print("Skipping ignored NVIDIA driver '%s'" % pkg_driver_name)
- continue
- if pkg_driver_name.endswith(("-server", "-server-open")):
- print("Ignoring server NVIDIA driver '%s'" % pkg_driver_name)
- ignored.append(pkg_driver_name)
- continue
-
- try:
- version = int(re.search(r"nvidia-driver-([0-9]{3}).*", pkg_driver_name).groups()[0])
- open_preferred = version >= 560
- except:
- open_preferred = False
-
- is_open = pkg_driver_name.endswith("-open")
- current_driver = device['drivers'][pkg_driver_name]
- recommended = current_driver.get("recommended", False) and current_driver.get("from_distro", False)
-
- if is_open:
- closed_name = pkg_driver_name.replace("-open", "")
- has_closed = closed_name in device['drivers']
- if has_closed:
- if open_preferred:
- print("Ignoring closed NVIDIA driver '%s' as the open one is preferred." % closed_name)
- ignored.append(closed_name)
- else:
- print("Ignoring open NVIDIA driver '%s' as a closed one exists and is preferred." % pkg_driver_name)
- if recommended:
- device['drivers'][closed_name]["recommended"] = True
- ignored.append(pkg_driver_name)
-
- for pkg_driver_name in device['drivers']:
- if pkg_driver_name in ignored:
- continue
- current_driver = device['drivers'][pkg_driver_name]
-
- # get general status
- driver_status = 'alternative'
- try:
- if (current_driver['recommended'] and current_driver['from_distro']):
- driver_status = 'recommended'
- except KeyError:
- pass
-
- builtin = False
- try:
- if current_driver['builtin']:
- builtin = True
- have_builtin = True
- except KeyError:
- pass
-
- try:
- pkg = self.apt_cache[pkg_driver_name]
- installed = pkg.is_installed
- if installed:
- version = pkg.installed.version
- summary = pkg.installed.summary
- else:
- version = pkg.candidate.version
- summary = pkg.candidate.summary
- description_line1 = "%s" % pkg.shortname
- description_line2 = "%s %s" % (_("Version"), version)
- description_line3 = "%s" % summary
- if driver_status == 'recommended':
- description_line1 = "%s (%s)" % (description_line1, _("recommended"))
- if current_driver['free'] and pkg.shortname != "broadcom-sta-dkms" and (not pkg.shortname.startswith("nvidia-")):
- description_line1 = "%s (%s)" % (description_line1, _("open-source"))
- if pkg.shortname.startswith("firmware-b43"):
- # B43 requires a connection to the Internet
- description_line1 = "%s (%s)" % (description_line1, _("requires a connection to the Internet"))
- description = "%s\n%s\n%s" % (description_line1, description_line2, description_line3)
- except KeyError:
- print("WARNING: a driver ({}) doesn't have any available package associated: {}".format(pkg_driver_name, current_driver))
- continue
-
- selected = False
- if not builtin and not returned_drivers['manually_installed']:
- selected = installed
- if installed:
- selected = True
- one_selected = True
-
- returned_drivers[driver_status].setdefault(pkg_driver_name, {'selected': selected,
- 'description': description,
- 'builtin': builtin,
- 'free': current_driver['free']})
-
- # adjust making the needed addition
- if not have_builtin:
- selected = False
- if not one_selected:
- selected = True
- returned_drivers["no_driver"] = {True: {'selected': selected,
- 'description': _("Do not use the device")}}
- else:
- # we have a builtin and no selection: builtin is the selected one then
- if not one_selected:
- for section in ('recommended', 'alternative'):
- for pkg_name in returned_drivers[section]:
- if returned_drivers[section][pkg_name]['builtin']:
- returned_drivers[section][pkg_name]['selected'] = True
-
- # compute overall status
- for section in returned_drivers:
- for keys in returned_drivers[section]:
- if returned_drivers[section][keys]['selected']:
- (overall_status, icon) = possible_overall_status[section]
-
- return overall_status, icon, returned_drivers
-
- def get_device_icon(self, device):
- vendor = device.get('vendor', _('Unknown'))
- model = device.get('model', _('Unknown'))
- icon = "generic"
- if "nvidia" in vendor.lower():
- icon = "nvidia"
- elif "radeon" in vendor.lower() or "radeon" in model.lower() or "Advanced Micro Devices" in vendor or "AMD" in vendor or "ATI" in vendor:
- icon = "ati"
- elif "broadcom" in vendor.lower():
- icon = "broadcom"
- elif "virtualbox" in vendor.lower() or "virtualbox" in model.lower():
- icon = "virtualbox"
-
- if "intel-microcode" in device['drivers']:
- icon = "intel"
- elif "amd64-microcode" in device['drivers']:
- icon = "amd"
-
- return GdkPixbuf.Pixbuf.new_from_file_at_size("/usr/share/linuxmint/mintdrivers/icons/%s.svg" % icon, 48, -1)
-
- def get_cpu_name(self):
- with open("/proc/cpuinfo") as cpuinfo:
- for line in cpuinfo:
- if "model name" in line:
- return re.sub( ".*model name.*:", "", line, 1).strip()
- return _("Processor")
-
- @_async
- def get_drivers_async(self):
- self.apt_cache = apt.Cache()
- self.devices = detect.system_device_drivers()
- if self.test_mode:
- dummy_device = {
- 'modalias': '',
- 'vendor': 'Linux Mint', 'model': 'Dummy Test Device',
- 'drivers': {
- 'mint-dev-pkg': {'free': False, 'from_distro': True, 'recommended': True},
- 'mint-dev-pkg-debconf': {'free': False, 'from_distro': True, 'recommended': False},
- 'linux-generic': {'free': True, 'builtin': True, 'from_distro': True, 'recommended': False}}
- }
- self.devices['dummy'] = dummy_device
- self.show_drivers()
-
- @idle
- def show_drivers(self):
- self.driver_changes = []
- self.orig_selection = {}
- # HACK: the case where the selection is actually "Do not use"; is a little
- # tricky to implement because you can't check for whether a package is
- # installed or any such thing. So let's keep a list of all the
- # "Do not use" radios, set those active first, then iterate through
- # orig_selection when doing a Reset.
- self.no_drv = []
- self.nonfree_drivers = 0
- self.ui_building = True
- self.dynamic_device_status = {}
- drivers_found = False
- if len(self.devices) != 0:
- for device in sorted(self.devices.keys()):
- (overall_status, icon, drivers) = self.gather_device_data(self.devices[device])
- is_cpu = False
- if "intel-microcode" in self.devices[device]['drivers'] or "amd64-microcode" in self.devices[device]['drivers']:
- is_cpu = True
- overall_status = _("Processor microcode")
- brand_icon = Gtk.Image()
- brand_icon.set_valign(Gtk.Align.START)
- brand_icon.set_halign(Gtk.Align.CENTER)
- brand_icon.set_from_pixbuf(self.get_device_icon(self.devices[device]))
- driver_status = Gtk.Image()
- driver_status.set_valign(Gtk.Align.START)
- driver_status.set_halign(Gtk.Align.CENTER)
- driver_status.set_from_icon_name(icon, Gtk.IconSize.MENU)
- device_box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.HORIZONTAL)
- device_box.pack_start(brand_icon, False, False, 6)
- device_detail = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
- device_box.pack_start(device_detail, True, True, 0)
- model_name = self.devices[device].get('model', None)
- vendor_name = self.devices[device].get('vendor', None)
- if is_cpu:
- device_name = self.get_cpu_name()
- elif vendor_name is None and model_name is None:
- device_name = _("Unknown")
- elif vendor_name is None:
- device_name = model_name
- elif model_name is None:
- device_name = vendor_name
- else:
- device_name = "%s: %s" % (vendor_name, model_name)
- if "vmware" in device_name.lower() or "virtualbox" in device_name.lower():
- print ("Ignoring device %s" % device_name)
- continue
- if drivers["manually_installed"]:
- print("Ignoring device: %s (manually_installed)" % device_name)
- continue
- drivers_found = True
- widget = Gtk.Label(label=device_name)
- widget.set_halign(Gtk.Align.START)
- device_detail.pack_start(widget, True, False, 0)
- widget = Gtk.Label(label="{}".format(overall_status))
- widget.set_halign(Gtk.Align.START)
- widget.set_use_markup(True)
- device_detail.pack_start(widget, True, False, 0)
- self.dynamic_device_status[device] = (driver_status, widget)
-
- option_group = None
- # define the order of introspection
- for section in ('recommended', 'alternative', 'manually_installed', 'no_driver'):
- for driver in sorted(drivers[section], key=lambda x: self.sort_string(drivers[section], x), reverse=True):
- radio_button = Gtk.RadioButton.new(None)
- label = Gtk.Label()
- label.set_markup(drivers[section][driver]['description'])
- radio_button.add(label)
- if option_group:
- radio_button.join_group(option_group)
- else:
- option_group = radio_button
- device_detail.pack_start(radio_button, True, False, 0)
- radio_button.set_active(drivers[section][driver]['selected'])
-
- if section == 'no_driver':
- self.no_drv.append(radio_button)
- if is_cpu:
- label.set_markup(_("Do not update the CPU microcode"))
- if section in ('manually_install', 'no_driver') or ('builtin' in drivers[section][driver] and drivers[section][driver]['builtin']):
- radio_button.connect("toggled", self.on_driver_selection_changed, device)
- else:
- radio_button.connect("toggled", self.on_driver_selection_changed, device, driver)
- if drivers['manually_installed'] and section != 'manually_installed' and "firmware" not in str(driver):
- radio_button.set_sensitive(False)
-
- self.box_driver_detail.pack_start(device_box, False, False, 6)
-
- if drivers_found:
- self.show_page("drivers_page")
- else:
- self.show_page("no_drivers_page")
- print("Your computer does not need any additional drivers")
-
- self.ui_building = False
- self.box_driver_detail.show_all()
- self.set_driver_action_status()
-
- def sort_string(self, drivers, x):
- value = x
- try:
- value = "%s %s" % (not drivers[x]['free'], value)
- except:
- pass #best effort (some driver options don't have a 'free' flag, and that's alright)
- return value
-
- def update_label_and_icons_from_status(self):
- """Update the current label and icon, computing the new device status"""
-
- for device in self.devices:
- if device in self.dynamic_device_status.keys():
- (overall_status, icon, drivers) = self.gather_device_data(self.devices[device])
- (driver_status, widget) = self.dynamic_device_status[device]
- driver_status.set_from_icon_name(icon, Gtk.IconSize.MENU)
- widget.set_label("{}".format(overall_status))
-
- def set_driver_action_status(self):
- # Update the label in case we end up having some kind of proprietary driver in use.
- if (not self.live_mode) and (os.path.exists('/var/run/reboot-required') or self.needs_restart):
- self.label_driver_action.set_label(_("You need to restart the computer to complete the driver changes."))
- self.button_driver_restart.set_visible(True)
- self.window_main.set_urgency_hint(True)
- return
-
- self.nonfree_drivers = 0
- for device in self.devices:
- for pkg_name in self.devices[device]['drivers']:
- pkg = self.apt_cache[pkg_name]
- if (not self.devices[device]['drivers'][pkg_name]['free'] or pkg_name == "broadcom-sta-dkms") and pkg.is_installed:
- self.nonfree_drivers = self.nonfree_drivers + 1
-
- if self.nonfree_drivers > 0:
- self.label_driver_action.set_label(gettext.ngettext(
- "%(count)d proprietary driver in use.",
- "%(count)d proprietary drivers in use.",
- self.nonfree_drivers)
- % {'count': self.nonfree_drivers})
- else:
- self.label_driver_action.set_label(_("No proprietary drivers are in use."))
-
-if __name__ == "__main__":
- Application()
- Gtk.main()
From 67360bd1679d309502fde2c02ba8686e0a744321 Mon Sep 17 00:00:00 2001
From: Chris Rios
Date: Sat, 15 Nov 2025 18:17:02 -0800
Subject: [PATCH 5/6] Added self-signing to the warning
---
usr/lib/linuxmint/mintdrivers/mintdrivers.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/usr/lib/linuxmint/mintdrivers/mintdrivers.py b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
index dac8b94..c1a6ce3 100755
--- a/usr/lib/linuxmint/mintdrivers/mintdrivers.py
+++ b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
@@ -128,7 +128,8 @@ def show_secure_boot_warning(self):
buttons=Gtk.ButtonsType.OK,
message_format=_("Warning: Secure Boot is enabled on your computer.\n\n"
"Some of the drivers you are about to install may not work until Secure Boot is disabled.\n\n"
- "Please refer to your computer's documentation for instructions on how to disable Secure Boot."),
+ "Alternatively, you may be able to self-sign kernel modules and enroll them with a Machine Owner Key (MOK) so they can load while Secure Boot is enabled.\n\n"
+ "Please refer to your computer's documentation or the mokutil/module signing documentation for instructions."),
)
dialog.run()
dialog.destroy()
From 9133230aaf46ead70c410b6ae2091f9c7a3d4441 Mon Sep 17 00:00:00 2001
From: Chris Rios
Date: Sat, 15 Nov 2025 18:21:36 -0800
Subject: [PATCH 6/6] Added references to self-signing to the warning
---
usr/lib/linuxmint/mintdrivers/mintdrivers.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/usr/lib/linuxmint/mintdrivers/mintdrivers.py b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
index c1a6ce3..9d818e7 100755
--- a/usr/lib/linuxmint/mintdrivers/mintdrivers.py
+++ b/usr/lib/linuxmint/mintdrivers/mintdrivers.py
@@ -127,9 +127,9 @@ def show_secure_boot_warning(self):
type=Gtk.MessageType.WARNING,
buttons=Gtk.ButtonsType.OK,
message_format=_("Warning: Secure Boot is enabled on your computer.\n\n"
- "Some of the drivers you are about to install may not work until Secure Boot is disabled.\n\n"
- "Alternatively, you may be able to self-sign kernel modules and enroll them with a Machine Owner Key (MOK) so they can load while Secure Boot is enabled.\n\n"
- "Please refer to your computer's documentation or the mokutil/module signing documentation for instructions."),
+ "Some drivers or kernel modules that require self-signing may not load while Secure Boot is enabled. "
+ "This can prevent drivers you install from working until Secure Boot is disabled or the modules are properly signed.\n\n"
+ "Please refer to your computer's documentation for instructions on disabling Secure Boot or on enrolling module signing keys."),
)
dialog.run()
dialog.destroy()