aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorVaxry <[email protected]>2024-05-13 21:47:59 +0100
committerVaxry <[email protected]>2024-05-14 23:13:35 +0100
commit94c20a186372aace78b188842848b873eb3ebbd7 (patch)
treeb7bc6aa3de054c2234d0db0836d720717b7354d6
parent3eeaea5be9324121678774761c0226fe98bf7e5b (diff)
downloadHyprland-94c20a186372aace78b188842848b873eb3ebbd7.tar.gz
Hyprland-94c20a186372aace78b188842848b873eb3ebbd7.zip
primary-selection: move to hyprland impl
-rw-r--r--CMakeLists.txt1
-rw-r--r--protocols/meson.build1
-rw-r--r--src/config/ConfigManager.cpp1
-rw-r--r--src/managers/ProtocolManager.cpp2
-rw-r--r--src/managers/SeatManager.cpp26
-rw-r--r--src/managers/SeatManager.hpp3
-rw-r--r--src/protocols/DataDeviceWlr.cpp36
-rw-r--r--src/protocols/DataDeviceWlr.hpp8
-rw-r--r--src/protocols/PrimarySelection.cpp338
-rw-r--r--src/protocols/PrimarySelection.hpp127
10 files changed, 529 insertions, 14 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3cdae161..9897185f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -293,6 +293,7 @@ protocolNew("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock
protocolNew("stable/tablet/tablet-v2.xml" "tablet-v2" false)
protocolNew("stable/presentation-time/presentation-time.xml" "presentation-time" false)
protocolNew("stable/xdg-shell/xdg-shell.xml" "xdg-shell" false)
+protocolNew("unstable/primary-selection/primary-selection-unstable-v1.xml" "primary-selection-unstable-v1" false)
protocolWayland()
diff --git a/protocols/meson.build b/protocols/meson.build
index d583c466..6b0b4d18 100644
--- a/protocols/meson.build
+++ b/protocols/meson.build
@@ -62,6 +62,7 @@ new_protocols = [
[wl_protocol_dir, 'stable/tablet/tablet-v2.xml'],
[wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'],
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
+ [wl_protocol_dir, 'unstable/primary-selection/primary-selection-unstable-v1.xml'],
]
wl_protos_src = []
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index 15d5ae81..bec651b2 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -346,6 +346,7 @@ CConfigManager::CConfigManager() {
m_pConfig->addConfigValue("misc:background_color", Hyprlang::INT{0xff111111});
m_pConfig->addConfigValue("misc:new_window_takes_over_fullscreen", Hyprlang::INT{0});
m_pConfig->addConfigValue("misc:initial_workspace_tracking", Hyprlang::INT{1});
+ m_pConfig->addConfigValue("misc:middle_click_paste", Hyprlang::INT{1});
m_pConfig->addConfigValue("group:insert_after_current", Hyprlang::INT{1});
m_pConfig->addConfigValue("group:focus_removed_window", Hyprlang::INT{1});
diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp
index 4b03263b..c43e4c56 100644
--- a/src/managers/ProtocolManager.cpp
+++ b/src/managers/ProtocolManager.cpp
@@ -30,6 +30,7 @@
#include "../protocols/PresentationTime.hpp"
#include "../protocols/XDGShell.hpp"
#include "../protocols/DataDeviceWlr.hpp"
+#include "../protocols/PrimarySelection.hpp"
#include "../protocols/core/Seat.hpp"
#include "../protocols/core/DataDevice.hpp"
@@ -71,6 +72,7 @@ CProtocolManager::CProtocolManager() {
PROTO::presentation = std::make_unique<CPresentationProtocol>(&wp_presentation_interface, 1, "Presentation");
PROTO::xdgShell = std::make_unique<CXDGShellProtocol>(&xdg_wm_base_interface, 6, "XDGShell");
PROTO::dataWlr = std::make_unique<CDataDeviceWLRProtocol>(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr");
+ PROTO::primarySelection = std::make_unique<CPrimarySelectionProtocol>(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection");
// Old protocol implementations.
// TODO: rewrite them to use hyprwayland-scanner.
diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp
index a8505610..3e2d595f 100644
--- a/src/managers/SeatManager.cpp
+++ b/src/managers/SeatManager.cpp
@@ -2,6 +2,7 @@
#include "../protocols/core/Seat.hpp"
#include "../protocols/core/DataDevice.hpp"
#include "../protocols/DataDeviceWlr.hpp"
+#include "../protocols/PrimarySelection.hpp"
#include "../Compositor.hpp"
#include "../devices/IKeyboard.hpp"
#include <algorithm>
@@ -447,7 +448,30 @@ void CSeatManager::setCurrentSelection(SP<IDataSource> source) {
if (source) {
selection.destroySelection = source->events.destroy.registerListener([this](std::any d) { setCurrentSelection(nullptr); });
PROTO::data->setSelection(source);
- PROTO::dataWlr->setSelection(source);
+ PROTO::dataWlr->setSelection(source, false);
+ }
+}
+
+void CSeatManager::setCurrentPrimarySelection(SP<IDataSource> source) {
+ if (source == selection.currentPrimarySelection) {
+ Debug::log(WARN, "[seat] duplicated setCurrentPrimarySelection?");
+ return;
+ }
+
+ selection.destroyPrimarySelection.reset();
+
+ if (selection.currentPrimarySelection)
+ selection.currentPrimarySelection->cancelled();
+
+ if (!source)
+ PROTO::primarySelection->setSelection(nullptr);
+
+ selection.currentPrimarySelection = source;
+
+ if (source) {
+ selection.destroyPrimarySelection = source->events.destroy.registerListener([this](std::any d) { setCurrentPrimarySelection(nullptr); });
+ PROTO::primarySelection->setSelection(source);
+ PROTO::dataWlr->setSelection(source, true);
}
}
diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp
index 81ca663d..f4efda70 100644
--- a/src/managers/SeatManager.hpp
+++ b/src/managers/SeatManager.hpp
@@ -109,9 +109,12 @@ class CSeatManager {
struct {
WP<IDataSource> currentSelection;
CHyprSignalListener destroySelection;
+ WP<IDataSource> currentPrimarySelection;
+ CHyprSignalListener destroyPrimarySelection;
} selection;
void setCurrentSelection(SP<IDataSource> source);
+ void setCurrentPrimarySelection(SP<IDataSource> source);
// do not write to directly, use set...
WP<IPointer> mouse;
diff --git a/src/protocols/DataDeviceWlr.cpp b/src/protocols/DataDeviceWlr.cpp
index a518b0ae..c039d3b4 100644
--- a/src/protocols/DataDeviceWlr.cpp
+++ b/src/protocols/DataDeviceWlr.cpp
@@ -145,7 +145,7 @@ CWLRDataDevice::CWLRDataDevice(SP<CZwlrDataControlDeviceV1> resource_) : resourc
source->markUsed();
LOGM(LOG, "wlr manager requests primary selection to {:x}", (uintptr_t)source.get());
- g_pSeatManager->setCurrentSelection(source);
+ g_pSeatManager->setCurrentPrimarySelection(source);
});
}
@@ -170,6 +170,10 @@ void CWLRDataDevice::sendSelection(SP<CWLRDataOffer> selection) {
resource->sendSelection(selection->resource.get());
}
+void CWLRDataDevice::sendPrimarySelection(SP<CWLRDataOffer> selection) {
+ resource->sendPrimarySelection(selection->resource.get());
+}
+
CWLRDataControlManagerResource::CWLRDataControlManagerResource(SP<CZwlrDataControlManagerV1> resource_) : resource(resource_) {
if (!good())
return;
@@ -259,9 +263,14 @@ void CDataDeviceWLRProtocol::destroyResource(CWLRDataOffer* resource) {
std::erase_if(m_vOffers, [&](const auto& other) { return other.get() == resource; });
}
-void CDataDeviceWLRProtocol::sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<IDataSource> sel) {
- if (!sel)
+void CDataDeviceWLRProtocol::sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<IDataSource> sel, bool primary) {
+ if (!sel) {
+ if (primary)
+ dev->resource->sendPrimarySelectionRaw(nullptr);
+ else
+ dev->resource->sendSelectionRaw(nullptr);
return;
+ }
const auto OFFER = m_vOffers.emplace_back(makeShared<CWLRDataOffer>(makeShared<CZwlrDataControlOfferV1>(dev->resource->client(), dev->resource->version(), 0), sel));
@@ -271,34 +280,41 @@ void CDataDeviceWLRProtocol::sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<ID
return;
}
- LOGM(LOG, "New offer {:x} for data source {:x}", (uintptr_t)OFFER.get(), (uintptr_t)sel.get());
+ OFFER->primary = primary;
+
+ LOGM(LOG, "New {}offer {:x} for data source {:x}", primary ? "primary " : " ", (uintptr_t)OFFER.get(), (uintptr_t)sel.get());
dev->sendDataOffer(OFFER);
OFFER->sendData();
- dev->sendSelection(OFFER);
+ if (primary)
+ dev->sendPrimarySelection(OFFER);
+ else
+ dev->sendSelection(OFFER);
}
-void CDataDeviceWLRProtocol::setSelection(SP<IDataSource> source) {
+void CDataDeviceWLRProtocol::setSelection(SP<IDataSource> source, bool primary) {
for (auto& o : m_vOffers) {
if (o->source && o->source->hasDnd())
continue;
+ if (o->primary != primary)
+ continue;
o->dead = true;
}
if (!source) {
- LOGM(LOG, "resetting selection");
+ LOGM(LOG, "resetting {}selection", primary ? "primary " : " ");
for (auto& d : m_vDevices) {
- sendSelectionToDevice(d, nullptr);
+ sendSelectionToDevice(d, nullptr, primary);
}
return;
}
- LOGM(LOG, "New selection for data source {:x}", (uintptr_t)source.get());
+ LOGM(LOG, "New {}selection for data source {:x}", primary ? "primary" : "", (uintptr_t)source.get());
for (auto& d : m_vDevices) {
- sendSelectionToDevice(d, source);
+ sendSelectionToDevice(d, source, primary);
}
}
diff --git a/src/protocols/DataDeviceWlr.hpp b/src/protocols/DataDeviceWlr.hpp
index 0b703347..193e918c 100644
--- a/src/protocols/DataDeviceWlr.hpp
+++ b/src/protocols/DataDeviceWlr.hpp
@@ -19,7 +19,8 @@ class CWLRDataOffer {
bool good();
void sendData();
- bool dead = false;
+ bool dead = false;
+ bool primary = false;
WP<IDataSource> source;
@@ -61,6 +62,7 @@ class CWLRDataDevice {
void sendDataOffer(SP<CWLRDataOffer> offer);
void sendSelection(SP<CWLRDataOffer> selection);
+ void sendPrimarySelection(SP<CWLRDataOffer> selection);
WP<CWLRDataDevice> self;
@@ -103,8 +105,8 @@ class CDataDeviceWLRProtocol : public IWaylandProtocol {
std::vector<SP<CWLRDataOffer>> m_vOffers;
//
- void setSelection(SP<IDataSource> source);
- void sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<IDataSource> sel);
+ void setSelection(SP<IDataSource> source, bool primary);
+ void sendSelectionToDevice(SP<CWLRDataDevice> dev, SP<IDataSource> sel, bool primary);
//
SP<CWLRDataDevice> dataDeviceForClient(wl_client*);
diff --git a/src/protocols/PrimarySelection.cpp b/src/protocols/PrimarySelection.cpp
new file mode 100644
index 00000000..78eb8d63
--- /dev/null
+++ b/src/protocols/PrimarySelection.cpp
@@ -0,0 +1,338 @@
+#include "PrimarySelection.hpp"
+#include <algorithm>
+#include "../managers/SeatManager.hpp"
+#include "core/Seat.hpp"
+#include "../config/ConfigValue.hpp"
+
+#define LOGM PROTO::primarySelection->protoLog
+
+CPrimarySelectionOffer::CPrimarySelectionOffer(SP<CZwpPrimarySelectionOfferV1> resource_, SP<IDataSource> source_) : source(source_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpPrimarySelectionOfferV1* r) { PROTO::primarySelection->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpPrimarySelectionOfferV1* r) { PROTO::primarySelection->destroyResource(this); });
+
+ resource->setReceive([this](CZwpPrimarySelectionOfferV1* 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 CPrimarySelectionOffer::good() {
+ return resource->resource();
+}
+
+void CPrimarySelectionOffer::sendData() {
+ if (!source)
+ return;
+
+ for (auto& m : source->mimes()) {
+ resource->sendOffer(m.c_str());
+ }
+}
+
+CPrimarySelectionSource::CPrimarySelectionSource(SP<CZwpPrimarySelectionSourceV1> resource_, SP<CPrimarySelectionDevice> device_) : device(device_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setData(this);
+
+ resource->setDestroy([this](CZwpPrimarySelectionSourceV1* r) {
+ events.destroy.emit();
+ PROTO::primarySelection->destroyResource(this);
+ });
+ resource->setOnDestroy([this](CZwpPrimarySelectionSourceV1* r) {
+ events.destroy.emit();
+ PROTO::primarySelection->destroyResource(this);
+ });
+
+ resource->setOffer([this](CZwpPrimarySelectionSourceV1* r, const char* mime) { mimeTypes.push_back(mime); });
+}
+
+CPrimarySelectionSource::~CPrimarySelectionSource() {
+ events.destroy.emit();
+}
+
+SP<CPrimarySelectionSource> CPrimarySelectionSource::fromResource(wl_resource* res) {
+ auto data = (CPrimarySelectionSource*)(((CZwpPrimarySelectionSourceV1*)wl_resource_get_user_data(res))->data());
+ return data ? data->self.lock() : nullptr;
+}
+
+bool CPrimarySelectionSource::good() {
+ return resource->resource();
+}
+
+std::vector<std::string> CPrimarySelectionSource::mimes() {
+ return mimeTypes;
+}
+
+void CPrimarySelectionSource::send(const std::string& mime, uint32_t fd) {
+ if (std::find(mimeTypes.begin(), mimeTypes.end(), mime) == mimeTypes.end()) {
+ LOGM(ERR, "Compositor/App bug: CPrimarySelectionSource::sendAskSend with non-existent mime");
+ close(fd);
+ return;
+ }
+
+ resource->sendSend(mime.c_str(), fd);
+ close(fd);
+}
+
+void CPrimarySelectionSource::accepted(const std::string& mime) {
+ if (std::find(mimeTypes.begin(), mimeTypes.end(), mime) == mimeTypes.end())
+ LOGM(ERR, "Compositor/App bug: CPrimarySelectionSource::sendAccepted with non-existent mime");
+
+ // primary sel has no accepted
+}
+
+void CPrimarySelectionSource::cancelled() {
+ resource->sendCancelled();
+}
+
+void CPrimarySelectionSource::error(uint32_t code, const std::string& msg) {
+ resource->error(code, msg);
+}
+
+CPrimarySelectionDevice::CPrimarySelectionDevice(SP<CZwpPrimarySelectionDeviceV1> resource_) : resource(resource_) {
+ if (!good())
+ return;
+
+ pClient = resource->client();
+
+ resource->setDestroy([this](CZwpPrimarySelectionDeviceV1* r) { PROTO::primarySelection->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpPrimarySelectionDeviceV1* r) { PROTO::primarySelection->destroyResource(this); });
+
+ resource->setSetSelection([this](CZwpPrimarySelectionDeviceV1* r, wl_resource* sourceR, uint32_t serial) {
+ static auto PPRIMARYSEL = CConfigValue<Hyprlang::INT>("misc:middle_click_paste");
+
+ if (!*PPRIMARYSEL) {
+ LOGM(LOG, "Ignoring primary selection: disabled in config");
+ g_pSeatManager->setCurrentPrimarySelection(nullptr);
+ return;
+ }
+
+ auto source = sourceR ? CPrimarySelectionSource::fromResource(sourceR) : CSharedPointer<CPrimarySelectionSource>{};
+ if (!source) {
+ LOGM(LOG, "wlr reset selection received");
+ g_pSeatManager->setCurrentPrimarySelection(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->setCurrentPrimarySelection(source);
+ });
+}
+
+bool CPrimarySelectionDevice::good() {
+ return resource->resource();
+}
+
+wl_client* CPrimarySelectionDevice::client() {
+ return pClient;
+}
+
+void CPrimarySelectionDevice::sendDataOffer(SP<CPrimarySelectionOffer> offer) {
+ resource->sendDataOffer(offer->resource.get());
+}
+
+void CPrimarySelectionDevice::sendSelection(SP<CPrimarySelectionOffer> selection) {
+ if (!selection)
+ resource->sendSelectionRaw(nullptr);
+ else
+ resource->sendSelection(selection->resource.get());
+}
+
+CPrimarySelectionManager::CPrimarySelectionManager(SP<CZwpPrimarySelectionDeviceManagerV1> resource_) : resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setOnDestroy([this](CZwpPrimarySelectionDeviceManagerV1* r) { PROTO::primarySelection->destroyResource(this); });
+
+ resource->setGetDevice([this](CZwpPrimarySelectionDeviceManagerV1* r, uint32_t id, wl_resource* seat) {
+ const auto RESOURCE =
+ PROTO::primarySelection->m_vDevices.emplace_back(makeShared<CPrimarySelectionDevice>(makeShared<CZwpPrimarySelectionDeviceV1>(r->client(), r->version(), id)));
+
+ if (!RESOURCE->good()) {
+ r->noMemory();
+ PROTO::primarySelection->m_vDevices.pop_back();
+ return;
+ }
+
+ RESOURCE->self = RESOURCE;
+ device = RESOURCE;
+
+ for (auto& s : sources) {
+ if (!s)
+ continue;
+ s->device = RESOURCE;
+ }
+
+ LOGM(LOG, "New primary selection data device bound at {:x}", (uintptr_t)RESOURCE.get());
+ });
+
+ resource->setCreateSource([this](CZwpPrimarySelectionDeviceManagerV1* r, uint32_t id) {
+ std::erase_if(sources, [](const auto& e) { return e.expired(); });
+
+ const auto RESOURCE = PROTO::primarySelection->m_vSources.emplace_back(
+ makeShared<CPrimarySelectionSource>(makeShared<CZwpPrimarySelectionSourceV1>(r->client(), r->version(), id), device.lock()));
+
+ if (!RESOURCE->good()) {
+ r->noMemory();
+ PROTO::primarySelection->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 primary selection data source bound at {:x}", (uintptr_t)RESOURCE.get());
+ });
+}
+
+bool CPrimarySelectionManager::good() {
+ return resource->resource();
+}
+
+CPrimarySelectionProtocol::CPrimarySelectionProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
+ ;
+}
+
+void CPrimarySelectionProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
+ const auto RESOURCE = m_vManagers.emplace_back(makeShared<CPrimarySelectionManager>(makeShared<CZwpPrimarySelectionDeviceManagerV1>(client, ver, id)));
+
+ if (!RESOURCE->good()) {
+ wl_client_post_no_memory(client);
+ m_vManagers.pop_back();
+ return;
+ }
+
+ LOGM(LOG, "New primary_seletion_manager at {:x}", (uintptr_t)RESOURCE.get());
+
+ // we need to do it here because protocols come before seatMgr
+ if (!listeners.onPointerFocusChange)
+ listeners.onPointerFocusChange = g_pSeatManager->events.pointerFocusChange.registerListener([this](std::any d) { this->onPointerFocus(); });
+}
+
+void CPrimarySelectionProtocol::destroyResource(CPrimarySelectionManager* resource) {
+ std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CPrimarySelectionProtocol::destroyResource(CPrimarySelectionSource* resource) {
+ std::erase_if(m_vSources, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CPrimarySelectionProtocol::destroyResource(CPrimarySelectionDevice* resource) {
+ std::erase_if(m_vDevices, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CPrimarySelectionProtocol::destroyResource(CPrimarySelectionOffer* resource) {
+ std::erase_if(m_vOffers, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CPrimarySelectionProtocol::sendSelectionToDevice(SP<CPrimarySelectionDevice> dev, SP<IDataSource> sel) {
+ if (!sel) {
+ dev->sendSelection(nullptr);
+ return;
+ }
+
+ const auto OFFER =
+ m_vOffers.emplace_back(makeShared<CPrimarySelectionOffer>(makeShared<CZwpPrimarySelectionOfferV1>(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 CPrimarySelectionProtocol::setSelection(SP<IDataSource> source) {
+ for (auto& o : m_vOffers) {
+ if (o->source && o->source->hasDnd())
+ continue;
+ o->dead = true;
+ }
+
+ if (!source) {
+ LOGM(LOG, "resetting selection");
+
+ if (!g_pSeatManager->state.pointerFocusResource)
+ return;
+
+ auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->state.pointerFocusResource->client());
+ if (DESTDEVICE)
+ sendSelectionToDevice(DESTDEVICE, nullptr);
+
+ return;
+ }
+
+ LOGM(LOG, "New selection for data source {:x}", (uintptr_t)source.get());
+
+ if (!g_pSeatManager->state.pointerFocusResource)
+ return;
+
+ auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->state.pointerFocusResource->client());
+
+ if (!DESTDEVICE) {
+ LOGM(LOG, "CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device");
+ return;
+ }
+
+ sendSelectionToDevice(DESTDEVICE, source);
+}
+
+void CPrimarySelectionProtocol::updateSelection() {
+ if (!g_pSeatManager->state.pointerFocusResource)
+ return;
+
+ auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->state.pointerFocusResource->client());
+
+ if (!DESTDEVICE) {
+ LOGM(LOG, "CPrimarySelectionProtocol::updateSelection: cannot send selection to a client without a data_device");
+ return;
+ }
+
+ sendSelectionToDevice(DESTDEVICE, g_pSeatManager->selection.currentPrimarySelection.lock());
+}
+
+void CPrimarySelectionProtocol::onPointerFocus() {
+ for (auto& o : m_vOffers) {
+ o->dead = true;
+ }
+
+ updateSelection();
+}
+
+SP<CPrimarySelectionDevice> CPrimarySelectionProtocol::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/PrimarySelection.hpp b/src/protocols/PrimarySelection.hpp
new file mode 100644
index 00000000..c33a00e8
--- /dev/null
+++ b/src/protocols/PrimarySelection.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <cstdint>
+#include "WaylandProtocol.hpp"
+#include "primary-selection-unstable-v1.hpp"
+#include "types/DataDevice.hpp"
+
+class CPrimarySelectionOffer;
+class CPrimarySelectionSource;
+class CPrimarySelectionDevice;
+class CPrimarySelectionManager;
+
+class CPrimarySelectionOffer {
+ public:
+ CPrimarySelectionOffer(SP<CZwpPrimarySelectionOfferV1> resource_, SP<IDataSource> source_);
+
+ bool good();
+ void sendData();
+
+ bool dead = false;
+
+ WP<IDataSource> source;
+
+ private:
+ SP<CZwpPrimarySelectionOfferV1> resource;
+
+ friend class CPrimarySelectionDevice;
+};
+
+class CPrimarySelectionSource : public IDataSource {
+ public:
+ CPrimarySelectionSource(SP<CZwpPrimarySelectionSourceV1> resource_, SP<CPrimarySelectionDevice> device_);
+ ~CPrimarySelectionSource();
+
+ static SP<CPrimarySelectionSource> fromResource(wl_resource*);
+
+ bool good();
+
+ virtual std::vector<std::string> 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<std::string> mimeTypes;
+ WP<CPrimarySelectionSource> self;
+ WP<CPrimarySelectionDevice> device;
+
+ private:
+ SP<CZwpPrimarySelectionSourceV1> resource;
+};
+
+class CPrimarySelectionDevice {
+ public:
+ CPrimarySelectionDevice(SP<CZwpPrimarySelectionDeviceV1> resource_);
+
+ bool good();
+ wl_client* client();
+
+ void sendDataOffer(SP<CPrimarySelectionOffer> offer);
+ void sendSelection(SP<CPrimarySelectionOffer> selection);
+
+ WP<CPrimarySelectionDevice> self;
+
+ private:
+ SP<CZwpPrimarySelectionDeviceV1> resource;
+ wl_client* pClient = nullptr;
+
+ friend class CPrimarySelectionProtocol;
+};
+
+class CPrimarySelectionManager {
+ public:
+ CPrimarySelectionManager(SP<CZwpPrimarySelectionDeviceManagerV1> resource_);
+
+ bool good();
+
+ WP<CPrimarySelectionDevice> device;
+ std::vector<WP<CPrimarySelectionSource>> sources;
+
+ private:
+ SP<CZwpPrimarySelectionDeviceManagerV1> resource;
+};
+
+class CPrimarySelectionProtocol : public IWaylandProtocol {
+ public:
+ CPrimarySelectionProtocol(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(CPrimarySelectionManager* resource);
+ void destroyResource(CPrimarySelectionDevice* resource);
+ void destroyResource(CPrimarySelectionSource* resource);
+ void destroyResource(CPrimarySelectionOffer* resource);
+
+ //
+ std::vector<SP<CPrimarySelectionManager>> m_vManagers;
+ std::vector<SP<CPrimarySelectionDevice>> m_vDevices;
+ std::vector<SP<CPrimarySelectionSource>> m_vSources;
+ std::vector<SP<CPrimarySelectionOffer>> m_vOffers;
+
+ //
+ void setSelection(SP<IDataSource> source);
+ void sendSelectionToDevice(SP<CPrimarySelectionDevice> dev, SP<IDataSource> sel);
+ void updateSelection();
+ void onPointerFocus();
+
+ //
+ SP<CPrimarySelectionDevice> dataDeviceForClient(wl_client*);
+
+ friend class CPrimarySelectionManager;
+ friend class CPrimarySelectionDevice;
+ friend class CPrimarySelectionSource;
+ friend class CPrimarySelectionOffer;
+ friend class CSeatManager;
+
+ struct {
+ CHyprSignalListener onPointerFocusChange;
+ } listeners;
+};
+
+namespace PROTO {
+ inline UP<CPrimarySelectionProtocol> primarySelection;
+};