Skip to content

Commit d8fd3f6

Browse files
committed
qui-domains: adjust shutdown failure actions
1 parent c2110b7 commit d8fd3f6

1 file changed

Lines changed: 94 additions & 27 deletions

File tree

qui/tray/domains.py

Lines changed: 94 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ async def perform_action(self):
192192
class ShutdownItem(VMActionMenuItem):
193193
"""Shutdown menu Item. When activated shutdowns the domain."""
194194

195-
def __init__(self, vm, icon_cache, force=False):
195+
def __init__(self, vm, icon_cache, force=False, follow_shift=True):
196196
if force:
197197
super().__init__(
198198
vm,
@@ -205,8 +205,11 @@ def __init__(self, vm, icon_cache, force=False):
205205
vm, label=_("Shutdown"), icon_cache=icon_cache, icon_name="shutdown"
206206
)
207207
self.force = force
208+
self.follow_shift = follow_shift
208209

209210
def set_force(self, force):
211+
if not self.follow_shift:
212+
return
210213
self.force = force
211214
if self.force:
212215
self.label.set_text(_("Force shutdown"))
@@ -332,18 +335,47 @@ async def perform_action(self, *_args, **_kwargs):
332335
).format(self.vm.name, str(ex)),
333336
)
334337
return
338+
if isinstance(ex, exc.QubesVMInUseError):
339+
title = _("Qube {0} is in use").format(self.vm.name)
340+
markup = _(
341+
"The qube <b>{0}</b> could not be shut down because it "
342+
"is in use by connected qubes.\n\n"
343+
"<b>Warning:</b> force shutdown may cause unexpected "
344+
"issues in connected qubes."
345+
).format(self.vm.name)
346+
action = "force"
347+
elif isinstance(ex, exc.QubesVMShutdownTimeoutError):
348+
title = _("Qube {0} shutdown timed out").format(self.vm.name)
349+
markup = _(
350+
"The qube <b>{0}</b> did not shut down within the "
351+
"expected time.\n\nYou can retry the shutdown or, "
352+
"if the problem persists, kill the qube."
353+
).format(self.vm.name)
354+
action = "timeout"
355+
else:
356+
title = _("Error restarting qube {0}").format(self.vm.name)
357+
markup = _(
358+
"The qube <b>{0}</b> could not be shut down. "
359+
"The following error occurred:\n"
360+
"<tt>{1}</tt>\n\n"
361+
"Would you like to kill the qube?"
362+
).format(self.vm.name, str(ex))
363+
action = "kill"
364+
335365
dialog = Gtk.MessageDialog(
336-
None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK_CANCEL
337-
)
338-
dialog.set_title("Error restarting qube")
339-
dialog.set_markup(
340-
f"The qube {self.vm.name} couldn't be shut down "
341-
"normally. The following error occurred: \n"
342-
f"<tt>{str(ex)}</tt>\n\n"
343-
"Do you want to force shutdown? \n\n<b>Warning:</b> "
344-
"this may cause unexpected issues in connected qubes."
366+
None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE
345367
)
346-
dialog.connect("response", self.react_to_question)
368+
dialog.set_title(title)
369+
dialog.set_markup(markup)
370+
dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
371+
if action == "force":
372+
dialog.add_button(_("Force shutdown"), Gtk.ResponseType.OK)
373+
elif action == "timeout":
374+
dialog.add_button(_("Retry shutdown"), Gtk.ResponseType.OK)
375+
dialog.add_button(_("Kill"), Gtk.ResponseType.YES)
376+
elif action == "kill":
377+
dialog.add_button(_("Kill"), Gtk.ResponseType.OK)
378+
dialog.connect("response", self.react_to_question, action)
347379
GLib.idle_add(dialog.show)
348380

349381
try:
@@ -366,20 +398,29 @@ async def perform_action(self, *_args, **_kwargs):
366398
).format(self.vm.name, str(ex)),
367399
)
368400

