summaryrefslogtreecommitdiff
path: root/Userland/Services/SpiceAgent/SpiceAgent.cpp
diff options
context:
space:
mode:
authorx-yl <kylepereira@mail.com>2021-07-12 18:25:37 +0400
committerAndreas Kling <kling@serenityos.org>2021-07-14 12:33:07 +0200
commitd4bb6a1a1ee30dafb1f03b08390adc2e3716855b (patch)
tree3a6141470d532f4d168624326a3f090631850330 /Userland/Services/SpiceAgent/SpiceAgent.cpp
parent42c5df7256ff27815a496ea13e6cb5b0f57238ba (diff)
downloadserenity-d4bb6a1a1ee30dafb1f03b08390adc2e3716855b.zip
SpiceAgent: Add a new spice agent service :^)
A SPICE agent communicates with the host OS to provide nifty features like clipboard sharing :^) This patch implements only plain-text clipboard sharing. See: github.com/freedesktop/spice-protocol/blob/master/spice/vd_agent.h
Diffstat (limited to 'Userland/Services/SpiceAgent/SpiceAgent.cpp')
-rw-r--r--Userland/Services/SpiceAgent/SpiceAgent.cpp202
1 files changed, 202 insertions, 0 deletions
diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp
new file mode 100644
index 0000000000..082800d0d3
--- /dev/null
+++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2021, Kyle Pereira <kyle@xylepereira.me>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "SpiceAgent.h"
+#include "ClipboardServerConnection.h"
+#include <AK/String.h>
+#include <LibC/memory.h>
+#include <LibC/unistd.h>
+
+SpiceAgent::SpiceAgent(int fd, ClipboardServerConnection& connection)
+ : m_fd(fd)
+ , m_clipboard_connection(connection)
+{
+ m_notifier = Core::Notifier::construct(fd, Core::Notifier::Read);
+ m_notifier->on_ready_to_read = [this] {
+ on_message_received();
+ };
+ m_clipboard_connection.on_data_changed = [this] {
+ if (m_just_set_clip) {
+ m_just_set_clip = false;
+ return;
+ }
+ auto grab_buffer = ClipboardGrab::make_buffer({ ClipboardType::Text });
+ send_message(grab_buffer);
+ };
+ auto buffer = AnnounceCapabilities::make_buffer(true, { Capability::ClipboardByDemand });
+ send_message(buffer);
+}
+
+void SpiceAgent::on_message_received()
+{
+ ChunkHeader header {};
+ read_n(&header, sizeof(header));
+ auto buffer = 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 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);
+ send_message(clipboard_buffer);
+ break;
+ }
+ case (u32)MessageType::ClipboardGrab: {
+ auto request_buffer = ClipboardRequest::make_buffer(ClipboardType::Text);
+ send_message(request_buffer);
+ break;
+ }
+ case (u32)MessageType::Clipboard: {
+ auto* clipboard_message = reinterpret_cast<Clipboard*>(message->data);
+ auto data_buffer = ByteBuffer::create_uninitialized(message->size - sizeof(u32));
+
+ const auto 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;
+ 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", {});
+ break;
+ }
+ default:
+ dbgln("Unhandled message type {}", message->type);
+ }
+}
+
+void SpiceAgent::read_n(void* dest, size_t n)
+{
+ 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;
+ }
+ }
+}
+
+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, const Vector<Capability>& capabilities)
+{
+ size_t required_size = sizeof(ChunkHeader) + sizeof(Message) + sizeof(AnnounceCapabilities);
+ auto buffer = ByteBuffer::create_uninitialized(required_size);
+ 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(const Vector<ClipboardType>& 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);
+ 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);
+ 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);
+ u8* data = buffer.data();
+
+ auto* message = initialize_headers(data, data_size, MessageType::ClipboardRequest);
+ new (message->data) ClipboardRequest {
+ .type = (u32)type
+ };
+
+ return buffer;
+}