From c543ee5c5bcfbb7dd31ab70f3a64e91933485275 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 14 Sep 2019 09:19:05 +0200 Subject: [PATCH] WindowServer+LibGUI: Store a "data type" with the clipboard content This will allow us to distinguish between different types of data stored on the clipboard. --- Libraries/LibGUI/GClipboard.cpp | 20 ++++++++++-- Libraries/LibGUI/GClipboard.h | 20 ++++++++++-- Libraries/LibGUI/GEventLoop.cpp | 6 ++++ Servers/WindowServer/WSAPITypes.h | 1 + Servers/WindowServer/WSClientConnection.cpp | 34 +++++++++++++++++---- Servers/WindowServer/WSClientConnection.h | 1 + Servers/WindowServer/WSClipboard.cpp | 8 +++-- Servers/WindowServer/WSClipboard.h | 7 ++++- Servers/WindowServer/WSEvent.h | 5 ++- Servers/WindowServer/WSEventLoop.cpp | 7 +++++ 10 files changed, 94 insertions(+), 15 deletions(-) diff --git a/Libraries/LibGUI/GClipboard.cpp b/Libraries/LibGUI/GClipboard.cpp index 1c3debce646..cfb8d29c23f 100644 --- a/Libraries/LibGUI/GClipboard.cpp +++ b/Libraries/LibGUI/GClipboard.cpp @@ -15,7 +15,7 @@ GClipboard::GClipboard() { } -String GClipboard::data() const +GClipboard::DataAndType GClipboard::data_and_type() const { WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::GetClipboardContents; @@ -31,10 +31,12 @@ String GClipboard::data() const 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); + auto data = String((const char*)shared_buffer->data(), response.clipboard.contents_size); + auto type = String(response.text, response.text_length); + return { data, type }; } -void GClipboard::set_data(const StringView& data) +void GClipboard::set_data(const StringView& data, const String& type) { WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::SetClipboardContents; @@ -51,6 +53,18 @@ void GClipboard::set_data(const StringView& data) shared_buffer->share_with(GWindowServerConnection::the().server_pid()); request.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id(); request.clipboard.contents_size = data.length(); + + ASSERT(type.length() < (ssize_t)sizeof(request.text)); + if (!type.is_null()) + strcpy(request.text, type.characters()); + request.text_length = type.length(); + auto response = GWindowServerConnection::the().sync_request(request, WSAPI_ServerMessage::Type::DidSetClipboardContents); ASSERT(response.clipboard.shared_buffer_id == shared_buffer->shared_buffer_id()); } + +void GClipboard::did_receive_clipboard_contents_changed(Badge, const String& data_type) +{ + if (on_content_change) + on_content_change(data_type); +} diff --git a/Libraries/LibGUI/GClipboard.h b/Libraries/LibGUI/GClipboard.h index dec491b8dd9..054f830d560 100644 --- a/Libraries/LibGUI/GClipboard.h +++ b/Libraries/LibGUI/GClipboard.h @@ -1,13 +1,29 @@ #pragma once +#include +#include #include +class GWindowServerConnection; + class GClipboard { public: static GClipboard& the(); - String data() const; - void set_data(const StringView&); + String data() const { return data_and_type().data; } + String type() const { return data_and_type().type; } + void set_data(const StringView&, const String& data_type = "text"); + + struct DataAndType { + String data; + String type; + }; + + DataAndType data_and_type() const; + + void did_receive_clipboard_contents_changed(Badge, const String& data_type); + + Function on_content_change; private: GClipboard(); diff --git a/Libraries/LibGUI/GEventLoop.cpp b/Libraries/LibGUI/GEventLoop.cpp index a27e8ef5b40..2d3c5e3d5d3 100644 --- a/Libraries/LibGUI/GEventLoop.cpp +++ b/Libraries/LibGUI/GEventLoop.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -244,6 +245,11 @@ void GWindowServerConnection::postprocess_bundles(Vector& continue; } + if (event.type == WSAPI_ServerMessage::Type::ClipboardContentsChanged) { + GClipboard::the().did_receive_clipboard_contents_changed({}, String(event.text, event.text_length)); + continue; + } + if (event.type == WSAPI_ServerMessage::Error) { dbgprintf("GEventLoop got error message from server\n"); dbgprintf(" - error message: %s\n", String(event.text, event.text_length).characters()); diff --git a/Servers/WindowServer/WSAPITypes.h b/Servers/WindowServer/WSAPITypes.h index fcc4c66c29f..501ba20ba0a 100644 --- a/Servers/WindowServer/WSAPITypes.h +++ b/Servers/WindowServer/WSAPITypes.h @@ -112,6 +112,7 @@ struct WSAPI_ServerMessage { DidSetResolution, DidSetWindowHasAlphaChannel, ScreenRectChanged, + ClipboardContentsChanged, __Begin_WM_Events__, WM_WindowRemoved, diff --git a/Servers/WindowServer/WSClientConnection.cpp b/Servers/WindowServer/WSClientConnection.cpp index b2105ec9309..c583c304997 100644 --- a/Servers/WindowServer/WSClientConnection.cpp +++ b/Servers/WindowServer/WSClientConnection.cpp @@ -87,6 +87,19 @@ void WSClientConnection::notify_about_new_screen_rect(const Rect& rect) post_message(message); } +void WSClientConnection::notify_about_clipboard_contents_changed() +{ + auto& clipboard = WSClipboard::the(); + WSAPI_ServerMessage message; + message.type = WSAPI_ServerMessage::Type::ClipboardContentsChanged; + message.clipboard.shared_buffer_id = -1; + message.clipboard.contents_size = -1; + ASSERT(clipboard.data_type().length() < (ssize_t)sizeof(message.text)); + strcpy(message.text, clipboard.data_type().characters()); + message.text_length = clipboard.data_type().length(); + post_message(message); +} + void WSClientConnection::event(CEvent& event) { if (static_cast(event).is_client_request()) { @@ -247,7 +260,11 @@ bool WSClientConnection::handle_message(const WSAPI_ClientMessage& message, cons CEventLoop::current().post_event(*this, make(client_id(), message.window_id)); break; case WSAPI_ClientMessage::Type::SetClipboardContents: - CEventLoop::current().post_event(*this, make(client_id(), message.clipboard.shared_buffer_id, message.clipboard.contents_size)); + if (message.text_length > (int)sizeof(message.text)) { + did_misbehave(); + return false; + } + CEventLoop::current().post_event(*this, make(client_id(), message.clipboard.shared_buffer_id, message.clipboard.contents_size, String(message.text, message.text_length))); break; case WSAPI_ClientMessage::Type::GetClipboardContents: CEventLoop::current().post_event(*this, make(client_id())); @@ -661,7 +678,7 @@ void WSClientConnection::handle_request(const WSAPISetClipboardContentsRequest& post_error("WSAPISetClipboardContentsRequest: Bad shared buffer ID"); return; } - WSClipboard::the().set_data(*shared_buffer, request.size()); + WSClipboard::the().set_data(*shared_buffer, request.size(), request.data_type()); WSAPI_ServerMessage response; response.type = WSAPI_ServerMessage::Type::DidSetClipboardContents; response.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id(); @@ -670,26 +687,31 @@ void WSClientConnection::handle_request(const WSAPISetClipboardContentsRequest& void WSClientConnection::handle_request(const WSAPIGetClipboardContentsRequest&) { + auto& clipboard = WSClipboard::the(); WSAPI_ServerMessage response; response.type = WSAPI_ServerMessage::Type::DidGetClipboardContents; response.clipboard.shared_buffer_id = -1; response.clipboard.contents_size = 0; - if (WSClipboard::the().size()) { + if (clipboard.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.. - RefPtr shared_buffer = SharedBuffer::create_with_size(WSClipboard::the().size()); + RefPtr shared_buffer = SharedBuffer::create_with_size(clipboard.size()); ASSERT(shared_buffer); - memcpy(shared_buffer->data(), WSClipboard::the().data(), WSClipboard::the().size()); + memcpy(shared_buffer->data(), clipboard.data(), clipboard.size()); shared_buffer->seal(); shared_buffer->share_with(client_pid()); response.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id(); - response.clipboard.contents_size = WSClipboard::the().size(); + response.clipboard.contents_size = clipboard.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 ref the buffer on his side. m_last_sent_clipboard_content = move(shared_buffer); } + ASSERT(clipboard.data_type().length() < (ssize_t)sizeof(response.text)); + if (!clipboard.data_type().is_null()) + strcpy(response.text, clipboard.data_type().characters()); + response.text_length = clipboard.data_type().length(); post_message(response); } diff --git a/Servers/WindowServer/WSClientConnection.h b/Servers/WindowServer/WSClientConnection.h index bc2e12b22e4..65fc37de6a7 100644 --- a/Servers/WindowServer/WSClientConnection.h +++ b/Servers/WindowServer/WSClientConnection.h @@ -35,6 +35,7 @@ public: void for_each_window(Callback); void notify_about_new_screen_rect(const Rect&); + void notify_about_clipboard_contents_changed(); void post_paint_message(WSWindow&); WSMenu* find_menu_by_id(int menu_id) diff --git a/Servers/WindowServer/WSClipboard.cpp b/Servers/WindowServer/WSClipboard.cpp index 6ffd245d992..8e7239ec6ff 100644 --- a/Servers/WindowServer/WSClipboard.cpp +++ b/Servers/WindowServer/WSClipboard.cpp @@ -36,9 +36,13 @@ void WSClipboard::clear() m_contents_size = 0; } -void WSClipboard::set_data(NonnullRefPtr&& data, int contents_size) +void WSClipboard::set_data(NonnullRefPtr&& data, int contents_size, const String& data_type) { - dbgprintf("WSClipboard::set_data <- %p (%u bytes)\n", data->data(), contents_size); + dbg() << "WSClipboard::set_data <- [" << data_type << "] " << data->data() << " (" << contents_size << " bytes)"; m_shared_buffer = move(data); m_contents_size = contents_size; + m_data_type = data_type; + + if (on_content_change) + on_content_change(); } diff --git a/Servers/WindowServer/WSClipboard.h b/Servers/WindowServer/WSClipboard.h index 276da2ab1d3..0896c878a35 100644 --- a/Servers/WindowServer/WSClipboard.h +++ b/Servers/WindowServer/WSClipboard.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -13,15 +14,19 @@ public: return m_shared_buffer; } + const String& data_type() const { return m_data_type; } const u8* data() const; int size() const; void clear(); - void set_data(NonnullRefPtr&&, int contents_size); + void set_data(NonnullRefPtr&&, int contents_size, const String& data_type); + + Function on_content_change; private: WSClipboard(); + String m_data_type; RefPtr m_shared_buffer; int m_contents_size { 0 }; }; diff --git a/Servers/WindowServer/WSEvent.h b/Servers/WindowServer/WSEvent.h index 4391d3f3649..d829b432158 100644 --- a/Servers/WindowServer/WSEvent.h +++ b/Servers/WindowServer/WSEvent.h @@ -504,19 +504,22 @@ private: class WSAPISetClipboardContentsRequest final : public WSAPIClientRequest { public: - explicit WSAPISetClipboardContentsRequest(int client_id, int shared_buffer_id, int size) + explicit WSAPISetClipboardContentsRequest(int client_id, int shared_buffer_id, int size, const String& data_type) : WSAPIClientRequest(WSEvent::APISetClipboardContentsRequest, client_id) , m_shared_buffer_id(shared_buffer_id) , m_size(size) + , m_data_type(data_type) { } int shared_buffer_id() const { return m_shared_buffer_id; } int size() const { return m_size; } + const String& data_type() const { return m_data_type; } private: int m_shared_buffer_id { 0 }; int m_size { 0 }; + String m_data_type; }; class WSAPIGetClipboardContentsRequest final : public WSAPIClientRequest { diff --git a/Servers/WindowServer/WSEventLoop.cpp b/Servers/WindowServer/WSEventLoop.cpp index 94e6de21099..df6a2e1a73a 100644 --- a/Servers/WindowServer/WSEventLoop.cpp +++ b/Servers/WindowServer/WSEventLoop.cpp @@ -1,3 +1,4 @@ +#include "WSClipboard.h" #include #include #include @@ -47,6 +48,12 @@ WSEventLoop::WSEventLoop() m_mouse_notifier = make(m_mouse_fd, CNotifier::Read); m_mouse_notifier->on_ready_to_read = [this] { drain_mouse(); }; + + WSClipboard::the().on_content_change = [&] { + WSClientConnection::for_each_client([&](auto& client) { + client.notify_about_clipboard_contents_changed(); + }); + }; } WSEventLoop::~WSEventLoop()