diff --git a/Applications/TextEditor/main.cpp b/Applications/TextEditor/main.cpp index 84b474cf04..9200794c22 100644 --- a/Applications/TextEditor/main.cpp +++ b/Applications/TextEditor/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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(); diff --git a/LibGUI/GClipboard.cpp b/LibGUI/GClipboard.cpp new file mode 100644 index 0000000000..d6698c8a35 --- /dev/null +++ b/LibGUI/GClipboard.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +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()); +} diff --git a/LibGUI/GClipboard.h b/LibGUI/GClipboard.h new file mode 100644 index 0000000000..fc8cb5c95f --- /dev/null +++ b/LibGUI/GClipboard.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class GClipboard { +public: + static GClipboard& the(); + + String data() const; + void set_data(const String&); + +private: + GClipboard(); +}; diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 9b1fd840e4..09acca6aad 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -33,6 +33,7 @@ LIBGUI_OBJS = \ GVariant.o \ GShortcut.o \ GTextEditor.o \ + GClipboard.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) diff --git a/WindowServer/Makefile b/WindowServer/Makefile index af30561588..13602d8edb 100644 --- a/WindowServer/Makefile +++ b/WindowServer/Makefile @@ -18,6 +18,7 @@ WINDOWSERVER_OBJS = \ WSMenuItem.o \ WSClientConnection.o \ WSWindowSwitcher.o \ + WSClipboard.o \ main.o APP = WindowServer diff --git a/WindowServer/WSAPITypes.h b/WindowServer/WSAPITypes.h index 1818e91155..535ce6b76a 100644 --- a/WindowServer/WSAPITypes.h +++ b/WindowServer/WSAPITypes.h @@ -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; }; }; diff --git a/WindowServer/WSClientConnection.cpp b/WindowServer/WSClientConnection.cpp index 082326df66..64327e80ba 100644 --- a/WindowServer/WSClientConnection.cpp +++ b/WindowServer/WSClientConnection.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -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 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(request)); case WSMessage::APIGetWindowRectRequest: return handle_request(static_cast(request)); + case WSMessage::APISetClipboardContentsRequest: + return handle_request(static_cast(request)); + case WSMessage::APIGetClipboardContentsRequest: + return handle_request(static_cast(request)); case WSMessage::APICreateWindowRequest: return handle_request(static_cast(request)); case WSMessage::APIDestroyWindowRequest: diff --git a/WindowServer/WSClientConnection.h b/WindowServer/WSClientConnection.h index 3e32c8f463..e23f0cc0e1 100644 --- a/WindowServer/WSClientConnection.h +++ b/WindowServer/WSClientConnection.h @@ -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 m_last_sent_clipboard_content; }; diff --git a/WindowServer/WSClipboard.cpp b/WindowServer/WSClipboard.cpp new file mode 100644 index 0000000000..1a7f8f2ea1 --- /dev/null +++ b/WindowServer/WSClipboard.cpp @@ -0,0 +1,48 @@ +#include + +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&& 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; +} diff --git a/WindowServer/WSClipboard.h b/WindowServer/WSClipboard.h new file mode 100644 index 0000000000..6c29bb8cd8 --- /dev/null +++ b/WindowServer/WSClipboard.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +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&&, int contents_size); + +private: + WSClipboard(); + virtual void on_message(WSMessage&) override; + + RetainPtr m_shared_buffer; + int m_contents_size { 0 }; +}; diff --git a/WindowServer/WSMessage.h b/WindowServer/WSMessage.h index 0dd4dd910b..98d16acb36 100644 --- a/WindowServer/WSMessage.h +++ b/WindowServer/WSMessage.h @@ -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) diff --git a/WindowServer/WSMessageLoop.cpp b/WindowServer/WSMessageLoop.cpp index bc240f0b63..a3fe8df808 100644 --- a/WindowServer/WSMessageLoop.cpp +++ b/WindowServer/WSMessageLoop.cpp @@ -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(client_id, message.window_id)); break; + case WSAPI_ClientMessage::Type::SetClipboardContents: + post_message(client, make(client_id, message.clipboard.shared_buffer_id, message.clipboard.contents_size)); + break; + case WSAPI_ClientMessage::Type::GetClipboardContents: + post_message(client, make(client_id)); + break; case WSAPI_ClientMessage::Type::InvalidateRect: post_message(client, make(client_id, message.window_id, message.window.rect)); break;