From c8b13bd053c32f59bf75b35941d2e938b9ff96b8 Mon Sep 17 00:00:00 2001 From: x-yl Date: Mon, 12 Jul 2021 18:16:19 +0400 Subject: [PATCH] SpiceAgent: Support copying and pasting images --- .../SpiceAgent/ClipboardServerConnection.cpp | 60 +++++++++++++ .../SpiceAgent/ClipboardServerConnection.h | 2 + Userland/Services/SpiceAgent/SpiceAgent.cpp | 88 +++++++++++++++++-- Userland/Services/SpiceAgent/SpiceAgent.h | 1 + 4 files changed, 143 insertions(+), 8 deletions(-) diff --git a/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp b/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp index 6c7ed5c5223..3dffb40659a 100644 --- a/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp +++ b/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp @@ -5,4 +5,64 @@ */ #include "ClipboardServerConnection.h" +#include +#include +#include +// Copied from LibGUI/Clipboard.cpp +RefPtr ClipboardServerConnection::get_bitmap() +{ + auto clipping = get_clipboard_data(); + + if (clipping.mime_type() != "image/x-serenityos") + return nullptr; + + HashMap const& metadata = clipping.metadata().entries(); + 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 = ByteBuffer::copy(clipping.data().data(), clipping.data().size()); + auto clipping_bitmap = Gfx::Bitmap::create_wrapper((Gfx::BitmapFormat)format.value(), { (int)width.value(), (int)height.value() }, scale.value(), pitch.value(), data.data()); + auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { (int)width.value(), (int)height.value() }, scale.value()); + + 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 ClipboardServerConnection::set_bitmap(Gfx::Bitmap const& bitmap) +{ + HashMap metadata; + metadata.set("width", String::number(bitmap.width())); + metadata.set("height", String::number(bitmap.height())); + metadata.set("scale", String::number(bitmap.scale())); + metadata.set("format", String::number((int)bitmap.format())); + metadata.set("pitch", String::number(bitmap.pitch())); + ReadonlyBytes data { bitmap.scanline(0), bitmap.size_in_bytes() }; + auto buffer = Core::AnonymousBuffer::create_with_size(bitmap.size_in_bytes()); + memcpy(buffer.data(), data.data(), data.size()); + this->async_set_clipboard_data(buffer, "image/x-serenityos", metadata); +} diff --git a/Userland/Services/SpiceAgent/ClipboardServerConnection.h b/Userland/Services/SpiceAgent/ClipboardServerConnection.h index cad8065ffe7..6c0bba4eb08 100644 --- a/Userland/Services/SpiceAgent/ClipboardServerConnection.h +++ b/Userland/Services/SpiceAgent/ClipboardServerConnection.h @@ -18,6 +18,8 @@ class ClipboardServerConnection final C_OBJECT(ClipboardServerConnection); Function on_data_changed; + RefPtr get_bitmap(); + void set_bitmap(Gfx::Bitmap const& bitmap); private: ClipboardServerConnection() diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp index 082800d0d36..782de719b30 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -9,6 +9,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include SpiceAgent::SpiceAgent(int fd, ClipboardServerConnection& connection) : m_fd(fd) @@ -23,13 +29,32 @@ SpiceAgent::SpiceAgent(int fd, ClipboardServerConnection& connection) m_just_set_clip = false; return; } - auto grab_buffer = ClipboardGrab::make_buffer({ ClipboardType::Text }); + auto mime = m_clipboard_connection.get_clipboard_data().mime_type(); + Optional type = mime_type_to_clipboard_type(mime); + if (!type.has_value()) + return; + + auto grab_buffer = ClipboardGrab::make_buffer({ *type }); send_message(grab_buffer); }; auto buffer = AnnounceCapabilities::make_buffer(true, { Capability::ClipboardByDemand }); send_message(buffer); } +Optional SpiceAgent::mime_type_to_clipboard_type(const String& mime) +{ + if (mime == "text/plain") + return ClipboardType::Text; + else if (mime == "image/jpeg") + return ClipboardType::JPG; + else if (mime == "image/bmp") + return ClipboardType::BMP; + else if (mime == "image/png" || mime == "image/x-serenityos") + return ClipboardType::PNG; + else + return {}; +} + void SpiceAgent::on_message_received() { ChunkHeader header {}; @@ -47,19 +72,50 @@ void SpiceAgent::on_message_received() break; } case (u32)MessageType::ClipboardRequest: { - auto clip_data = m_clipboard_connection.get_clipboard_data().data(); - ByteBuffer byte_buffer = ByteBuffer::copy(clip_data.data(), clip_data.size()); - auto clipboard_buffer = Clipboard::make_buffer(ClipboardType::Text, byte_buffer); + auto* request_message = reinterpret_cast(message->data); + auto clipboard = m_clipboard_connection.get_clipboard_data(); + auto& mime = clipboard.mime_type(); + ByteBuffer byte_buffer; + if (mime == "image/x-serenityos") { + auto bitmap = m_clipboard_connection.get_bitmap(); + byte_buffer = Gfx::PNGWriter::encode(*bitmap); + } else { + auto clip_data = clipboard.data(); + byte_buffer = ByteBuffer::copy(clip_data.data(), clip_data.size()); + } + auto clipboard_buffer = Clipboard::make_buffer((ClipboardType)request_message->type, byte_buffer); send_message(clipboard_buffer); break; } case (u32)MessageType::ClipboardGrab: { - auto request_buffer = ClipboardRequest::make_buffer(ClipboardType::Text); + auto* grab_message = reinterpret_cast(message->data); + auto found_type = ClipboardType::None; + for (size_t i = 0; i < (message->size / 4); i++) { + auto type = (ClipboardType)grab_message->types[i]; + if (found_type == ClipboardType::None) { + found_type = static_cast(type); + } else if (found_type == ClipboardType::Text) { + switch (type) { + case ClipboardType::PNG: + case ClipboardType::BMP: + case ClipboardType::JPG: + found_type = type; + break; + default: + break; + } + } + } + if (found_type == ClipboardType::None) + return; + + auto request_buffer = ClipboardRequest::make_buffer(found_type); send_message(request_buffer); break; } case (u32)MessageType::Clipboard: { auto* clipboard_message = reinterpret_cast(message->data); + auto type = (ClipboardType)clipboard_message->type; auto data_buffer = ByteBuffer::create_uninitialized(message->size - sizeof(u32)); const auto total_bytes = message->size - sizeof(Clipboard); @@ -74,9 +130,25 @@ void SpiceAgent::on_message_received() } m_just_set_clip = true; - auto anon_buffer = Core::AnonymousBuffer::create_with_size(data_buffer.size()); - memcpy(anon_buffer.data(), data_buffer.data(), data_buffer.size()); - m_clipboard_connection.async_set_clipboard_data(anon_buffer, "text/plain", {}); + if (type == ClipboardType::Text) { + auto anon_buffer = Core::AnonymousBuffer::create_with_size(data_buffer.size()); + memcpy(anon_buffer.data(), data_buffer.data(), data_buffer.size()); + m_clipboard_connection.async_set_clipboard_data(anon_buffer, "text/plain", {}); + return; + } else { + RefPtr bitmap; + if (type == ClipboardType::PNG) { + bitmap = Gfx::load_png_from_memory(data_buffer.data(), data_buffer.size()); + } else if (type == ClipboardType::BMP) { + bitmap = Gfx::load_bmp_from_memory(data_buffer.data(), data_buffer.size()); + } else if (type == ClipboardType::JPG) { + bitmap = Gfx::load_jpg_from_memory(data_buffer.data(), data_buffer.size()); + } else { + dbgln("Unknown clipboard type: {}", (u32)type); + return; + } + m_clipboard_connection.set_bitmap(*bitmap); + } break; } default: diff --git a/Userland/Services/SpiceAgent/SpiceAgent.h b/Userland/Services/SpiceAgent/SpiceAgent.h index 24bef40b69c..8fe64cb31fc 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.h +++ b/Userland/Services/SpiceAgent/SpiceAgent.h @@ -124,4 +124,5 @@ private: bool m_just_set_clip { false }; void read_n(void* dest, size_t n); static Message* initialize_headers(u8* data, size_t additional_data_size, MessageType type); + static Optional mime_type_to_clipboard_type(const String& mime); };