diff --git a/CMakeLists.txt b/CMakeLists.txt index 80483c4f..3cdae161 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,7 @@ protocolNew("protocols/wlr-virtual-pointer-unstable-v1.xml" "wlr-virtual-pointer protocolNew("protocols/input-method-unstable-v2.xml" "input-method-unstable-v2" true) protocolNew("protocols/wlr-output-management-unstable-v1.xml" "wlr-output-management-unstable-v1" true) protocolNew("protocols/kde-server-decoration.xml" "kde-server-decoration" true) +protocolNew("protocols/wlr-data-control-unstable-v1.xml" "wlr-data-control-unstable-v1" true) protocolNew("subprojects/hyprland-protocols/protocols/hyprland-focus-grab-v1.xml" "hyprland-focus-grab-v1" true) protocolNew("protocols/wlr-layer-shell-unstable-v1.xml" "wlr-layer-shell-unstable-v1" true) protocolNew("staging/tearing-control/tearing-control-v1.xml" "tearing-control-v1" false) diff --git a/protocols/meson.build b/protocols/meson.build index 4b7aa200..adedbf8e 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -42,6 +42,7 @@ new_protocols = [ ['wlr-output-management-unstable-v1.xml'], ['kde-server-decoration.xml'], ['wlr-layer-shell-unstable-v1.xml'], + ['wlr-data-control-unstable-v1.xml'], [hl_protocol_dir, 'protocols/hyprland-focus-grab-v1.xml'], [wl_protocol_dir, 'staging/tearing-control/tearing-control-v1.xml'], [wl_protocol_dir, 'staging/fractional-scale/fractional-scale-v1.xml'], diff --git a/protocols/wlr-data-control-unstable-v1.xml b/protocols/wlr-data-control-unstable-v1.xml new file mode 100644 index 00000000..75e8671b --- /dev/null +++ b/protocols/wlr-data-control-unstable-v1.xml @@ -0,0 +1,278 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + This protocol allows a privileged client to control data devices. In + particular, the client will be able to manage the current selection and take + the role of a clipboard manager. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-seat data device + controls. + + + + + Create a new data source. + + + + + + + Create a data device that can be used to manage a seat's selection. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This interface allows a client to manage a seat's selection. + + When the seat is destroyed, this object becomes inert. + + + + + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. + + + + + + + Destroys the data device object. + + + + + + The data_offer event introduces a new wlr_data_control_offer object, + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. + + + + + + + The selection event is sent out to notify the client of a new + wlr_data_control_offer for the selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client when a new + selection is set. The wlr_data_control_offer is valid until a new + wlr_data_control_offer or NULL is received. The client must destroy the + previous selection wlr_data_control_offer, if any, upon receiving this + event. + + The first selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This data control object is no longer valid and should be destroyed by + the client. + + + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + + + + + + The wlr_data_control_source object is the source side of a + wlr_data_control_offer. It is created by the source client in a data + transfer and provides a way to describe the offered data and a way to + respond to requests to transfer the data. + + + + + + + + + This request adds a MIME type to the set of MIME types advertised to + targets. Can be called several times to offer multiple types. + + Calling this after wlr_data_control_device.set_selection is a protocol + error. + + + + + + + Destroys the data source object. + + + + + + Request for data from the client. Send the data as the specified MIME + type over the passed file descriptor, then close it. + + + + + + + + This data source is no longer valid. The data source has been replaced + by another data source. + + The client should clean up and destroy this data source. + + + + + + + A wlr_data_control_offer represents a piece of data offered for transfer + by another client (the source client). The offer describes the different + MIME types that the data can be converted to and provides the mechanism + for transferring the data directly from the source client. + + + + + To transfer the offered data, the client issues this request and + indicates the MIME type it wants to receive. The transfer happens + through the passed file descriptor (typically created with the pipe + system call). The source client writes the data in the MIME type + representation requested and then closes the file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + then closes its end, at which point the transfer is complete. + + This request may happen multiple times for different MIME types. + + + + + + + + Destroys the data offer object. + + + + + + Sent immediately after creating the wlr_data_control_offer object. + One event per offered MIME type. + + + + + diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 8167103f..4b03263b 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -29,6 +29,7 @@ #include "../protocols/LayerShell.hpp" #include "../protocols/PresentationTime.hpp" #include "../protocols/XDGShell.hpp" +#include "../protocols/DataDeviceWlr.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" @@ -69,6 +70,7 @@ CProtocolManager::CProtocolManager() { PROTO::layerShell = std::make_unique(&zwlr_layer_shell_v1_interface, 5, "LayerShell"); PROTO::presentation = std::make_unique(&wp_presentation_interface, 1, "Presentation"); PROTO::xdgShell = std::make_unique(&xdg_wm_base_interface, 6, "XDGShell"); + PROTO::dataWlr = std::make_unique(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr"); // Old protocol implementations. // TODO: rewrite them to use hyprwayland-scanner. diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp index 76840bd3..a8505610 100644 --- a/src/managers/SeatManager.cpp +++ b/src/managers/SeatManager.cpp @@ -1,6 +1,7 @@ #include "SeatManager.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" +#include "../protocols/DataDeviceWlr.hpp" #include "../Compositor.hpp" #include "../devices/IKeyboard.hpp" #include @@ -446,6 +447,7 @@ void CSeatManager::setCurrentSelection(SP source) { if (source) { selection.destroySelection = source->events.destroy.registerListener([this](std::any d) { setCurrentSelection(nullptr); }); PROTO::data->setSelection(source); + PROTO::dataWlr->setSelection(source); } } diff --git a/src/protocols/DataDeviceWlr.cpp b/src/protocols/DataDeviceWlr.cpp new file mode 100644 index 00000000..a518b0ae --- /dev/null +++ b/src/protocols/DataDeviceWlr.cpp @@ -0,0 +1,310 @@ +#include "DataDeviceWlr.hpp" +#include +#include "../managers/SeatManager.hpp" +#include "core/Seat.hpp" + +#define LOGM PROTO::dataWlr->protoLog + +CWLRDataOffer::CWLRDataOffer(SP resource_, SP source_) : source(source_), resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CZwlrDataControlOfferV1* r) { PROTO::dataWlr->destroyResource(this); }); + resource->setOnDestroy([this](CZwlrDataControlOfferV1* r) { PROTO::dataWlr->destroyResource(this); }); + + resource->setReceive([this](CZwlrDataControlOfferV1* r, const char* mime, int32_t fd) { + if (!source) { + LOGM(WARN, "Possible bug: Receive on an offer w/o a source"); + close(fd); + return; + } + + if (dead) { + LOGM(WARN, "Possible bug: Receive on an offer that's dead"); + close(fd); + return; + } + + LOGM(LOG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)source.get()); + + source->send(mime, fd); + }); +} + +bool CWLRDataOffer::good() { + return resource->resource(); +} + +void CWLRDataOffer::sendData() { + if (!source) + return; + + for (auto& m : source->mimes()) { + resource->sendOffer(m.c_str()); + } +} + +CWLRDataSource::CWLRDataSource(SP resource_, SP device_) : device(device_), resource(resource_) { + if (!good()) + return; + + resource->setData(this); + + resource->setDestroy([this](CZwlrDataControlSourceV1* r) { + events.destroy.emit(); + PROTO::dataWlr->destroyResource(this); + }); + resource->setOnDestroy([this](CZwlrDataControlSourceV1* r) { + events.destroy.emit(); + PROTO::dataWlr->destroyResource(this); + }); + + resource->setOffer([this](CZwlrDataControlSourceV1* r, const char* mime) { mimeTypes.push_back(mime); }); +} + +CWLRDataSource::~CWLRDataSource() { + events.destroy.emit(); +} + +SP CWLRDataSource::fromResource(wl_resource* res) { + auto data = (CWLRDataSource*)(((CZwlrDataControlSourceV1*)wl_resource_get_user_data(res))->data()); + return data ? data->self.lock() : nullptr; +} + +bool CWLRDataSource::good() { + return resource->resource(); +} + +std::vector CWLRDataSource::mimes() { + return mimeTypes; +} + +void CWLRDataSource::send(const std::string& mime, uint32_t fd) { + if (std::find(mimeTypes.begin(), mimeTypes.end(), mime) == mimeTypes.end()) { + LOGM(ERR, "Compositor/App bug: CWLRDataSource::sendAskSend with non-existent mime"); + close(fd); + return; + } + + resource->sendSend(mime.c_str(), fd); + close(fd); +} + +void CWLRDataSource::accepted(const std::string& mime) { + if (std::find(mimeTypes.begin(), mimeTypes.end(), mime) == mimeTypes.end()) + LOGM(ERR, "Compositor/App bug: CWLRDataSource::sendAccepted with non-existent mime"); + + // wlr has no accepted +} + +void CWLRDataSource::cancelled() { + resource->sendCancelled(); +} + +void CWLRDataSource::error(uint32_t code, const std::string& msg) { + resource->error(code, msg); +} + +CWLRDataDevice::CWLRDataDevice(SP resource_) : resource(resource_) { + if (!good()) + return; + + pClient = resource->client(); + + resource->setDestroy([this](CZwlrDataControlDeviceV1* r) { PROTO::dataWlr->destroyResource(this); }); + resource->setOnDestroy([this](CZwlrDataControlDeviceV1* r) { PROTO::dataWlr->destroyResource(this); }); + + resource->setSetSelection([this](CZwlrDataControlDeviceV1* r, wl_resource* sourceR) { + auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer{}; + if (!source) { + LOGM(LOG, "wlr reset selection received"); + g_pSeatManager->setCurrentSelection(nullptr); + return; + } + + if (source && source->used()) + LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + + source->markUsed(); + + LOGM(LOG, "wlr manager requests selection to {:x}", (uintptr_t)source.get()); + g_pSeatManager->setCurrentSelection(source); + }); + + resource->setSetPrimarySelection([this](CZwlrDataControlDeviceV1* r, wl_resource* sourceR) { + auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer{}; + if (!source) { + LOGM(LOG, "wlr reset primary selection received"); + g_pSeatManager->setCurrentSelection(nullptr); + return; + } + + if (source && source->used()) + LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + + source->markUsed(); + + LOGM(LOG, "wlr manager requests primary selection to {:x}", (uintptr_t)source.get()); + g_pSeatManager->setCurrentSelection(source); + }); +} + +bool CWLRDataDevice::good() { + return resource->resource(); +} + +wl_client* CWLRDataDevice::client() { + return pClient; +} + +void CWLRDataDevice::sendInitialSelections() { + PROTO::dataWlr->sendSelectionToDevice(self.lock(), g_pSeatManager->selection.currentSelection.lock(), false); + PROTO::dataWlr->sendSelectionToDevice(self.lock(), g_pSeatManager->selection.currentPrimarySelection.lock(), true); +} + +void CWLRDataDevice::sendDataOffer(SP offer) { + resource->sendDataOffer(offer->resource.get()); +} + +void CWLRDataDevice::sendSelection(SP selection) { + resource->sendSelection(selection->resource.get()); +} + +CWLRDataControlManagerResource::CWLRDataControlManagerResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CZwlrDataControlManagerV1* r) { PROTO::dataWlr->destroyResource(this); }); + resource->setOnDestroy([this](CZwlrDataControlManagerV1* r) { PROTO::dataWlr->destroyResource(this); }); + + resource->setGetDataDevice([this](CZwlrDataControlManagerV1* r, uint32_t id, wl_resource* seat) { + const auto RESOURCE = PROTO::dataWlr->m_vDevices.emplace_back(makeShared(makeShared(r->client(), r->version(), id))); + + if (!RESOURCE->good()) { + r->noMemory(); + PROTO::dataWlr->m_vDevices.pop_back(); + return; + } + + RESOURCE->self = RESOURCE; + device = RESOURCE; + + for (auto& s : sources) { + if (!s) + continue; + s->device = RESOURCE; + } + + RESOURCE->sendInitialSelections(); + + LOGM(LOG, "New wlr data device bound at {:x}", (uintptr_t)RESOURCE.get()); + }); + + resource->setCreateDataSource([this](CZwlrDataControlManagerV1* r, uint32_t id) { + std::erase_if(sources, [](const auto& e) { return e.expired(); }); + + const auto RESOURCE = + PROTO::dataWlr->m_vSources.emplace_back(makeShared(makeShared(r->client(), r->version(), id), device.lock())); + + if (!RESOURCE->good()) { + r->noMemory(); + PROTO::dataWlr->m_vSources.pop_back(); + return; + } + + if (!device) + LOGM(WARN, "New data source before a device was created"); + + RESOURCE->self = RESOURCE; + + sources.push_back(RESOURCE); + + LOGM(LOG, "New wlr data source bound at {:x}", (uintptr_t)RESOURCE.get()); + }); +} + +bool CWLRDataControlManagerResource::good() { + return resource->resource(); +} + +CDataDeviceWLRProtocol::CDataDeviceWLRProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CDataDeviceWLRProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } + + LOGM(LOG, "New wlr_data_control_manager at {:x}", (uintptr_t)RESOURCE.get()); +} + +void CDataDeviceWLRProtocol::destroyResource(CWLRDataControlManagerResource* resource) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == resource; }); +} + +void CDataDeviceWLRProtocol::destroyResource(CWLRDataSource* resource) { + std::erase_if(m_vSources, [&](const auto& other) { return other.get() == resource; }); +} + +void CDataDeviceWLRProtocol::destroyResource(CWLRDataDevice* resource) { + std::erase_if(m_vDevices, [&](const auto& other) { return other.get() == resource; }); +} + +void CDataDeviceWLRProtocol::destroyResource(CWLRDataOffer* resource) { + std::erase_if(m_vOffers, [&](const auto& other) { return other.get() == resource; }); +} + +void CDataDeviceWLRProtocol::sendSelectionToDevice(SP dev, SP sel) { + if (!sel) + return; + + const auto OFFER = m_vOffers.emplace_back(makeShared(makeShared(dev->resource->client(), dev->resource->version(), 0), sel)); + + if (!OFFER->good()) { + dev->resource->noMemory(); + m_vOffers.pop_back(); + return; + } + + LOGM(LOG, "New offer {:x} for data source {:x}", (uintptr_t)OFFER.get(), (uintptr_t)sel.get()); + + dev->sendDataOffer(OFFER); + OFFER->sendData(); + dev->sendSelection(OFFER); +} + +void CDataDeviceWLRProtocol::setSelection(SP source) { + for (auto& o : m_vOffers) { + if (o->source && o->source->hasDnd()) + continue; + o->dead = true; + } + + if (!source) { + LOGM(LOG, "resetting selection"); + + for (auto& d : m_vDevices) { + sendSelectionToDevice(d, nullptr); + } + + return; + } + + LOGM(LOG, "New selection for data source {:x}", (uintptr_t)source.get()); + + for (auto& d : m_vDevices) { + sendSelectionToDevice(d, source); + } +} + +SP CDataDeviceWLRProtocol::dataDeviceForClient(wl_client* c) { + auto it = std::find_if(m_vDevices.begin(), m_vDevices.end(), [c](const auto& e) { return e->client() == c; }); + if (it == m_vDevices.end()) + return nullptr; + return *it; +} diff --git a/src/protocols/DataDeviceWlr.hpp b/src/protocols/DataDeviceWlr.hpp new file mode 100644 index 00000000..0b703347 --- /dev/null +++ b/src/protocols/DataDeviceWlr.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "wlr-data-control-unstable-v1.hpp" +#include "types/DataDevice.hpp" + +class CWLRDataControlManagerResource; +class CWLRDataSource; +class CWLRDataDevice; +class CWLRDataOffer; + +class CWLRDataOffer { + public: + CWLRDataOffer(SP resource_, SP source); + + bool good(); + void sendData(); + + bool dead = false; + + WP source; + + private: + SP resource; + + friend class CWLRDataDevice; +}; + +class CWLRDataSource : public IDataSource { + public: + CWLRDataSource(SP resource_, SP device_); + ~CWLRDataSource(); + static SP fromResource(wl_resource*); + + bool good(); + + virtual std::vector mimes(); + virtual void send(const std::string& mime, uint32_t fd); + virtual void accepted(const std::string& mime); + virtual void cancelled(); + virtual void error(uint32_t code, const std::string& msg); + + std::vector mimeTypes; + WP self; + WP device; + + private: + SP resource; +}; + +class CWLRDataDevice { + public: + CWLRDataDevice(SP resource_); + + bool good(); + wl_client* client(); + void sendInitialSelections(); + + void sendDataOffer(SP offer); + void sendSelection(SP selection); + + WP self; + + private: + SP resource; + wl_client* pClient = nullptr; + + friend class CDataDeviceWLRProtocol; +}; + +class CWLRDataControlManagerResource { + public: + CWLRDataControlManagerResource(SP resource_); + + bool good(); + + WP device; + std::vector> sources; + + private: + SP resource; +}; + +class CDataDeviceWLRProtocol : public IWaylandProtocol { + public: + CDataDeviceWLRProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + private: + void destroyResource(CWLRDataControlManagerResource* resource); + void destroyResource(CWLRDataSource* resource); + void destroyResource(CWLRDataDevice* resource); + void destroyResource(CWLRDataOffer* resource); + + // + std::vector> m_vManagers; + std::vector> m_vSources; + std::vector> m_vDevices; + std::vector> m_vOffers; + + // + void setSelection(SP source); + void sendSelectionToDevice(SP dev, SP sel); + + // + SP dataDeviceForClient(wl_client*); + + friend class CSeatManager; + friend class CWLRDataControlManagerResource; + friend class CWLRDataSource; + friend class CWLRDataDevice; + friend class CWLRDataOffer; +}; + +namespace PROTO { + inline UP dataWlr; +}; diff --git a/src/protocols/core/DataDevice.cpp b/src/protocols/core/DataDevice.cpp index d332a0be..2b9503b1 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -297,6 +297,8 @@ CWLDataDeviceManagerResource::CWLDataDeviceManagerResource(SPself = RESOURCE; + sources.push_back(RESOURCE); + LOGM(LOG, "New data source bound at {:x}", (uintptr_t)RESOURCE.get()); });