Skip to content

Commit 83f6a50

Browse files
committed
qui-domains: adjust shutdown failure actions
1 parent d7ecd51 commit 83f6a50

1 file changed

Lines changed: 132 additions & 96 deletions

File tree

qui/tray/domains.py

Lines changed: 132 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -192,21 +192,36 @@ 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__(
196+
self,
197+
vm,
198+
icon_cache,
199+
force=False,
200+
follow_shift=True,
201+
label=None,
202+
force_label=None,
203+
icon_name="shutdown",
204+
):
196205
if force:
197206
super().__init__(
198207
vm,
199-
label=_("Force shutdown"),
208+
label=force_label or _("Force shutdown"),
200209
icon_cache=icon_cache,
201-
icon_name="shutdown",
210+
icon_name=icon_name,
202211
)
203212
else:
204213
super().__init__(
205-
vm, label=_("Shutdown"), icon_cache=icon_cache, icon_name="shutdown"
214+
vm,
215+
label=label or _("Shutdown"),
216+
icon_cache=icon_cache,
217+
icon_name=icon_name,
206218
)
207219
self.force = force
220+
self.follow_shift = follow_shift
208221

209222
def set_force(self, force):
223+
if not self.follow_shift:
224+
return
210225
self.force = force
211226
if self.force:
212227
self.label.set_text(_("Force shutdown"))
@@ -226,89 +241,97 @@ async def perform_action(self):
226241
).format(self.vm.name, str(ex)),
227242
)
228243
return
229-
if isinstance(ex, exc.QubesVMInUseError):
230-
title = _("Qube {0} is in use").format(self.vm.name)
231-
markup = _(
232-
"The qube <b>{0}</b> could not be shut down because it "
233-
"is in use by connected qubes.\n\n"
234-
"<b>Warning:</b> force shutdown may cause unexpected "
235-
"issues in connected qubes."
236-
).format(self.vm.name)
237-
button_label = _("Force shutdown")
238-
action = "force"
239-
elif isinstance(ex, exc.QubesVMShutdownTimeoutError):
240-
title = _("Qube {0} shutdown timed out").format(self.vm.name)
241-
markup = _(
242-
"The qube <b>{0}</b> did not shut down within the "
243-
"expected time.\n\nYou can retry the shutdown or, "
244-
"if the problem persists, kill the qube."
245-
).format(self.vm.name)
246-
button_label = None # timeout uses custom buttons below
247-
action = "timeout"
248-
else:
249-
title = _("Error shutting down qube {0}").format(self.vm.name)
250-
markup = _(
251-
"The qube <b>{0}</b> could not be shut down. "
252-
"The following error occurred:\n"
253-
"<tt>{1}</tt>\n\n"
254-
"Would you like to kill the qube?"
255-
).format(self.vm.name, str(ex))
256-
button_label = _("Kill")
257-
action = "kill"
258-
259-
dialog = Gtk.MessageDialog(
260-
None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE
261-
)
262-
dialog.set_title(title)
263-
dialog.set_markup(markup)
264-
dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
265-
if action == "timeout":
266-
dialog.add_button(_("Retry shutdown"), Gtk.ResponseType.OK)
267-
dialog.add_button(_("Kill"), Gtk.ResponseType.YES)
268-
else:
269-
dialog.add_button(button_label, Gtk.ResponseType.OK)
270-
dialog.connect("response", self.react_to_question, action)
271-
GLib.idle_add(dialog.show)
244+
self.show_shutdown_dialog(ex)
245+
246+
def get_shutdown_question(self, ex):
247+
if isinstance(ex, exc.QubesVMInUseError):
248+
title = _("Qube {0} is in use").format(self.vm.name)
249+
markup = _(
250+
"The qube <b>{0}</b> could not be shut down because it "
251+
"is in use by connected qubes.\n\n"
252+
"<b>Warning:</b> force shutdown may cause unexpected "
253+
"issues in connected qubes."
254+
).format(self.vm.name)
255+
action = "force"
256+
elif isinstance(ex, exc.QubesVMShutdownTimeoutError):
257+
title = _("Qube {0} shutdown timed out").format(self.vm.name)
258+
markup = _(
259+
"The qube <b>{0}</b> did not shut down within the "
260+
"expected time.\n\nYou can retry the shutdown or, "
261+
"if the problem persists, kill the qube."
262+
).format(self.vm.name)
263+
action = "timeout"
264+
else:
265+
title = _("Error shutting down qube {0}").format(self.vm.name)
266+
markup = _(
267+
"The qube <b>{0}</b> could not be shut down. "
268+
"The following error occurred:\n"
269+
"<tt>{1}</tt>\n\n"
270+
"Would you like to kill the qube?"
271+
).format(self.vm.name, str(ex))
272+
action = "kill"
273+
return title, markup, action
274+
275+
def show_shutdown_dialog(self, ex):
276+
title, markup, action = self.get_shutdown_question(ex)
277+
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE)
278+
dialog.set_title(title)
279+
dialog.set_markup(markup)
280+
dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
281+
if action == "force":
282+
dialog.add_button(_("Force shutdown"), Gtk.ResponseType.OK)
283+
elif action == "timeout":
284+
dialog.add_button(_("Retry shutdown"), Gtk.ResponseType.OK)
285+
dialog.add_button(_("Kill"), Gtk.ResponseType.YES)
286+
elif action == "kill":
287+
dialog.add_button(_("Kill"), Gtk.ResponseType.OK)
288+
dialog.connect("response", self.react_to_question, action)
289+
GLib.idle_add(dialog.show)
272290

