WindowServer+LibGUI: Add a server-side clipboard.

On the client side, use GClipboard's data() and set_data(String) to access
the global clipboard. :^)
This commit is contained in:
Andreas Kling 2019-03-08 13:27:19 +01:00
parent eda0866992
commit 6820f9e14f
Notes: sideshowbarker 2024-07-19 15:07:29 +09:00
12 changed files with 250 additions and 2 deletions

View file

@ -8,6 +8,7 @@
#include <LibGUI/GTextEditor.h>
#include <LibGUI/GAction.h>
#include <LibGUI/GFontDatabase.h>
#include <LibGUI/GClipboard.h>
#include <AK/StringBuilder.h>
#include <unistd.h>
#include <stdio.h>
@ -83,11 +84,14 @@ int main(int argc, char** argv)
});
auto copy_action = GAction::create("Copy", { Mod_Ctrl, Key_C }, GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/copyfile16.rgb", { 16, 16 }), [&] (const GAction&) {
printf("Copy: \"%s\"\n", text_editor->selected_text().characters());
auto selected_text = text_editor->selected_text();
printf("Copy: \"%s\"\n", selected_text.characters());
GClipboard::the().set_data(selected_text);
});
auto paste_action = GAction::create("Paste", { Mod_Ctrl, Key_V }, GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/paste16.rgb", { 16, 16 }), [&] (const GAction&) {
dbgprintf("FIXME: Implement Edit/Paste");
auto paste_text = GClipboard::the().data();
printf("Paste: \"%s\"\n", paste_text.characters());
});
auto menubar = make<GMenuBar>();

51
LibGUI/GClipboard.cpp Normal file
View file

@ -0,0 +1,51 @@
#include <LibGUI/GClipboard.h>
#include <LibGUI/GEventLoop.h>
#include <WindowServer/WSAPITypes.h>
#include <LibC/SharedBuffer.h>
GClipboard& GClipboard::the()
{
static GClipboard* s_the;
if (!s_the)
s_the = new GClipboard;
return *s_the;
}
GClipboard::GClipboard()
{
}
String GClipboard::data() const
{
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::GetClipboardContents;
auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidGetClipboardContents);
if (response.clipboard.shared_buffer_id < 0)
return { };
auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(response.clipboard.shared_buffer_id);
if (!shared_buffer) {
dbgprintf("GClipboard::data() failed to attach to the shared buffer\n");
return { };
}
if (response.clipboard.contents_size > shared_buffer->size()) {
dbgprintf("GClipboard::data() clipping contents size is greater than shared buffer size\n");
return { };
}
return String((const char*)shared_buffer->data(), response.clipboard.contents_size);
}
void GClipboard::set_data(const String& data)
{
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::SetClipboardContents;
auto shared_buffer = SharedBuffer::create(GEventLoop::main().server_pid(), data.length() + 1);
if (!shared_buffer) {
dbgprintf("GClipboard::set_data() failed to create a shared buffer\n");
return;
}
memcpy(shared_buffer->data(), data.characters(), data.length() + 1);
request.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id();
request.clipboard.contents_size = data.length();
auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidSetClipboardContents);
ASSERT(response.clipboard.shared_buffer_id == shared_buffer->shared_buffer_id());
}

14
LibGUI/GClipboard.h Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include <AK/AKString.h>
class GClipboard {
public:
static GClipboard& the();
String data() const;
void set_data(const String&);
private:
GClipboard();
};

View file

@ -33,6 +33,7 @@ LIBGUI_OBJS = \
GVariant.o \
GShortcut.o \
GTextEditor.o \
GClipboard.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)

View file

@ -18,6 +18,7 @@ WINDOWSERVER_OBJS = \
WSMenuItem.o \
WSClientConnection.o \
WSWindowSwitcher.o \
WSClipboard.o \
main.o
APP = WindowServer

View file

