diff options
Diffstat (limited to 'xbmc/windowing/wayland/SeatSelection.cpp')
-rw-r--r-- | xbmc/windowing/wayland/SeatSelection.cpp | 199 |
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); +} |