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(); }