273291
def react_to_question(self, widget, response, action):
274292
if response not in (Gtk.ResponseType.OK, Gtk.ResponseType.YES):
275293
widget.destroy()
276294
return
277295
try:
278-
if action == "force":
279-
self.vm.shutdown(force=True)
280-
elif action == "timeout":
281-
if response == Gtk.ResponseType.YES:
282-
self.vm.kill()
283-
elif response == Gtk.ResponseType.OK:
284-
self.vm.shutdown(force=False)
285-
elif action == "kill" and response == Gtk.ResponseType.OK:
286-
self.vm.kill()
296+
self.do_shutdown_dialog_action(response, action)
287297
except exc.QubesException as ex:
288-
show_error(
289-
_("Error shutting down qube"),
290-
_(
291-
"The following error occurred while attempting to "
292-
"shut down qube {0}:\n{1}"
293-
).format(self.vm.name, str(ex)),
294-
)
298+
if action == "timeout" and response == Gtk.ResponseType.OK:
299+
self.show_shutdown_dialog(ex)
300+
else:
301+
show_error(
302+
_("Error shutting down qube"),
303+
_(
304+
"The following error occurred while attempting to "
305+
"shut down qube {0}:\n{1}"
306+
).format(self.vm.name, str(ex)),
307+
)
295308
widget.destroy()
296309

310+
def do_shutdown_dialog_action(self, response, action):
311+
if action == "force":
312+
self.vm.shutdown(force=True)
313+
elif action == "timeout":
314+
if response == Gtk.ResponseType.YES:
315+
self.vm.kill()
316+
elif response == Gtk.ResponseType.OK:
317+
self.vm.shutdown(force=False)
318+
elif action == "kill" and response == Gtk.ResponseType.OK:
319+
self.vm.kill()
320+
297321

298-
class RestartItem(VMActionMenuItem):
322+
class RestartItem(ShutdownItem):
299323
"""Restart menu Item. When activated shutdowns the domain and
300324
then starts it again."""
301325

302326
def __init__(self, vm, icon_cache, force=False):
303-
if force:
304-
super().__init__(
305-
vm, label=_("Force restart"), icon_cache=icon_cache, icon_name="restart"
306-
)
307-
else:
308-
super().__init__(
309-
vm, label=_("Restart"), icon_cache=icon_cache, icon_name="restart"
310-
)
311-
self.force = force
327+
super().__init__(
328+
vm,
329+
icon_cache,
330+
force=force,
331+
label=_("Restart"),
332+
force_label=_("Force restart"),
333+
icon_name="restart",
334+
)
312335
self.give_up = False
313336

