瀏覽代碼

WindowServer+LibGUI: Add a server-side clipboard.

On the client side, use GClipboard's data() and set_data(String) to access
the global clipboard. :^)
Andreas Kling 6 年之前
父節點
當前提交
6820f9e14f

+ 6 - 2
Applications/TextEditor/main.cpp

@@ -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 - 0
LibGUI/GClipboard.cpp

@@ -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 - 0
LibGUI/GClipboard.h

@@ -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();
+};

+ 1 - 0
LibGUI/Makefile

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

+ 1 - 0
WindowServer/Makefile

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

+ 12 - 0
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;
     };
 };
 

+ 42 - 0
WindowServer/WSClientConnection.cpp

@@ -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:

+ 4 - 0
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<SharedBuffer> m_last_sent_clipboard_content;
 };

+ 48 - 0
WindowServer/WSClipboard.cpp

@@ -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 - 0
WindowServer/WSClipboard.h

@@ -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 };
+};

+ 36 - 0
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)

+ 6 - 0
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<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;