mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 00:08:11 +00:00
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
12 changed files with 250 additions and 2 deletions
|
@ -8,6 +8,7 @@
|
||||||
#include <LibGUI/GTextEditor.h>
|
#include <LibGUI/GTextEditor.h>
|
||||||
#include <LibGUI/GAction.h>
|
#include <LibGUI/GAction.h>
|
||||||
#include <LibGUI/GFontDatabase.h>
|
#include <LibGUI/GFontDatabase.h>
|
||||||
|
#include <LibGUI/GClipboard.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdio.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&) {
|
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&) {
|
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>();
|
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 \
|
GVariant.o \
|
||||||
GShortcut.o \
|
GShortcut.o \
|
||||||
GTextEditor.o \
|
GTextEditor.o \
|
||||||
|
GClipboard.o \
|
||||||
GWindow.o
|
GWindow.o
|
||||||
|
|
||||||
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)
|
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)
|
||||||
|
|
|
@ -18,6 +18,7 @@ WINDOWSERVER_OBJS = \
|
||||||
WSMenuItem.o \
|
WSMenuItem.o \
|
||||||
WSClientConnection.o \
|
WSClientConnection.o \
|
||||||
WSWindowSwitcher.o \
|
WSWindowSwitcher.o \
|
||||||
|
WSClipboard.o \
|
||||||
main.o
|
main.o
|
||||||
|
|
||||||
APP = WindowServer
|
APP = WindowServer
|
||||||
|
|
|
@ -85,6 +85,8 @@ struct WSAPI_ServerMessage {
|
||||||
DidGetWindowRect,
|
DidGetWindowRect,
|
||||||
DidGetWindowBackingStore,
|
DidGetWindowBackingStore,
|
||||||
Greeting,
|
Greeting,
|
||||||
|
DidGetClipboardContents,
|
||||||
|
DidSetClipboardContents,
|
||||||
};
|
};
|
||||||
Type type { Invalid };
|
Type type { Invalid };
|
||||||
int window_id { -1 };
|
int window_id { -1 };
|
||||||
|
@ -128,6 +130,10 @@ struct WSAPI_ServerMessage {
|
||||||
int shared_buffer_id;
|
int shared_buffer_id;
|
||||||
bool has_alpha_channel;
|
bool has_alpha_channel;
|
||||||
} backing;
|
} backing;
|
||||||
|
struct {
|
||||||
|
int shared_buffer_id;
|
||||||
|
int contents_size;
|
||||||
|
} clipboard;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -154,6 +160,8 @@ struct WSAPI_ClientMessage {
|
||||||
SetGlobalCursorTracking,
|
SetGlobalCursorTracking,
|
||||||
SetWindowOpacity,
|
SetWindowOpacity,
|
||||||
SetWindowBackingStore,
|
SetWindowBackingStore,
|
||||||
|
GetClipboardContents,
|
||||||
|
SetClipboardContents,
|
||||||
};
|
};
|
||||||
Type type { Invalid };
|
Type type { Invalid };
|
||||||
int window_id { -1 };
|
int window_id { -1 };
|
||||||
|
@ -183,6 +191,10 @@ struct WSAPI_ClientMessage {
|
||||||
int shared_buffer_id;
|
int shared_buffer_id;
|
||||||
bool has_alpha_channel;
|
bool has_alpha_channel;
|
||||||
} backing;
|
} backing;
|
||||||
|
struct {
|
||||||
|
int shared_buffer_id;
|
||||||
|
int contents_size;
|
||||||
|
} clipboard;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <WindowServer/WSWindow.h>
|
#include <WindowServer/WSWindow.h>
|
||||||
#include <WindowServer/WSWindowManager.h>
|
#include <WindowServer/WSWindowManager.h>
|
||||||
#include <WindowServer/WSAPITypes.h>
|
#include <WindowServer/WSAPITypes.h>
|
||||||
|
#include <WindowServer/WSClipboard.h>
|
||||||
#include <SharedBuffer.h>
|
#include <SharedBuffer.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -308,6 +309,43 @@ void WSClientConnection::handle_request(WSAPIGetWindowRectRequest& request)
|
||||||
post_message(response);
|
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)
|
void WSClientConnection::handle_request(WSAPICreateWindowRequest& request)
|
||||||
{
|
{
|
||||||
int window_id = m_next_window_id++;
|
int window_id = m_next_window_id++;
|
||||||
|
@ -457,6 +495,10 @@ void WSClientConnection::on_request(WSAPIClientRequest& request)
|
||||||
return handle_request(static_cast<WSAPISetWindowRectRequest&>(request));
|
return handle_request(static_cast<WSAPISetWindowRectRequest&>(request));
|
||||||
case WSMessage::APIGetWindowRectRequest:
|
case WSMessage::APIGetWindowRectRequest:
|
||||||
return handle_request(static_cast<WSAPIGetWindowRectRequest&>(request));
|
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:
|
case WSMessage::APICreateWindowRequest:
|
||||||
return handle_request(static_cast<WSAPICreateWindowRequest&>(request));
|
return handle_request(static_cast<WSAPICreateWindowRequest&>(request));
|
||||||
case WSMessage::APIDestroyWindowRequest:
|
case WSMessage::APIDestroyWindowRequest:
|
||||||
|
|
|
@ -46,6 +46,8 @@ private:
|
||||||
void handle_request(WSAPIGetWindowTitleRequest&);
|
void handle_request(WSAPIGetWindowTitleRequest&);
|
||||||
void handle_request(WSAPISetWindowRectRequest&);
|
void handle_request(WSAPISetWindowRectRequest&);
|
||||||
void handle_request(WSAPIGetWindowRectRequest&);
|
void handle_request(WSAPIGetWindowRectRequest&);
|
||||||
|
void handle_request(WSAPISetClipboardContentsRequest&);
|
||||||
|
void handle_request(WSAPIGetClipboardContentsRequest&);
|
||||||
void handle_request(WSAPICreateWindowRequest&);
|
void handle_request(WSAPICreateWindowRequest&);
|
||||||
void handle_request(WSAPIDestroyWindowRequest&);
|
void handle_request(WSAPIDestroyWindowRequest&);
|
||||||
void handle_request(WSAPIInvalidateRectRequest&);
|
void handle_request(WSAPIInvalidateRectRequest&);
|
||||||
|
@ -69,4 +71,6 @@ private:
|
||||||
int m_next_menubar_id { 10000 };
|
int m_next_menubar_id { 10000 };
|
||||||
int m_next_menu_id { 20000 };
|
int m_next_menu_id { 20000 };
|
||||||
int m_next_window_id { 1982 };
|
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,
|
APISetGlobalCursorTrackingRequest,
|
||||||
APISetWindowOpacityRequest,
|
APISetWindowOpacityRequest,
|
||||||
APISetWindowBackingStoreRequest,
|
APISetWindowBackingStoreRequest,
|
||||||
|
APISetClipboardContentsRequest,
|
||||||
|
APIGetClipboardContentsRequest,
|
||||||
__End_API_Client_Requests,
|
__End_API_Client_Requests,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -262,6 +264,40 @@ private:
|
||||||
int m_window_id { 0 };
|
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 {
|
class WSAPISetWindowOpacityRequest final : public WSAPIClientRequest {
|
||||||
public:
|
public:
|
||||||
explicit WSAPISetWindowOpacityRequest(int client_id, int window_id, float opacity)
|
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:
|
case WSAPI_ClientMessage::Type::GetWindowRect:
|
||||||
post_message(client, make<WSAPIGetWindowRectRequest>(client_id, message.window_id));
|
post_message(client, make<WSAPIGetWindowRectRequest>(client_id, message.window_id));
|
||||||
break;
|
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:
|
case WSAPI_ClientMessage::Type::InvalidateRect:
|
||||||
post_message(client, make<WSAPIInvalidateRectRequest>(client_id, message.window_id, message.window.rect));
|
post_message(client, make<WSAPIInvalidateRectRequest>(client_id, message.window_id, message.window.rect));
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue