diff options
author | x-yl <kylepereira@mail.com> | 2021-07-12 18:16:19 +0400 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-07-14 12:33:07 +0200 |
commit | c8b13bd053c32f59bf75b35941d2e938b9ff96b8 (patch) | |
tree | 25b0179c372420fad1acd876426a93bab823d9d2 /Userland | |
parent | d4bb6a1a1ee30dafb1f03b08390adc2e3716855b (diff) | |
download | serenity-c8b13bd053c32f59bf75b35941d2e938b9ff96b8.zip |
SpiceAgent: Support copying and pasting images
Diffstat (limited to 'Userland')
4 files changed, 143 insertions, 8 deletions
diff --git a/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp b/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp index 6c7ed5c522..3dffb40659 100644 --- a/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp +++ b/Userland/Services/SpiceAgent/ClipboardServerConnection.cpp @@ -5,4 +5,64 @@ */ #include "ClipboardServerConnection.h" +#include <AK/ByteBuffer.h> +#include <AK/Function.h> +#include <LibGfx/Bitmap.h> +// Copied from LibGUI/Clipboard.cpp +RefPtr<Gfx::Bitmap> ClipboardServerConnection::get_bitmap() +{ + auto clipping = get_clipboard_data(); + + if (clipping.mime_type() != "image/x-serenityos") + return nullptr; + + HashMap<String, String> 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<void>(), 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<String, String> 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<u8>(), 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 cad8065ffe..6c0bba4eb0 100644 --- a/Userland/Services/SpiceAgent/ClipboardServerConnection.h +++ b/Userland/Services/SpiceAgent/ClipboardServerConnection.h @@ -18,6 +18,8 @@ class ClipboardServerConnection final C_OBJECT(ClipboardServerConnection); Function<void()> on_data_changed; + RefPtr<Gfx::Bitmap> 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 082800d0d3..782de719b3 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -9,6 +9,12 @@ #include <AK/String.h> #include <LibC/memory.h> #include <LibC/unistd.h> +#include <LibGfx/BMPLoader.h> +#include <LibGfx/BMPWriter.h> +#include <LibGfx/Bitmap.h> +#include <LibGfx/JPGLoader.h> +#include <LibGfx/PNGLoader.h> +#include <LibGfx/PNGWriter.h> 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<ClipboardType> 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::ClipboardType> 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<void>(), clip_data.size()); - auto clipboard_buffer = Clipboard::make_buffer(ClipboardType::Text, byte_buffer); + auto* request_message = reinterpret_cast<ClipboardRequest*>(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<void>(), 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<ClipboardGrab*>(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<ClipboardType>(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<Clipboard*>(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<void>(), 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<void>(), data_buffer.data(), data_buffer.size()); + m_clipboard_connection.async_set_clipboard_data(anon_buffer, "text/plain", {}); + return; + } else { + RefPtr<Gfx::Bitmap> 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 24bef40b69..8fe64cb31f 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<ClipboardType> mime_type_to_clipboard_type(const String& mime); }; |