@ -85,6 +85,8 @@ struct WSAPI_ServerMessage {
DidGetWindowRect,
DidGetWindowBackingStore,
Greeting,
DidGetClipboardContents,
DidSetClipboardContents,
};
Type type { Invalid };
int window_id { -1 };
@ -128,6 +130,10 @@ struct WSAPI_ServerMessage {
int shared_buffer_id;
bool has_alpha_channel;
} backing;
struct {
int shared_buffer_id;
int contents_size;
} clipboard;
};
};
@ -154,6 +160,8 @@ struct WSAPI_ClientMessage {
SetGlobalCursorTracking,
SetWindowOpacity,
SetWindowBackingStore,
GetClipboardContents,
SetClipboardContents,
};
Type type { Invalid };
int window_id { -1 };
@ -183,6 +191,10 @@ struct WSAPI_ClientMessage {
int shared_buffer_id;
bool has_alpha_channel;
} backing;
struct {
int shared_buffer_id;
int contents_size;
} clipboard;
};
};

View file

@ -6,6 +6,7 @@
#include <WindowServer/WSWindow.h>
#include <WindowServer/WSWindowManager.h>
#include <WindowServer/WSAPITypes.h>
#include <WindowServer/WSClipboard.h>
#include <SharedBuffer.h>
#include <sys/ioctl.h>
#include <unistd.h>
@ -308,6 +309,43 @@ void WSClientConnection::handle_request(WSAPIGetWindowRectRequest& request)
post_message(response);
}
void WSClientConnection::handle_request(WSAPISetClipboardContentsRequest& request)
{
auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(request.shared_buffer_id());
if (!shared_buffer) {
post_error("Bad shared buffer ID");
return;
}
WSClipboard::the().set_data(*shared_buffer, request.size());
WSAPI_ServerMessage response;
response.type = WSAPI_ServerMessage::Type::DidSetClipboardContents;
response.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id();
post_message(response);
}
void WSClientConnection::handle_request(WSAPIGetClipboardContentsRequest&)
{
WSAPI_ServerMessage response;
response.type = WSAPI_ServerMessage::Type::DidGetClipboardContents;
response.clipboard.shared_buffer_id = -1;
response.clipboard.contents_size = 0;
if (WSClipboard::the().size()) {
// FIXME: Optimize case where an app is copy/pasting within itself.
// We can just reuse the SharedBuffer then, since it will have the same peer PID.
// It would be even nicer if a SharedBuffer could have an arbitrary number of clients..
RetainPtr<SharedBuffer> shared_buffer = SharedBuffer::create(m_pid, WSClipboard::the().size());
ASSERT(shared_buffer);
memcpy(shared_buffer->data(), WSClipboard::the().data(), WSClipboard::the().size());
response.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id();
response.clipboard.contents_size = WSClipboard::the().size();
// FIXME: This is a workaround for the fact that SharedBuffers will go away if neither side is retaining them.
// After we respond to GetClipboardContents, we have to wait for the client to retain the buffer on his side.
m_last_sent_clipboard_content = move(shared_buffer);
}
post_message(response);
}
void WSClientConnection::handle_request(WSAPICreateWindowRequest& request)
{
int window_id = m_next_window_id++;
@ -457,6 +495,10 @@ void WSClientConnection::on_request(WSAPIClientRequest& request)
return handle_request(static_cast<WSAPISetWindowRectRequest&>(request));
case WSMessage::APIGetWindowRectRequest:
return handle_request(static_cast<WSAPIGetWindowRectRequest&>(request));
case WSMessage::APISetClipboardContentsRequest:
return handle_request(static_cast<WSAPISetClipboardContentsRequest&>(request));
case WSMessage::APIGetClipboardContentsRequest:
return handle_request(static_cast<WSAPIGetClipboardContentsRequest&>(request));
case WSMessage::APICreateWindowRequest:
return handle_request(static_cast<WSAPICreateWindowRequest&>(request));
case WSMessage::APIDestroyWindowRequest:

View file

@ -46,6 +46,8 @@ private:
void handle_request(WSAPIGetWindowTitleRequest&);
void handle_request(WSAPISetWindowRectRequest&);
void handle_request(WSAPIGetWindowRectRequest&);
void handle_request(WSAPISetClipboardContentsRequest&);
void handle_request(WSAPIGetClipboardContentsRequest&);
void handle_request(WSAPICreateWindowRequest&);
void handle_request(WSAPIDestroyWindowRequest&);
void handle_request(WSAPIInvalidateRectRequest&);
@ -69,4 +71,6 @@ private:
int m_next_menubar_id { 10000 };
int m_next_menu_id { 20000 };
int m_next_window_id { 1982 };
RetainPtr<SharedBuffer> m_last_sent_clipboard_content;
};