369-
def react_to_question(self, widget, response):
370-
if response == Gtk.ResponseType.OK:
371-
try:
401+
def react_to_question(self, widget, response, action):
402+
if response not in (Gtk.ResponseType.OK, Gtk.ResponseType.YES):
403+
self.give_up = True
404+
widget.destroy()
405+
return
406+
try:
407+
if action == "force":
372408
self.vm.shutdown(force=True)
373-
except exc.QubesException as ex:
374-
show_error(
375-
_("Error shutting down qube"),
376-
_(
377-
"The following error occurred while attempting to "
378-
"shut down qube {0}:\n{1}"
379-
).format(self.vm.name, str(ex)),
380-
)
381-
self.give_up = True
382-
else:
409+
elif action == "timeout":
410+
if response == Gtk.ResponseType.YES:
411+
self.vm.kill()
412+
elif response == Gtk.ResponseType.OK:
413+
self.vm.shutdown(force=False)
414+
elif action == "kill" and response == Gtk.ResponseType.OK:
415+
self.vm.kill()
416+
except exc.QubesException as ex:
417+
show_error(
418+
_("Error shutting down qube"),
419+
_(
420+
"The following error occurred while attempting to "
421+
"shut down qube {0}:\n{1}"
422+
).format(self.vm.name, str(ex)),
423+
)
383424
self.give_up = True
384425
widget.destroy()
385426

@@ -538,7 +579,7 @@ def __init__(self):
538579
class StartedMenu(Gtk.Menu):
539580
"""The sub-menu for a started domain"""
540581

541-
def __init__(self, vm, app, icon_cache):
582+
def __init__(self, vm, app, icon_cache, shutdown_failed=False):
542583
super().__init__()
543584
self.vm = vm
544585
self.app = app
@@ -552,7 +593,17 @@ def __init__(self, vm, app, icon_cache):
552593

553594
self.add(PreferencesItem(self.vm, icon_cache))
554595
self.add(PauseItem(self.vm, icon_cache))
555-
self.add(ShutdownItem(self.vm, icon_cache, force=app.shift_pressed))
596+
self.add(
597+
ShutdownItem(
598+
self.vm,
599+
icon_cache,
600+
force=app.shift_pressed and not shutdown_failed,
601+
follow_shift=not shutdown_failed,
602+
)
603+
)
604+
if shutdown_failed:
605+
self.add(ShutdownItem(self.vm, icon_cache, force=True, follow_shift=False))
606+
self.add(KillItem(self.vm, icon_cache))
556607
if self.vm.klass != "DispVM" or not self.vm.auto_cleanup:
557608
self.add(RestartItem(self.vm, icon_cache, force=app.shift_pressed))
558609

@@ -700,6 +751,7 @@ def __init__(self, vm, app, icon_cache, state=None):
700751
self.vm = vm
701752
self.app = app
702753
self.icon_cache = icon_cache
754+
self.shutdown_failed = False
703755
self.decorator = qui.decorators.DomainDecorator(vm)
704756

705757
# Main horizontal box
@@ -753,7 +805,12 @@ def _set_submenu(self, state):
753805
self.vm, self.icon_cache, working_correctly=(state == "Running")
754806
)
755807
elif state == "Running":
756-
submenu = StartedMenu(self.vm, self.app, self.icon_cache)
808+
submenu = StartedMenu(
809+
self.vm,
810+
self.app,
811+
self.icon_cache,
812+
shutdown_failed=self.shutdown_failed,
813+
)
757814
elif state == "Paused":
758815
submenu = PausedMenu(self.vm, self.icon_cache)
759816
else:
@@ -1166,6 +1223,16 @@ def update_domain_item(self, vm, event, **kwargs):
11661223
# it's a fragile DispVM
11671224
state = "Transient"
11681225

1226+
if event == "domain-shutdown-failed":
1227+
item.shutdown_failed = True
1228+
elif event in (
1229+
"domain-start",
1230+
"domain-pre-start",
1231+
"domain-shutdown",
1232+
"domain-pre-shutdown",
1233+
):
1234+
item.shutdown_failed = False
1235+
11691236
item.update_state(state)
11701237

11711238
if event == "domain-shutdown":

0 commit comments

Comments
 (0)