From 0d98920930638c723bb3306af3b19d7d78ad513d Mon Sep 17 00:00:00 2001 From: Caoimhe Date: Sat, 13 May 2023 17:53:48 +0100 Subject: [PATCH] SpiceAgent: Implement sending clipboard data to the spice server --- Userland/Services/SpiceAgent/Message.cpp | 21 +++++++ Userland/Services/SpiceAgent/Message.h | 1 + Userland/Services/SpiceAgent/SpiceAgent.cpp | 64 +++++++++++++++++++++ Userland/Services/SpiceAgent/SpiceAgent.h | 8 +++ 4 files changed, 94 insertions(+) diff --git a/Userland/Services/SpiceAgent/Message.cpp b/Userland/Services/SpiceAgent/Message.cpp index ffd5a0817d..ca0e06fe77 100644 --- a/Userland/Services/SpiceAgent/Message.cpp +++ b/Userland/Services/SpiceAgent/Message.cpp @@ -43,6 +43,27 @@ ErrorOr clipboard_data_type_from_raw_value(u32 value) return static_cast(value); } +ErrorOr clipboard_data_type_from_mime_type(String const& mime_type) +{ + if (mime_type == "text/plain") + return ClipboardDataType::Text; + + // We treat image/x-serenityos as a standard PNG here + if (mime_type == "image/png" || mime_type == "image/x-serenityos") + return ClipboardDataType::PNG; + + if (mime_type == "image/bitmap") + return ClipboardDataType::BMP; + + if (mime_type == "image/jpeg") + return ClipboardDataType::JPG; + + if (mime_type == "image/tiff") + return ClipboardDataType::TIFF; + + return Error::from_string_literal("Unable to determine clipboard data type!"); +} + ErrorOr AnnounceCapabilitiesMessage::read_from_stream(AK::Stream& stream) { // If this message is a capabilities request, we don't have to parse anything else. diff --git a/Userland/Services/SpiceAgent/Message.h b/Userland/Services/SpiceAgent/Message.h index 6a411b74d7..29456cc307 100644 --- a/Userland/Services/SpiceAgent/Message.h +++ b/Userland/Services/SpiceAgent/Message.h @@ -53,6 +53,7 @@ enum class ClipboardDataType : u32 { ErrorOr clipboard_data_type_to_mime_type(ClipboardDataType type); ErrorOr clipboard_data_type_from_raw_value(u32 value); +ErrorOr clipboard_data_type_from_mime_type(String const& mime_type); class Message { public: diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp index f0e52268c4..1cf46a1d45 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace SpiceAgent { @@ -40,6 +41,59 @@ ErrorOr SpiceAgent::start() auto capabilities_message = AnnounceCapabilitiesMessage(false, m_capabilities); TRY(this->send_message(capabilities_message)); + GUI::Clipboard::the().on_change = [this](auto const& mime_type) { + auto result = this->on_clipboard_update(String::from_deprecated_string(mime_type).release_value_but_fixme_should_propagate_errors()); + if (result.is_error()) { + dbgln("Failed to inform the spice server of a clipboard update: {}", result.release_error()); + } + }; + + return {}; +} + +ErrorOr SpiceAgent::on_clipboard_update(String const& mime_type) +{ + // If we just copied something to the clipboard, we shouldn't do anything here. + if (m_clipboard_dirty) { + m_clipboard_dirty = false; + return {}; + } + + // If the clipboard has just been cleared, we shouldn't send anything. + if (mime_type.is_empty()) { + return {}; + } + + // Notify the spice server about new content being available. + auto clipboard_data_type = TRY(clipboard_data_type_from_mime_type(mime_type)); + auto message = ClipboardGrabMessage({ clipboard_data_type }); + TRY(this->send_message(message)); + + return {}; +} + +ErrorOr SpiceAgent::send_clipboard_contents(ClipboardDataType data_type) +{ + auto data_and_type = GUI::Clipboard::the().fetch_data_and_type(); + auto requested_mime_type = TRY(clipboard_data_type_to_mime_type(data_type)); + + // We have an exception for `image/x-serenityos`, where we treat it as a PNG when talking to the spice server. + auto is_serenity_image = data_and_type.mime_type == "image/x-serenityos" && data_type == ClipboardDataType::PNG; + if (!is_serenity_image && requested_mime_type.to_deprecated_string() != data_and_type.mime_type) { + // If the requested mime type doesn't match what's on the clipboard, we won't send anything back. + return Error::from_string_literal("Requested mime type doesn't match the clipboard's contents!"); + } + + // If the mime type is `image/x-serenityos`, we need to encode the image that's on the clipboard as a PNG. + auto clipboard_data = data_and_type.data; + if (is_serenity_image) { + auto bitmap = data_and_type.as_bitmap(); + clipboard_data = TRY(Gfx::PNGWriter::encode(*bitmap)); + } + + auto message = ClipboardMessage(data_type, clipboard_data); + TRY(this->send_message(message)); + return {}; } @@ -91,6 +145,15 @@ ErrorOr SpiceAgent::on_message_received() break; } + case Message::Type::ClipboardRequest: { + dbgln("The spice server has requsted our clipboard's contents"); + + auto message = TRY(ClipboardRequestMessage::read_from_stream(stream)); + TRY(this->send_clipboard_contents(message.data_type())); + + 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); @@ -138,6 +201,7 @@ ErrorOr SpiceAgent::did_receive_clipboard_message(ClipboardMessage& messag return Error::from_string_literal("Unsupported clipboard data type!"); } + m_clipboard_dirty = true; return {}; } diff --git a/Userland/Services/SpiceAgent/SpiceAgent.h b/Userland/Services/SpiceAgent/SpiceAgent.h index 7eb9809f14..2ea1893f6e 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.h +++ b/Userland/Services/SpiceAgent/SpiceAgent.h @@ -64,9 +64,17 @@ private: RefPtr m_notifier; + bool m_clipboard_dirty { false }; + // Fired when we receive clipboard data from the spice server. ErrorOr did_receive_clipboard_message(ClipboardMessage& message); + // Fired when the user's clipboard changes + ErrorOr on_clipboard_update(String const& mime_type); + + // Sends the GUI::Clipboard's current contents to the spice server + ErrorOr send_clipboard_contents(ClipboardDataType data_type); + ErrorOr on_message_received(); ErrorOr read_message_buffer(); };