From e9ee2f01a6add923aa4ea7791be0c48855faec47 Mon Sep 17 00:00:00 2001 From: deeplow Date: Wed, 15 Jan 2025 14:15:00 +0000 Subject: [PATCH] WIP: Print Dialog: hack to obtain exceptions raised in print dialog Exceptions raised in GTK code are trapped. However, the print logic needs to know the result of the print dialog (usually communicated through exceptions). A context manager is used to keep track of the exception the GTK code would ideally raise. --- .../securedrop_export/print/print_dialog.py | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/export/securedrop_export/print/print_dialog.py b/export/securedrop_export/print/print_dialog.py index f6d970417..cb1191a28 100644 --- a/export/securedrop_export/print/print_dialog.py +++ b/export/securedrop_export/print/print_dialog.py @@ -8,23 +8,60 @@ logger = logging.getLogger(__name__) -def open_print_dialog(file_to_print): - app = PrintDialog(file_to_print) - app.run() + +class GtkExceptionRaiser: + """ + Context manager to keep track of exceptions to be raised after GTK exits + + This is a workaround for the fact that GTK does not behave like regular + libraries. Exceptions raised by the GUI code are always caught within GTK. + The context manager provides a way to store these exceptions. + + Usage: + + class SomeApplication(Gtk.Application): + def __init__(self, raise_later_func): + super().__init__() + self.raise_later_func = raise_later_func + + [...] + + def on_something_bad_happening(self): + self.raise_later_func(Exception("something happned")) + self.quit() + + with GtkExceptionRaiser() as raise_later_func: + app = SomeApplication(raise_later_func) + app.run() + """ + def __init__(self): + self.exception_to_raise = None + + def raise_later_func(self, exception): + self.exception_to_raise = exception + + def __enter__(self): + return self.raise_later_func + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.exception_to_raise: + raise self.exception_to_raise class PrintDialog(Gtk.Application): - def __init__(self, file_to_print): + def __init__(self, file_to_print, raise_later_func): super().__init__(application_id="org.securedrop.PrintDialog") self.file_to_print = file_to_print + self.raise_later_func = raise_later_func self.connect("activate", self.on_activate) def on_activate(self, app): window = Gtk.Window(application=app) + window.hide() self.dialog = Gtk.PrintUnixDialog.new("Print Document", window) self.dialog.connect("response", self.on_response) + self.dialog.connect("close", self.quit) self.dialog.show() - window.hide() def on_response(self, parent_widget, response_id): if response_id == Gtk.ResponseType.OK: @@ -36,13 +73,23 @@ def on_response(self, parent_widget, response_id): job = Gtk.PrintJob.new("print job", printer, settings, page_setup) job.set_source_file(self.file_to_print) job.send(self.on_job_complete, user_data=None) - elif response_id == Gtk.ResponseType.APPLY: # Preview (if available) - pass elif response_id == Gtk.ResponseType.CANCEL: # FIXME should this exist or should it simply cancel and not report errors - raise ExportException(sdstatus=Status.ERROR_PRINT, sderror="User canceled dialog") + self.raise_later_func( + ExportException(sdstatus=Status.ERROR_PRINT, sderror="User canceled dialog") + ) + self.quit() + elif response_id == Gtk.ResponseType.DELETE_EVENT: + self.quit() def on_job_complete(self, print_job, user_data, error): if error: - self.quit() - raise ExportException(sdstatus=Status.ERROR_PRINT, sderror=error.message) + self.raise_later_func( + ExportException(sdstatus=Status.ERROR_PRINT, sderror=error.message) + ) + self.quit() + +def open_print_dialog(file_to_print): + with GtkExceptionRaiser() as raise_later_func: + app = PrintDialog(file_to_print, raise_later_func) + app.run()