1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 12:17:44 +00:00

SpiceAgent: Implement setting the user's clipboard contents for text

We also now use GUI::Clipboard for setting the clipboard contents,
instead of using a custom connection to the Clipboard server.
This commit is contained in:
Caoimhe 2023-05-13 14:20:41 +01:00 committed by Andreas Kling
parent 9c4538a9a8
commit 3b6d63f723
8 changed files with 96 additions and 124 deletions

View file

@ -7,9 +7,8 @@ set(SOURCES
main.cpp
Message.cpp
SpiceAgent.cpp
ConnectionToClipboardServer.cpp
)
serenity_bin(SpiceAgent)
target_link_libraries(SpiceAgent PRIVATE LibGfx LibCore LibIPC LibMain)
target_link_libraries(SpiceAgent PRIVATE LibCore LibGfx LibGUI LibMain)
add_dependencies(SpiceAgent Clipboard)

View file

@ -1,75 +0,0 @@
/*
* Copyright (c) 2021, Kyle Pereira <kyle@xylepereira.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ConnectionToClipboardServer.h"
#include <AK/ByteBuffer.h>
#include <AK/Function.h>
#include <LibGfx/Bitmap.h>
// Copied from LibGUI/Clipboard.cpp
RefPtr<Gfx::Bitmap> ConnectionToClipboardServer::get_bitmap()
{
auto clipping = get_clipboard_data();
if (clipping.mime_type() != "image/x-serenityos")
return nullptr;
HashMap<DeprecatedString, DeprecatedString> const& metadata = clipping.metadata();
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 = clipping.data().data<void>();
auto clipping_bitmap_or_error = Gfx::Bitmap::create_wrapper((Gfx::BitmapFormat)format.value(), { (int)width.value(), (int)height.value() }, scale.value(), pitch.value(), const_cast<void*>(data));
if (clipping_bitmap_or_error.is_error())
return nullptr;
auto clipping_bitmap = clipping_bitmap_or_error.release_value();
auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { (int)width.value(), (int)height.value() }, scale.value());
if (bitmap_or_error.is_error())
return nullptr;
auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
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 ConnectionToClipboardServer::set_bitmap(Gfx::Bitmap const& bitmap)
{
HashMap<DeprecatedString, DeprecatedString> metadata;
metadata.set("width", DeprecatedString::number(bitmap.width()));
metadata.set("height", DeprecatedString::number(bitmap.height()));
metadata.set("scale", DeprecatedString::number(bitmap.scale()));
metadata.set("format", DeprecatedString::number((int)bitmap.format()));
metadata.set("pitch", DeprecatedString::number(bitmap.pitch()));
ReadonlyBytes data { bitmap.scanline(0), bitmap.size_in_bytes() };
auto buffer_or_error = Core::AnonymousBuffer::create_with_size(bitmap.size_in_bytes());
VERIFY(!buffer_or_error.is_error());
auto buffer = buffer_or_error.release_value();
memcpy(buffer.data<u8>(), data.data(), data.size());
this->async_set_clipboard_data(buffer, "image/x-serenityos", move(metadata));
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2021, Kyle Pereira <kyle@xylepereira.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <Clipboard/ClipboardClientEndpoint.h>
#include <Clipboard/ClipboardServerEndpoint.h>
#include <LibGfx/Bitmap.h>
#include <LibIPC/ConnectionToServer.h>
class ConnectionToClipboardServer final
: public IPC::ConnectionToServer<ClipboardClientEndpoint, ClipboardServerEndpoint>
, public ClipboardClientEndpoint {
IPC_CLIENT_CONNECTION(ConnectionToClipboardServer, "/tmp/session/%sid/portal/clipboard"sv)
public:
Function<void()> on_data_changed;
RefPtr<Gfx::Bitmap> get_bitmap();
void set_bitmap(Gfx::Bitmap const& bitmap);
private:
ConnectionToClipboardServer(NonnullOwnPtr<Core::LocalSocket> socket)
: IPC::ConnectionToServer<ClipboardClientEndpoint, ClipboardServerEndpoint>(*this, move(socket))
{
}
virtual void clipboard_data_changed(DeprecatedString const&) override
{
on_data_changed();
}
};

View file

@ -111,4 +111,34 @@ ErrorOr<String> ClipboardRequestMessage::debug_description()
return builder.to_string();
}
ErrorOr<ClipboardMessage> ClipboardMessage::read_from_stream(AK::Stream& stream)
{
auto value = TRY(stream.read_value<u32>());
if (value >= to_underlying(ClipboardDataType::__End)) {
return Error::from_string_literal("Unsupported clipboard type");
}
auto type = static_cast<ClipboardDataType>(value);
auto contents = TRY(stream.read_until_eof());
return ClipboardMessage(type, contents);
}
ErrorOr<void> ClipboardMessage::write_to_stream(AK::Stream& stream)
{
TRY(stream.write_value(data_type()));
TRY(stream.write_until_depleted(contents()));
return {};
}
ErrorOr<String> ClipboardMessage::debug_description()
{
StringBuilder builder;
TRY(builder.try_append("Clipboard { "sv));
TRY(builder.try_appendff("data_type = {}, ", data_type()));
TRY(builder.try_appendff("contents.size() = {}", contents().size()));
TRY(builder.try_append(" }"sv));
return builder.to_string();
}
}

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Format.h>
#include <AK/Forward.h>
#include <AK/Vector.h>
@ -150,6 +151,29 @@ private:
ClipboardDataType m_data_type;
};
// Used to send the clipboard's contents to the client/server.
class ClipboardMessage : public Message {
public:
ClipboardMessage(ClipboardDataType data_type, ByteBuffer const& contents)
: Message(Type::Clipboard)
, m_data_type(data_type)
, m_contents(contents)
{
}
static ErrorOr<ClipboardMessage> read_from_stream(AK::Stream& stream);
ErrorOr<void> write_to_stream(AK::Stream& stream);
ErrorOr<String> debug_description() override;
ClipboardDataType data_type() { return m_data_type; }
ByteBuffer const& contents() { return m_contents; }
private:
ClipboardDataType m_data_type;
ByteBuffer m_contents;
};
}
namespace AK {

View file

@ -6,21 +6,19 @@
*/
#include "SpiceAgent.h"
#include "ConnectionToClipboardServer.h"
#include <AK/Debug.h>
#include <LibGUI/Clipboard.h>
namespace SpiceAgent {
ErrorOr<NonnullOwnPtr<SpiceAgent>> SpiceAgent::create(StringView device_path)
{
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 });
return try_make<SpiceAgent>(move(device), Vector { Capability::ClipboardByDemand });
}
SpiceAgent::SpiceAgent(NonnullOwnPtr<Core::File> spice_device, ConnectionToClipboardServer& clipboard_connection, Vector<Capability> const& capabilities)
SpiceAgent::SpiceAgent(NonnullOwnPtr<Core::File> spice_device, Vector<Capability> const& capabilities)
: m_spice_device(move(spice_device))
, m_clipboard_connection(clipboard_connection)
, m_capabilities(capabilities)
{
m_notifier = Core::Notifier::construct(
@ -82,6 +80,16 @@ ErrorOr<void> SpiceAgent::on_message_received()
break;
}
case Message::Type::Clipboard: {
auto message = TRY(ClipboardMessage::read_from_stream(stream));
if (message.data_type() == ClipboardDataType::None)
break;
TRY(this->did_receive_clipboard_message(message));
break;
}
// We ignore certain messages to prevent it from clogging up the logs.
case Message::Type::MonitorsConfig:
dbgln_if(SPICE_AGENT_DEBUG, "Ignored message: {}", header);
@ -95,6 +103,21 @@ ErrorOr<void> SpiceAgent::on_message_received()
return {};
}
ErrorOr<void> SpiceAgent::did_receive_clipboard_message(ClipboardMessage& message)
{
dbgln_if(SPICE_AGENT_DEBUG, "Attempting to parse clipboard data of type: {}", message.data_type());
switch (message.data_type()) {
case ClipboardDataType::Text: {
// The default mime_type for set_data is `text/plain`.
GUI::Clipboard::the().set_data(message.contents());
return {};
}
default:
return Error::from_string_literal("Unsupported clipboard data type!");
}
}
ErrorOr<ByteBuffer> SpiceAgent::read_message_buffer()
{
auto port = TRY(m_spice_device->read_value<Port>());

View file

@ -7,10 +7,11 @@
#pragma once
#include "ConnectionToClipboardServer.h"
#include "Message.h"
#include "MessageHeader.h"
#include <AK/MemoryStream.h>
#include <AK/Vector.h>
#include <LibCore/File.h>
#include <LibCore/Notifier.h>
namespace SpiceAgent {
@ -26,7 +27,7 @@ public:
};
static ErrorOr<NonnullOwnPtr<SpiceAgent>> create(StringView device_path);
SpiceAgent(NonnullOwnPtr<Core::File> spice_device, ConnectionToClipboardServer& clipboard_connection, Vector<Capability> const& capabilities);
SpiceAgent(NonnullOwnPtr<Core::File> spice_device, Vector<Capability> const& capabilities);
ErrorOr<void> start();
@ -61,11 +62,13 @@ public:
private:
NonnullOwnPtr<Core::File> m_spice_device;
ConnectionToClipboardServer& m_clipboard_connection;
Vector<Capability> m_capabilities;
RefPtr<Core::Notifier> m_notifier;
// Fired when we receive clipboard data from the spice server.
ErrorOr<void> did_receive_clipboard_message(ClipboardMessage& message);
ErrorOr<void> on_message_received();
ErrorOr<ByteBuffer> read_message_buffer();
};

View file

@ -7,26 +7,28 @@
#include "SpiceAgent.h"
#include <LibCore/System.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
#include <LibIPC/ConnectionToServer.h>
#include <LibMain/Main.h>
#include <fcntl.h>
static constexpr auto SPICE_DEVICE = "/dev/hvc0p1"sv;
ErrorOr<int> serenity_main(Main::Arguments)
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::EventLoop loop;
// We use the application to be able to easily write to the user's clipboard.
auto app = TRY(GUI::Application::create(arguments));
// 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));
auto agent = TRY(SpiceAgent::SpiceAgent::create(SPICE_DEVICE));
TRY(agent->start());
return loop.exec();
return app->exec();
}