mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 14:07:45 +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:
parent
9c4538a9a8
commit
3b6d63f723
8 changed files with 96 additions and 124 deletions
|
@ -7,9 +7,8 @@ set(SOURCES
|
||||||
main.cpp
|
main.cpp
|
||||||
Message.cpp
|
Message.cpp
|
||||||
SpiceAgent.cpp
|
SpiceAgent.cpp
|
||||||
ConnectionToClipboardServer.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_bin(SpiceAgent)
|
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)
|
add_dependencies(SpiceAgent Clipboard)
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -111,4 +111,34 @@ ErrorOr<String> ClipboardRequestMessage::debug_description()
|
||||||
return builder.to_string();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/ByteBuffer.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
#include <AK/Forward.h>
|
#include <AK/Forward.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
@ -150,6 +151,29 @@ private:
|
||||||
ClipboardDataType m_data_type;
|
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 {
|
namespace AK {
|
||||||
|
|
|
@ -6,21 +6,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "SpiceAgent.h"
|
#include "SpiceAgent.h"
|
||||||
#include "ConnectionToClipboardServer.h"
|
#include <AK/Debug.h>
|
||||||
|
#include <LibGUI/Clipboard.h>
|
||||||
|
|
||||||
namespace SpiceAgent {
|
namespace SpiceAgent {
|
||||||
|
|
||||||
ErrorOr<NonnullOwnPtr<SpiceAgent>> SpiceAgent::create(StringView device_path)
|
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 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), Vector { Capability::ClipboardByDemand });
|
||||||
|
|
||||||
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)
|
SpiceAgent::SpiceAgent(NonnullOwnPtr<Core::File> spice_device, Vector<Capability> const& capabilities)
|
||||||
: m_spice_device(move(spice_device))
|
: m_spice_device(move(spice_device))
|
||||||
, m_clipboard_connection(clipboard_connection)
|
|
||||||
, m_capabilities(capabilities)
|
, m_capabilities(capabilities)
|
||||||
{
|
{
|
||||||
m_notifier = Core::Notifier::construct(
|
m_notifier = Core::Notifier::construct(
|
||||||
|
@ -82,6 +80,16 @@ ErrorOr<void> SpiceAgent::on_message_received()
|
||||||
break;
|
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.
|
// We ignore certain messages to prevent it from clogging up the logs.
|
||||||
case Message::Type::MonitorsConfig:
|
case Message::Type::MonitorsConfig:
|
||||||
dbgln_if(SPICE_AGENT_DEBUG, "Ignored message: {}", header);
|
dbgln_if(SPICE_AGENT_DEBUG, "Ignored message: {}", header);
|
||||||
|
@ -95,6 +103,21 @@ ErrorOr<void> SpiceAgent::on_message_received()
|
||||||
return {};
|
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()
|
ErrorOr<ByteBuffer> SpiceAgent::read_message_buffer()
|
||||||
{
|
{
|
||||||
auto port = TRY(m_spice_device->read_value<Port>());
|
auto port = TRY(m_spice_device->read_value<Port>());
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ConnectionToClipboardServer.h"
|
|
||||||
#include "Message.h"
|
#include "Message.h"
|
||||||
#include "MessageHeader.h"
|
#include "MessageHeader.h"
|
||||||
|
#include <AK/MemoryStream.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
#include <LibCore/Notifier.h>
|
#include <LibCore/Notifier.h>
|
||||||
|
|
||||||
namespace SpiceAgent {
|
namespace SpiceAgent {
|
||||||
|
@ -26,7 +27,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
static ErrorOr<NonnullOwnPtr<SpiceAgent>> create(StringView device_path);
|
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();
|
ErrorOr<void> start();
|
||||||
|
|
||||||
|
@ -61,11 +62,13 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullOwnPtr<Core::File> m_spice_device;
|
NonnullOwnPtr<Core::File> m_spice_device;
|
||||||
ConnectionToClipboardServer& m_clipboard_connection;
|
|
||||||
Vector<Capability> m_capabilities;
|
Vector<Capability> m_capabilities;
|
||||||
|
|
||||||
RefPtr<Core::Notifier> m_notifier;
|
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<void> on_message_received();
|
||||||
ErrorOr<ByteBuffer> read_message_buffer();
|
ErrorOr<ByteBuffer> read_message_buffer();
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,26 +7,28 @@
|
||||||
|
|
||||||
#include "SpiceAgent.h"
|
#include "SpiceAgent.h"
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
|
#include <LibGUI/Application.h>
|
||||||
|
#include <LibGUI/Clipboard.h>
|
||||||
#include <LibIPC/ConnectionToServer.h>
|
#include <LibIPC/ConnectionToServer.h>
|
||||||
#include <LibMain/Main.h>
|
#include <LibMain/Main.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
static constexpr auto SPICE_DEVICE = "/dev/hvc0p1"sv;
|
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:
|
// 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).
|
// 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`.
|
// 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::pledge("unix rpath wpath stdio sendfd recvfd cpath"));
|
||||||
TRY(Core::System::unveil(SPICE_DEVICE, "rwc"sv));
|
TRY(Core::System::unveil(SPICE_DEVICE, "rwc"sv));
|
||||||
TRY(Core::System::unveil("/tmp/session/%sid/portal/clipboard", "rw"));
|
|
||||||
TRY(Core::System::unveil(nullptr, nullptr));
|
TRY(Core::System::unveil(nullptr, nullptr));
|
||||||
|
|
||||||
auto agent = TRY(SpiceAgent::SpiceAgent::create(SPICE_DEVICE));
|
auto agent = TRY(SpiceAgent::SpiceAgent::create(SPICE_DEVICE));
|
||||||
TRY(agent->start());
|
TRY(agent->start());
|
||||||
|
|
||||||
return loop.exec();
|
return app->exec();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue