--- a/widget/gtk/nsPrintDialogGTK.cpp
+++ b/widget/gtk/nsPrintDialogGTK.cpp
@@ -19,17 +19,30 @@
#include "nsIFile.h"
#include "nsIStringBundle.h"
#include "nsIPrintSettingsService.h"
#include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsIBaseWindow.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShell.h"
+#include "nsIGIOService.h"
#include "WidgetUtils.h"
+#include "nsIObserverService.h"
+
+// for gdk_x11_window_get_xid
+#include <gdk/gdkx.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <gio/gunixfdlist.h>
+
+// for dlsym
+#include <dlfcn.h>
+#include "MainThreadUtils.h"
using namespace mozilla;
using namespace mozilla::widget;
static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
@@ -508,24 +521,525 @@ nsPrintDialogServiceGTK::~nsPrintDialogS
}
NS_IMETHODIMP
nsPrintDialogServiceGTK::Init()
{
return NS_OK;
}
+// Used to obtain window handle. The portal use this handle
+// to ensure that print dialog is modal.
+typedef void (*WindowHandleExported) (GtkWindow *window,
+ const char *handle,
+ gpointer user_data);
+
+typedef void (*GtkWindowHandleExported) (GtkWindow *window,
+ const char *handle,
+ gpointer user_data);
+#ifdef MOZ_WAYLAND
+typedef struct {
+ GtkWindow *window;
+ WindowHandleExported callback;
+ gpointer user_data;
+} WaylandWindowHandleExportedData;
+
+static void
+wayland_window_handle_exported (GdkWindow *window,
+ const char *wayland_handle_str,
+ gpointer user_data)
+{
+ WaylandWindowHandleExportedData *data =
+ static_cast<WaylandWindowHandleExportedData*>(user_data);
+ char *handle_str;
+
+ handle_str = g_strdup_printf ("wayland:%s", wayland_handle_str);
+ data->callback (data->window, handle_str, data->user_data);
+ g_free (handle_str);
+}
+#endif
+
+// Get window handle for the portal, taken from gtk/gtkwindow.c
+// (currently not exported)
+static gboolean
+window_export_handle(GtkWindow *window,
+ GtkWindowHandleExported callback,
+ gpointer user_data)
+{
+ if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(window))))
+ {
+ GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ char *handle_str;
+ guint32 xid = (guint32) gdk_x11_window_get_xid(gdk_window);
+
+ handle_str = g_strdup_printf("x11:%x", xid);
+ callback(window, handle_str, user_data);
+ g_free(handle_str);
+ return true;
+ }
+#ifdef MOZ_WAYLAND
+ else
+ {
+ GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ WaylandWindowHandleExportedData *data;
+
+ data = g_new0(WaylandWindowHandleExportedData, 1);
+ data->window = window;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ static auto s_gdk_wayland_window_export_handle =
+ reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported,
+ gpointer, GDestroyNotify)>
+ (dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle"));
+ if (!s_gdk_wayland_window_export_handle ||
+ !s_gdk_wayland_window_export_handle(gdk_window,
+ wayland_window_handle_exported,
+ data, g_free)) {
+ g_free (data);
+ return false;
+ } else {
+ return true;
+ }
+ }
+#endif
+
+ g_warning("Couldn't export handle, unsupported windowing system");
+
+ return false;
+}
+/**
+ * Communication class with the GTK print portal handler
+ *
+ * To print document from flatpak we need to use print portal because
+ * printers are not directly accessible in the sandboxed environment.
+ *
+ * At first we request portal to show the print dialog to let user choose
+ * printer settings. We use DBUS interface for that (PreparePrint method).
+ *
+ * Next we force application to print to temporary file and after the writing
+ * to the file is finished we pass its file descriptor to the portal.
+ * Portal will pass duplicate of the file descriptor to the printer which
+ * user selected before (by DBUS Print method).
+ *
+ * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show
+ * is expecting sync execution, we need to create a new GMainLoop during the
+ * print portal dialog is running. The loop is stopped after the dialog
+ * is closed.
+ */
+class nsFlatpakPrintPortal: public nsIObserver
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ public:
+ explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings);
+ nsresult PreparePrintRequest(GtkWindow* aWindow);
+ static void OnWindowExportHandleDone(GtkWindow *aWindow,
+ const char* aWindowHandleStr,
+ gpointer aUserData);
+ void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr);
+ static void OnPreparePrintResponse(GDBusConnection *connection,
+ const char *sender_name,
+ const char *object_path,
+ const char *interface_name,
+ const char *signal_name,
+ GVariant *parameters,
+ gpointer data);
+ GtkPrintOperationResult GetResult();
+ private:
+ virtual ~nsFlatpakPrintPortal();
+ void FinishPrintDialog(GVariant* parameters);
+ nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings;
+ GDBusProxy* mProxy;
+ guint32 mToken;
+ GMainLoop* mLoop;
+ GtkPrintOperationResult mResult;
+ guint mResponseSignalId;
+ GtkWindow* mParentWindow;
+};
+
+NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver)
+
+nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings):
+ mPrintAndPageSettings(aPrintSettings),
+ mProxy(nullptr),
+ mLoop(nullptr),
+ mParentWindow(nullptr)
+{
+}
+
+/**
+ * Creates GDBusProxy, query for window handle and create a new GMainLoop.
+ *
+ * The GMainLoop is to be run from GetResult() and be quitted during
+ * FinishPrintDialog.
+ *
+ * @param aWindow toplevel application window which is used as parent of print
+ * dialog
+ */
+nsresult
+nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow)
+{
+ NS_PRECONDITION(aWindow, "aWindow must not be null");
+ NS_PRECONDITION(mPrintAndPageSettings, "mPrintAndPageSettings must not be null");
+
+ GError* error = nullptr;
+ mProxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ "org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Print",
+ nullptr,
+ &error);
+ if (mProxy == nullptr) {
+ NS_WARNING(nsPrintfCString("Unable to create dbus proxy: %s", error->message).get());
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ // The window handler is returned async, we will continue by PreparePrint method
+ // when it is returned.
+ if (!window_export_handle(aWindow,
+ &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) {
+ NS_WARNING("Unable to get window handle for creating modal print dialog.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoop = g_main_loop_new (NULL, FALSE);
+ return NS_OK;
+}
+
+void
+nsFlatpakPrintPortal::OnWindowExportHandleDone(GtkWindow* aWindow,
+ const char* aWindowHandleStr,
+ gpointer aUserData)
+{
+ nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(aUserData);
+ printPortal->PreparePrint(aWindow, aWindowHandleStr);
+}
+
+/**
+ * Ask print portal to show the print dialog.
+ *
+ * Print and page settings and window handle are passed to the portal to prefill
+ * last used settings.
+ */
+void
+nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr)
+{
+ GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings();
+ GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup();
+
+ // We need to remember GtkWindow to unexport window handle after it is
+ // no longer needed by the portal dialog (apply only on non-X11 sessions).
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ mParentWindow = aWindow;
+ }
+
+ GVariantBuilder opt_builder;
+ g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
+ char* token = g_strdup_printf("mozilla%d", g_random_int_range (0, G_MAXINT));
+ g_variant_builder_add(&opt_builder, "{sv}", "handle_token",
+ g_variant_new_string(token));
+ g_free(token);
+ GVariant* options = g_variant_builder_end(&opt_builder);
+ static auto s_gtk_print_settings_to_gvariant =
+ reinterpret_cast<GVariant* (*)(GtkPrintSettings*)>
+ (dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant"));
+ static auto s_gtk_page_setup_to_gvariant =
+ reinterpret_cast<GVariant* (*)(GtkPageSetup *)>
+ (dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant"));
+ if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) {
+ mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
+ FinishPrintDialog(nullptr);
+ return;
+ }
+
+ // Get translated window title
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> printBundle;
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+ nsAutoString intlPrintTitle;
+ printBundle->GetStringFromName("printTitleGTK", intlPrintTitle);
+
+ GError* error = nullptr;
+ GVariant *ret = g_dbus_proxy_call_sync(mProxy,
+ "PreparePrint",
+ g_variant_new ("(ss@a{sv}@a{sv}@a{sv})",
+ aWindowHandleStr,
+ NS_ConvertUTF16toUTF8(intlPrintTitle).get(), // Title of the window
+ s_gtk_print_settings_to_gvariant(gtkSettings),
+ s_gtk_page_setup_to_gvariant(pageSetup),
+ options),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ nullptr,
+ &error);
+ if (ret == nullptr) {
+ NS_WARNING(nsPrintfCString("Unable to call dbus proxy: %s", error->message).get());
+ g_error_free (error);
+ mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
+ FinishPrintDialog(nullptr);
+ return;
+ }
+
+ const char* handle = nullptr;
+ g_variant_get (ret, "(&o)", &handle);
+ if (strcmp (aWindowHandleStr, handle) != 0)
+ {
+ aWindowHandleStr = g_strdup (handle);
+ g_dbus_connection_signal_unsubscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
+ }
+ mResponseSignalId =
+ g_dbus_connection_signal_subscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)),
+ "org.freedesktop.portal.Desktop",
+ "org.freedesktop.portal.Request",
+ "Response",
+ aWindowHandleStr,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ &nsFlatpakPrintPortal::OnPreparePrintResponse,
+ this, NULL);
+
+}
+
+void
+nsFlatpakPrintPortal::OnPreparePrintResponse(GDBusConnection *connection,
+ const char *sender_name,
+ const char *object_path,
+ const char *interface_name,
+ const char *signal_name,
+ GVariant *parameters,
+ gpointer data)
+{
+ nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data);
+ printPortal->FinishPrintDialog(parameters);
+}
+
+/**
+ * When the dialog is accepted, read print and page settings and token.
+ *
+ * Token is later used for printing portal as print operation identifier.
+ * Print and page settings are modified in-place and stored to
+ * mPrintAndPageSettings.
+ */
+void
+nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters)
+{
+ // This ends GetResult() method
+ if (mLoop) {
+ g_main_loop_quit (mLoop);
+ mLoop = nullptr;
+ }
+
+ if (!parameters) {
+ // mResult should be already defined
+ return;
+ }
+
+ guint32 response;
+ GVariant *options;
+
+ g_variant_get (parameters, "(u@a{sv})", &response, &options);
+ mResult = GTK_PRINT_OPERATION_RESULT_CANCEL;
+ if (response == 0) {
+ GVariant *v;
+
+ char *filename;
+ char *uri;
+ v = g_variant_lookup_value (options, "settings", G_VARIANT_TYPE_VARDICT);
+ static auto s_gtk_print_settings_new_from_gvariant =
+ reinterpret_cast<GtkPrintSettings* (*)(GVariant*)>
+ (dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant"));
+
+ GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v);
+ g_variant_unref (v);
+
+ v = g_variant_lookup_value (options, "page-setup", G_VARIANT_TYPE_VARDICT);
+ static auto s_gtk_page_setup_new_from_gvariant =
+ reinterpret_cast<GtkPageSetup* (*)(GVariant*)>
+ (dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant"));
+ GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v);
+ g_variant_unref (v);
+
+ g_variant_lookup (options, "token", "u", &mToken);
+
+ // Force printing to file because only filedescriptor of the file
+ // can be passed to portal
+ int fd = g_file_open_tmp("gtkprintXXXXXX", &filename, NULL);
+ uri = g_filename_to_uri(filename, NULL, NULL);
+ gtk_print_settings_set(printSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, uri);
+ g_free (uri);
+ close (fd);
+
+ // Save native settings in the session object
+ mPrintAndPageSettings->SetGtkPrintSettings(printSettings);
+ mPrintAndPageSettings->SetGtkPageSetup(pageSetup);
+
+ // Portal consumes PDF file
+ mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
+
+ // We need to set to print to file
+ mPrintAndPageSettings->SetPrintToFile(true);
+
+ mResult = GTK_PRINT_OPERATION_RESULT_APPLY;
+ }
+}
+
+/**
+ * Get result of the print dialog.
+ *
+ * This call blocks until FinishPrintDialog is called.
+ *
+ */
+GtkPrintOperationResult
+nsFlatpakPrintPortal::GetResult() {
+ // If the mLoop has not been initialized we haven't go thru PreparePrint method
+ if (!NS_IsMainThread() || !mLoop) {
+ return GTK_PRINT_OPERATION_RESULT_ERROR;
+ }
+ // Calling g_main_loop_run stops current code until g_main_loop_quit is called
+ g_main_loop_run(mLoop);
+
+ // Free resources we've allocated in order to show print dialog.
+#ifdef MOZ_WAYLAND
+ if (mParentWindow) {
+ GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow));
+ static auto s_gdk_wayland_window_unexport_handle =
+ reinterpret_cast<void (*)(GdkWindow*)>
+ (dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle"));
+ if (s_gdk_wayland_window_unexport_handle) {
+ s_gdk_wayland_window_unexport_handle(gdk_window);
+ }
+ }
+#endif
+ return mResult;
+}
+
+/**
+ * Send file descriptor of the file which contains document to the portal to
+ * finish the print operation.
+ */
+NS_IMETHODIMP
+nsFlatpakPrintPortal::Observe(nsISupports *aObject, const char * aTopic,
+ const char16_t * aData)
+{
+ // Check that written file match to the stored filename in case multiple
+ // print operations are in progress.
+ nsAutoString filenameStr;
+ mPrintAndPageSettings->GetToFileName(filenameStr);
+ if (!nsDependentString(aData).Equals(filenameStr)) {
+ // Different file is finished, not for this instance
+ return NS_OK;
+ }
+ int fd, idx;
+ fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY|O_CLOEXEC);
+ static auto s_g_unix_fd_list_new =
+ reinterpret_cast<GUnixFDList* (*)(void)>
+ (dlsym(RTLD_DEFAULT, "g_unix_fd_list_new"));
+ NS_ASSERTION(s_g_unix_fd_list_new, "Cannot find g_unix_fd_list_new function.");
+
+ GUnixFDList *fd_list = s_g_unix_fd_list_new();
+ static auto s_g_unix_fd_list_append =
+ reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)>
+ (dlsym(RTLD_DEFAULT, "g_unix_fd_list_append"));
+ idx = s_g_unix_fd_list_append(fd_list, fd, NULL);
+ close(fd);
+
+ GVariantBuilder opt_builder;
+ g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&opt_builder, "{sv}", "token",
+ g_variant_new_uint32(mToken));
+ g_dbus_proxy_call_with_unix_fd_list(
+ mProxy,
+ "Print",
+ g_variant_new("(ssh@a{sv})",
+ "", /* window */
+ "Print", /* title */
+ idx,
+ g_variant_builder_end(&opt_builder)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ fd_list,
+ NULL,
+ NULL, // TODO portal result cb function
+ nullptr); // data
+ g_object_unref(fd_list);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ // Let the nsFlatpakPrintPortal instance die
+ os->RemoveObserver(this, "print-to-file-finished");
+ return NS_OK;
+}
+
+nsFlatpakPrintPortal::~nsFlatpakPrintPortal() {
+ if (mProxy)
+ g_object_unref(mProxy);
+ if (mLoop)
+ g_main_loop_quit(mLoop);
+}
+
NS_IMETHODIMP
nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter *aParent,
nsIPrintSettings *aSettings,
nsIWebBrowserPrint *aWebBrowserPrint)
{
NS_PRECONDITION(aParent, "aParent must not be null");
NS_PRECONDITION(aSettings, "aSettings must not be null");
+ // Check for the flatpak portal first
+ nsCOMPtr<nsIGIOService> giovfs =
+ do_GetService(NS_GIOSERVICE_CONTRACTID);
+ bool shouldUsePortal;
+ giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
+ if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) {
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+
+ nsCOMPtr<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings));
+ RefPtr<nsFlatpakPrintPortal> fpPrintPortal =
+ new nsFlatpakPrintPortal(printSettingsGTK);
+
+ nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called
+ GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult();
+
+ rv = NS_OK;
+ switch (printDialogResult) {
+ case GTK_PRINT_OPERATION_RESULT_APPLY:
+ {
+ nsCOMPtr<nsIObserver> observer = do_QueryInterface(fpPrintPortal);
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+ // Observer waits until notified that the file with the content
+ // to print has been written.
+ rv = os->AddObserver(observer, "print-to-file-finished", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case GTK_PRINT_OPERATION_RESULT_CANCEL:
+ rv = NS_ERROR_ABORT;
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ rv = NS_ERROR_ABORT;
+ }
+ return rv;
+ }
+
nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
nsresult rv = printDialog.ImportSettings(aSettings);
NS_ENSURE_SUCCESS(rv, rv);
const gint response = printDialog.Run();
// Handle the result