Skip to content

Commit 3e0a49f

Browse files
committed
qui-domains: adjust shutdown failure actions
1 parent d7ecd51 commit 3e0a49f

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

@@ -547,7 +588,7 @@ def __init__(self, is_preload=False):
547588
class StartedMenu(Gtk.Menu):
548589
"""The sub-menu for a started domain"""
549590

550-
def __init__(self, vm, app, icon_cache):
591+
def __init__(self, vm, app, icon_cache, shutdown_failed=False):
551592
super().__init__()
552593
self.vm = vm
553594
self.app = app
@@ -561,7 +602,17 @@ def __init__(self, vm, app, icon_cache):
561602

562603
self.add(PreferencesItem(self.vm, icon_cache))
563604
self.add(PauseItem(self.vm, icon_cache))
564-
self.add(ShutdownItem(self.vm, icon_cache, force=app.shift_pressed))
605+
self.add(
606+
ShutdownItem(
607+
self.vm,
608+
icon_cache,
609+
force=app.shift_pressed and not shutdown_failed,
610+
follow_shift=not shutdown_failed,
611+
)
612+
)
613+
if shutdown_failed:
614+
self.add(ShutdownItem(self.vm, icon_cache, force=True, follow_shift=False))
615+
self.add(KillItem(self.vm, icon_cache))
565616
if self.vm.klass != "DispVM" or not self.vm.auto_cleanup:
566617
self.add(RestartItem(self.vm, icon_cache, force=app.shift_pressed))
567618

@@ -709,6 +760,7 @@ def __init__(self, vm, app, icon_cache, state=None):
709760
self.vm = vm
710761
self.app = app
711762
self.icon_cache = icon_cache
763+
self.shutdown_failed = False
712764
self.decorator = qui.decorators.DomainDecorator(vm)
713765

714766
# Main horizontal box
@@ -766,7 +818,12 @@ def _set_submenu(self, state):
766818
is_preload=is_preload,
767819
)
768820
elif state == "Running":
769-
submenu = StartedMenu(self.vm, self.app, self.icon_cache)
821+
submenu = StartedMenu(
822+
self.vm,
823+
self.app,
824+
self.icon_cache,
825+
shutdown_failed=self.shutdown_failed,
826+
)
770827
elif state == "Paused":
771828
submenu = PausedMenu(self.vm, self.icon_cache)
772829
else:
@@ -1182,6 +1239,16 @@ def update_domain_item(self, vm, event, **kwargs):
11821239
# it's a fragile DispVM
11831240
state = "Transient"
11841241

1242+
if event == "domain-shutdown-failed":
1243+
item.shutdown_failed = True
1244+
elif event in (
1245+
"domain-start",
1246+
"domain-pre-start",
1247+
"domain-shutdown",
1248+
"domain-pre-shutdown",
1249+
):
1250+
item.shutdown_failed = False
1251+
11851252
item.update_state(state)
11861253

11871254
if event == "property-reset:is_preload":

0 commit comments

Comments
 (0)