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.
This commit is contained in:
Caoimhe 2023-05-13 13:08:23 +01:00 committed by Andreas Kling
parent fd4f00ee91
commit 79c73dd260
Notes: sideshowbarker 2024-07-16 20:05:14 +09:00
7 changed files with 320 additions and 370 deletions

View File

@ -5,6 +5,7 @@ serenity_component(
set(SOURCES
main.cpp
Message.cpp
SpiceAgent.cpp
ConnectionToClipboardServer.cpp
)

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Message.h"
#include <AK/MemoryStream.h>
#include <AK/Stream.h>
#include <AK/String.h>
namespace SpiceAgent {
ErrorOr<AnnounceCapabilitiesMessage> 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<u32>()) == 1;
if (is_requesting) {
return AnnounceCapabilitiesMessage(is_requesting);
}
return Error::from_string_literal("Unexpected non-requesting announce capabilities message received!");
}
ErrorOr<void> AnnounceCapabilitiesMessage::write_to_stream(AK::Stream& stream)
{
TRY(stream.write_value<u32>(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<String> 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();
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <AK/Vector.h>
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<String> 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<Capability> capabilities = {})
: Message(Type::AnnounceCapabilities)
, m_is_request(is_request)
, m_capabilities(move(capabilities))
{
}
static ErrorOr<AnnounceCapabilitiesMessage> read_from_stream(AK::Stream& stream);
ErrorOr<void> write_to_stream(AK::Stream& stream);
ErrorOr<String> debug_description() override;
bool is_request() const& { return m_is_request; }
Vector<Capability> const& capabilities() { return m_capabilities; }
private:
bool m_is_request { false };
Vector<Capability> m_capabilities;
};
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Message.h"
#include <AK/ByteBuffer.h>
#include <AK/Format.h>
#include <AK/Forward.h>
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<SpiceAgent::MessageHeader> : public AK::GenericTraits<SpiceAgent::MessageHeader> {
static constexpr bool is_trivially_serializable() { return true; }
};
namespace AK {
template<>
struct Formatter<SpiceAgent::MessageHeader> : Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, SpiceAgent::MessageHeader const& header)
{
return Formatter<FormatString>::format(builder,
"MessageHeader {{ protocol_version = {}, type = {}, opaque = {}, data_size = {} }}"sv,
header.protocol_version(), to_underlying(header.type()), header.opaque(), header.data_size());
}
};
}

View File

@ -1,310 +1,93 @@
/*
* Copyright (c) 2021, Kyle Pereira <kyle@xylepereira.me>
* Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "SpiceAgent.h"
#include "ConnectionToClipboardServer.h"
#include <AK/DeprecatedString.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/BMPLoader.h>
#include <LibGfx/ImageFormats/JPEGLoader.h>
#include <LibGfx/ImageFormats/PNGLoader.h>
#include <LibGfx/ImageFormats/PNGWriter.h>
#include <memory.h>
#include <unistd.h>
SpiceAgent::SpiceAgent(int fd, ConnectionToClipboardServer& connection)
: m_fd(fd)
, m_clipboard_connection(connection)
namespace SpiceAgent {
ErrorOr<NonnullOwnPtr<SpiceAgent>> 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<SpiceAgent>(move(device), clipboard_connection, Vector { Capability::ClipboardByDemand });
}
SpiceAgent::SpiceAgent(NonnullOwnPtr<Core::File> spice_device, ConnectionToClipboardServer& clipboard_connection, Vector<Capability> 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<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(DeprecatedString const& mime)
ErrorOr<void> 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<void> 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<Message*>(buffer.data());
switch (message->type) {
case (u32)MessageType::AnnounceCapabilities: {
auto* capabilities_message = reinterpret_cast<AnnounceCapabilities*>(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<ClipboardRequest*>(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<void>(), 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<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::JPEG:
found_type = type;
break;
default:
break;
}
}
}
if (found_type == ClipboardType::None)
auto header = TRY(stream.read_value<MessageHeader>());
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<Clipboard*>(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<void>(), data_buffer.data(), data_buffer.size());
m_clipboard_connection.async_set_clipboard_data(anon_buffer, "text/plain", {});
}
return {};
} else {
ErrorOr<Gfx::ImageFrameDescriptor> 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<ByteBuffer> 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<Port>());
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<Capability> 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<ClipboardType> 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<u32>());
auto buffer = TRY(ByteBuffer::create_uninitialized(size));
TRY(m_spice_device->read_until_filled(buffer));
return buffer;
}
};

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Kyle Pereira <kyle@xylepereira.me>
* Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,123 +8,65 @@
#pragma once
#include "ConnectionToClipboardServer.h"
#include <AK/ByteBuffer.h>
#include "Message.h"
#include "MessageHeader.h"
#include <AK/Vector.h>
#include <LibCore/Notifier.h>
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<NonnullOwnPtr<SpiceAgent>> create(StringView device_path);
SpiceAgent(NonnullOwnPtr<Core::File> spice_device, ConnectionToClipboardServer& clipboard_connection, Vector<Capability> const& capabilities);
struct [[gnu::packed]] Message {
u32 protocol;
u32 type;
u64 opaque;
u32 size;
u8 data[];
};
ErrorOr<void> 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<typename T>
ErrorOr<void> 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<u32>(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<Capability> 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<ClipboardType> 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<Core::Notifier> m_notifier;
NonnullOwnPtr<Core::File> m_spice_device;
ConnectionToClipboardServer& m_clipboard_connection;
Vector<Capability> m_capabilities;
RefPtr<Core::Notifier> m_notifier;
ErrorOr<void> 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<ClipboardType> mime_type_to_clipboard_type(DeprecatedString const& mime);
ErrorOr<ByteBuffer> read_message_buffer();
};
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Kyle Pereira <kyle@xylepereira.me>
* Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -16,15 +17,16 @@ ErrorOr<int> 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();
}