From 3b6d63f72381ee5078e640e3a4c150798c119724 Mon Sep 17 00:00:00 2001 From: Caoimhe Date: Sat, 13 May 2023 14:20:41 +0100 Subject: [PATCH] SpiceAgent: Implement setting the user's clipboard contents for text We also now use GUI::Clipboard for setting the clipboard contents, instead of using a custom connection to the Clipboard server. --- Userland/Services/SpiceAgent/CMakeLists.txt | 3 +- .../ConnectionToClipboardServer.cpp | 75 ------------------- .../SpiceAgent/ConnectionToClipboardServer.h | 34 --------- Userland/Services/SpiceAgent/Message.cpp | 30 ++++++++ Userland/Services/SpiceAgent/Message.h | 24 ++++++ Userland/Services/SpiceAgent/SpiceAgent.cpp | 35 +++++++-- Userland/Services/SpiceAgent/SpiceAgent.h | 9 ++- Userland/Services/SpiceAgent/main.cpp | 10 ++- 8 files changed, 96 insertions(+), 124 deletions(-) delete mode 100644 Userland/Services/SpiceAgent/ConnectionToClipboardServer.cpp delete mode 100644 Userland/Services/SpiceAgent/ConnectionToClipboardServer.h diff --git a/Userland/Services/SpiceAgent/CMakeLists.txt b/Userland/Services/SpiceAgent/CMakeLists.txt index f47b1fdbf6..f2eb8c3a2b 100644 --- a/Userland/Services/SpiceAgent/CMakeLists.txt +++ b/Userland/Services/SpiceAgent/CMakeLists.txt @@ -7,9 +7,8 @@ set(SOURCES main.cpp Message.cpp SpiceAgent.cpp - ConnectionToClipboardServer.cpp ) serenity_bin(SpiceAgent) -target_link_libraries(SpiceAgent PRIVATE LibGfx LibCore LibIPC LibMain) +target_link_libraries(SpiceAgent PRIVATE LibCore LibGfx LibGUI LibMain) add_dependencies(SpiceAgent Clipboard) diff --git a/Userland/Services/SpiceAgent/ConnectionToClipboardServer.cpp b/Userland/Services/SpiceAgent/ConnectionToClipboardServer.cpp deleted file mode 100644 index 0d7c316b39..0000000000 --- a/Userland/Services/SpiceAgent/ConnectionToClipboardServer.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021, Kyle Pereira - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "ConnectionToClipboardServer.h" -#include -#include -#include - -// Copied from LibGUI/Clipboard.cpp -RefPtr ConnectionToClipboardServer::get_bitmap() -{ - auto clipping = get_clipboard_data(); - - if (clipping.mime_type() != "image/x-serenityos") - return nullptr; - - HashMap const& metadata = clipping.metadata(); - auto width = metadata.get("width").value_or("0").to_uint(); - if (!width.has_value() || width.value() == 0) - return nullptr; - - auto height = metadata.get("height").value_or("0").to_uint(); - if (!height.has_value() || height.value() == 0) - return nullptr; - - auto scale = metadata.get("scale").value_or("0").to_uint(); - if (!scale.has_value() || scale.value() == 0) - return nullptr; - - auto pitch = metadata.get("pitch").value_or("0").to_uint(); - if (!pitch.has_value() || pitch.value() == 0) - return nullptr; - - auto format = metadata.get("format").value_or("0").to_uint(); - if (!format.has_value() || format.value() == 0) - return nullptr; - - auto data = clipping.data().data(); - auto clipping_bitmap_or_error = Gfx::Bitmap::create_wrapper((Gfx::BitmapFormat)format.value(), { (int)width.value(), (int)height.value() }, scale.value(), pitch.value(), const_cast(data)); - if (clipping_bitmap_or_error.is_error()) - return nullptr; - auto clipping_bitmap = clipping_bitmap_or_error.release_value(); - auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { (int)width.value(), (int)height.value() }, scale.value()); - if (bitmap_or_error.is_error()) - return nullptr; - auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors(); - for (int y = 0; y < clipping_bitmap->physical_height(); ++y) { - for (int x = 0; x < clipping_bitmap->physical_width(); ++x) { - auto pixel = clipping_bitmap->get_pixel(x, y); - bitmap->set_pixel(x, y, pixel); - } - } - - return bitmap; -} - -// Copied from LibGUI/Clipboard.cpp -void ConnectionToClipboardServer::set_bitmap(Gfx::Bitmap const& bitmap) -{ - HashMap metadata; - metadata.set("width", DeprecatedString::number(bitmap.width())); - metadata.set("height", DeprecatedString::number(bitmap.height())); - metadata.set("scale", DeprecatedString::number(bitmap.scale())); - metadata.set("format", DeprecatedString::number((int)bitmap.format())); - metadata.set("pitch", DeprecatedString::number(bitmap.pitch())); - ReadonlyBytes data { bitmap.scanline(0), bitmap.size_in_bytes() }; - auto buffer_or_error = Core::AnonymousBuffer::create_with_size(bitmap.size_in_bytes()); - VERIFY(!buffer_or_error.is_error()); - auto buffer = buffer_or_error.release_value(); - memcpy(buffer.data(), data.data(), data.size()); - this->async_set_clipboard_data(buffer, "image/x-serenityos", move(metadata)); -} diff --git a/Userland/Services/SpiceAgent/ConnectionToClipboardServer.h b/Userland/Services/SpiceAgent/ConnectionToClipboardServer.h deleted file mode 100644 index 60b50591c8..0000000000 --- a/Userland/Services/SpiceAgent/ConnectionToClipboardServer.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021, Kyle Pereira - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -class ConnectionToClipboardServer final - : public IPC::ConnectionToServer - , public ClipboardClientEndpoint { - IPC_CLIENT_CONNECTION(ConnectionToClipboardServer, "/tmp/session/%sid/portal/clipboard"sv) - -public: - Function on_data_changed; - RefPtr get_bitmap(); - void set_bitmap(Gfx::Bitmap const& bitmap); - -private: - ConnectionToClipboardServer(NonnullOwnPtr socket) - : IPC::ConnectionToServer(*this, move(socket)) - { - } - virtual void clipboard_data_changed(DeprecatedString const&) override - { - on_data_changed(); - } -}; diff --git a/Userland/Services/SpiceAgent/Message.cpp b/Userland/Services/SpiceAgent/Message.cpp index fa2706c359..f446125129 100644 --- a/Userland/Services/SpiceAgent/Message.cpp +++ b/Userland/Services/SpiceAgent/Message.cpp @@ -111,4 +111,34 @@ ErrorOr ClipboardRequestMessage::debug_description() return builder.to_string(); } +ErrorOr ClipboardMessage::read_from_stream(AK::Stream& stream) +{ + auto value = TRY(stream.read_value()); + if (value >= to_underlying(ClipboardDataType::__End)) { + return Error::from_string_literal("Unsupported clipboard type"); + } + + auto type = static_cast(value); + auto contents = TRY(stream.read_until_eof()); + return ClipboardMessage(type, contents); +} + +ErrorOr ClipboardMessage::write_to_stream(AK::Stream& stream) +{ + TRY(stream.write_value(data_type())); + TRY(stream.write_until_depleted(contents())); + + return {}; +} + +ErrorOr ClipboardMessage::debug_description() +{ + StringBuilder builder; + TRY(builder.try_append("Clipboard { "sv)); + TRY(builder.try_appendff("data_type = {}, ", data_type())); + TRY(builder.try_appendff("contents.size() = {}", contents().size())); + TRY(builder.try_append(" }"sv)); + return builder.to_string(); +} + } diff --git a/Userland/Services/SpiceAgent/Message.h b/Userland/Services/SpiceAgent/Message.h index 8c70b1a205..43799993a9 100644 --- a/Userland/Services/SpiceAgent/Message.h +++ b/Userland/Services/SpiceAgent/Message.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -150,6 +151,29 @@ private: ClipboardDataType m_data_type; }; +// Used to send the clipboard's contents to the client/server. +class ClipboardMessage : public Message { +public: + ClipboardMessage(ClipboardDataType data_type, ByteBuffer const& contents) + : Message(Type::Clipboard) + , m_data_type(data_type) + , m_contents(contents) + { + } + + static ErrorOr read_from_stream(AK::Stream& stream); + + ErrorOr write_to_stream(AK::Stream& stream); + ErrorOr debug_description() override; + + ClipboardDataType data_type() { return m_data_type; } + ByteBuffer const& contents() { return m_contents; } + +private: + ClipboardDataType m_data_type; + ByteBuffer m_contents; +}; + } namespace AK { diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp index bb96d634be..d1991353dc 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -6,21 +6,19 @@ */ #include "SpiceAgent.h" -#include "ConnectionToClipboardServer.h" +#include +#include namespace SpiceAgent { ErrorOr> SpiceAgent::create(StringView device_path) { auto device = TRY(Core::File::open(device_path, Core::File::OpenMode::ReadWrite | Core::File::OpenMode::Nonblocking)); - auto clipboard_connection = TRY(ConnectionToClipboardServer::try_create()); - - return try_make(move(device), clipboard_connection, Vector { Capability::ClipboardByDemand }); + return try_make(move(device), Vector { Capability::ClipboardByDemand }); } -SpiceAgent::SpiceAgent(NonnullOwnPtr spice_device, ConnectionToClipboardServer& clipboard_connection, Vector const& capabilities) +SpiceAgent::SpiceAgent(NonnullOwnPtr spice_device, Vector const& capabilities) : m_spice_device(move(spice_device)) - , m_clipboard_connection(clipboard_connection) , m_capabilities(capabilities) { m_notifier = Core::Notifier::construct( @@ -82,6 +80,16 @@ ErrorOr SpiceAgent::on_message_received() break; } + case Message::Type::Clipboard: { + auto message = TRY(ClipboardMessage::read_from_stream(stream)); + if (message.data_type() == ClipboardDataType::None) + break; + + TRY(this->did_receive_clipboard_message(message)); + + break; + } + // We ignore certain messages to prevent it from clogging up the logs. case Message::Type::MonitorsConfig: dbgln_if(SPICE_AGENT_DEBUG, "Ignored message: {}", header); @@ -95,6 +103,21 @@ ErrorOr SpiceAgent::on_message_received() return {}; } +ErrorOr SpiceAgent::did_receive_clipboard_message(ClipboardMessage& message) +{ + dbgln_if(SPICE_AGENT_DEBUG, "Attempting to parse clipboard data of type: {}", message.data_type()); + + switch (message.data_type()) { + case ClipboardDataType::Text: { + // The default mime_type for set_data is `text/plain`. + GUI::Clipboard::the().set_data(message.contents()); + return {}; + } + default: + return Error::from_string_literal("Unsupported clipboard data type!"); + } +} + ErrorOr SpiceAgent::read_message_buffer() { auto port = TRY(m_spice_device->read_value()); diff --git a/Userland/Services/SpiceAgent/SpiceAgent.h b/Userland/Services/SpiceAgent/SpiceAgent.h index c9bbe1d265..8ecf4fec3b 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.h +++ b/Userland/Services/SpiceAgent/SpiceAgent.h @@ -7,10 +7,11 @@ #pragma once -#include "ConnectionToClipboardServer.h" #include "Message.h" #include "MessageHeader.h" +#include #include +#include #include namespace SpiceAgent { @@ -26,7 +27,7 @@ public: }; static ErrorOr> create(StringView device_path); - SpiceAgent(NonnullOwnPtr spice_device, ConnectionToClipboardServer& clipboard_connection, Vector const& capabilities); + SpiceAgent(NonnullOwnPtr spice_device, Vector const& capabilities); ErrorOr start(); @@ -61,11 +62,13 @@ public: private: NonnullOwnPtr m_spice_device; - ConnectionToClipboardServer& m_clipboard_connection; Vector m_capabilities; RefPtr m_notifier; + // Fired when we receive clipboard data from the spice server. + ErrorOr did_receive_clipboard_message(ClipboardMessage& message); + ErrorOr on_message_received(); ErrorOr read_message_buffer(); }; diff --git a/Userland/Services/SpiceAgent/main.cpp b/Userland/Services/SpiceAgent/main.cpp index dafe7a657a..ff2caafa05 100644 --- a/Userland/Services/SpiceAgent/main.cpp +++ b/Userland/Services/SpiceAgent/main.cpp @@ -7,26 +7,28 @@ #include "SpiceAgent.h" #include +#include +#include #include #include #include static constexpr auto SPICE_DEVICE = "/dev/hvc0p1"sv; -ErrorOr serenity_main(Main::Arguments) +ErrorOr serenity_main(Main::Arguments arguments) { - Core::EventLoop loop; + // We use the application to be able to easily write to the user's clipboard. + auto app = TRY(GUI::Application::create(arguments)); // FIXME: Make Core::File support reading and writing, but without creating: // By default, Core::File opens the file descriptor with O_CREAT when using OpenMode::Write (and subsequently, OpenMode::ReadWrite). // To minimise confusion for people that have already used Core::File, we can probably just do `OpenMode::ReadWrite | OpenMode::DontCreate`. TRY(Core::System::pledge("unix rpath wpath stdio sendfd recvfd cpath")); TRY(Core::System::unveil(SPICE_DEVICE, "rwc"sv)); - TRY(Core::System::unveil("/tmp/session/%sid/portal/clipboard", "rw")); TRY(Core::System::unveil(nullptr, nullptr)); auto agent = TRY(SpiceAgent::SpiceAgent::create(SPICE_DEVICE)); TRY(agent->start()); - return loop.exec(); + return app->exec(); }