| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/printing/common/print_dialog_linux_portal.h" |
| |
| #include <dbus/dbus-shared.h> |
| #include <dbus/dbus.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/span.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "components/dbus/thread_linux/dbus_thread_linux.h" |
| #include "components/dbus/utils/call_method.h" |
| #include "components/dbus/utils/read_value.h" |
| #include "components/dbus/utils/write_value.h" |
| #include "components/dbus/xdg/portal.h" |
| #include "components/dbus/xdg/request.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "dbus/bus.h" |
| #include "dbus/message.h" |
| #include "dbus/object_proxy.h" |
| #include "printing/metafile.h" |
| #include "printing/print_settings.h" |
| #include "printing/units.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/linux/linux_ui.h" |
| #include "ui/linux/linux_ui_delegate.h" |
| |
| namespace printing { |
| |
| namespace { |
| |
| constexpr char kPortalServiceName[] = "org.freedesktop.portal.Desktop"; |
| constexpr char kPortalObjectPath[] = "/org/freedesktop/portal/desktop"; |
| constexpr char kPrintInterfaceName[] = "org.freedesktop.portal.Print"; |
| |
| constexpr char kMethodPreparePrint[] = "PreparePrint"; |
| constexpr char kMethodPrint[] = "Print"; |
| |
| constexpr char kDBusInterfaceName[] = "org.freedesktop.DBus"; |
| constexpr char kMethodGetId[] = "GetId"; |
| |
| // Keys for settings dictionary. Defined in org.freedesktop.portal.Print spec. |
| // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Print.html |
| constexpr char kKeyCollate[] = "collate"; |
| constexpr char kKeyCopies[] = "n-copies"; |
| constexpr char kKeyDefaultSource[] = "default-source"; |
| constexpr char kKeyDither[] = "dither"; |
| constexpr char kKeyDuplex[] = "duplex"; |
| constexpr char kKeyFinishings[] = "finishings"; |
| constexpr char kKeyMediaType[] = "media-type"; |
| constexpr char kKeyNumberUpLayout[] = "number-up-layout"; |
| constexpr char kKeyNumberUp[] = "number-up"; |
| constexpr char kKeyOrientation[] = "orientation"; |
| constexpr char kKeyOutputBasename[] = "output-basename"; |
| constexpr char kKeyOutputBin[] = "output-bin"; |
| constexpr char kKeyOutputFileFormat[] = "output-file-format"; |
| constexpr char kKeyOutputUri[] = "output-uri"; |
| constexpr char kKeyPageRanges[] = "page-ranges"; |
| constexpr char kKeyPageSet[] = "page-set"; |
| constexpr char kKeyPaperFormat[] = "paper-format"; |
| constexpr char kKeyPaperHeight[] = "paper-height"; |
| constexpr char kKeyPaperWidth[] = "paper-width"; |
| constexpr char kKeyPrintPages[] = "print-pages"; |
| constexpr char kKeyPrinterLpi[] = "printer-lpi"; |
| constexpr char kKeyQuality[] = "quality"; |
| constexpr char kKeyResolutionX[] = "resolution-x"; |
| constexpr char kKeyResolutionY[] = "resolution-y"; |
| constexpr char kKeyResolution[] = "resolution"; |
| constexpr char kKeyReverse[] = "reverse"; |
| constexpr char kKeyScale[] = "scale"; |
| constexpr char kKeyUseColor[] = "use-color"; |
| |
| constexpr char kKeyName[] = "Name"; |
| constexpr char kKeyPpdName[] = "PPDName"; |
| |
| constexpr char kValueAll[] = "all"; |
| constexpr char kValueFalse[] = "false"; |
| constexpr char kValueHorizontal[] = "horizontal"; |
| constexpr char kValueLandscape[] = "landscape"; |
| constexpr char kValuePortrait[] = "portrait"; |
| constexpr char kValueRanges[] = "ranges"; |
| constexpr char kValueReverseLandscape[] = "reverse_landscape"; |
| constexpr char kValueSelection[] = "selection"; |
| constexpr char kValueSimplex[] = "simplex"; |
| constexpr char kValueTrue[] = "true"; |
| constexpr char kValueVertical[] = "vertical"; |
| |
| constexpr char kPageRangeSeparator[] = ","; |
| constexpr char kRangeSeparator[] = "-"; |
| constexpr char kResolutionDimensionSeparator[] = "x"; |
| |
| // Standard scale factor for the print preview. |
| constexpr double kScaleFactor = 100.0; |
| |
| // Keys for page setup dictionary |
| constexpr char kKeyPageSetupHeight[] = "Height"; |
| constexpr char kKeyPageSetupMarginBottom[] = "MarginBottom"; |
| constexpr char kKeyPageSetupMarginLeft[] = "MarginLeft"; |
| constexpr char kKeyPageSetupMarginRight[] = "MarginRight"; |
| constexpr char kKeyPageSetupMarginTop[] = "MarginTop"; |
| constexpr char kKeyPageSetupOrientation[] = "Orientation"; |
| constexpr char kKeyPageSetupWidth[] = "Width"; |
| |
| // Keys for options/results |
| constexpr char kKeyModal[] = "modal"; |
| constexpr char kKeyPageSetup[] = "page-setup"; |
| constexpr char kKeySettings[] = "settings"; |
| constexpr char kKeyToken[] = "token"; |
| |
| constexpr int kXdgPortalRequiredVersion = 3; |
| |
| constexpr char16_t kPrintDialogLinuxPortalDeviceName[] = |
| u"PrintDialogLinuxPortal"; |
| |
| void WriteDataToPipe(base::ScopedFD fd, std::vector<char> data) { |
| base::File file(std::move(fd)); |
| if (!file.WriteAtCurrentPosAndCheck(base::as_byte_span(data))) { |
| LOG(ERROR) << "Failed to save metafile to pipe"; |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG) |
| // Serializes a D-Bus value into a byte vector. |
| template <typename T> |
| std::vector<uint8_t> SerializeValue(const T& value) { |
| std::unique_ptr<dbus::Response> message = dbus::Response::CreateEmpty(); |
| dbus::MessageWriter writer(message.get()); |
| dbus_utils::WriteValue(writer, value); |
| |
| char* marshalled_data = nullptr; |
| int len = 0; |
| if (!dbus_message_marshal(message->raw_message(), &marshalled_data, &len)) { |
| return {}; |
| } |
| |
| // SAFETY: dbus_message_marshal guarantees that `marshalled_data` points to a |
| // buffer of `len` bytes. |
| auto data_span = |
| UNSAFE_BUFFERS(base::span(marshalled_data, static_cast<size_t>(len))); |
| auto bytes_span = base::as_bytes(data_span); |
| std::vector<uint8_t> result(bytes_span.begin(), bytes_span.end()); |
| |
| dbus_free(marshalled_data); |
| return result; |
| } |
| |
| // Deserializes a D-Bus value from a byte span. |
| template <typename T> |
| std::optional<T> DeserializeValue(base::span<const uint8_t> data) { |
| DBusError error; |
| dbus_error_init(&error); |
| DBusMessage* raw_message = dbus_message_demarshal( |
| reinterpret_cast<const char*>(data.data()), data.size(), &error); |
| if (dbus_error_is_set(&error)) { |
| dbus_error_free(&error); |
| return std::nullopt; |
| } |
| if (!raw_message) { |
| return std::nullopt; |
| } |
| |
| std::unique_ptr<dbus::Response> message = |
| dbus::Response::FromRawMessage(raw_message); |
| dbus::MessageReader reader(message.get()); |
| return dbus_utils::ReadValue<T>(reader); |
| } |
| #endif |
| |
| dbus_xdg::Dictionary ConvertSettingsToDictionary( |
| const PrintSettings& settings) { |
| dbus_xdg::Dictionary dict; |
| |
| // Orientation |
| dict[kKeyOrientation] = dbus_utils::Variant::Wrap<"s">( |
| settings.landscape() ? kValueLandscape : kValuePortrait); |
| |
| // Copies |
| dict[kKeyCopies] = |
| dbus_utils::Variant::Wrap<"s">(base::NumberToString(settings.copies())); |
| |
| // Collate |
| dict[kKeyCollate] = dbus_utils::Variant::Wrap<"s">( |
| settings.collate() ? kValueTrue : kValueFalse); |
| |
| // Color |
| dict[kKeyUseColor] = dbus_utils::Variant::Wrap<"s">( |
| settings.color() != mojom::ColorModel::kGray ? kValueTrue : kValueFalse); |
| |
| // Duplex |
| std::string duplex; |
| switch (settings.duplex_mode()) { |
| case mojom::DuplexMode::kSimplex: |
| duplex = kValueSimplex; |
| break; |
| case mojom::DuplexMode::kLongEdge: |
| duplex = kValueVertical; |
| break; |
| case mojom::DuplexMode::kShortEdge: |
| duplex = kValueHorizontal; |
| break; |
| default: |
| duplex = kValueSimplex; |
| } |
| dict[kKeyDuplex] = dbus_utils::Variant::Wrap<"s">(duplex); |
| |
| // Resolution |
| if (settings.dpi_horizontal() > 0 && settings.dpi_vertical() > 0) { |
| std::string res = base::NumberToString(settings.dpi_horizontal()); |
| if (settings.dpi_horizontal() != settings.dpi_vertical()) { |
| res += kResolutionDimensionSeparator + |
| base::NumberToString(settings.dpi_vertical()); |
| } |
| dict[kKeyResolution] = dbus_utils::Variant::Wrap<"s">(res); |
| } |
| |
| // Ranges |
| if (settings.ranges().empty()) { |
| dict[kKeyPrintPages] = dbus_utils::Variant::Wrap<"s">( |
| settings.selection_only() ? kValueSelection : kValueAll); |
| } else { |
| dict[kKeyPrintPages] = dbus_utils::Variant::Wrap<"s">(kValueRanges); |
| std::vector<std::string> ranges_strs; |
| for (const auto& range : settings.ranges()) { |
| if (range.from == range.to) { |
| ranges_strs.push_back(base::NumberToString(range.from)); |
| } else { |
| ranges_strs.push_back(base::NumberToString(range.from) + |
| kRangeSeparator + base::NumberToString(range.to)); |
| } |
| } |
| dict[kKeyPageRanges] = dbus_utils::Variant::Wrap<"s">( |
| base::JoinString(ranges_strs, kPageRangeSeparator)); |
| } |
| |
| return dict; |
| } |
| |
| dbus_xdg::Dictionary ConvertPageSetupToDictionary( |
| const PrintSettings& settings) { |
| dbus_xdg::Dictionary dict; |
| |
| const auto& media = settings.requested_media(); |
| if (!media.size_microns.IsEmpty()) { |
| dict[kKeyPageSetupWidth] = dbus_utils::Variant::Wrap<"d">( |
| static_cast<double>(media.size_microns.width()) / kMicronsPerMm); |
| dict[kKeyPageSetupHeight] = dbus_utils::Variant::Wrap<"d">( |
| static_cast<double>(media.size_microns.height()) / kMicronsPerMm); |
| } |
| |
| if (!media.vendor_id.empty()) { |
| dict[kKeyPpdName] = dbus_utils::Variant::Wrap<"s">(media.vendor_id); |
| dict[kKeyName] = dbus_utils::Variant::Wrap<"s">(media.vendor_id); |
| } |
| |
| // Margins |
| double top_mm = 0; |
| double bottom_mm = 0; |
| double left_mm = 0; |
| double right_mm = 0; |
| bool margins_set = false; |
| |
| if (settings.margin_type() == mojom::MarginType::kCustomMargins) { |
| const auto& margins = settings.requested_custom_margins_in_microns(); |
| top_mm = static_cast<double>(margins.top) / kMicronsPerMm; |
| bottom_mm = static_cast<double>(margins.bottom) / kMicronsPerMm; |
| left_mm = static_cast<double>(margins.left) / kMicronsPerMm; |
| right_mm = static_cast<double>(margins.right) / kMicronsPerMm; |
| margins_set = true; |
| } else { |
| // Attempt to calculate from device units if DPI is available. |
| int dpi = settings.dpi(); |
| if (dpi > 0) { |
| const auto& page_setup = settings.page_setup_device_units(); |
| const auto& physical_size = page_setup.physical_size(); |
| const auto& printable_area = page_setup.printable_area(); |
| |
| if (!physical_size.IsEmpty()) { |
| double conversion = kMmPerInch / dpi; |
| |
| left_mm = printable_area.x() * conversion; |
| top_mm = printable_area.y() * conversion; |
| right_mm = |
| (physical_size.width() - printable_area.right()) * conversion; |
| bottom_mm = |
| (physical_size.height() - printable_area.bottom()) * conversion; |
| margins_set = true; |
| } |
| } |
| } |
| |
| if (margins_set) { |
| dict[kKeyPageSetupMarginTop] = dbus_utils::Variant::Wrap<"d">(top_mm); |
| dict[kKeyPageSetupMarginBottom] = dbus_utils::Variant::Wrap<"d">(bottom_mm); |
| dict[kKeyPageSetupMarginLeft] = dbus_utils::Variant::Wrap<"d">(left_mm); |
| dict[kKeyPageSetupMarginRight] = dbus_utils::Variant::Wrap<"d">(right_mm); |
| } |
| |
| // Orientation matches settings |
| dict[kKeyPageSetupOrientation] = dbus_utils::Variant::Wrap<"s">( |
| settings.landscape() ? kValueLandscape : kValuePortrait); |
| return dict; |
| } |
| |
| void ParseSettingsFromDictionary(dbus_xdg::Dictionary& dict, |
| PrintSettings* settings) { |
| if (auto it = dict.find(kKeyOrientation); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| settings->SetOrientation(*val == kValueLandscape || |
| *val == kValueReverseLandscape); |
| } |
| } |
| |
| if (auto it = dict.find(kKeyCopies); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| int copies = 0; |
| if (base::StringToInt(*val, &copies)) { |
| settings->set_copies(copies); |
| } |
| } |
| } |
| |
| if (auto it = dict.find(kKeyCollate); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| settings->set_collate(*val == kValueTrue); |
| } |
| } |
| |
| if (auto it = dict.find(kKeyUseColor); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| settings->set_color(*val == kValueTrue ? mojom::ColorModel::kColor |
| : mojom::ColorModel::kGray); |
| } |
| } |
| |
| if (auto it = dict.find(kKeyDuplex); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| if (*val == kValueSimplex) { |
| settings->set_duplex_mode(mojom::DuplexMode::kSimplex); |
| } else if (*val == kValueVertical) { |
| settings->set_duplex_mode(mojom::DuplexMode::kLongEdge); |
| } else if (*val == kValueHorizontal) { |
| settings->set_duplex_mode(mojom::DuplexMode::kShortEdge); |
| } |
| } |
| } |
| |
| if (auto it = dict.find(kKeyResolution); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| int dpi_x = 0; |
| int dpi_y = 0; |
| std::string_view s = *val; |
| auto pos = s.find(kResolutionDimensionSeparator); |
| if (pos == std::string_view::npos) { |
| if (base::StringToInt(s, &dpi_x) && dpi_x > 0) { |
| settings->set_dpi(dpi_x); |
| } |
| } else { |
| std::string_view sx = s.substr(0, pos); |
| std::string_view sy = s.substr(pos + 1); |
| if (base::StringToInt(sx, &dpi_x) && base::StringToInt(sy, &dpi_y) && |
| dpi_x > 0 && dpi_y > 0) { |
| settings->set_dpi_xy(dpi_x, dpi_y); |
| } |
| } |
| } |
| } |
| |
| if (auto it = dict.find(kKeyScale); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| double scale = 0; |
| if (base::StringToDouble(*val, &scale)) { |
| settings->set_scale_factor(scale / kScaleFactor); |
| } |
| } |
| } |
| |
| if (auto it = dict.find(kKeyPrintPages); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| if (*val == kValueSelection) { |
| settings->set_selection_only(true); |
| } else if (*val == kValueAll) { |
| settings->set_selection_only(false); |
| settings->set_ranges({}); |
| } |
| } |
| } |
| |
| if (auto it = dict.find(kKeyPageRanges); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| PageRanges ranges; |
| for (const auto& range_str : |
| base::SplitString(*val, kPageRangeSeparator, base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_ALL)) { |
| std::vector<std::string> parts = |
| base::SplitString(range_str, kRangeSeparator, base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_ALL); |
| int from = 0; |
| int to = 0; |
| if (parts.size() == 1 && base::StringToInt(parts[0], &from)) { |
| if (from >= 0) { |
| ranges.push_back( |
| {static_cast<uint32_t>(from), static_cast<uint32_t>(from)}); |
| } |
| } else if (parts.size() == 2 && base::StringToInt(parts[0], &from) && |
| base::StringToInt(parts[1], &to)) { |
| if (from >= 0 && to >= 0) { |
| ranges.push_back( |
| {static_cast<uint32_t>(from), static_cast<uint32_t>(to)}); |
| } |
| } |
| } |
| settings->set_ranges(ranges); |
| } |
| } |
| |
| if (auto it = dict.find(kKeyMediaType); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| settings->set_media_type(*val); |
| } |
| } |
| |
| if (auto it = dict.find(kKeyNumberUp); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| int nup = 1; |
| if (base::StringToInt(*val, &nup) && nup > 0) { |
| settings->set_pages_per_sheet(nup); |
| } |
| } |
| } |
| |
| // Advanced settings mapping |
| static constexpr const char* kAdvancedKeys[] = { |
| kKeyReverse, kKeyDither, kKeyFinishings, |
| kKeyPageSet, kKeyOutputBin, kKeyOutputUri, |
| kKeyOutputBasename, kKeyOutputFileFormat, kKeyPrinterLpi, |
| kKeyNumberUpLayout, kKeyPaperFormat, kKeyPaperWidth, |
| kKeyPaperHeight, kKeyDefaultSource, kKeyQuality, |
| kKeyResolutionX, kKeyResolutionY, |
| }; |
| |
| for (const char* key : kAdvancedKeys) { |
| if (auto it = dict.find(key); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| settings->advanced_settings().emplace(key, |
| base::Value(std::move(*val))); |
| } |
| } |
| } |
| } |
| |
| void ParsePageSetupFromDictionary(dbus_xdg::Dictionary& dict, |
| PrintSettings* settings) { |
| // Page setup properties are usually in mm. |
| double width_mm = 0; |
| double height_mm = 0; |
| |
| if (auto it = dict.find(kKeyPageSetupWidth); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<double>()) { |
| width_mm = *val; |
| } |
| } |
| if (auto it = dict.find(kKeyPageSetupHeight); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<double>()) { |
| height_mm = *val; |
| } |
| } |
| |
| if (width_mm > 0 && height_mm > 0) { |
| PrintSettings::RequestedMedia media = settings->requested_media(); |
| media.size_microns = gfx::Size(static_cast<int>(width_mm * kMicronsPerMm), |
| static_cast<int>(height_mm * kMicronsPerMm)); |
| settings->set_requested_media(media); |
| } |
| |
| // Margins |
| double top_mm = 0; |
| double bottom_mm = 0; |
| double left_mm = 0; |
| double right_mm = 0; |
| bool margins_set = false; |
| if (auto it = dict.find(kKeyPageSetupMarginTop); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<double>()) { |
| top_mm = *val; |
| margins_set = true; |
| } |
| } |
| if (auto it = dict.find(kKeyPageSetupMarginBottom); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<double>()) { |
| bottom_mm = *val; |
| margins_set = true; |
| } |
| } |
| if (auto it = dict.find(kKeyPageSetupMarginLeft); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<double>()) { |
| left_mm = *val; |
| margins_set = true; |
| } |
| } |
| if (auto it = dict.find(kKeyPageSetupMarginRight); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<double>()) { |
| right_mm = *val; |
| margins_set = true; |
| } |
| } |
| |
| if (margins_set) { |
| PageMargins margins; |
| margins.top = static_cast<int>(top_mm * kMicronsPerMm); |
| margins.bottom = static_cast<int>(bottom_mm * kMicronsPerMm); |
| margins.left = static_cast<int>(left_mm * kMicronsPerMm); |
| margins.right = static_cast<int>(right_mm * kMicronsPerMm); |
| settings->SetCustomMargins(margins); |
| } |
| |
| if (auto it = dict.find(kKeyPageSetupOrientation); it != dict.end()) { |
| if (auto val = std::move(it->second).Take<std::string>()) { |
| settings->SetOrientation(*val == kValueLandscape || |
| *val == kValueReverseLandscape); |
| } |
| } |
| |
| // Calculate and set printer printable area. |
| int dpi = settings->dpi(); |
| if (dpi <= 0) { |
| // This should generally be set by ParseSettingsFromDictionary or default. |
| dpi = kDefaultPdfDpi; |
| settings->set_dpi(dpi); |
| } |
| |
| // Convert mm to inches (25.4 mm per inch) then to device units (dots). |
| gfx::Size physical_size_device_units( |
| static_cast<int>(width_mm * dpi / kMmPerInch), |
| static_cast<int>(height_mm * dpi / kMmPerInch)); |
| |
| gfx::Rect printable_area_device_units( |
| static_cast<int>(left_mm * dpi / kMmPerInch), |
| static_cast<int>(top_mm * dpi / kMmPerInch), |
| static_cast<int>((width_mm - left_mm - right_mm) * dpi / kMmPerInch), |
| static_cast<int>((height_mm - top_mm - bottom_mm) * dpi / kMmPerInch)); |
| |
| settings->SetPrinterPrintableArea(physical_size_device_units, |
| printable_area_device_units, |
| /*landscape_needs_flip=*/true); |
| } |
| |
| void OnPrintResponse(dbus_xdg::Results results) { |
| if (!results.has_value()) { |
| LOG(ERROR) << "Portal Print failed"; |
| } |
| } |
| |
| void StartPrintRequest(const std::string& title, |
| base::ScopedFD fd, |
| scoped_refptr<dbus::Bus> bus, |
| const std::string& parent_handle, |
| std::optional<uint32_t> token) { |
| CHECK(!bus->GetConnectionName().empty()); |
| dbus::ObjectProxy* portal_proxy = bus->GetObjectProxy( |
| kPortalServiceName, dbus::ObjectPath(kPortalObjectPath)); |
| |
| dbus_xdg::Dictionary options; |
| if (token) { |
| options[kKeyToken] = dbus_utils::Variant::Wrap<"u">(*token); |
| } |
| |
| // Print(parent_window, title, fd, options) |
| auto request = std::make_unique<dbus_xdg::Request>( |
| bus, portal_proxy, kPrintInterfaceName, kMethodPrint, std::move(options), |
| base::BindOnce(&OnPrintResponse), parent_handle, title, std::move(fd)); |
| // In the OOP print service, the dialog may be destroyed before the print |
| // request completes. Release the request to avoid cancelling it. |
| request->Release(); |
| } |
| |
| void OnBusNameAcquired(const std::string& title, |
| base::ScopedFD fd, |
| scoped_refptr<dbus::Bus> bus, |
| const std::string& parent_handle, |
| std::optional<uint32_t> token, |
| dbus_utils::CallMethodResultSig<"s"> results) { |
| if (!bus->IsConnected()) { |
| // Failed to connect to D-Bus. |
| return; |
| } |
| |
| StartPrintRequest(title, std::move(fd), std::move(bus), parent_handle, token); |
| } |
| |
| void EnsureDBusInitialized(const std::string& title, |
| base::ScopedFD fd, |
| scoped_refptr<dbus::Bus> bus, |
| const std::string& parent_handle, |
| std::optional<uint32_t> token) { |
| if (bus->IsConnected()) { |
| StartPrintRequest(title, std::move(fd), std::move(bus), parent_handle, |
| token); |
| return; |
| } |
| |
| // In the OOP print service, the D-Bus connection may not be initialized. |
| // Make a no-op call to initialize it and ensure the bus has a name, which |
| // is required for XDG portal requests. |
| auto* object_proxy = |
| bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); |
| dbus_utils::CallMethod<"", "s">( |
| object_proxy, kDBusInterfaceName, kMethodGetId, |
| base::BindOnce(&OnBusNameAcquired, title, std::move(fd), std::move(bus), |
| parent_handle, token)); |
| } |
| |
| } // namespace |
| |
| PrintDialogLinuxPortal::PrintDialogLinuxPortal(PrintingContextLinux* context, |
| scoped_refptr<dbus::Bus> bus) |
| : context_(context), |
| bus_(bus ? std::move(bus) : dbus_thread_linux::GetSharedSessionBus()), |
| task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {} |
| |
| PrintDialogLinuxPortal::~PrintDialogLinuxPortal() = default; |
| |
| void PrintDialogLinuxPortal::UseDefaultSettings() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (fallback_dialog_) { |
| fallback_dialog_->UseDefaultSettings(); |
| return; |
| } |
| |
| auto settings = std::make_unique<PrintSettings>(); |
| settings->set_dpi(kDefaultPdfDpi); |
| gfx::Size physical_size(static_cast<int>(kA4WidthInch * kDefaultPdfDpi), |
| static_cast<int>(kA4HeightInch * kDefaultPdfDpi)); |
| gfx::Rect printable_area(physical_size); |
| settings->SetPrinterPrintableArea(physical_size, printable_area, true); |
| context_->InitWithSettings(std::move(settings)); |
| } |
| |
| void PrintDialogLinuxPortal::UpdateSettings( |
| std::unique_ptr<PrintSettings> settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (fallback_dialog_) { |
| fallback_dialog_->UpdateSettings(std::move(settings)); |
| return; |
| } |
| settings_ = std::make_unique<PrintSettings>(*settings); |
| context_->InitWithSettings(std::move(settings)); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG) |
| void PrintDialogLinuxPortal::LoadPrintSettings(const PrintSettings& settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (fallback_dialog_) { |
| fallback_dialog_->LoadPrintSettings(settings); |
| return; |
| } |
| settings_ = std::make_unique<PrintSettings>(settings); |
| |
| const std::vector<uint8_t>* settings_data = |
| settings.system_print_dialog_data().FindBlob( |
| kLinuxSystemPrintDialogDataPrintSettingsBin); |
| if (settings_data) { |
| if (auto dict = DeserializeValue<dbus_xdg::Dictionary>(*settings_data)) { |
| ParseSettingsFromDictionary(*dict, settings_.get()); |
| } |
| } |
| |
| const std::vector<uint8_t>* page_setup_data = |
| settings.system_print_dialog_data().FindBlob( |
| kLinuxSystemPrintDialogDataPageSetupBin); |
| if (page_setup_data) { |
| if (auto dict = DeserializeValue<dbus_xdg::Dictionary>(*page_setup_data)) { |
| ParsePageSetupFromDictionary(*dict, settings_.get()); |
| } |
| } |
| |
| const std::string* token_str = settings.system_print_dialog_data().FindString( |
| kLinuxSystemPrintDialogDataPrintToken); |
| if (token_str) { |
| unsigned int token_value = 0; |
| if (base::StringToUint(*token_str, &token_value)) { |
| token_ = token_value; |
| } |
| } |
| |
| const std::string* parent_handle_str = |
| settings.system_print_dialog_data().FindString( |
| kLinuxSystemPrintDialogDataParentHandle); |
| if (parent_handle_str) { |
| parent_handle_ = *parent_handle_str; |
| } |
| } |
| #endif |
| |
| void PrintDialogLinuxPortal::ShowDialog( |
| gfx::NativeView parent_view, |
| bool has_selection, |
| PrintingContextLinux::PrintSettingsCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| parent_view_ = parent_view; |
| callback_ = std::move(callback); |
| |
| dbus_xdg::RequestXdgDesktopPortal( |
| bus_.get(), base::BindOnce(&PrintDialogLinuxPortal::OnPortalAvailable, |
| weak_factory_.GetWeakPtr(), has_selection)); |
| } |
| |
| void PrintDialogLinuxPortal::OnPortalAvailable(bool has_selection, |
| uint32_t version) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (version < kXdgPortalRequiredVersion) { |
| UseFallback(has_selection); |
| return; |
| } |
| |
| // Export the window handle to the portal. |
| if (auto* delegate = ui::LinuxUiDelegate::GetInstance()) { |
| aura::Window* window = parent_view_ ? parent_view_ : nullptr; |
| if (window && window->GetRootWindow()) { |
| // Assuming aura::Window* as NativeView. |
| delegate->ExportWindowHandle( |
| window->GetHost()->GetAcceleratedWidget(), |
| base::BindOnce(&PrintDialogLinuxPortal::OnWindowHandleExported, |
| weak_factory_.GetWeakPtr(), has_selection)); |
| return; |
| } |
| } |
| |
| // No window or no delegate, call with empty handle. |
| OnWindowHandleExported(has_selection, ""); |
| } |
| |
| void PrintDialogLinuxPortal::OnWindowHandleExported(bool has_selection, |
| std::string handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| parent_handle_ = std::move(handle); |
| |
| dbus::ObjectProxy* portal_proxy = bus_->GetObjectProxy( |
| kPortalServiceName, dbus::ObjectPath(kPortalObjectPath)); |
| |
| dbus_xdg::Dictionary options; |
| options[kKeyModal] = dbus_utils::Variant::Wrap<"b">(true); |
| if (has_selection) { |
| options["has_selected_pages"] = dbus_utils::Variant::Wrap<"b">(true); |
| } |
| |
| // Prepare settings dictionary. |
| dbus_xdg::Dictionary settings_dict; |
| dbus_xdg::Dictionary page_setup_dict; |
| if (settings_) { |
| settings_dict = ConvertSettingsToDictionary(*settings_); |
| page_setup_dict = ConvertPageSetupToDictionary(*settings_); |
| } |
| |
| std::string title = l10n_util::GetStringUTF8(IDS_PRINT); |
| |
| // PreparePrint(parent_window, title, settings, page_setup, options) |
| request_ = std::make_unique<dbus_xdg::Request>( |
| bus_, portal_proxy, kPrintInterfaceName, kMethodPreparePrint, |
| std::move(options), |
| base::BindOnce(&PrintDialogLinuxPortal::OnPreparePrintResponse, |
| weak_factory_.GetWeakPtr()), |
| parent_handle_, title, std::move(settings_dict), |
| std::move(page_setup_dict)); |
| } |
| |
| void PrintDialogLinuxPortal::OnPreparePrintResponse(dbus_xdg::Results results) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| request_.reset(); |
| |
| if (!results.has_value()) { |
| std::move(callback_).Run(mojom::ResultCode::kCanceled); |
| return; |
| } |
| |
| // Save the token for the subsequent Print call. |
| if (auto it = results->find(kKeyToken); it != results->end()) { |
| if (auto token = std::move(it->second).Take<uint32_t>()) { |
| token_ = *token; |
| } |
| } |
| |
| // Ensure settings_ exists. |
| if (!settings_) { |
| settings_ = std::make_unique<PrintSettings>(); |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG) |
| base::Value::Dict dialog_data; |
| #endif |
| |
| if (auto it = results->find(kKeySettings); it != results->end()) { |
| if (auto settings_dict = |
| std::move(it->second).Take<dbus_xdg::Dictionary>()) { |
| #if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG) |
| dialog_data.Set(kLinuxSystemPrintDialogDataPrintSettingsBin, |
| SerializeValue(*settings_dict)); |
| #endif |
| |
| ParseSettingsFromDictionary(*settings_dict, settings_.get()); |
| } |
| } |
| |
| // Ensure valid DPI before processing page setup. |
| if (settings_->dpi() <= 0) { |
| settings_->set_dpi(kDefaultPdfDpi); |
| } |
| |
| if (auto it = results->find(kKeyPageSetup); it != results->end()) { |
| if (auto page_setup_dict = |
| std::move(it->second).Take<dbus_xdg::Dictionary>()) { |
| #if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG) |
| dialog_data.Set(kLinuxSystemPrintDialogDataPageSetupBin, |
| SerializeValue(*page_setup_dict)); |
| #endif |
| |
| ParsePageSetupFromDictionary(*page_setup_dict, settings_.get()); |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG) |
| if (token_) { |
| dialog_data.Set(kLinuxSystemPrintDialogDataPrintToken, |
| base::NumberToString(*token_)); |
| } |
| dialog_data.Set(kLinuxSystemPrintDialogDataParentHandle, parent_handle_); |
| settings_->set_system_print_dialog_data(std::move(dialog_data)); |
| |
| // A dummy device name is required. |
| settings_->set_device_name(kPrintDialogLinuxPortalDeviceName); |
| #endif |
| |
| // Update context with the settings selected by the user (or confirmed). |
| if (context_) { |
| context_->InitWithSettings(std::move(settings_)); |
| } |
| |
| std::move(callback_).Run(mojom::ResultCode::kSuccess); |
| } |
| |
| void PrintDialogLinuxPortal::PrintDocument( |
| const MetafilePlayer& metafile, |
| const std::u16string& document_name) { |
| // NB: This may be called from a different sequence. |
| if (fallback_dialog_) { |
| fallback_dialog_->PrintDocument(metafile, document_name); |
| return; |
| } |
| |
| base::ScopedFD read_fd; |
| base::ScopedFD write_fd; |
| if (!base::CreatePipe(&read_fd, &write_fd)) { |
| LOG(ERROR) << "Failed to create pipe for printing"; |
| return; |
| } |
| |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&EnsureDBusInitialized, base::UTF16ToUTF8(document_name), |
| std::move(read_fd), bus_, parent_handle_, |
| std::move(token_))); |
| |
| std::vector<char> data; |
| if (metafile.GetDataAsVector(&data)) { |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, |
| base::BindOnce(&WriteDataToPipe, std::move(write_fd), std::move(data))); |
| } else { |
| LOG(ERROR) << "Failed to get data from metafile"; |
| } |
| } |
| |
| void PrintDialogLinuxPortal::UseFallback(bool has_selection) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (auto* linux_ui = ui::LinuxUi::instance()) { |
| fallback_dialog_ = linux_ui->CreatePrintDialog(context_); |
| } |
| |
| if (!fallback_dialog_) { |
| // Should not happen if LinuxUi is working, but handle gracefully. |
| std::move(callback_).Run(mojom::ResultCode::kFailed); |
| return; |
| } |
| |
| if (settings_) { |
| fallback_dialog_->UpdateSettings(std::move(settings_)); |
| } else { |
| fallback_dialog_->UseDefaultSettings(); |
| } |
| |
| fallback_dialog_->ShowDialog(parent_view_, has_selection, |
| std::move(callback_)); |
| } |
| |
| } // namespace printing |