314337
def set_force(self, force):
@@ -332,19 +355,7 @@ async def perform_action(self, *_args, **_kwargs):
332355
).format(self.vm.name, str(ex)),
333356
)
334357
return
335-
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."
345-
)
346-
dialog.connect("response", self.react_to_question)
347-
GLib.idle_add(dialog.show)
358+
self.show_shutdown_dialog(ex)
348359

349360
try:
350361
while self.vm.is_running():
@@ -366,11 +377,17 @@ async def perform_action(self, *_args, **_kwargs):
366377
).format(self.vm.name, str(ex)),
367378
)
368379

369-
def react_to_question(self, widget, response):
370-
if response == Gtk.ResponseType.OK:
371-
try:
372-
self.vm.shutdown(force=True)
373-
except exc.QubesException as ex:
380+
def react_to_question(self, widget, response, action):
381+
if response not in (Gtk.ResponseType.OK, Gtk.ResponseType.YES):
382+
self.give_up = True
383+
widget.destroy()
384+
return
385+
try:
386+
self.do_shutdown_dialog_action(response, action)
387+
except exc.QubesException as ex:
388+
if action == "timeout" and response == Gtk.ResponseType.OK:
389+
self.show_shutdown_dialog(ex)
390+
else:
374391
show_error(
375392
_("Error shutting down qube"),
376393
_(
@@ -379,8 +396,6 @@ def react_to_question(self, widget, response):
379396
).format(self.vm.name, str(ex)),
380397
)
381398
self.give_up = True
382-
else:
383-
self.give_up = True
384399
widget.destroy()
385400

386401

@@ -547,7 +562,7 @@ def __init__(self, is_preload=False):
547562
class StartedMenu(Gtk.Menu):
548563
"""The sub-menu for a started domain"""
549564

550-
def __init__(self, vm, app, icon_cache):
565+
def __init__(self, vm, app, icon_cache, shutdown_failed=False):
551566
super().__init__()
552567
self.vm = vm
553568
self.app = app
@@ -561,7 +576,17 @@ def __init__(self, vm, app, icon_cache):
561576

562577
self.add(PreferencesItem(self.vm, icon_cache))
563578
self.add(PauseItem(self.vm, icon_cache))
564-
self.add(ShutdownItem(self.vm, icon_cache, force=app.shift_pressed))
579+
self.add(
580+
ShutdownItem(
581+
self.vm,
582+
icon_cache,
583+
force=app.shift_pressed and not shutdown_failed,
584+
follow_shift=not shutdown_failed,
585+
)
586+
)
587+
if shutdown_failed:
588+
self.add(ShutdownItem(self.vm, icon_cache, force=True, follow_shift=False))
589+
self.add(KillItem(self.vm, icon_cache))
565590
if self.vm.klass != "DispVM" or not self.vm.auto_cleanup:
566591
self.add(RestartItem(self.vm, icon_cache, force=app.shift_pressed))
567592

@@ -709,6 +734,7 @@ def __init__(self, vm, app, icon_cache, state=None):
709734
self.vm = vm
710735
self.app = app
711736
self.icon_cache = icon_cache
737+
self.shutdown_failed = False
712738
self.decorator = qui.decorators.DomainDecorator(vm)
713739

714740
# Main horizontal box
@@ -766,7 +792,12 @@ def _set_submenu(self, state):
766792
is_preload=is_preload,
767793
)
768794
elif state == "Running":
769-
submenu = StartedMenu(self.vm, self.app, self.icon_cache)
795+
submenu = StartedMenu(
796+
self.vm,
797+
self.app,
798+
self.icon_cache,
799+
shutdown_failed=self.shutdown_failed,
800+
)
770801
elif state == "Paused":
771802
submenu = PausedMenu(self.vm, self.icon_cache)
772803
else:
@@ -1182,6 +1213,11 @@ def update_domain_item(self, vm, event, **kwargs):
11821213
# it's a fragile DispVM
11831214
state = "Transient"
11841215

1216+
if event == "domain-shutdown-failed":
1217+
item.shutdown_failed = True
1218+
elif event in ("domain-start", "domain-pre-start", "domain-shutdown"):
1219+
item.shutdown_failed = False
1220+
11851221
item.update_state(state)
11861222

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

0 commit comments

Comments
 (0)