1390 lines
41 KiB
C++
1390 lines
41 KiB
C++
/*
|
|
* Deskflow -- mouse and keyboard sharing utility
|
|
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
|
|
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
|
|
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
|
*/
|
|
|
|
#include "platform/XWindowsClipboard.h"
|
|
|
|
#include "arch/Arch.h"
|
|
#include "base/Log.h"
|
|
#include "base/Stopwatch.h"
|
|
#include "common/Common.h"
|
|
#include "platform/XWindowsClipboardBMPConverter.h"
|
|
#include "platform/XWindowsClipboardHTMLConverter.h"
|
|
#include "platform/XWindowsClipboardTextConverter.h"
|
|
#include "platform/XWindowsClipboardUCS2Converter.h"
|
|
#include "platform/XWindowsClipboardUTF8Converter.h"
|
|
#include "platform/XWindowsUtil.h"
|
|
|
|
#include <X11/Xatom.h>
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
//
|
|
// XWindowsClipboard
|
|
//
|
|
|
|
XWindowsClipboard::XWindowsClipboard(Display *display, Window window, ClipboardID id)
|
|
: m_display(display),
|
|
m_window(window),
|
|
m_id(id)
|
|
{
|
|
// get some atoms
|
|
m_atomTargets = XInternAtom(m_display, "TARGETS", False);
|
|
m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False);
|
|
m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False);
|
|
m_atomInteger = XInternAtom(m_display, "INTEGER", False);
|
|
m_atomAtom = XInternAtom(m_display, "ATOM", False);
|
|
m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False);
|
|
m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False);
|
|
m_atomINCR = XInternAtom(m_display, "INCR", False);
|
|
m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False);
|
|
m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False);
|
|
m_atomMotifClipAccess = XInternAtom(m_display, "_MOTIF_CLIP_LOCK_ACCESS_VALID", False);
|
|
m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False);
|
|
|
|
// set selection atom based on clipboard id
|
|
switch (id) {
|
|
case kClipboardClipboard:
|
|
m_selection = XInternAtom(m_display, "CLIPBOARD", False);
|
|
break;
|
|
|
|
default:
|
|
m_selection = XA_PRIMARY;
|
|
break;
|
|
}
|
|
|
|
// add converters, most desired first
|
|
m_converters.push_back(new XWindowsClipboardHTMLConverter(m_display, "text/html"));
|
|
m_converters.push_back(new XWindowsClipboardHTMLConverter(m_display, "application/x-moz-nativehtml"));
|
|
m_converters.push_back(new XWindowsClipboardBMPConverter(m_display));
|
|
m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display, "text/plain;charset=UTF-8", true));
|
|
m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display, "text/plain;charset=utf-8", true));
|
|
m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display, "UTF8_STRING"));
|
|
m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display, "text/plain;charset=ISO-10646-UCS-2"));
|
|
m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display, "text/unicode"));
|
|
m_converters.push_back(new XWindowsClipboardTextConverter(m_display, "text/plain"));
|
|
m_converters.push_back(new XWindowsClipboardTextConverter(m_display, "STRING"));
|
|
|
|
// we have no data
|
|
clearCache();
|
|
}
|
|
|
|
XWindowsClipboard::~XWindowsClipboard()
|
|
{
|
|
clearReplies();
|
|
clearConverters();
|
|
}
|
|
|
|
void XWindowsClipboard::lost(Time time)
|
|
{
|
|
LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time));
|
|
if (m_owner) {
|
|
m_owner = false;
|
|
m_timeLost = time;
|
|
clearCache();
|
|
}
|
|
}
|
|
|
|
void XWindowsClipboard::addRequest(Window owner, Window requestor, Atom target, ::Time time, Atom property)
|
|
{
|
|
// must be for our window and we must have owned the selection
|
|
// at the given time.
|
|
bool success = false;
|
|
if (owner == m_window) {
|
|
LOG(
|
|
(CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection,
|
|
XWindowsUtil::atomToString(m_display, target).c_str(), requestor,
|
|
XWindowsUtil::atomToString(m_display, property).c_str())
|
|
);
|
|
if (wasOwnedAtTime(time)) {
|
|
if (target == m_atomMultiple && property != None) {
|
|
// add a multiple request. property may not be None
|
|
// according to ICCCM.
|
|
success = insertMultipleReply(requestor, time, property);
|
|
} else {
|
|
addSimpleRequest(requestor, target, time, property);
|
|
|
|
// addSimpleRequest() will have already handled failure
|
|
success = true;
|
|
}
|
|
} else {
|
|
LOG((CLOG_DEBUG1 "clipboard not owned at time %d", time));
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
// send failure
|
|
LOG((CLOG_DEBUG1 "clipboard request was not added"));
|
|
insertReply(new Reply(requestor, target, time));
|
|
}
|
|
|
|
// send notifications that are pending
|
|
pushReplies();
|
|
}
|
|
|
|
bool XWindowsClipboard::addSimpleRequest(Window requestor, Atom target, ::Time time, Atom property)
|
|
{
|
|
// obsolete requestors may supply a None property. in
|
|
// that case we use the target as the property to store
|
|
// the conversion.
|
|
if (property == None) {
|
|
property = target;
|
|
}
|
|
|
|
// handle targets
|
|
std::string data;
|
|
Atom type = None;
|
|
int format = 0;
|
|
if (target == m_atomTargets) {
|
|
type = getTargetsData(data, &format);
|
|
} else if (target == m_atomTimestamp) {
|
|
type = getTimestampData(data, &format);
|
|
} else {
|
|
const IXWindowsClipboardConverter *converter = getConverter(target);
|
|
if (converter != nullptr) {
|
|
const auto clipboardFormat = static_cast<int>(converter->getFormat());
|
|
if (m_added[clipboardFormat]) {
|
|
try {
|
|
data = converter->fromIClipboard(m_data[clipboardFormat]);
|
|
format = converter->getDataSize();
|
|
type = converter->getAtom();
|
|
} catch (...) {
|
|
// ignore -- cannot convert
|
|
LOG((CLOG_WARN "error while converting clipboard data"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type != None) {
|
|
// success
|
|
LOG((CLOG_DEBUG1 "clipboard request added"));
|
|
insertReply(new Reply(requestor, target, time, property, data, type, format));
|
|
return true;
|
|
} else {
|
|
// failure
|
|
LOG((CLOG_DEBUG1 "clipboard request not added"));
|
|
insertReply(new Reply(requestor, target, time));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool XWindowsClipboard::processRequest(Window requestor, ::Time /*time*/, Atom property)
|
|
{
|
|
ReplyMap::iterator index = m_replies.find(requestor);
|
|
if (index == m_replies.end()) {
|
|
// unknown requestor window
|
|
return false;
|
|
}
|
|
LOG(
|
|
(CLOG_DEBUG1 "received property %s delete from 0x08%x", XWindowsUtil::atomToString(m_display, property).c_str(),
|
|
requestor)
|
|
);
|
|
|
|
// find the property in the known requests. it should be the
|
|
// first property but we'll check 'em all if we have to.
|
|
ReplyList &replies = index->second;
|
|
for (auto index2 = replies.begin(); index2 != replies.end(); ++index2) {
|
|
const Reply *reply = *index2;
|
|
if (reply->m_replied && reply->m_property == property) {
|
|
// if reply is complete then remove it and start the
|
|
// next one.
|
|
pushReplies(index, replies, index2);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool XWindowsClipboard::destroyRequest(Window requestor)
|
|
{
|
|
ReplyMap::iterator index = m_replies.find(requestor);
|
|
if (index == m_replies.end()) {
|
|
// unknown requestor window
|
|
return false;
|
|
}
|
|
|
|
// destroy all replies for this window
|
|
clearReplies(index->second);
|
|
m_replies.erase(index);
|
|
|
|
// note -- we don't stop watching the window for events because
|
|
// we're called in response to the window being destroyed.
|
|
|
|
return true;
|
|
}
|
|
|
|
Window XWindowsClipboard::getWindow() const
|
|
{
|
|
return m_window;
|
|
}
|
|
|
|
Atom XWindowsClipboard::getSelection() const
|
|
{
|
|
return m_selection;
|
|
}
|
|
|
|
bool XWindowsClipboard::empty()
|
|
{
|
|
assert(m_open);
|
|
|
|
LOG((CLOG_DEBUG "empty clipboard %d", m_id));
|
|
|
|
// assert ownership of clipboard
|
|
XSetSelectionOwner(m_display, m_selection, m_window, m_time);
|
|
if (XGetSelectionOwner(m_display, m_selection) != m_window) {
|
|
LOG((CLOG_WARN "failed to grab clipboard %d", m_id));
|
|
return false;
|
|
}
|
|
|
|
// clear all data. since we own the data now, the cache is up
|
|
// to date.
|
|
clearCache();
|
|
m_cached = true;
|
|
|
|
// FIXME -- actually delete motif clipboard items?
|
|
// FIXME -- do anything to motif clipboard properties?
|
|
|
|
// save time
|
|
m_timeOwned = m_time;
|
|
m_timeLost = 0;
|
|
|
|
// we're the owner now
|
|
m_owner = true;
|
|
LOG((CLOG_DEBUG "grabbed clipboard %d", m_id));
|
|
|
|
return true;
|
|
}
|
|
|
|
void XWindowsClipboard::add(Format format, const std::string &data)
|
|
{
|
|
assert(m_open);
|
|
assert(m_owner);
|
|
|
|
LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
|
|
|
|
const auto formatID = static_cast<int>(format);
|
|
m_data[formatID] = data;
|
|
m_added[formatID] = true;
|
|
|
|
// FIXME -- set motif clipboard item?
|
|
}
|
|
|
|
bool XWindowsClipboard::open(Time time) const
|
|
{
|
|
if (m_open) {
|
|
LOG((CLOG_WARN "failed to open clipboard: already opened"));
|
|
return false;
|
|
}
|
|
|
|
LOG((CLOG_DEBUG "open clipboard %d", m_id));
|
|
|
|
// assume not motif
|
|
m_motif = false;
|
|
|
|
// lock clipboard
|
|
if (m_id == kClipboardClipboard) {
|
|
if (!motifLockClipboard()) {
|
|
return false;
|
|
}
|
|
|
|
// check if motif owns the selection. unlock motif clipboard
|
|
// if it does not.
|
|
m_motif = motifOwnsClipboard();
|
|
LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not "));
|
|
if (!m_motif) {
|
|
motifUnlockClipboard();
|
|
}
|
|
}
|
|
|
|
// now open
|
|
m_open = true;
|
|
m_time = time;
|
|
|
|
// be sure to flush the cache later if it's dirty
|
|
m_checkCache = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void XWindowsClipboard::close() const
|
|
{
|
|
assert(m_open);
|
|
|
|
LOG((CLOG_DEBUG "close clipboard %d", m_id));
|
|
|
|
// unlock clipboard
|
|
if (m_motif) {
|
|
motifUnlockClipboard();
|
|
}
|
|
|
|
m_motif = false;
|
|
m_open = false;
|
|
}
|
|
|
|
IClipboard::Time XWindowsClipboard::getTime() const
|
|
{
|
|
checkCache();
|
|
return m_timeOwned;
|
|
}
|
|
|
|
bool XWindowsClipboard::has(Format format) const
|
|
{
|
|
assert(m_open);
|
|
|
|
fillCache();
|
|
return m_added[static_cast<int>(format)];
|
|
}
|
|
|
|
std::string XWindowsClipboard::get(Format format) const
|
|
{
|
|
assert(m_open);
|
|
|
|
fillCache();
|
|
return m_data[static_cast<int>(format)];
|
|
}
|
|
|
|
void XWindowsClipboard::clearConverters()
|
|
{
|
|
for (auto index = m_converters.begin(); index != m_converters.end(); ++index) {
|
|
delete *index;
|
|
}
|
|
m_converters.clear();
|
|
}
|
|
|
|
IXWindowsClipboardConverter *XWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
|
|
{
|
|
IXWindowsClipboardConverter *converter = nullptr;
|
|
for (auto index = m_converters.begin(); index != m_converters.end(); ++index) {
|
|
converter = *index;
|
|
if (converter->getAtom() == target) {
|
|
break;
|
|
}
|
|
}
|
|
if (converter == nullptr) {
|
|
LOG((CLOG_DEBUG1 " no converter for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
|
|
return nullptr;
|
|
}
|
|
|
|
// optionally skip already handled targets
|
|
if (const auto formatID = static_cast<int>(converter->getFormat()); onlyIfNotAdded && m_added[formatID]) {
|
|
LOG((CLOG_DEBUG1 " skipping handled format %d", formatID));
|
|
return nullptr;
|
|
}
|
|
|
|
return converter;
|
|
}
|
|
|
|
void XWindowsClipboard::checkCache() const
|
|
{
|
|
if (!m_checkCache) {
|
|
return;
|
|
}
|
|
m_checkCache = false;
|
|
|
|
// get the time the clipboard ownership was taken by the current
|
|
// owner.
|
|
if (m_motif) {
|
|
m_timeOwned = motifGetTime();
|
|
} else {
|
|
m_timeOwned = icccmGetTime();
|
|
}
|
|
|
|
// if we can't get the time then use the time passed to us
|
|
if (m_timeOwned == 0) {
|
|
m_timeOwned = m_time;
|
|
}
|
|
|
|
// if the cache is dirty then flush it
|
|
if (m_timeOwned != m_cacheTime) {
|
|
clearCache();
|
|
}
|
|
}
|
|
|
|
void XWindowsClipboard::clearCache() const
|
|
{
|
|
const_cast<XWindowsClipboard *>(this)->doClearCache();
|
|
}
|
|
|
|
void XWindowsClipboard::doClearCache()
|
|
{
|
|
m_checkCache = false;
|
|
m_cached = false;
|
|
for (int32_t index = 0; index < static_cast<int>(Format::TotalFormats); ++index) {
|
|
m_data[index] = "";
|
|
m_added[index] = false;
|
|
}
|
|
}
|
|
|
|
void XWindowsClipboard::fillCache() const
|
|
{
|
|
// get the selection data if not already cached
|
|
checkCache();
|
|
if (!m_cached) {
|
|
const_cast<XWindowsClipboard *>(this)->doFillCache();
|
|
}
|
|
}
|
|
|
|
void XWindowsClipboard::doFillCache()
|
|
{
|
|
if (m_motif) {
|
|
motifFillCache();
|
|
} else {
|
|
icccmFillCache();
|
|
}
|
|
m_checkCache = false;
|
|
m_cached = true;
|
|
m_cacheTime = m_timeOwned;
|
|
}
|
|
|
|
void XWindowsClipboard::icccmFillCache()
|
|
{
|
|
LOG((CLOG_DEBUG "icccm fill clipboard %d", m_id));
|
|
|
|
// see if we can get the list of available formats from the selection.
|
|
// if not then use a default list of formats. note that some clipboard
|
|
// owners are broken and report TARGETS as the type of the TARGETS data
|
|
// instead of the correct type ATOM; allow either.
|
|
const Atom atomTargets = m_atomTargets;
|
|
Atom target;
|
|
std::string data;
|
|
if (!icccmGetSelection(atomTargets, &target, &data) || (target != m_atomAtom && target != m_atomTargets)) {
|
|
LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
|
|
data = "";
|
|
XWindowsUtil::appendAtomData(data, XA_STRING);
|
|
}
|
|
|
|
XWindowsUtil::convertAtomProperty(data);
|
|
auto targets = static_cast<const Atom *>(static_cast<const void *>(data.data()));
|
|
const uint32_t numTargets = data.size() / sizeof(Atom);
|
|
LOG((CLOG_DEBUG " available targets: %s", XWindowsUtil::atomsToString(m_display, targets, numTargets).c_str()));
|
|
|
|
// try each converter in order (because they're in order of
|
|
// preference).
|
|
for (ConverterList::const_iterator index = m_converters.begin(); index != m_converters.end(); ++index) {
|
|
const IXWindowsClipboardConverter *converter = *index;
|
|
const auto formatID = static_cast<int>(converter->getFormat());
|
|
|
|
// skip already handled targets
|
|
if (m_added[formatID]) {
|
|
continue;
|
|
}
|
|
|
|
// see if atom is in target list
|
|
Atom target = None;
|
|
// XXX -- just ask for the converter's target to see if it's
|
|
// available rather than checking TARGETS. i've seen clipboard
|
|
// owners that don't report all the targets they support.
|
|
target = converter->getAtom();
|
|
/*
|
|
for (uint32_t i = 0; i < numTargets; ++i) {
|
|
if (converter->getAtom() == targets[i]) {
|
|
target = targets[i];
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
if (target == None) {
|
|
continue;
|
|
}
|
|
|
|
// get the data
|
|
Atom actualTarget;
|
|
std::string targetData;
|
|
if (!icccmGetSelection(target, &actualTarget, &targetData)) {
|
|
LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
|
|
continue;
|
|
}
|
|
|
|
// add to clipboard and note we've done it
|
|
m_data[formatID] = converter->toIClipboard(targetData);
|
|
m_added[formatID] = true;
|
|
LOG(
|
|
(CLOG_DEBUG "added format %d for target %s (%u %s)", formatID,
|
|
XWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(),
|
|
targetData.size() == 1 ? "byte" : "bytes")
|
|
);
|
|
}
|
|
}
|
|
|
|
bool XWindowsClipboard::icccmGetSelection(Atom target, Atom *actualTarget, std::string *data) const
|
|
{
|
|
assert(actualTarget != nullptr);
|
|
assert(data != nullptr);
|
|
|
|
// request data conversion
|
|
if (CICCCMGetClipboard getter(m_window, m_time, m_atomData);
|
|
!getter.readClipboard(m_display, m_selection, target, actualTarget, data)) {
|
|
LOG((CLOG_DEBUG1 "can't get data for selection target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
|
|
LOGC(getter.error(), (CLOG_WARN "icccm violation by clipboard owner"));
|
|
return false;
|
|
} else if (*actualTarget == None) {
|
|
LOG((CLOG_DEBUG1 "selection conversion failed for target %s", XWindowsUtil::atomToString(m_display, target).c_str())
|
|
);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
IClipboard::Time XWindowsClipboard::icccmGetTime() const
|
|
{
|
|
Atom actualTarget;
|
|
std::string data;
|
|
if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) && actualTarget == m_atomInteger) {
|
|
Time time = *static_cast<const Time *>(static_cast<const void *>(data.data()));
|
|
LOG((CLOG_DEBUG1 "got ICCCM time %d", time));
|
|
return time;
|
|
} else {
|
|
// no timestamp
|
|
LOG((CLOG_DEBUG1 "can't get ICCCM time"));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool XWindowsClipboard::motifLockClipboard() const
|
|
{
|
|
// fail if anybody owns the lock (even us, so this is non-recursive)
|
|
Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
|
|
if (lockOwner != None) {
|
|
LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
|
|
return false;
|
|
}
|
|
|
|
// try to grab the lock
|
|
// FIXME -- is this right? there's a race condition here --
|
|
// A grabs successfully, B grabs successfully, A thinks it
|
|
// still has the grab until it gets a SelectionClear.
|
|
Time time = XWindowsUtil::getCurrentTime(m_display, m_window);
|
|
XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time);
|
|
lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
|
|
if (lockOwner != m_window) {
|
|
LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
|
|
return false;
|
|
}
|
|
|
|
LOG((CLOG_DEBUG1 "locked motif clipboard"));
|
|
return true;
|
|
}
|
|
|
|
void XWindowsClipboard::motifUnlockClipboard() const
|
|
{
|
|
LOG((CLOG_DEBUG1 "unlocked motif clipboard"));
|
|
|
|
// fail if we don't own the lock
|
|
if (Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); lockOwner != m_window) {
|
|
return;
|
|
}
|
|
|
|
// release lock
|
|
Time time = XWindowsUtil::getCurrentTime(m_display, m_window);
|
|
XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
|
|
}
|
|
|
|
bool XWindowsClipboard::motifOwnsClipboard() const
|
|
{
|
|
// get the current selection owner
|
|
// FIXME -- this can't be right. even if the window is destroyed
|
|
// Motif will still have a valid clipboard. how can we tell if
|
|
// some other client owns CLIPBOARD?
|
|
Window owner = XGetSelectionOwner(m_display, m_selection);
|
|
if (owner == None) {
|
|
return false;
|
|
}
|
|
|
|
// get the Motif clipboard header property from the root window
|
|
Atom target;
|
|
int32_t format;
|
|
std::string data;
|
|
if (Window root = RootWindow(m_display, DefaultScreen(m_display));
|
|
!XWindowsUtil::getWindowProperty(m_display, root, m_atomMotifClipHeader, &data, &target, &format, False)) {
|
|
return false;
|
|
}
|
|
|
|
// check the owner window against the current clipboard owner
|
|
if (data.size() >= sizeof(MotifClipHeader)) {
|
|
MotifClipHeader header;
|
|
std::memcpy(&header, data.data(), sizeof(header));
|
|
if ((header.m_id == MotifClip::Header) && (static_cast<Window>(header.m_selectionOwner) == owner)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void XWindowsClipboard::motifFillCache()
|
|
{
|
|
LOG((CLOG_DEBUG "motif fill clipboard %d", m_id));
|
|
|
|
// get the Motif clipboard header property from the root window
|
|
Atom target;
|
|
int32_t format;
|
|
std::string data;
|
|
Window root = RootWindow(m_display, DefaultScreen(m_display));
|
|
if (!XWindowsUtil::getWindowProperty(m_display, root, m_atomMotifClipHeader, &data, &target, &format, False)) {
|
|
return;
|
|
}
|
|
|
|
MotifClipHeader header;
|
|
if (data.size() < sizeof(header)) { // check that the header is okay
|
|
return;
|
|
}
|
|
std::memcpy(&header, data.data(), sizeof(header));
|
|
if (header.m_id != MotifClip::Header || header.m_numItems < 1) {
|
|
return;
|
|
}
|
|
|
|
// get the Motif item property from the root window
|
|
static const int buffer_size = 18 + 20;
|
|
char name[buffer_size];
|
|
snprintf(name, buffer_size, "_MOTIF_CLIP_ITEM_%d", header.m_item);
|
|
Atom atomItem = XInternAtom(m_display, name, False);
|
|
data = "";
|
|
if (!XWindowsUtil::getWindowProperty(m_display, root, atomItem, &data, &target, &format, False)) {
|
|
return;
|
|
}
|
|
|
|
MotifClipItem item;
|
|
if (data.size() < sizeof(item)) { // check that the item is okay
|
|
return;
|
|
}
|
|
std::memcpy(&item, data.data(), sizeof(item));
|
|
if (item.m_id != MotifClip::Item || item.m_numFormats - item.m_numDeletedFormats < 1) {
|
|
return;
|
|
}
|
|
|
|
// format list is after static item structure elements
|
|
const int32_t numFormats = item.m_numFormats - item.m_numDeletedFormats;
|
|
auto formats = static_cast<const int32_t *>(static_cast<const void *>(item.m_size + data.data()));
|
|
|
|
// get the available formats
|
|
using MotifFormatMap = std::map<Atom, std::string>;
|
|
MotifFormatMap motifFormats;
|
|
for (int32_t i = 0; i < numFormats; ++i) {
|
|
// get Motif format property from the root window
|
|
snprintf(name, buffer_size, "_MOTIF_CLIP_ITEM_%d", formats[i]);
|
|
Atom atomFormat = XInternAtom(m_display, name, False);
|
|
std::string data;
|
|
if (!XWindowsUtil::getWindowProperty(m_display, root, atomFormat, &data, &target, &format, False)) {
|
|
continue;
|
|
}
|
|
|
|
// check that the format is okay
|
|
MotifClipFormat motifFormat;
|
|
if (data.size() < sizeof(motifFormat)) {
|
|
continue;
|
|
}
|
|
std::memcpy(&motifFormat, data.data(), sizeof(motifFormat));
|
|
if (motifFormat.m_id != MotifClip::Format || motifFormat.m_length < 0 || motifFormat.m_type == None ||
|
|
motifFormat.m_deleted != 0) {
|
|
continue;
|
|
}
|
|
|
|
// save it
|
|
motifFormats.insert(std::make_pair(motifFormat.m_type, data));
|
|
}
|
|
|
|
// try each converter in order (because they're in order of
|
|
// preference).
|
|
for (ConverterList::const_iterator index = m_converters.begin(); index != m_converters.end(); ++index) {
|
|
const IXWindowsClipboardConverter *converter = *index;
|
|
const auto formatID = static_cast<int>(converter->getFormat());
|
|
|
|
// skip already handled targets
|
|
if (m_added[formatID]) {
|
|
continue;
|
|
}
|
|
|
|
// see if atom is in target list
|
|
MotifFormatMap::const_iterator index2 = motifFormats.find(converter->getAtom());
|
|
if (index2 == motifFormats.end()) {
|
|
continue;
|
|
}
|
|
|
|
// get format
|
|
MotifClipFormat motifFormat;
|
|
std::memcpy(&motifFormat, index2->second.data(), sizeof(motifFormat));
|
|
const Atom target = motifFormat.m_type;
|
|
|
|
// get the data (finally)
|
|
Atom actualTarget;
|
|
std::string targetData;
|
|
if (!motifGetSelection(&motifFormat, &actualTarget, &targetData)) {
|
|
LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str()));
|
|
continue;
|
|
}
|
|
|
|
// add to clipboard and note we've done it
|
|
m_data[formatID] = converter->toIClipboard(targetData);
|
|
m_added[formatID] = true;
|
|
LOG((CLOG_DEBUG "added format %d for target %s", format, XWindowsUtil::atomToString(m_display, target).c_str()));
|
|
}
|
|
}
|
|
|
|
bool XWindowsClipboard::motifGetSelection(const MotifClipFormat *format, Atom *actualTarget, std::string *data) const
|
|
{
|
|
// if the current clipboard owner and the owner indicated by the
|
|
// motif clip header are the same then transfer via a property on
|
|
// the root window, otherwise transfer as a normal ICCCM client.
|
|
if (!motifOwnsClipboard()) {
|
|
return icccmGetSelection(format->m_type, actualTarget, data);
|
|
}
|
|
|
|
// use motif way
|
|
// FIXME -- this isn't right. it'll only work if the data is
|
|
// already stored on the root window and only if it fits in a
|
|
// property. motif has some scheme for transferring part by
|
|
// part that i don't know.
|
|
static const int buffer_size = 18 + 20;
|
|
char name[buffer_size];
|
|
snprintf(name, buffer_size, "_MOTIF_CLIP_ITEM_%d", format->m_data);
|
|
Atom target = XInternAtom(m_display, name, False);
|
|
Window root = RootWindow(m_display, DefaultScreen(m_display));
|
|
return XWindowsUtil::getWindowProperty(m_display, root, target, data, actualTarget, nullptr, False);
|
|
}
|
|
|
|
IClipboard::Time XWindowsClipboard::motifGetTime() const
|
|
{
|
|
return icccmGetTime();
|
|
}
|
|
|
|
bool XWindowsClipboard::insertMultipleReply(Window requestor, ::Time time, Atom property)
|
|
{
|
|
// get the requested targets
|
|
Atom target;
|
|
int32_t format;
|
|
std::string data;
|
|
if (!XWindowsUtil::getWindowProperty(m_display, requestor, property, &data, &target, &format, False)) {
|
|
// can't get the requested targets
|
|
return false;
|
|
}
|
|
|
|
// fail if the requested targets isn't of the correct form
|
|
if (format != 32 || target != m_atomAtomPair) {
|
|
return false;
|
|
}
|
|
|
|
// data is a list of atom pairs: target, property
|
|
XWindowsUtil::convertAtomProperty(data);
|
|
auto targets = static_cast<const Atom *>(static_cast<const void *>(data.data()));
|
|
const uint32_t numTargets = data.size() / sizeof(Atom);
|
|
|
|
// add replies for each target
|
|
bool changed = false;
|
|
for (uint32_t i = 0; i < numTargets; i += 2) {
|
|
const Atom target = targets[i + 0];
|
|
const Atom property = targets[i + 1];
|
|
if (!addSimpleRequest(requestor, target, time, property)) {
|
|
// note that we can't perform the requested conversion
|
|
XWindowsUtil::replaceAtomData(data, i, None);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
// update the targets property if we changed it
|
|
if (changed) {
|
|
XWindowsUtil::setWindowProperty(m_display, requestor, property, data.data(), data.size(), target, format);
|
|
}
|
|
|
|
// add reply for MULTIPLE request
|
|
insertReply(new Reply(requestor, m_atomMultiple, time, property, std::string(), None, 32));
|
|
|
|
return true;
|
|
}
|
|
|
|
void XWindowsClipboard::insertReply(Reply *reply)
|
|
{
|
|
assert(reply != nullptr);
|
|
|
|
// note -- we must respond to requests in order if requestor,target,time
|
|
// are the same, otherwise we can use whatever order we like with one
|
|
// exception: each reply in a MULTIPLE reply must be handled in order
|
|
// as well. those replies will almost certainly not share targets so
|
|
// we can't simply use requestor,target,time as map index.
|
|
//
|
|
// instead we'll use just the requestor. that's more restrictive than
|
|
// necessary but we're guaranteed to do things in the right order.
|
|
// note that we could also include the time in the map index and still
|
|
// ensure the right order. but since that'll just make it harder to
|
|
// find the right reply when handling property notify events we stick
|
|
// to just the requestor.
|
|
|
|
const bool newWindow = !m_replies.contains(reply->m_requestor);
|
|
m_replies[reply->m_requestor].push_back(reply);
|
|
|
|
// adjust requestor's event mask if we haven't done so already. we
|
|
// want events in case the window is destroyed or any of its
|
|
// properties change.
|
|
if (newWindow) {
|
|
// note errors while we adjust event masks
|
|
bool error = false;
|
|
{
|
|
XWindowsUtil::ErrorLock lock(m_display, &error);
|
|
|
|
// get and save the current event mask
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(m_display, reply->m_requestor, &attr);
|
|
m_eventMasks[reply->m_requestor] = attr.your_event_mask;
|
|
|
|
// add the events we want
|
|
XSelectInput(m_display, reply->m_requestor, attr.your_event_mask | StructureNotifyMask | PropertyChangeMask);
|
|
}
|
|
|
|
// if we failed then the window has already been destroyed
|
|
if (error) {
|
|
m_replies.erase(reply->m_requestor);
|
|
delete reply;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XWindowsClipboard::pushReplies()
|
|
{
|
|
// send the first reply for each window if that reply hasn't
|
|
// been sent yet.
|
|
for (auto index = m_replies.begin(); index != m_replies.end();) {
|
|
assert(!index->second.empty());
|
|
auto listit = index->second.begin();
|
|
while (listit != index->second.end()) {
|
|
if (!(*listit)->m_replied)
|
|
break;
|
|
++listit;
|
|
}
|
|
if (listit != index->second.end() && !(*listit)->m_replied) {
|
|
pushReplies(index, index->second, listit);
|
|
} else {
|
|
++index;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XWindowsClipboard::pushReplies(ReplyMap::iterator &mapIndex, ReplyList &replies, ReplyList::iterator index)
|
|
{
|
|
Reply *reply = *index;
|
|
while (sendReply(reply)) {
|
|
// reply is complete. discard it and send the next reply,
|
|
// if any.
|
|
index = replies.erase(index);
|
|
delete reply;
|
|
if (index == replies.end()) {
|
|
break;
|
|
}
|
|
reply = *index;
|
|
}
|
|
|
|
// if there are no more replies in the list then remove the list
|
|
// and stop watching the requestor for events.
|
|
if (replies.empty()) {
|
|
XWindowsUtil::ErrorLock lock(m_display);
|
|
Window requestor = mapIndex->first;
|
|
XSelectInput(m_display, requestor, m_eventMasks[requestor]);
|
|
m_replies.erase(mapIndex++);
|
|
m_eventMasks.erase(requestor);
|
|
} else {
|
|
++mapIndex;
|
|
}
|
|
}
|
|
|
|
bool XWindowsClipboard::sendReply(Reply *reply)
|
|
{
|
|
assert(reply != nullptr);
|
|
|
|
// bail out immediately if reply is done
|
|
if (reply->m_done) {
|
|
LOG((
|
|
CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property
|
|
));
|
|
return true;
|
|
}
|
|
|
|
// start in failed state if property is None
|
|
bool failed = (reply->m_property == None);
|
|
if (!failed) {
|
|
LOG(
|
|
(CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target,
|
|
reply->m_property)
|
|
);
|
|
|
|
// send using INCR if already sending incrementally or if reply
|
|
// is too large, otherwise just send it.
|
|
const uint32_t maxRequestSize = 3 * XMaxRequestSize(m_display);
|
|
const bool useINCR = (reply->m_data.size() > maxRequestSize);
|
|
|
|
// send INCR reply if incremental and we haven't replied yet
|
|
if (useINCR && !reply->m_replied) {
|
|
uint32_t size = reply->m_data.size();
|
|
if (!XWindowsUtil::setWindowProperty(
|
|
m_display, reply->m_requestor, reply->m_property, &size, 4, m_atomINCR, 32
|
|
)) {
|
|
failed = true;
|
|
}
|
|
}
|
|
|
|
// send more INCR reply or entire non-incremental reply
|
|
else {
|
|
// how much more data should we send?
|
|
uint32_t size = reply->m_data.size() - reply->m_ptr;
|
|
if (size > maxRequestSize)
|
|
size = maxRequestSize;
|
|
|
|
// send it
|
|
if (!XWindowsUtil::setWindowProperty(
|
|
m_display, reply->m_requestor, reply->m_property, reply->m_data.data() + reply->m_ptr, size,
|
|
reply->m_type, reply->m_format
|
|
)) {
|
|
failed = true;
|
|
} else {
|
|
reply->m_ptr += size;
|
|
|
|
// we've finished the reply if we just sent the zero
|
|
// size incremental chunk or if we're not incremental.
|
|
reply->m_done = (size == 0 || !useINCR);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we've failed then delete the property and say we're done.
|
|
// if we haven't replied yet then we can send a failure notify,
|
|
// otherwise we've failed in the middle of an incremental
|
|
// transfer; i don't know how to cancel that so i'll just send
|
|
// the final zero-length property.
|
|
// FIXME -- how do you gracefully cancel an incremental transfer?
|
|
if (failed) {
|
|
LOG((
|
|
CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property
|
|
));
|
|
reply->m_done = true;
|
|
if (reply->m_property != None) {
|
|
XWindowsUtil::ErrorLock lock(m_display);
|
|
XDeleteProperty(m_display, reply->m_requestor, reply->m_property);
|
|
}
|
|
|
|
if (!reply->m_replied) {
|
|
sendNotify(reply->m_requestor, m_selection, reply->m_target, None, reply->m_time);
|
|
|
|
// don't wait for any reply (because we're not expecting one)
|
|
return true;
|
|
} else {
|
|
static const char dummy = 0;
|
|
XWindowsUtil::setWindowProperty(
|
|
m_display, reply->m_requestor, reply->m_property, &dummy, 0, reply->m_type, reply->m_format
|
|
);
|
|
|
|
// wait for delete notify
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// notification already sended
|
|
if (reply->m_replied) {
|
|
return false;
|
|
}
|
|
|
|
LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)
|
|
);
|
|
reply->m_replied = true;
|
|
|
|
// nothing to log
|
|
if (CLOG->getFilter() < LogLevel::Debug2) {
|
|
sendNotify(
|
|
reply->m_requestor, m_selection, reply->m_target, reply->m_property, static_cast<unsigned int>(reply->m_time)
|
|
);
|
|
|
|
// wait for delete notify
|
|
return false;
|
|
}
|
|
|
|
// dump every property on the requestor window to the debug2
|
|
// log. we've seen what appears to be a bug in lesstif and
|
|
// knowing the properties may help design a workaround, if
|
|
// it becomes necessary.
|
|
XWindowsUtil::ErrorLock lock(m_display);
|
|
int n;
|
|
Atom *props = XListProperties(m_display, reply->m_requestor, &n);
|
|
LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor));
|
|
for (int i = 0; i < n; ++i) {
|
|
Atom target;
|
|
std::string data;
|
|
char *name = XGetAtomName(m_display, props[i]);
|
|
if (!XWindowsUtil::getWindowProperty(m_display, reply->m_requestor, props[i], &data, &target, nullptr, False)) {
|
|
LOG((CLOG_DEBUG2 " %s: <can't read property>", name));
|
|
} else {
|
|
// convert to hex if contains non ascii symbols
|
|
if (std::ranges::find_if(data, [](const unsigned char &c) { return c < 32 || c > 126; }) != data.end()) {
|
|
const std::string hex_digits = "0123456789abcdef";
|
|
std::string tmp;
|
|
tmp.reserve(data.length() * 3);
|
|
std::ranges::for_each(data, [&hex_digits, &tmp](const unsigned char &c) {
|
|
tmp += hex_digits[c >> 16];
|
|
tmp += hex_digits[c & 15];
|
|
tmp += ' ';
|
|
});
|
|
data = tmp;
|
|
}
|
|
char *type = XGetAtomName(m_display, target);
|
|
LOG((CLOG_DEBUG2 " %s (%s): %s", name, type, data.c_str()));
|
|
if (type != nullptr) {
|
|
XFree(type);
|
|
}
|
|
}
|
|
if (name != nullptr) {
|
|
XFree(name);
|
|
}
|
|
}
|
|
if (props != nullptr) {
|
|
XFree(props);
|
|
}
|
|
|
|
sendNotify(reply->m_requestor, m_selection, reply->m_target, reply->m_property, reply->m_time);
|
|
|
|
// wait for delete notify
|
|
return false;
|
|
}
|
|
|
|
void XWindowsClipboard::clearReplies()
|
|
{
|
|
for (auto index = m_replies.begin(); index != m_replies.end(); ++index) {
|
|
clearReplies(index->second);
|
|
}
|
|
m_replies.clear();
|
|
m_eventMasks.clear();
|
|
}
|
|
|
|
void XWindowsClipboard::clearReplies(ReplyList &replies)
|
|
{
|
|
for (auto reply : replies) {
|
|
delete reply;
|
|
}
|
|
replies.clear();
|
|
}
|
|
|
|
void XWindowsClipboard::sendNotify(Window requestor, Atom selection, Atom target, Atom property, Time time)
|
|
{
|
|
XEvent event;
|
|
event.xselection.type = SelectionNotify;
|
|
event.xselection.display = m_display;
|
|
event.xselection.requestor = requestor;
|
|
event.xselection.selection = selection;
|
|
event.xselection.target = target;
|
|
event.xselection.property = property;
|
|
event.xselection.time = time;
|
|
XWindowsUtil::ErrorLock lock(m_display);
|
|
XSendEvent(m_display, requestor, False, 0, &event);
|
|
}
|
|
|
|
bool XWindowsClipboard::wasOwnedAtTime(::Time time) const
|
|
{
|
|
// not owned if we've never owned the selection
|
|
checkCache();
|
|
if (m_timeOwned == 0) {
|
|
return false;
|
|
}
|
|
|
|
// if time is CurrentTime then return true if we still own the
|
|
// selection and false if we do not. else if we still own the
|
|
// selection then get the current time, otherwise use
|
|
// m_timeLost as the end time.
|
|
Time lost = m_timeLost;
|
|
if (m_timeLost == 0) {
|
|
if (time == CurrentTime) {
|
|
return true;
|
|
} else {
|
|
lost = XWindowsUtil::getCurrentTime(m_display, m_window);
|
|
}
|
|
} else {
|
|
if (time == CurrentTime) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// compare time to range
|
|
Time duration = lost - m_timeOwned;
|
|
Time when = time - m_timeOwned;
|
|
return (/*when >= 0 &&*/ when <= duration);
|
|
}
|
|
|
|
Atom XWindowsClipboard::getTargetsData(std::string &data, int *format) const
|
|
{
|
|
assert(format != nullptr);
|
|
|
|
// add standard targets
|
|
XWindowsUtil::appendAtomData(data, m_atomTargets);
|
|
XWindowsUtil::appendAtomData(data, m_atomMultiple);
|
|
XWindowsUtil::appendAtomData(data, m_atomTimestamp);
|
|
|
|
// add targets we can convert to
|
|
for (auto index = m_converters.begin(); index != m_converters.end(); ++index) {
|
|
const IXWindowsClipboardConverter *converter = *index;
|
|
|
|
// skip formats we don't have
|
|
if (m_added[static_cast<int>(converter->getFormat())]) {
|
|
XWindowsUtil::appendAtomData(data, converter->getAtom());
|
|
}
|
|
}
|
|
|
|
*format = 32;
|
|
return m_atomAtom;
|
|
}
|
|
|
|
Atom XWindowsClipboard::getTimestampData(std::string &data, int *format) const
|
|
{
|
|
assert(format != nullptr);
|
|
|
|
checkCache();
|
|
XWindowsUtil::appendTimeData(data, m_timeOwned);
|
|
*format = 32;
|
|
return m_atomInteger;
|
|
}
|
|
|
|
//
|
|
// XWindowsClipboard::CICCCMGetClipboard
|
|
//
|
|
|
|
XWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(Window requestor, Time time, Atom property)
|
|
: m_requestor(requestor),
|
|
m_time(time),
|
|
m_property(property)
|
|
{
|
|
}
|
|
|
|
bool XWindowsClipboard::CICCCMGetClipboard::readClipboard(
|
|
Display *display, Atom selection, Atom target, Atom *actualTarget, std::string *data
|
|
)
|
|
{
|
|
assert(actualTarget != nullptr);
|
|
assert(data != nullptr);
|
|
|
|
LOG(
|
|
(CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", XWindowsUtil::atomToString(display, selection).c_str(),
|
|
XWindowsUtil::atomToString(display, target).c_str(), m_requestor)
|
|
);
|
|
|
|
m_atomNone = XInternAtom(display, "NONE", False);
|
|
m_atomIncr = XInternAtom(display, "INCR", False);
|
|
|
|
// save output pointers
|
|
m_actualTarget = actualTarget;
|
|
m_data = data;
|
|
|
|
// assume failure
|
|
*m_actualTarget = None;
|
|
*m_data = "";
|
|
|
|
// delete target property
|
|
XDeleteProperty(display, m_requestor, m_property);
|
|
|
|
// select window for property changes
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(display, m_requestor, &attr);
|
|
XSelectInput(display, m_requestor, attr.your_event_mask | PropertyChangeMask);
|
|
|
|
// request data conversion
|
|
XConvertSelection(display, selection, target, m_property, m_requestor, m_time);
|
|
|
|
// synchronize with server before we start following timeout countdown
|
|
XSync(display, False);
|
|
|
|
// Xlib inexplicably omits the ability to wait for an event with
|
|
// a timeout. (it's inexplicable because there's no portable way
|
|
// to do it.) we'll poll until we have what we're looking for or
|
|
// a timeout expires. we use a timeout so we don't get locked up
|
|
// by badly behaved selection owners.
|
|
XEvent xevent;
|
|
std::vector<XEvent> events;
|
|
Stopwatch timeout(false); // timer not stopped, not triggered
|
|
static const double s_timeout = 0.25; // FIXME -- is this too short?
|
|
bool noWait = false;
|
|
while (!m_done && !m_failed) {
|
|
// fail if timeout has expired
|
|
if (timeout.getTime() >= s_timeout) {
|
|
m_failed = true;
|
|
break;
|
|
}
|
|
|
|
// process events if any otherwise sleep
|
|
if (noWait || XPending(display) > 0) {
|
|
while (!m_done && !m_failed && (noWait || XPending(display) > 0)) {
|
|
// fail if timeout has expired
|
|
if (timeout.getTime() >= s_timeout) {
|
|
m_failed = true;
|
|
break;
|
|
}
|
|
|
|
XNextEvent(display, &xevent);
|
|
if (!processEvent(display, &xevent)) {
|
|
// not processed so save it
|
|
events.push_back(xevent);
|
|
} else {
|
|
// reset timer since we've made some progress
|
|
timeout.reset();
|
|
|
|
// don't sleep anymore, just block waiting for events.
|
|
// we're assuming here that the clipboard owner will
|
|
// complete the protocol correctly. if we continue to
|
|
// sleep we'll get very bad performance.
|
|
noWait = true;
|
|
}
|
|
}
|
|
} else {
|
|
Arch::sleep(0.01);
|
|
}
|
|
}
|
|
|
|
// put unprocessed events back
|
|
for (uint32_t i = events.size(); i > 0; --i) {
|
|
XPutBackEvent(display, &events[i - 1]);
|
|
}
|
|
|
|
// restore mask
|
|
XSelectInput(display, m_requestor, attr.your_event_mask);
|
|
|
|
// return success or failure
|
|
LOG((CLOG_DEBUG1 "request %s after %fs", m_failed ? "failed" : "succeeded", timeout.getTime()));
|
|
return !m_failed;
|
|
}
|
|
|
|
bool XWindowsClipboard::CICCCMGetClipboard::processEvent(Display *display, const XEvent *xevent)
|
|
{
|
|
// process event
|
|
switch (xevent->type) {
|
|
case DestroyNotify:
|
|
if (xevent->xdestroywindow.window == m_requestor) {
|
|
m_failed = true;
|
|
return true;
|
|
}
|
|
|
|
// not interested
|
|
return false;
|
|
|
|
case SelectionNotify:
|
|
if (xevent->xselection.requestor == m_requestor) {
|
|
// done if we can't convert
|
|
if (xevent->xselection.property == None || xevent->xselection.property == m_atomNone) {
|
|
m_done = true;
|
|
return true;
|
|
}
|
|
|
|
// proceed if conversion successful
|
|
else if (xevent->xselection.property == m_property) {
|
|
m_reading = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// otherwise not interested
|
|
return false;
|
|
|
|
case PropertyNotify:
|
|
// proceed if conversion successful and we're receiving more data
|
|
if (xevent->xproperty.window == m_requestor && xevent->xproperty.atom == m_property &&
|
|
xevent->xproperty.state == PropertyNewValue) {
|
|
if (!m_reading) {
|
|
// we haven't gotten the SelectionNotify yet
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// otherwise not interested
|
|
return false;
|
|
|
|
default:
|
|
// not interested
|
|
return false;
|
|
}
|
|
|
|
// get the data from the property
|
|
Atom target;
|
|
const std::string::size_type oldSize = m_data->size();
|
|
if (!XWindowsUtil::getWindowProperty(display, m_requestor, m_property, m_data, &target, nullptr, True)) {
|
|
// unable to read property
|
|
m_failed = true;
|
|
return true;
|
|
}
|
|
|
|
// note if incremental. if we're already incremental then the
|
|
// selection owner is busted. if the INCR property has no size
|
|
// then the selection owner is busted.
|
|
if (target == m_atomIncr) {
|
|
if (m_incr || m_data->size() == oldSize) {
|
|
m_failed = true;
|
|
m_error = true;
|
|
} else {
|
|
m_incr = true;
|
|
|
|
// discard INCR data
|
|
*m_data = "";
|
|
}
|
|
}
|
|
|
|
// handle incremental chunks
|
|
else if (m_incr) {
|
|
// if first incremental chunk then save target
|
|
if (oldSize == 0) {
|
|
LOG((CLOG_DEBUG1 " INCR first chunk, target %s", XWindowsUtil::atomToString(display, target).c_str()));
|
|
*m_actualTarget = target;
|
|
}
|
|
|
|
// secondary chunks must have the same target
|
|
else {
|
|
if (target != *m_actualTarget) {
|
|
LOG((CLOG_WARN " INCR target mismatch"));
|
|
m_failed = true;
|
|
m_error = true;
|
|
}
|
|
}
|
|
|
|
// note if this is the final chunk
|
|
if (m_data->size() == oldSize) {
|
|
LOG((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size()));
|
|
m_done = true;
|
|
}
|
|
}
|
|
|
|
// not incremental; save the target.
|
|
else {
|
|
LOG((CLOG_DEBUG1 " target %s", XWindowsUtil::atomToString(display, target).c_str()));
|
|
*m_actualTarget = target;
|
|
m_done = true;
|
|
}
|
|
|
|
// this event has been processed
|
|
LOGC(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size()));
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// XWindowsClipboard::Reply
|
|
//
|
|
|
|
XWindowsClipboard::Reply::Reply(Window requestor, Atom target, ::Time time)
|
|
: m_requestor(requestor),
|
|
m_target(target),
|
|
m_time(time),
|
|
m_property(None),
|
|
m_type(None),
|
|
m_format(32)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
XWindowsClipboard::Reply::Reply(
|
|
Window requestor, Atom target, ::Time time, Atom property, const std::string &data, Atom type, int format
|
|
)
|
|
: m_requestor(requestor),
|
|
m_target(target),
|
|
m_time(time),
|
|
m_property(property),
|
|
m_data(data),
|
|
m_type(type),
|
|
m_format(format)
|
|
{
|
|
// do nothing
|
|
}
|