diff --git a/dangerzone/gui/updater.py b/dangerzone/gui/updater.py index 0513e78..411fd59 100644 --- a/dangerzone/gui/updater.py +++ b/dangerzone/gui/updater.py @@ -43,6 +43,24 @@ about updates, check our UPDATE_CHECK_COOLDOWN_SECS = 60 * 60 * 12 # Check for updates at most every 12 hours. +class UpdateCheckPrompt(Alert): + """The prompt that asks the users if they want to enable update checks.""" + + x_pressed = False + + def closeEvent(self, event: QtCore.QEvent) -> None: + """Detect when a user has pressed "X" in the title bar. + + This function is called when a user clicks on "X" in the title bar. We want to + differentiate between the user clicking on "Cancel" and clicking on "X", since + in the second case, we want to remind them again on the next run. + + See: https://stackoverflow.com/questions/70851063/pyqt-differentiate-between-close-function-and-title-bar-close-x + """ + self.x_pressed = True + event.accept() + + class UpdateReport: """A report for an update check.""" @@ -98,17 +116,20 @@ class UpdaterThread(QtCore.QThread): def check(self, val: bool) -> None: self.dangerzone.settings.set("updater_check", val, autosave=True) - def prompt_for_checks(self) -> bool: + def prompt_for_checks(self) -> Optional[bool]: """Ask the user if they want to be informed about Dangerzone updates.""" log.debug("Prompting the user for update checks") # FIXME: Handle the case where a user clicks on "X", instead of explicitly # making a choice. We should probably ask them again on the next run. - check = Alert( + prompt = UpdateCheckPrompt( self.dangerzone, message=MSG_CONFIRM_UPDATE_CHECKS, ok_text="Yes", cancel_text="No", - ).launch() + ) + check = prompt.launch() + if not check and prompt.x_pressed: + return None return bool(check) def should_check_for_updates(self) -> bool: @@ -140,7 +161,7 @@ class UpdaterThread(QtCore.QThread): if self.check is None: log.debug("User has not been asked yet for update checks") self.check = self.prompt_for_checks() - return self.check + return bool(self.check) elif not self.check: log.debug("User has expressed that they don't want to check for updates") return False diff --git a/tests/gui/test_updater.py b/tests/gui/test_updater.py index 59b9c63..fa0acc2 100644 --- a/tests/gui/test_updater.py +++ b/tests/gui/test_updater.py @@ -156,18 +156,19 @@ def test_user_prompts( # # When Dangerzone runs for a second time, users can be prompted to enable update # checks. Depending on their answer, we should either enable or disable them. - alert_mock = mocker.MagicMock() - monkeypatch.setattr(updater_module, "Alert", alert_mock) + mocker.patch("dangerzone.gui.updater.UpdateCheckPrompt") + prompt_mock = updater_module.UpdateCheckPrompt + prompt_mock().x_pressed = False # Check disabling update checks. - alert_mock().launch.return_value = False + prompt_mock().launch.return_value = False # type: ignore [attr-defined] expected_settings["updater_check"] = False assert updater.should_check_for_updates() == False assert updater.dangerzone.settings.get_updater_settings() == expected_settings # Reset the "updater_check" field and check enabling update checks. updater.dangerzone.settings.set("updater_check", None) - alert_mock().launch.return_value = True + prompt_mock().launch.return_value = True # type: ignore [attr-defined] expected_settings["updater_check"] = True assert updater.should_check_for_updates() == True assert updater.dangerzone.settings.get_updater_settings() == expected_settings @@ -176,7 +177,7 @@ def test_user_prompts( # # From the third run onwards, users should never be prompted for enabling update # checks. - alert_mock.side_effect = RuntimeError("Should not be called") + prompt_mock().side_effect = RuntimeError("Should not be called") # type: ignore [attr-defined] for check in [True, False]: updater.dangerzone.settings.set("updater_check", check) assert updater.should_check_for_updates() == check