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:
parent
eda0866992
commit
6820f9e14f
Notes:
sideshowbarker
2024-07-19 15:07:29 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/6820f9e14f2
12 changed files with 250 additions and 2 deletions
|
@ -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
51
LibGUI/GClipboard.cpp
Normal 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
14
LibGUI/GClipboard.h
Normal 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();
|
||||
};
|
|
@ -33,6 +33,7 @@ LIBGUI_OBJS = \
|
|||
GVariant.o \
|
||||
GShortcut.o \
|
||||
GTextEditor.o \
|
||||
GClipboard.o \
|
||||
GWindow.o
|
||||
|
||||
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)
|
||||
|
|
|
@ -18,6 +18,7 @@ WINDOWSERVER_OBJS = \
|
|||
WSMenuItem.o \
|
||||
WSClientConnection.o \
|
||||
WSWindowSwitcher.o \
|
||||
WSClipboard.o \
|
||||
main.o
|
||||
|
||||
APP = WindowServer
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
48
WindowServer/WSClipboard.cpp
Normal file
48
WindowServer/WSClipboard.cpp
Normal 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;
|
||||
}
|
29
WindowServer/WSClipboard.h
Normal file
29
WindowServer/WSClipboard.h
Normal 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 };
|
||||
};
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue