From aa5a655c1b4b6da80acb97ed3d374d67c77c1ac8 Mon Sep 17 00:00:00 2001 From: Sharpienero <8890281+Sharpienero@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:48:09 -0400 Subject: [PATCH] Show the uninstall button whenever there is something to uninstall Visibility was tied to service.is_running(), so the button is hidden in every state where the service is not up: never installed, installed but stopped, and failed to start. The last two are when removing it matters most, and a user in that state has no way to do it from the app. The service is also not the only thing run_uninstall() removes. The udev rule, the WirePlumber rule and the mix sinks all outlive it, so they can be present with the button hidden and nothing in the UI admitting they exist. Gate on anything_installed() instead, which is the OR of the four things run_uninstall() acts on. It is deliberately not the inverse of needs_setup(): a partial install both needs setup and has things left to remove. Also list the WirePlumber rule and the mix sinks in the confirmation dialog, which claimed only the service and USB permissions. --- wavexlr/app.py | 12 +++++++++--- wavexlr/setup.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/wavexlr/app.py b/wavexlr/app.py index ff8791e..272caf0 100644 --- a/wavexlr/app.py +++ b/wavexlr/app.py @@ -284,7 +284,6 @@ def _update_service_status(self): self.audio_status_icon.set_from_icon_name("emblem-ok-symbolic") self.audio_status_icon.remove_css_class("dim-label") self.audio_status_row.set_subtitle("Audio service running") - self.uninstall_btn.set_visible(True) else: self.audio_status_icon.set_from_icon_name("dialog-warning-symbolic") # Distinguish a service that never came up from one that is not @@ -297,12 +296,19 @@ def _update_service_status(self): else: subtitle = "Audio service not running" self.audio_status_row.set_subtitle(subtitle) - self.uninstall_btn.set_visible(False) + + # Shown whenever there is something to remove, rather than only while + # the service runs. A stopped or failed service is when removing it + # matters most, and the udev rule and the config drop-ins outlive it + # either way. + self.uninstall_btn.set_visible(setup.anything_installed()) def _on_uninstall_clicked(self, btn): dialog = Adw.AlertDialog( heading="Uninstall Capture Fix?", - body="This will remove the audio service and USB permissions.\n\nYou can reinstall them by restarting OpenWave.", + body="This will remove the audio service, the WirePlumber rule, " + "the mix sinks and the USB permissions.\n\nYou can reinstall " + "them by restarting OpenWave.", ) dialog.add_response("cancel", "Cancel") dialog.add_response("uninstall", "Uninstall") diff --git a/wavexlr/setup.py b/wavexlr/setup.py index a6384eb..ad50ccf 100644 --- a/wavexlr/setup.py +++ b/wavexlr/setup.py @@ -87,6 +87,20 @@ def needs_setup(): ) +def anything_installed(): + """Whether any part of the integration is still on disk. + + Not the inverse of needs_setup(): a partial install both needs setup and + has things left to remove. + """ + return ( + udev_installed() + or service_installed() + or wireplumber_installed() + or mixes_installed() + ) + + def install_udev(): """Install udev rules via pkexec.""" rules = "\n".join(UDEV_RULES)