From 79c73dd2601b44b7b48085655c3cec4cb739fae7 Mon Sep 17 00:00:00 2001 From: Caoimhe Date: Sat, 13 May 2023 13:08:23 +0100 Subject: [PATCH] SpiceAgent: Let's start rewriting the messaging system :^) The old message system was very dependent on syscalls, and the overall structure made it hard to implement new features. The new message system is pretty expandible, where each message has its own dedicated class. As well as this, we now use Core::File and AK::Stream for reading and writing messages. Using AK::Stream also allows us to change the actual data source (in this case, Core::File) without having to update a whole lot of code in the future. --- Userland/Services/SpiceAgent/CMakeLists.txt | 1 + Userland/Services/SpiceAgent/Message.cpp | 55 ++++ Userland/Services/SpiceAgent/Message.h | 101 ++++++ Userland/Services/SpiceAgent/MessageHeader.h | 65 ++++ Userland/Services/SpiceAgent/SpiceAgent.cpp | 317 +++---------------- Userland/Services/SpiceAgent/SpiceAgent.h | 137 +++----- Userland/Services/SpiceAgent/main.cpp | 14 +- 7 files changed, 320 insertions(+), 370 deletions(-) create mode 100644 Userland/Services/SpiceAgent/Message.cpp create mode 100644 Userland/Services/SpiceAgent/Message.h create mode 100644 Userland/Services/SpiceAgent/MessageHeader.h diff --git a/Userland/Services/SpiceAgent/CMakeLists.txt b/Userland/Services/SpiceAgent/CMakeLists.txt index 4fc96824f2..f47b1fdbf6 100644 --- a/Userland/Services/SpiceAgent/CMakeLists.txt +++ b/Userland/Services/SpiceAgent/CMakeLists.txt @@ -5,6 +5,7 @@ serenity_component( set(SOURCES main.cpp + Message.cpp SpiceAgent.cpp ConnectionToClipboardServer.cpp ) diff --git a/Userland/Services/SpiceAgent/Message.cpp b/Userland/Services/SpiceAgent/Message.cpp new file mode 100644 index 0000000000..6b2bed2736 --- /dev/null +++ b/Userland/Services/SpiceAgent/Message.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, Caoimhe Byrne + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Message.h" +#include +#include +#include + +namespace SpiceAgent { + +ErrorOr AnnounceCapabilitiesMessage::read_from_stream(AK::Stream& stream) +{ + // If this message is a capabilities request, we don't have to parse anything else. + auto is_requesting = TRY(stream.read_value()) == 1; + if (is_requesting) { + return AnnounceCapabilitiesMessage(is_requesting); + } + + return Error::from_string_literal("Unexpected non-requesting announce capabilities message received!"); +} + +ErrorOr AnnounceCapabilitiesMessage::write_to_stream(AK::Stream& stream) +{ + TRY(stream.write_value(is_request())); + + // Each bit in this u32 indicates if a certain capability is enabled or not. + u32 capabilities_bits = 0; + for (auto capability : capabilities()) { + // FIXME: At the moment, we only support up to 32 capabilities as the Spice protocol + // only contains 17 capabilities. + auto capability_value = to_underlying(capability); + VERIFY(capability_value < 32); + + capabilities_bits |= 1 << capability_value; + } + + TRY(stream.write_value(capabilities_bits)); + + return {}; +} + +ErrorOr AnnounceCapabilitiesMessage::debug_description() +{ + StringBuilder builder; + TRY(builder.try_append("AnnounceCapabilities { "sv)); + TRY(builder.try_appendff("is_request = {}, ", is_request())); + TRY(builder.try_appendff("capabilities.size() = {}", capabilities().size())); + TRY(builder.try_append(" }"sv)); + return builder.to_string(); +} + +} diff --git a/Userland/Services/SpiceAgent/Message.h b/Userland/Services/SpiceAgent/Message.h new file mode 100644 index 0000000000..6799f9eefa --- /dev/null +++ b/Userland/Services/SpiceAgent/Message.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023, Caoimhe Byrne + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace SpiceAgent { + +static constexpr u32 AGENT_PROTOCOL = 1; + +// Used to communicate what the client/or server is capable of. +// Not a lot of documentation is available for all of these, but the headers contain some information: +// https://gitlab.freedesktop.org/spice/spice-protocol/-/blob/master/spice/vd_agent.h +enum class Capability : u32 { + MouseState = 0, + MonitorsConfig, + Reply, + Clipboard, + DisplayConfig, + ClipboardByDemand, + ClipboardSelection, + SparseMonitorsConfig, + GuestLineEndLF, + GuestLineEndCRLF, + MaxClipboard, + AudioVolumeSync, + MonitorsConfigPosition, + FileTransferDisabled, + FileTransferDetailedErrors, + GraphicsCardInfo, + ClipboardNoReleaseOnRegrab, + ClipboardGrabSerial +}; + +class Message { +public: + // The spice protocol headers contain a bit of documentation about these, but nothing major: + // https://gitlab.freedesktop.org/spice/spice-protocol/-/blob/master/spice/vd_agent.h + enum class Type : u32 { + MouseState = 1, + MonitorsConfig, + Reply, + Clipboard, + DisplayConfig, + AnnounceCapabilities, + ClipboardGrab, + ClipboardRequest, + ClipboardRelease, + FileTransferStart, + FileTransferStatus, + FileTransferData, + Disconnected, + MaxClipboard, + VolumeSync, + GraphicsDeviceInfo + }; + + Message(Type type) + : m_type(type) + { + } + + Type type() { return m_type; } + + virtual ErrorOr debug_description() = 0; + virtual ~Message() = default; + +private: + Type m_type; +}; + +// Sent to the server to tell it what we are capable of. +// See the Capabilities enum to see the available capabilities. +class AnnounceCapabilitiesMessage : public Message { +public: + AnnounceCapabilitiesMessage(bool is_request, Vector capabilities = {}) + : Message(Type::AnnounceCapabilities) + , m_is_request(is_request) + , m_capabilities(move(capabilities)) + { + } + + static ErrorOr read_from_stream(AK::Stream& stream); + + ErrorOr write_to_stream(AK::Stream& stream); + ErrorOr debug_description() override; + + bool is_request() const& { return m_is_request; } + Vector const& capabilities() { return m_capabilities; } + +private: + bool m_is_request { false }; + Vector m_capabilities; +}; + +} diff --git a/Userland/Services/SpiceAgent/MessageHeader.h b/Userland/Services/SpiceAgent/MessageHeader.h new file mode 100644 index 0000000000..baa08ba019 --- /dev/null +++ b/Userland/Services/SpiceAgent/MessageHeader.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, Caoimhe Byrne + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Message.h" +#include +#include +#include + +namespace SpiceAgent { + +// An incoming or outgoing message header. +// This contains information about the message, like how long it is, the type, etc. +class [[gnu::packed]] MessageHeader { +public: + MessageHeader(Message::Type type, u32 data_size, u32 protocol_version = AGENT_PROTOCOL, u64 opaque = 0) + : m_protocol_version(protocol_version) + , m_type(type) + , m_opaque(opaque) + , m_data_size(data_size) + { + } + + Message::Type type() const { return m_type; }; + u32 data_size() const { return m_data_size; }; + u32 protocol_version() const { return m_protocol_version; }; + u64 opaque() const { return m_opaque; }; + +private: + // The protocol version being used. + u32 m_protocol_version { AGENT_PROTOCOL }; + + // The message type present in `data`. + Message::Type m_type { Message::Type::MouseState }; + + // A placeholder for message types which only need to pass a single integer as message data, + // for message types which have more data it is always set to 0. + u64 m_opaque { 0 }; + + // The size of the data in the message following this header. + u32 m_data_size { 0 }; +}; + +} + +template<> +struct AK::Traits : public AK::GenericTraits { + static constexpr bool is_trivially_serializable() { return true; } +}; + +namespace AK { +template<> +struct Formatter : Formatter { + ErrorOr format(FormatBuilder& builder, SpiceAgent::MessageHeader const& header) + { + return Formatter::format(builder, + "MessageHeader {{ protocol_version = {}, type = {}, opaque = {}, data_size = {} }}"sv, + header.protocol_version(), to_underlying(header.type()), header.opaque(), header.data_size()); + } +}; +} diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp index 3e3e4743b1..0ef134e7a8 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -1,310 +1,93 @@ /* * Copyright (c) 2021, Kyle Pereira + * Copyright (c) 2023, Caoimhe Byrne * * SPDX-License-Identifier: BSD-2-Clause */ #include "SpiceAgent.h" #include "ConnectionToClipboardServer.h" -#include -#include -#include -#include -#include -#include -#include -#include -SpiceAgent::SpiceAgent(int fd, ConnectionToClipboardServer& connection) - : m_fd(fd) - , m_clipboard_connection(connection) +namespace SpiceAgent { + +ErrorOr> SpiceAgent::create(StringView device_path) { - m_notifier = Core::Notifier::construct(fd, Core::Notifier::Type::Read); + 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 }); +} + +SpiceAgent::SpiceAgent(NonnullOwnPtr spice_device, ConnectionToClipboardServer& clipboard_connection, Vector const& capabilities) + : m_spice_device(move(spice_device)) + , m_clipboard_connection(clipboard_connection) + , m_capabilities(capabilities) +{ + m_notifier = Core::Notifier::construct( + m_spice_device->fd(), + Core::Notifier::Type::Read); + m_notifier->on_activation = [this] { auto result = on_message_received(); if (result.is_error()) { - warnln("Failed to handle message: {}", result.release_error()); + dbgln("Failed to handle message: {}", result.release_error()); } }; - - m_clipboard_connection.on_data_changed = [this] { - if (m_just_set_clip) { - m_just_set_clip = false; - return; - } - 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(DeprecatedString const& mime) +ErrorOr SpiceAgent::start() { - if (mime == "text/plain") - return ClipboardType::Text; - - if (mime == "image/jpeg") - return ClipboardType::JPEG; - - if (mime == "image/bmp") - return ClipboardType::BMP; - - if (mime == "image/png" || mime == "image/x-serenityos") - return ClipboardType::PNG; + // The server usually requests this from us anyways, but there's no harm in sending it. + auto capabilities_message = AnnounceCapabilitiesMessage(false, m_capabilities); + TRY(this->send_message(capabilities_message)); return {}; } ErrorOr SpiceAgent::on_message_received() { - ChunkHeader header {}; - read_n(&header, sizeof(header)); + auto buffer = TRY(this->read_message_buffer()); + auto stream = FixedMemoryStream(buffer.bytes()); - auto buffer = TRY(ByteBuffer::create_uninitialized(header.size)); - read_n(buffer.data(), buffer.size()); - - auto* message = reinterpret_cast(buffer.data()); - switch (message->type) { - case (u32)MessageType::AnnounceCapabilities: { - auto* capabilities_message = reinterpret_cast(message->data); - if (capabilities_message->request) { - auto capabilities_buffer = AnnounceCapabilities::make_buffer(false, { Capability::ClipboardByDemand }); - send_message(capabilities_buffer); - } - break; - } - case (u32)MessageType::ClipboardRequest: { - auto* request_message = reinterpret_cast(message->data); - auto clipboard = m_clipboard_connection.get_clipboard_data(); - auto& mime = clipboard.mime_type(); - - ByteBuffer backing_byte_buffer; - ReadonlyBytes bytes; - - if (mime == "image/x-serenityos") { - auto bitmap = m_clipboard_connection.get_bitmap(); - backing_byte_buffer = MUST(Gfx::PNGWriter::encode(*bitmap)); - bytes = backing_byte_buffer; - } else { - auto data = clipboard.data(); - bytes = { data.data(), data.size() }; - } - - auto clipboard_buffer = Clipboard::make_buffer((ClipboardType)request_message->type, bytes); - send_message(clipboard_buffer); - break; - } - case (u32)MessageType::ClipboardGrab: { - 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::JPEG: - found_type = type; - break; - default: - break; - } - } - } - - if (found_type == ClipboardType::None) + auto header = TRY(stream.read_value()); + switch (header.type()) { + case Message::Type::AnnounceCapabilities: { + auto message = TRY(AnnounceCapabilitiesMessage::read_from_stream(stream)); + if (!message.is_request()) return {}; - auto request_buffer = ClipboardRequest::make_buffer(found_type); - send_message(request_buffer); + dbgln("The spice server has requested our capabilities"); + + auto capabilities_message = AnnounceCapabilitiesMessage(false, m_capabilities); + TRY(this->send_message(capabilities_message)); + break; } - case (u32)MessageType::Clipboard: { - auto* clipboard_message = reinterpret_cast(message->data); - auto type = (ClipboardType)clipboard_message->type; - auto data_buffer = TRY(ByteBuffer::create_uninitialized(message->size - sizeof(u32))); - auto const total_bytes = message->size - sizeof(Clipboard); - auto bytes_copied = header.size - sizeof(Message) - sizeof(Clipboard); - memcpy(data_buffer.data(), clipboard_message->data, bytes_copied); - - while (bytes_copied < total_bytes) { - ChunkHeader next_header; - read_n(&next_header, sizeof(ChunkHeader)); - read_n(data_buffer.data() + bytes_copied, next_header.size); - bytes_copied += next_header.size; - } - - m_just_set_clip = true; - if (type == ClipboardType::Text) { - if (data_buffer.is_empty()) { - m_clipboard_connection.async_set_clipboard_data({}, "text/plain", {}); - } else { - auto anon_buffer = TRY(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 { - ErrorOr frame_or_error = Gfx::ImageFrameDescriptor {}; - if (type == ClipboardType::PNG) { - if (Gfx::PNGImageDecoderPlugin::sniff({ data_buffer.data(), data_buffer.size() })) { - auto png_decoder = TRY(Gfx::PNGImageDecoderPlugin::create({ data_buffer.data(), data_buffer.size() })); - if (!png_decoder->initialize().is_error()) - frame_or_error = png_decoder->frame(0); - } - } else if (type == ClipboardType::BMP) { - if (Gfx::BMPImageDecoderPlugin::sniff({ data_buffer.data(), data_buffer.size() })) { - auto bmp_decoder = TRY(Gfx::BMPImageDecoderPlugin::create({ data_buffer.data(), data_buffer.size() })); - if (!bmp_decoder->initialize().is_error()) - frame_or_error = bmp_decoder->frame(0); - } - } else if (type == ClipboardType::JPEG) { - if (Gfx::JPEGImageDecoderPlugin::sniff({ data_buffer.data(), data_buffer.size() })) { - auto jpeg_decoder = TRY(Gfx::JPEGImageDecoderPlugin::create({ data_buffer.data(), data_buffer.size() })); - if (!jpeg_decoder->initialize().is_error()) - frame_or_error = jpeg_decoder->frame(0); - } - } else { - dbgln("Unknown clipboard type: {}", (u32)type); - return {}; - } - - auto const& bitmap = frame_or_error.value().image; - m_clipboard_connection.set_bitmap(*bitmap); - } + // We ignore certain messages to prevent it from clogging up the logs. + case Message::Type::MonitorsConfig: + dbgln_if(SPICE_AGENT_DEBUG, "Ignored message: {}", header); break; - } + default: - dbgln("Unhandled message type {}", message->type); + dbgln("Unknown message received: {}", header); + break; } return {}; } -void SpiceAgent::read_n(void* dest, size_t n) +ErrorOr SpiceAgent::read_message_buffer() { - size_t bytes_read = 0; - while (bytes_read < n) { - int nread = read(m_fd, (u8*)dest + bytes_read, n - bytes_read); - if (nread > 0) { - bytes_read += nread; - } else if (errno == EAGAIN) { - continue; - } else { - dbgln("Failed to read: {}", errno); - return; - } + auto port = TRY(m_spice_device->read_value()); + if (port != Port::Client) { + return Error::from_string_literal("Attempted to read message bytes from a port that wasn't meant for the client!"); } -} -void SpiceAgent::send_message(ByteBuffer const& buffer) -{ - size_t bytes_written = 0; - while (bytes_written < buffer.size()) { - int result = write(m_fd, buffer.data() + bytes_written, buffer.size() - bytes_written); - if (result < 0) { - dbgln("Failed to write: {}", errno); - return; - } - bytes_written += result; - } -} - -SpiceAgent::Message* SpiceAgent::initialize_headers(u8* data, size_t additional_data_size, MessageType type) -{ - new (data) ChunkHeader { - (u32)Port::Client, - (u32)(sizeof(Message) + additional_data_size) - }; - - auto* message = new (data + sizeof(ChunkHeader)) Message { - AGENT_PROTOCOL, - (u32)type, - 0, - (u32)additional_data_size - }; - return message; -} - -ByteBuffer SpiceAgent::AnnounceCapabilities::make_buffer(bool request, Vector const& capabilities) -{ - size_t required_size = sizeof(ChunkHeader) + sizeof(Message) + sizeof(AnnounceCapabilities); - auto buffer = ByteBuffer::create_uninitialized(required_size).release_value_but_fixme_should_propagate_errors(); // FIXME: Handle possible OOM situation. - u8* data = buffer.data(); - - auto* message = initialize_headers(data, sizeof(AnnounceCapabilities), MessageType::AnnounceCapabilities); - - auto* announce_message = new (message->data) AnnounceCapabilities { - request, - {} - }; - - for (auto& cap : capabilities) { - announce_message->caps[0] |= (1 << (u32)cap); - } - - return buffer; -} - -ByteBuffer SpiceAgent::ClipboardGrab::make_buffer(Vector const& types) -{ - VERIFY(types.size() > 0); - size_t variable_data_size = sizeof(u32) * types.size(); - size_t required_size = sizeof(ChunkHeader) + sizeof(Message) + variable_data_size; - auto buffer = ByteBuffer::create_uninitialized(required_size).release_value_but_fixme_should_propagate_errors(); // FIXME: Handle possible OOM situation. - u8* data = buffer.data(); - - auto* message = initialize_headers(data, variable_data_size, MessageType::ClipboardGrab); - - auto* grab_message = new (message->data) ClipboardGrab {}; - - for (auto i = 0u; i < types.size(); i++) { - grab_message->types[i] = (u32)types[i]; - } - - return buffer; -} - -ByteBuffer SpiceAgent::Clipboard::make_buffer(ClipboardType type, ReadonlyBytes contents) -{ - size_t data_size = sizeof(Clipboard) + contents.size(); - size_t required_size = sizeof(ChunkHeader) + sizeof(Message) + data_size; - auto buffer = ByteBuffer::create_uninitialized(required_size).release_value_but_fixme_should_propagate_errors(); // FIXME: Handle possible OOM situation. - u8* data = buffer.data(); - - auto* message = initialize_headers(data, data_size, MessageType::Clipboard); - - auto* clipboard_message = new (message->data) Clipboard { - .type = (u32)type - }; - - memcpy(clipboard_message->data, contents.data(), contents.size()); - - return buffer; -} - -ByteBuffer SpiceAgent::ClipboardRequest::make_buffer(ClipboardType type) -{ - size_t data_size = sizeof(ClipboardRequest); - size_t required_size = sizeof(ChunkHeader) + sizeof(Message) + data_size; - auto buffer = ByteBuffer::create_uninitialized(required_size).release_value_but_fixme_should_propagate_errors(); // FIXME: Handle possible OOM situation. - u8* data = buffer.data(); - - auto* message = initialize_headers(data, data_size, MessageType::ClipboardRequest); - new (message->data) ClipboardRequest { - .type = (u32)type - }; + auto size = TRY(m_spice_device->read_value()); + auto buffer = TRY(ByteBuffer::create_uninitialized(size)); + TRY(m_spice_device->read_until_filled(buffer)); return buffer; } +}; diff --git a/Userland/Services/SpiceAgent/SpiceAgent.h b/Userland/Services/SpiceAgent/SpiceAgent.h index 5849447a14..c9bbe1d265 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.h +++ b/Userland/Services/SpiceAgent/SpiceAgent.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Kyle Pereira + * Copyright (c) 2023, Caoimhe Byrne * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,123 +8,65 @@ #pragma once #include "ConnectionToClipboardServer.h" -#include +#include "Message.h" +#include "MessageHeader.h" #include #include +namespace SpiceAgent { + class SpiceAgent { public: - SpiceAgent(int fd, ConnectionToClipboardServer&); - - static constexpr u32 AGENT_PROTOCOL = 1; - enum class Port { + // Indicates where the message has come from. + enum class Port : u32 { Client = 1, + + // There are currently no messages which are meant for the server, so all messages sent by the agent (us) with this port are discarded. Server }; - struct [[gnu::packed]] ChunkHeader { - u32 port {}; - u32 size {}; - }; + static ErrorOr> create(StringView device_path); + SpiceAgent(NonnullOwnPtr spice_device, ConnectionToClipboardServer& clipboard_connection, Vector const& capabilities); - struct [[gnu::packed]] Message { - u32 protocol; - u32 type; - u64 opaque; - u32 size; - u8 data[]; - }; + ErrorOr start(); - enum class MessageType { - MouseState = 1, // server -> client - MonitorsConfig, // client -> agent|server - Reply, // agent -> client - Clipboard, // both directions - DisplayConfig, // client -> agent - AnnounceCapabilities, // both directions - ClipboardGrab, // both directions - ClipboardRequest, // both directions - ClipboardRelease, // both directions - FileTransferStart, - FileTransferStatus, - FileTransferData, - Disconnected, - MaxClipboard, - VolumeSync, - GraphicsDeviceInfo, - }; + template + ErrorOr send_message(T message) + { + // Attempt to write the message's data to a stream. + auto message_stream = AK::AllocatingMemoryStream(); + TRY(message.write_to_stream(message_stream)); - enum class Capability { - MouseState = 0, - MonitorsConfig, - Reply, - Clipboard, - DisplayConfig, - ClipboardByDemand, - ClipboardSelection, - SparseMonitorsConfig, - GuestLineEndLF, - GuestLineEndCRLF, - MaxClipboard, - AudioVolumeSync, - MonitorsConfigPosition, - FileTransferDisabled, - FileTransferDetailedErrors, - GraphicsCardInfo, - ClipboardNoReleaseOnRegrab, - ClipboardGrabSerial, - __End, - }; + // Create a header to be sent. + auto header_stream = AK::AllocatingMemoryStream(); + auto header = MessageHeader(message.type(), message_stream.used_buffer_size()); + TRY(header_stream.write_value(header)); - enum class ClipboardType { - None = 0, - Text, - PNG, - BMP, - TIFF, - JPEG, - FileList, - __Count - }; + // Currently, there are no messages from the agent which are meant for the server. + // So, all messages sent by the agent with a port of Port::Server get dropped silently. + TRY(m_spice_device->write_value(Port::Client)); - constexpr static size_t CAPABILITIES_SIZE = ((size_t)Capability::__End + 31) / 32; + // The length of the subsequent data. + auto length = header_stream.used_buffer_size() + message_stream.used_buffer_size(); + TRY(m_spice_device->write_value(length)); - struct [[gnu::packed]] AnnounceCapabilities { - u32 request; - u32 caps[CAPABILITIES_SIZE]; + // The message's header. + TRY(m_spice_device->write_until_depleted(TRY(header_stream.read_until_eof()))); - static ByteBuffer make_buffer(bool request, Vector const& capabilities); - }; + // The message content. + TRY(m_spice_device->write_until_depleted(TRY(message_stream.read_until_eof()))); - struct [[gnu::packed]] ClipboardGrab { - u32 types[0]; - - static ByteBuffer make_buffer(Vector const&); - }; - - struct [[gnu::packed]] Clipboard { - u32 type; - u8 data[]; - - static ByteBuffer make_buffer(ClipboardType, ReadonlyBytes); - }; - - struct [[gnu::packed]] ClipboardRequest { - u32 type; - - static ByteBuffer make_buffer(ClipboardType); - }; + return {}; + } private: - int m_fd { -1 }; - RefPtr m_notifier; + NonnullOwnPtr m_spice_device; ConnectionToClipboardServer& m_clipboard_connection; + Vector m_capabilities; + + RefPtr m_notifier; ErrorOr on_message_received(); - - void send_message(ByteBuffer const& buffer); - 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(DeprecatedString const& mime); + ErrorOr read_message_buffer(); }; +} diff --git a/Userland/Services/SpiceAgent/main.cpp b/Userland/Services/SpiceAgent/main.cpp index 89843e19ef..dafe7a657a 100644 --- a/Userland/Services/SpiceAgent/main.cpp +++ b/Userland/Services/SpiceAgent/main.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Kyle Pereira + * Copyright (c) 2023, Caoimhe Byrne * * SPDX-License-Identifier: BSD-2-Clause */ @@ -16,15 +17,16 @@ ErrorOr serenity_main(Main::Arguments) { Core::EventLoop loop; - TRY(Core::System::pledge("unix rpath wpath stdio sendfd recvfd")); - TRY(Core::System::unveil(SPICE_DEVICE, "rw"sv)); + // 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)); - int serial_port_fd = TRY(Core::System::open(SPICE_DEVICE, O_RDWR)); - - auto conn = TRY(ConnectionToClipboardServer::try_create()); - auto agent = SpiceAgent(serial_port_fd, conn); + auto agent = TRY(SpiceAgent::SpiceAgent::create(SPICE_DEVICE)); + TRY(agent->start()); return loop.exec(); }