View file

@ -0,0 +1,48 @@
#include <WindowServer/WSClipboard.h>
WSClipboard& WSClipboard::the()
{
static WSClipboard* s_the;
if (!s_the)
s_the = new WSClipboard;
return *s_the;
}
WSClipboard::WSClipboard()
{
}
WSClipboard::~WSClipboard()
{
}
void WSClipboard::on_message(WSMessage&)
{
}
const byte* WSClipboard::data() const
{
if (!m_shared_buffer)
return nullptr;
return (const byte*)m_shared_buffer->data();
}
int WSClipboard::size() const
{
if (!m_shared_buffer)
return 0;
return m_contents_size;
}
void WSClipboard::clear()
{
m_shared_buffer = nullptr;
m_contents_size = 0;
}
void WSClipboard::set_data(Retained<SharedBuffer>&& data, int contents_size)
{
dbgprintf("WSClipboard::set_data <- %p (%u bytes)\n", data->data(), contents_size);
m_shared_buffer = move(data);
m_contents_size = contents_size;
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <AK/AKString.h>
#include <WindowServer/WSMessageReceiver.h>
#include <SharedBuffer.h>
class WSClipboard final : public WSMessageReceiver {
public:
static WSClipboard& the();
virtual ~WSClipboard() override;
bool has_data() const
{
return m_shared_buffer;
}
const byte* data() const;
int size() const;
void clear();
void set_data(Retained<SharedBuffer>&&, int contents_size);
private:
WSClipboard();
virtual void on_message(WSMessage&) override;
RetainPtr<SharedBuffer> m_shared_buffer;
int m_contents_size { 0 };
};

View file

@ -45,6 +45,8 @@ public:
APISetGlobalCursorTrackingRequest,
APISetWindowOpacityRequest,
APISetWindowBackingStoreRequest,
APISetClipboardContentsRequest,
APIGetClipboardContentsRequest,
__End_API_Client_Requests,
};
@ -262,6 +264,40 @@ private:
int m_window_id { 0 };
};
class WSAPISetClipboardContentsRequest final : public WSAPIClientRequest {
public:
explicit WSAPISetClipboardContentsRequest(int client_id, int shared_buffer_id, int size)
: WSAPIClientRequest(WSMessage::APISetClipboardContentsRequest, client_id)
, m_client_id(client_id)
, m_shared_buffer_id(shared_buffer_id)
, m_size(size)
{
}
int client_id() const { return m_client_id; }
int shared_buffer_id() const { return m_shared_buffer_id; }
int size() const { return m_size; }
private:
int m_client_id { 0 };
int m_shared_buffer_id { 0 };
int m_size { 0 };
};
class WSAPIGetClipboardContentsRequest final : public WSAPIClientRequest {
public:
explicit WSAPIGetClipboardContentsRequest(int client_id)
: WSAPIClientRequest(WSMessage::APIGetClipboardContentsRequest, client_id)
, m_client_id(client_id)
{
}
int client_id() const { return m_client_id; }
private:
int m_client_id { 0 };
};
class WSAPISetWindowOpacityRequest final : public WSAPIClientRequest {
public:
explicit WSAPISetWindowOpacityRequest(int client_id, int window_id, float opacity)

View file

@ -300,6 +300,12 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess
case WSAPI_ClientMessage::Type::GetWindowRect:
post_message(client, make<WSAPIGetWindowRectRequest>(client_id, message.window_id));
break;
case WSAPI_ClientMessage::Type::SetClipboardContents:
post_message(client, make<WSAPISetClipboardContentsRequest>(client_id, message.clipboard.shared_buffer_id, message.clipboard.contents_size));
break;
case WSAPI_ClientMessage::Type::GetClipboardContents:
post_message(client, make<WSAPIGetClipboardContentsRequest>(client_id));
break;
case WSAPI_ClientMessage::Type::InvalidateRect:
post_message(client, make<WSAPIInvalidateRectRequest>(client_id, message.window_id, message.window.rect));
break;