Comment 15 for bug 686859

TJ (tj) wrote :

I was caught by this yesterday with an amd64 16.04.1 DVD install to a BIOS+GPT custom ("Something Else") configuration where I had forgotten to create the GPT BIOS Boot Partition for GRUB's core.

The Bootloader Error dialog is modal with only a GtkButton "gtk-ok". Reading the ubiquity Python code and XML user interface description files it looks as if the OK button is disabled if *any* changes are made to the device GtkComboBox where the user-selected device is invalid (which is checked by the grub_verify_loop() handler.

In my case there was no alternative valid option so once I'd interacted with the GtkComboBox the OK button was disabled and I couldn't close the modal dialog.

So the issue seems to be that once the OK button has been disabled it is never re-enabled when selecting other valid options.

I spent quite some time trying to understand and track the execution flow but didn't get to the point of testing my bug-fix hypothesis due to lack of time but it is detailed at the end of this comment if anyone cares to test it.

/use/lib/ubiquity/ubiquity/frontend/gtk_ui.py::Wizard.run(): (line ~718)

        # Auto-connecting signals with additional parameters does not work.
        self.grub_new_device_entry.connect(
            'changed', self.grub_verify_loop, self.grub_fail_okbutton)

This connects a signal from the device GtkComboBox to the grub_verify_loop() function.

/usr/lib/ubiquity/ubiquity/frontend/gtk_ui.py::Wizard.grub_verify_loop() (line ~1706)

    def grub_verify_loop(self, widget, okbutton):
        if widget is not None:
            if validation.check_grub_device(widget.get_child().get_text()):
                okbutton.set_sensitive(True)
            else:
                okbutton.set_sensitive(False)

Which will disable the OK button if the device is not valid.

/usr/lib/ubiquity/ubiquity/frontend/gtk_ui.py::bootloader_dialog(): (line ~1775)
...
        response = self.bootloader_fail_dialog.run()
        self.bootloader_fail_dialog.hide()
        if response == Gtk.ResponseType.OK:
            if self.grub_new_device.get_active():
                return self.grub_new_device_entry.get_child().get_text()
            elif self.grub_no_new_device.get_active():
                return 'skip'
            else:
                return ''
        else:
            return ''

because bootloader_fail_dialog.run() blocks and never returns the subsequent .hide() and response handling never occurs.

In /usr/share/ubiquity/gtk/ubiquity.ui the OK button id is "grub_fail_okbutton" and it is attached to:

 <action-widget response="-5">grub_fail_okbutton</action-widget>

The XML also defines a "toggled" signal attached to each of the radio buttons that calls the toggle_grub_fail() handler.

/usr/lib/ubiquity/ubiquity/frontend/gtk_ui.py::Wizard.toggle_grub_fail() (line ~1761)

    def toggle_grub_fail(self, unused_widget):
        if self.grub_no_new_device.get_active():
            self.no_grub_warn.show()
            self.grub_new_device_entry.set_sensitive(False)
            self.abort_warn.hide()
        elif self.grub_fail_option.get_active():
            self.abort_warn.show()
            self.no_grub_warn.hide()
            self.grub_new_device_entry.set_sensitive(False)
        else:
            self.abort_warn.hide()
            self.no_grub_warn.hide()
            self.grub_new_device_entry.set_sensitive(True)

This changes the visibility of the option-specific warnings that appear when those options are selected and alters the enabled status of the GtkComboBox.

So I guess the solution is to add two identical lines here to ensure the OK button is enabled for the 'proceed without GRUB' or 'Cancel installtion' options:

    def toggle_grub_fail(self, unused_widget):
        if self.grub_no_new_device.get_active():
            self.no_grub_warn.show()
            self.grub_new_device_entry.set_sensitive(False)

            self.okbutton.set_sensitive(True)

            self.abort_warn.hide()
        elif self.grub_fail_option.get_active():
            self.abort_warn.show()
            self.no_grub_warn.hide()
            self.grub_new_device_entry.set_sensitive(False)

            self.okbutton.set_sensitive(True)

        else:
            self.abort_warn.hide()
            self.no_grub_warn.hide()
            self.grub_new_device_entry.set_sensitive(True)