summaryrefslogtreecommitdiffstats
path: root/xbmc/windowing/wayland/SeatSelection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/windowing/wayland/SeatSelection.cpp')
-rw-r--r--xbmc/windowing/wayland/SeatSelection.cpp199
1 files changed, 199 insertions, 0 deletions
diff --git a/xbmc/windowing/wayland/SeatSelection.cpp b/xbmc/windowing/wayland/SeatSelection.cpp
new file mode 100644
index 0000000..f66555a
--- /dev/null
+++ b/xbmc/windowing/wayland/SeatSelection.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SeatSelection.h"
+
+#include "Connection.h"
+#include "Registry.h"
+#include "WinEventsWayland.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/utils/FileHandle.h"
+
+#include <cerrno>
+#include <chrono>
+#include <cstring>
+#include <mutex>
+#include <system_error>
+#include <utility>
+
+#include <poll.h>
+#include <unistd.h>
+
+using namespace KODI::UTILS::POSIX;
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+const std::vector<std::string> MIME_TYPES_PREFERENCE =
+{
+ "text/plain;charset=utf-8",
+ "text/plain;charset=iso-8859-1",
+ "text/plain;charset=us-ascii",
+ "text/plain"
+};
+
+}
+
+CSeatSelection::CSeatSelection(CConnection& connection, wayland::seat_t const& seat)
+{
+ wayland::data_device_manager_t manager;
+ {
+ CRegistry registry{connection};
+ registry.RequestSingleton(manager, 1, 3, false);
+ registry.Bind();
+ }
+
+ if (!manager)
+ {
+ CLog::Log(LOGWARNING, "No data device manager announced by compositor, clipboard will not be available");
+ return;
+ }
+
+ m_dataDevice = manager.get_data_device(seat);
+
+ // Class is created in response to seat add events - so no events can get lost
+ m_dataDevice.on_data_offer() = [this](wayland::data_offer_t offer)
+ {
+ // We don't know yet whether this is drag-and-drop or selection, so collect
+ // MIME types in either case
+ m_currentOffer = std::move(offer);
+ m_mimeTypeOffers.clear();
+ m_currentOffer.on_offer() = [this](std::string mime)
+ {
+ m_mimeTypeOffers.push_back(std::move(mime));
+ };
+ };
+ m_dataDevice.on_selection() = [this](const wayland::data_offer_t& offer)
+ {
+ std::unique_lock<CCriticalSection> lock(m_currentSelectionMutex);
+ m_matchedMimeType.clear();
+
+ if (offer != m_currentOffer)
+ {
+ // Selection was not previously introduced by offer (could be NULL for example)
+ m_currentSelection.proxy_release();
+ }
+ else
+ {
+ m_currentSelection = offer;
+ std::string offers = StringUtils::Join(m_mimeTypeOffers, ", ");
+
+ // Match MIME type by priority: Find first preferred MIME type that is in the
+ // set of offered types
+ // Charset is not case-sensitive in MIME type spec, so match case-insensitively
+ auto mimeIt = std::find_first_of(MIME_TYPES_PREFERENCE.cbegin(), MIME_TYPES_PREFERENCE.cend(),
+ m_mimeTypeOffers.cbegin(), m_mimeTypeOffers.cend(),
+ // static_cast needed for overload resolution
+ static_cast<bool (*)(std::string const&, std::string const&)> (&StringUtils::EqualsNoCase));
+ if (mimeIt != MIME_TYPES_PREFERENCE.cend())
+ {
+ m_matchedMimeType = *mimeIt;
+ CLog::Log(LOGDEBUG, "Chose selection MIME type {} out of offered {}", m_matchedMimeType,
+ offers);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Could not find compatible MIME type for selection data (offered: {})",
+ offers);
+ }
+ }
+ };
+}
+
+std::string CSeatSelection::GetSelectionText() const
+{
+ std::unique_lock<CCriticalSection> lock(m_currentSelectionMutex);
+ if (!m_currentSelection || m_matchedMimeType.empty())
+ {
+ return "";
+ }
+
+ std::array<int, 2> fds;
+ if (pipe(fds.data()) != 0)
+ {
+ CLog::LogF(LOGERROR, "Could not open pipe for selection data transfer: {}",
+ std::strerror(errno));
+ return "";
+ }
+
+ CFileHandle readFd{fds[0]};
+ CFileHandle writeFd{fds[1]};
+
+ m_currentSelection.receive(m_matchedMimeType, writeFd);
+ lock.unlock();
+ // Make sure the other party gets the request as soon as possible
+ CWinEventsWayland::Flush();
+ // Fd now gets sent to the other party -> make sure our write end is closed
+ // so we get POLLHUP when the other party closes its write fd
+ writeFd.reset();
+
+ pollfd fd =
+ {
+ .fd = readFd,
+ .events = POLLIN,
+ .revents = 0
+ };
+
+ // UI will block in this function when Ctrl+V is pressed, so timeout should be
+ // rather short!
+ const std::chrono::seconds TIMEOUT{1};
+ const std::size_t MAX_SIZE{4096};
+ std::array<char, MAX_SIZE> buffer;
+
+ auto start = std::chrono::steady_clock::now();
+ std::size_t totalBytesRead{0};
+
+ do
+ {
+ auto now = std::chrono::steady_clock::now();
+ // Do not permit negative timeouts (would cause infinitely long poll)
+ auto remainingTimeout = std::max(std::chrono::milliseconds(0), std::chrono::duration_cast<std::chrono::milliseconds> (TIMEOUT - (now - start))).count();
+ // poll() for changes until poll signals POLLHUP and the remaining data was read
+ int ret{poll(&fd, 1, remainingTimeout)};
+ if (ret == 0)
+ {
+ // Timeout
+ CLog::LogF(LOGERROR, "Reading from selection data pipe timed out");
+ return "";
+ }
+ else if (ret < 0 && errno == EINTR)
+ {
+ continue;
+ }
+ else if (ret < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Error polling selection pipe");
+ }
+ else if (fd.revents & POLLNVAL || fd.revents & POLLERR)
+ {
+ CLog::LogF(LOGERROR, "poll() indicated error on selection pipe");
+ return "";
+ }
+ else if (fd.revents & POLLIN)
+ {
+ if (totalBytesRead >= buffer.size())
+ {
+ CLog::LogF(LOGERROR, "Selection data is too big, aborting read");
+ return "";
+ }
+ ssize_t readBytes{read(fd.fd, buffer.data() + totalBytesRead, buffer.size() - totalBytesRead)};
+ if (readBytes < 0)
+ {
+ CLog::LogF(LOGERROR, "read() from selection pipe failed: {}", std::strerror(errno));
+ return "";
+ }
+ totalBytesRead += readBytes;
+ }
+ }
+ while (!(fd.revents & POLLHUP));
+
+ return std::string(buffer.data(), totalBytesRead);
+}