From b125845127cbde1640997f00e43fc7410838a353 Mon Sep 17 00:00:00 2001 From: Dmitry Samoylik Date: Fri, 27 Sep 2024 12:42:21 +0300 Subject: [PATCH] Implement show EULA before installation --- ...lement-show-EULA-before-installation.patch | 488 ++++++++++++++++++ SOURCES/russian-34.25.4.7.po | 31 ++ SPECS/anaconda.spec | 6 +- 3 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 SOURCES/0017-Implement-show-EULA-before-installation.patch diff --git a/SOURCES/0017-Implement-show-EULA-before-installation.patch b/SOURCES/0017-Implement-show-EULA-before-installation.patch new file mode 100644 index 0000000..c76d522 --- /dev/null +++ b/SOURCES/0017-Implement-show-EULA-before-installation.patch @@ -0,0 +1,488 @@ +From 2716b292b9d42961222fd645c6b2d281d46d6688 Mon Sep 17 00:00:00 2001 +From: Dmitry Samoylik +Date: Thu, 26 Sep 2024 16:57:48 +0300 +Subject: [PATCH] Implement show EULA before installation + +--- + data/anaconda.conf | 4 +- + pyanaconda/core/configuration/license.py | 2 - + pyanaconda/core/eula.py | 23 ++++ + pyanaconda/ui/categories/eula.py | 14 +++ + pyanaconda/ui/gui/spokes/eula.glade | 136 +++++++++++++++++++++++ + pyanaconda/ui/gui/spokes/eula.py | 107 ++++++++++++++++++ + pyanaconda/ui/tui/spokes/eula.py | 128 +++++++++++++++++++++ + 7 files changed, 409 insertions(+), 5 deletions(-) + create mode 100644 pyanaconda/core/eula.py + create mode 100644 pyanaconda/ui/categories/eula.py + create mode 100644 pyanaconda/ui/gui/spokes/eula.glade + create mode 100644 pyanaconda/ui/gui/spokes/eula.py + create mode 100644 pyanaconda/ui/tui/spokes/eula.py + +diff --git a/data/anaconda.conf b/data/anaconda.conf +index b5878e3..c15e99d 100644 +--- a/data/anaconda.conf ++++ b/data/anaconda.conf +@@ -308,9 +308,7 @@ password_policies = + # If the given distribution has an EULA & feels the need to + # tell the user about it fill in this variable by a path + # pointing to a file with the EULA on the installed system. +-# +-# This is currently used just to show the path to the file to +-# the user at the end of the installation. ++ + eula = + + +diff --git a/pyanaconda/core/configuration/license.py b/pyanaconda/core/configuration/license.py +index 04c44bf..a51f52a 100644 +--- a/pyanaconda/core/configuration/license.py ++++ b/pyanaconda/core/configuration/license.py +@@ -31,7 +31,5 @@ class LicenseSection(Section): + tell the user about it fill in this variable by a path + pointing to a file with the EULA on the installed system. + +- This is currently used just to show the path to the file to +- the user at the end of the installation. + """ + return self._get_option("eula", str) +diff --git a/pyanaconda/core/eula.py b/pyanaconda/core/eula.py +new file mode 100644 +index 0000000..15a393e +--- /dev/null ++++ b/pyanaconda/core/eula.py +@@ -0,0 +1,23 @@ ++import os ++from pyanaconda.core.configuration.anaconda import conf ++ ++def get_license_file_name(): ++ """Get filename of the license file best matching current localization settings. ++ :return: filename of the license file or None if no license file found ++ :rtype: str or None ++ """ ++ if not conf.license.eula: ++ return None ++ ++ if not os.path.exists(conf.license.eula): ++ return None ++ ++ return conf.license.eula ++ ++ ++def eula_available(): ++ """Report if it looks like there is an EULA available on the system. ++ :return: True if an EULA seems to be available, False otherwise ++ :rtype: bool ++ """ ++ return bool(get_license_file_name()) +diff --git a/pyanaconda/ui/categories/eula.py b/pyanaconda/ui/categories/eula.py +new file mode 100644 +index 0000000..0a4fe96 +--- /dev/null ++++ b/pyanaconda/ui/categories/eula.py +@@ -0,0 +1,14 @@ ++from pyanaconda.ui.categories import SpokeCategory ++from pyanaconda.core.i18n import _ ++ ++__all__ = ["LicensingCategory"] ++ ++class LicensingCategory(SpokeCategory): ++ ++ @staticmethod ++ def get_title(): ++ return _("LICENSING") ++ ++ @staticmethod ++ def get_sort_order(): ++ return 900 +diff --git a/pyanaconda/ui/gui/spokes/eula.glade b/pyanaconda/ui/gui/spokes/eula.glade +new file mode 100644 +index 0000000..1d340f0 +--- /dev/null ++++ b/pyanaconda/ui/gui/spokes/eula.glade +@@ -0,0 +1,136 @@ ++ ++ ++ ++ ++ ++ The license will go here ++ ++ ++ False ++ True ++ True ++ License Information ++ ++ ++ ++ False ++ vertical ++ 6 ++ ++ ++ False ++ ++ ++ False ++ 6 ++ 6 ++ 6 ++ ++ ++ ++ ++ False ++ False ++ 0 ++ ++ ++ ++ ++ False ++ 0.8 ++ 0.8 ++ ++ ++ False ++ vertical ++ ++ ++ True ++ False ++ start ++ 24 ++ 6 ++ License Agreement: ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ True ++ vertical ++ ++ ++ True ++ True ++ in ++ ++ ++ True ++ True ++ 18 ++ True ++ True ++ natural ++ 12 ++ False ++ word ++ 12 ++ 12 ++ False ++ eulaBuffer ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ I _accept the license agreement ++ True ++ True ++ False ++ True ++ 0 ++ True ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/pyanaconda/ui/gui/spokes/eula.py b/pyanaconda/ui/gui/spokes/eula.py +new file mode 100644 +index 0000000..a487c6b +--- /dev/null ++++ b/pyanaconda/ui/gui/spokes/eula.py +@@ -0,0 +1,107 @@ ++import logging ++ ++from pyanaconda.ui.common import FirstbootOnlySpokeMixIn ++from pyanaconda.ui.gui.spokes import NormalSpoke ++from pyanaconda.core.i18n import _, CN_ ++from pyanaconda.core import eula ++from pyanaconda.ui.categories.eula import LicensingCategory ++from pyanaconda.anaconda_loggers import get_module_logger ++from pykickstart.constants import FIRSTBOOT_RECONFIG ++ ++log = get_module_logger(__name__) ++__all__ = ["EULASpoke"] ++ ++ ++class EULASpoke(FirstbootOnlySpokeMixIn, NormalSpoke): ++ """The EULA spoke""" ++ ++ builderObjects = ["eulaBuffer", "eulaWindow"] ++ mainWidgetName = "eulaWindow" ++ uiFile = "spokes/eula.glade" ++ icon = "application-certificate-symbolic" ++ title = CN_("GUI|Spoke", "_License Information") ++ category = LicensingCategory ++ ++ @staticmethod ++ def get_screen_id(): ++ """Return a unique id of this UI screen.""" ++ return "license-information" ++ ++ def initialize(self): ++ log.debug("initializing the EULA spoke") ++ NormalSpoke.initialize(self) ++ ++ self._have_eula = True ++ self._eula_buffer = self.builder.get_object("eulaBuffer") ++ self._agree_check_button = self.builder.get_object("agreeCheckButton") ++ self._agree_label = self._agree_check_button.get_child() ++ self._agree_text = self._agree_label.get_text() ++ ++ log.debug("looking for the license file") ++ license_file = eula.get_license_file_name() ++ if not license_file: ++ log.error("no license found") ++ self._have_eula = False ++ self._eula_buffer.set_text(_("No license found")) ++ return ++ ++ # if there is "eula <...>" in kickstart, use its value ++ if self.data.eula.agreed is not None: ++ self._agree_check_button.set_active(self.data.eula.agreed) ++ ++ self._eula_buffer.set_text("") ++ itr = self._eula_buffer.get_iter_at_offset(0) ++ log.debug("opening the license file") ++ with open(license_file, "r") as fobj: ++ # insert the first line without prefixing with space ++ try: ++ first_line = next(fobj) ++ except StopIteration: ++ # nothing in the file ++ return ++ self._eula_buffer.insert(itr, first_line.strip()) ++ ++ # EULA file may be preformatted for the console, we want to let Gtk ++ # format it (blank lines should be preserved) ++ for line in fobj: ++ stripped_line = line.strip() ++ if stripped_line: ++ self._eula_buffer.insert(itr, " " + stripped_line) ++ else: ++ self._eula_buffer.insert(itr, "\n\n") ++ ++ def refresh(self): ++ self._agree_check_button.set_sensitive(self._have_eula) ++ self._agree_check_button.set_active(self.data.eula.agreed) ++ ++ def apply(self): ++ self.data.eula.agreed = self._agree_check_button.get_active() ++ ++ @property ++ def completed(self): ++ return not self._have_eula or self.data.eula.agreed ++ ++ @property ++ def status(self): ++ if not self._have_eula: ++ return _("No license found") ++ ++ return _("License accepted") if self.data.eula.agreed else _("License not accepted") ++ ++ @classmethod ++ def should_run(cls, environment, data): ++ if eula.eula_available(): ++ # don't run if we are in initial-setup in reconfig mode and the EULA has already been accepted ++ if FirstbootOnlySpokeMixIn.should_run(environment, data) and data and data.firstboot.firstboot == FIRSTBOOT_RECONFIG and data.eula.agreed: ++ log.debug("not running license spoke: reconfig mode & license already accepted") ++ return False ++ return True ++ return False ++ ++ def on_check_button_toggled(self, *args): ++ if self._agree_check_button.get_active(): ++ log.debug("license is now accepted") ++ self._agree_label.set_markup("%s" % self._agree_text) ++ else: ++ log.debug("license no longer accepted") ++ self._agree_label.set_markup(self._agree_text) +diff --git a/pyanaconda/ui/tui/spokes/eula.py b/pyanaconda/ui/tui/spokes/eula.py +new file mode 100644 +index 0000000..a3e8e62 +--- /dev/null ++++ b/pyanaconda/ui/tui/spokes/eula.py +@@ -0,0 +1,128 @@ ++import logging ++ ++from pyanaconda.ui.tui.spokes import NormalTUISpoke ++from simpleline.render.widgets import TextWidget, CheckboxWidget ++from simpleline.render.containers import ListRowContainer ++from simpleline.render.screen import UIScreen, InputState ++from simpleline.render.screen_handler import ScreenHandler ++from pyanaconda.ui.common import FirstbootOnlySpokeMixIn ++from pyanaconda.core import eula ++from pyanaconda.ui.categories.eula import LicensingCategory ++from pyanaconda.core.i18n import _, N_ ++from pykickstart.constants import FIRSTBOOT_RECONFIG ++ ++log = logging.getLogger("initial-setup") ++ ++__all__ = ["EULASpoke"] ++ ++ ++class EULASpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): ++ """The EULA spoke providing ways to read the license and agree/disagree with it.""" ++ ++ category = LicensingCategory ++ ++ @staticmethod ++ def get_screen_id(): ++ """Return a unique id of this UI screen.""" ++ return "license-information" ++ ++ def __init__(self, *args, **kwargs): ++ NormalTUISpoke.__init__(self, *args, **kwargs) ++ self.title = _("License information") ++ self._container = None ++ ++ def initialize(self): ++ NormalTUISpoke.initialize(self) ++ ++ def refresh(self, args=None): ++ NormalTUISpoke.refresh(self, args) ++ ++ self._container = ListRowContainer(1) ++ ++ log.debug("license found") ++ # make the options aligned to the same column (the checkbox has the ++ # '[ ]' prepended) ++ self._container.add(TextWidget("%s\n" % _("Read the License Agreement")), ++ self._show_license_screen_callback) ++ ++ self._container.add(CheckboxWidget(title=_("I accept the license agreement"), ++ completed=self.data.eula.agreed), ++ self._license_accepted_callback) ++ self.window.add_with_separator(self._container) ++ ++ @property ++ def completed(self): ++ # Either there is no EULA available, or user agrees/disagrees with it. ++ return self.data.eula.agreed ++ ++ @property ++ def mandatory(self): ++ # This spoke is always mandatory. ++ return True ++ ++ @property ++ def status(self): ++ return _("License accepted") if self.data.eula.agreed else _("License not accepted") ++ ++ @classmethod ++ def should_run(cls, environment, data): ++ if eula.eula_available(): ++ # don't run if we are in initial-setup in reconfig mode and the EULA has already been accepted ++ if FirstbootOnlySpokeMixIn.should_run(environment, data) and data and data.firstboot.firstboot == FIRSTBOOT_RECONFIG and data.eula.agreed: ++ log.debug("not running license spoke: reconfig mode & license already accepted") ++ return False ++ return True ++ return False ++ ++ def apply(self): ++ # nothing needed here, the agreed field is changed in the input method ++ pass ++ ++ def input(self, args, key): ++ if not self._container.process_user_input(key): ++ return key ++ ++ return InputState.PROCESSED ++ ++ @staticmethod ++ def _show_license_screen_callback(data): ++ # show license ++ log.debug("showing the license") ++ eula_screen = LicenseScreen() ++ ScreenHandler.push_screen(eula_screen) ++ ++ def _license_accepted_callback(self, data): ++ # toggle EULA agreed checkbox by changing ksdata ++ log.debug("license accepted state changed to: %s", self.data.eula.agreed) ++ self.data.eula.agreed = not self.data.eula.agreed ++ self.redraw() ++ ++ ++class LicenseScreen(UIScreen): ++ """Screen showing the License without any input from user requested.""" ++ ++ def __init__(self): ++ super().__init__() ++ ++ self._license_file = eula.get_license_file_name() ++ ++ def refresh(self, args=None): ++ super().refresh(args) ++ ++ # read the license file and make it one long string so that it can be ++ # processed by the TextWidget to fit in the screen in a best possible ++ # way ++ log.debug("reading the license file") ++ with open(self._license_file, 'r') as f: ++ license_text = f.read() ++ ++ self.window.add_with_separator(TextWidget(license_text)) ++ ++ def input(self, args, key): ++ """ Handle user input. """ ++ return InputState.PROCESSED_AND_CLOSE ++ ++ def prompt(self, args=None): ++ # we don't want to prompt user, just close the screen ++ self.close() ++ return None +-- +2.39.2 + diff --git a/SOURCES/russian-34.25.4.7.po b/SOURCES/russian-34.25.4.7.po index 1edb03d..f10e7b6 100644 --- a/SOURCES/russian-34.25.4.7.po +++ b/SOURCES/russian-34.25.4.7.po @@ -7647,6 +7647,37 @@ msgstr "Закрыть" msgid "Starting Install to Hard Drive" msgstr "Начинается установка на жёсткий диск" +msgid "LICENSING" +msgstr "ЛИЦЕНЗИРОВАНИЕ" + +msgctxt "GUI|Spoke" +msgid "_License Information" +msgstr "О _лицензии" + +msgid "License information" +msgstr "О лицензии" + +msgid "License Information" +msgstr "О лицензии" + +msgid "License accepted" +msgstr "Лицензия принята" + +msgid "License not accepted" +msgstr "Лицензия не принята" + +msgid "Read the License Agreement" +msgstr "Прочитайте лицензионное соглашение" + +msgid "I accept the license agreement" +msgstr "Принимаю лицензионное соглашение" + +msgid "I _accept the license agreement" +msgstr "_Принимаю лицензионное соглашение" + +msgid "License Agreement:" +msgstr "Лицензионное соглашение:" + #~ msgid "Failed to attach subscription." #~ msgstr "Не удалось прикрепить подписку." diff --git a/SPECS/anaconda.spec b/SPECS/anaconda.spec index 48f1338..13fcb8f 100644 --- a/SPECS/anaconda.spec +++ b/SPECS/anaconda.spec @@ -1,7 +1,7 @@ Summary: Graphical system installer Name: anaconda Version: 34.25.4.9 -Release: 1%{?dist}.inferit.1 +Release: 1%{?dist}.inferit.2 License: GPLv2+ and MIT URL: http://fedoraproject.org/wiki/Anaconda @@ -31,6 +31,7 @@ Patch1013: 0013-Fix-smt-url.patch Patch1014: 0014-Make-branding-free-Russian-translation.patch Patch1015: 0015-Disable-sphere-url.patch Patch1016: 0016-Set-LatGrkCyr-8x16-as-default-font-instead-of-eurlat.patch +Patch1017: 0017-Implement-show-EULA-before-installation.patch # Versions of required components (done so we make sure the buildrequires # match the requires versions of things). @@ -441,6 +442,9 @@ desktop-file-install --dir=%{buildroot}%{_datadir}/applications %{buildroot}%{_d %{_prefix}/libexec/anaconda/dd_* %changelog +* Thu Sep 26 2024 Dmitry Samoylik - 34.25.4.9-1.inferit.2 +- Implement show EULA before installation + * Mon Aug 12 2024 Arkady L. Shane - 34.25.4.9-1.inferit.1 - Set LatGrkCyr-8x16 as default font instead of eurlatgr