Skip to content

Commit

Permalink
Replace XPP with Gtk print dialog and dependencies
Browse files Browse the repository at this point in the history
Note: system-wide dependencies made available from
securedrop-export venv due to the need to access python3-gi.
Otherwise it would require too many build dependencies and complexity.
Given that python3-gi is already required by some Qubes components,
it does not add too many new dependencies.
  • Loading branch information
deeplow committed Jan 13, 2025
1 parent bdf52bc commit 078a14e
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 39 deletions.
48 changes: 48 additions & 0 deletions export/securedrop_export/print/print_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk

import logging
from securedrop_export.print.status import Status
from securedrop_export.exceptions import ExportException

logger = logging.getLogger(__name__)

def open_print_dialog(file_to_print):
app = PrintDialog(file_to_print)
app.run()


class PrintDialog(Gtk.Application):
def __init__(self, file_to_print):
super().__init__(application_id="org.securedrop.PrintDialog")
self.file_to_print = file_to_print
self.connect("activate", self.on_activate)

def on_activate(self, app):
window = Gtk.Window(application=app)
self.dialog = Gtk.PrintUnixDialog.new("Print Document", window)
self.dialog.connect("response", self.on_response)
self.dialog.show()
window.hide()

def on_response(self, parent_widget, response_id):
if response_id == Gtk.ResponseType.OK:
print(f"OK")
self.dialog.hide()
settings = self.dialog.get_settings()
printer = self.dialog.get_selected_printer()
page_setup = self.dialog.get_page_setup()
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")

def on_job_complete(self, print_job, user_data, error):
if error:
self.quit()
raise ExportException(sdstatus=Status.ERROR_PRINT, sderror=error.message)
41 changes: 2 additions & 39 deletions export/securedrop_export/print/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,31 +113,6 @@ def printer_test(self) -> Status:
# a success status here
return Status.PRINT_TEST_PAGE_SUCCESS

def _wait_for_print(self):
"""
Use lpstat to ensure the job was fully transfered to the printer
Return True if print was successful, otherwise throw ExportException.
Currently, the handler `handler` is defined in `exceptions.py`.
"""
signal.signal(signal.SIGALRM, handler)
signal.alarm(self.printer_wait_timeout)
printer_idle_string = f"printer {self.printer_name} is idle"
while True:
try:
logger.info(f"Running lpstat waiting for printer {self.printer_name}")
output = subprocess.check_output(["lpstat", "-p", self.printer_name])
if printer_idle_string in output.decode("utf-8"):
logger.info("Print completed")
return True
else:
time.sleep(5)
except subprocess.CalledProcessError:
raise ExportException(sdstatus=Status.ERROR_PRINT)
except TimeoutException:
logger.error(f"Timeout waiting for printer {self.printer_name}")
raise ExportException(sdstatus=Status.ERROR_PRINT)
return True

def _check_printer_setup(self) -> None:
"""
Check printer setup.
Expand Down Expand Up @@ -420,20 +395,8 @@ def _print_file(self, file_to_print: Path):
logger.error(f"Something went wrong: {file_to_print} not found")
raise ExportException(sdstatus=Status.ERROR_PRINT)

logger.info(f"Sending file to printer {self.printer_name}")
try:
# We can switch to using libreoffice --pt $printer_cups_name
# here, and either print directly (headless) or use the GUI
subprocess.check_call(
["xpp", "-P", self.printer_name, file_to_print],
)
except subprocess.CalledProcessError as e:
raise ExportException(sdstatus=Status.ERROR_PRINT, sderror=e.output)

# This is an addition to ensure that the entire print job is transferred over.
# If the job is not fully transferred within the timeout window, the user
# will see an error message.
self._wait_for_print()
logger.info("Opening print dialog")
open_print_dialog(str(file_to_print))

def check_output_and_stderr(
self, command: str, error_status: Status, ignore_stderr_startswith=None
Expand Down

0 comments on commit 078a14e

Please sign in to comment.