spacr.qt.shutdown

Provide graceful-stop, force-quit, and force-restart controls for Qt.

Cooperative cancellation remains the default because it lets active writes finish safely. When a worker cannot respond, this module presents the user with the consequences of stopping immediately, supports a verified restart record, and can repeat the choice after RECHECK_MS.

Classes

GracefulQuitWatcher

Re-ask about force quitting while a graceful stop is still running.

Functions

ask_how_to_quit(→ str)

Ask whether to stop cooperatively or to kill.

describe_active(→ str)

One line per running job, for the prompts above.

force_quit_now(→ None)

Flush available logs and terminate the process immediately.

restart_spacr([run_folders, launcher, exiter])

Save the current state and restart spaCR in a new process.

style_as_danger(→ None)

Paint button in the theme's danger colour.

Module Contents

class spacr.qt.shutdown.GracefulQuitWatcher(parent: PySide6.QtWidgets.QWidget | None, still_running: Callable[[], bool], *, what: str, describe: Callable[[], str] | None = None, on_force: Callable[[], None] | None = None, interval_ms: int = RECHECK_MS)[source]

Bases: PySide6.QtCore.QObject

Re-ask about force quitting while a graceful stop is still running.

Owned by whoever started the graceful attempt. It holds no reference to the jobs themselves – it calls still_running() each time, so a caller is free to retire handles underneath it.

Stops asking as soon as still_running() reports False, and stops for good once force is chosen, so a user who is already leaving is not asked a second time on the way out.

Parameters:
  • parent – widget the question is shown over, or None.

  • still_running – called with no arguments before each prompt and answers whether anything is left to wait for. A callable rather than a list of handles, so the caller may retire them underneath.

  • what – what is still running, named in the question the user reads. Keyword-only.

  • describe – called with no arguments for a longer line under that question, when there is more worth saying than what.

  • on_force – called if the user chooses to force the quit. Nothing here kills anything itself.

  • interval_ms – milliseconds between prompts.

Arm the watcher that asks again while something is still running.

Parameters:
  • parent – the window the question is asked on, or None.

  • still_running – called to ask whether the work is still going.

  • what – what is running, named in the question.

  • describe – called for a longer description of the work.

  • on_force – called when the user chooses to quit anyway; defaults to the module’s own force-quit.

  • interval_ms – how often to ask again.

start() → None[source]

Begin the five-minute cycle, unless it is already finished.

stop() → None[source]

Stop watching for the quit signal.

IDEMPOTENT: shutdown can be reached by more than one route, and a second stop must not be an error.

spacr.qt.shutdown.ask_how_to_quit(parent: PySide6.QtWidgets.QWidget | None, *, what: str, detail: str = '', verb: str = 'Quit', offer_restart: bool = False, restart_detail: str = '') → str[source]

Ask whether to stop cooperatively or to kill.

Parameters:
  • parent – widget that owns and centres the modal question, or None for an application-level dialog.

  • what – what is being quit, in the user’s words – “spaCR” or the name of a module. It is used in the sentence, so it reads as a noun: “Quit Mask Generation?”.

  • verb – the word for what is about to happen. “Quit” for the application and the Home banner, “Stop” for a module’s Stop button – a button labelled Stop that opens a dialog headed “Quit” reads as the wrong dialog, and a user who thinks they have mis-clicked cancels out of the thing they wanted.

  • detail – appended under the question. Callers use it to name what is still running, because “something is still running” is not enough information to choose with.

  • offer_restart – add a Force restart button. This option is disabled by default for ordinary quit dialogs.

  • restart_detail – what Force restart will cost, from spacr.restart_state.warning_text(). Shown only when the button is offered, and REQUIRED to be meaningful when it is – a button whose consequences are not on screen beside it is one people press once.

Returns:

GRACEFUL, FORCE, RESTART or CANCEL.

Cancel is the default button and the escape action. Force quit is reachable in one click but is never what a stray Return key does, and Force restart is LAST because it is the most destructive thing on the dialog.

spacr.qt.shutdown.describe_active(handles: Iterable) → str[source]

One line per running job, for the prompts above.

“Something is still running” is not information anybody can decide with; the name of the module and how long it has been going is.

Parameters:

handles – running job handles; each one’s app_key (or 'job') and elapsed() seconds, shown in whole minutes, are listed.

spacr.qt.shutdown.force_quit_now(exit_code: int = 1) → None[source]

Flush available logs and terminate the process immediately.

This function uses os._exit(), so Python finalizers, atexit handlers, and Qt teardown do not run. Use it only after the user confirms a force quit.

Parameters:

exit_code – process exit status.

spacr.qt.shutdown.restart_spacr(module: str, settings=None, *, running=(), run_folders=(), launcher=None, exiter=None) → bool[source]

Save the current state and restart spaCR in a new process.

The restart is cancelled if the state cannot be written and verified.

Parameters:
  • module – key of the module to reopen.

  • settings – module settings to restore after launch.

  • running – active-run records for the restart summary.

  • run_folders – paths that may contain interrupted-run output.

  • launcher – optional process-launch function. Defaults to a detached subprocess.Popen call.

  • exiter – optional exit function. Defaults to force_quit_now().

Returns:

True when the replacement process was started; False when saving or launching failed and the current process remains open.

spacr.qt.shutdown.style_as_danger(button: PySide6.QtWidgets.QPushButton, palette: dict | None = None) → None[source]

Paint button in the theme’s danger colour.

Scoped to the one button rather than added to the application sheet: this is the only red control on its row, and a global #DangerButton rule would be a new thing for every later screen to trip over.

Parameters:

button – the push button to restyle; it is given the object name DangerButton when it has none.