mirror of
https://github.com/debauchee/barrier.git
synced 2024-11-30 21:39:19 +03:00
fee4095624
synergy.cpp and server.cpp into cmd/synergyd as synergyd.cpp. Moved and renamed related files. Moved remaining source files into lib/.... Modified and added makefiles as appropriate. Result is that library files are under lib with each library in its own directory and program files are under cmd with each command in its own directory.
1443 lines
37 KiB
C++
1443 lines
37 KiB
C++
#include "CXWindowsClipboard.h"
|
|
#include "CXWindowsClipboardTextConverter.h"
|
|
#include "CXWindowsClipboardUCS2Converter.h"
|
|
#include "CXWindowsClipboardUTF8Converter.h"
|
|
#include "CXWindowsUtil.h"
|
|
#include "CThread.h"
|
|
#include "CLog.h"
|
|
#include "CStopwatch.h"
|
|
#include "stdvector.h"
|
|
#include <cstdio>
|
|
#include <X11/Xatom.h>
|
|
|
|
//
|
|
// CXWindowsClipboard
|
|
//
|
|
|
|
CXWindowsClipboard::CXWindowsClipboard(Display* display,
|
|
Window window, ClipboardID id) :
|
|
m_display(display),
|
|
m_window(window),
|
|
m_id(id),
|
|
m_open(false),
|
|
m_time(0),
|
|
m_owner(false),
|
|
m_timeOwned(0),
|
|
m_timeLost(0)
|
|
{
|
|
// 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;
|
|
|
|
case kClipboardSelection:
|
|
default:
|
|
m_selection = XA_PRIMARY;
|
|
break;
|
|
}
|
|
|
|
// add converters, most desired first
|
|
m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
|
|
"text/plain;charset=UTF-8"));
|
|
m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
|
|
"UTF8_STRING"));
|
|
m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
|
|
"text/plain;charset=ISO-10646-UCS-2"));
|
|
m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
|
|
"text/unicode"));
|
|
m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
|
|
"text/plain"));
|
|
m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
|
|
"STRING"));
|
|
|
|
// we have no data
|
|
clearCache();
|
|
}
|
|
|
|
CXWindowsClipboard::~CXWindowsClipboard()
|
|
{
|
|
clearReplies();
|
|
clearConverters();
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::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
|
|
CXWindowsClipboard::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 %d by 0x%08x (property=%d)", m_selection, target, requestor, property));
|
|
if (wasOwnedAtTime(time)) {
|
|
if (target == m_atomMultiple) {
|
|
// add a multiple request. property may not be None
|
|
// according to ICCCM.
|
|
if (property != None) {
|
|
success = insertMultipleReply(requestor, time, property);
|
|
}
|
|
}
|
|
else {
|
|
addSimpleRequest(requestor, target, time, property);
|
|
|
|
// addSimpleRequest() will have already handled failure
|
|
success = true;
|
|
}
|
|
}
|
|
else {
|
|
log((CLOG_DEBUG1 "failed, not owned at time %d", time));
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
// send failure
|
|
log((CLOG_DEBUG1 "failed"));
|
|
insertReply(new CReply(requestor, target, time));
|
|
}
|
|
|
|
// send notifications that are pending
|
|
pushReplies();
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::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
|
|
CString 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 {
|
|
IXWindowsClipboardConverter* converter = getConverter(target);
|
|
if (converter != NULL) {
|
|
IClipboard::EFormat clipboardFormat = converter->getFormat();
|
|
if (m_added[clipboardFormat]) {
|
|
try {
|
|
data = converter->fromIClipboard(m_data[clipboardFormat]);
|
|
format = converter->getDataSize();
|
|
type = converter->getAtom();
|
|
}
|
|
catch (...) {
|
|
// ignore -- cannot convert
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type != None) {
|
|
// success
|
|
log((CLOG_DEBUG1 "success"));
|
|
insertReply(new CReply(requestor, target, time,
|
|
property, data, type, format));
|
|
return true;
|
|
}
|
|
else {
|
|
// failure
|
|
log((CLOG_DEBUG1 "failed"));
|
|
insertReply(new CReply(requestor, target, time));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::processRequest(Window requestor,
|
|
::Time /*time*/, Atom property)
|
|
{
|
|
CReplyMap::iterator index = m_replies.find(requestor);
|
|
if (index == m_replies.end()) {
|
|
// unknown requestor window
|
|
return false;
|
|
}
|
|
log((CLOG_DEBUG1 "received property %d delete from 0x08%x", property, requestor));
|
|
|
|
// find the property in the known requests. it should be the
|
|
// first property but we'll check 'em all if we have to.
|
|
CReplyList& replies = index->second;
|
|
for (CReplyList::iterator index2 = replies.begin();
|
|
index2 != replies.end(); ++index2) {
|
|
CReply* 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
|
|
CXWindowsClipboard::destroyRequest(Window requestor)
|
|
{
|
|
CReplyMap::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
|
|
CXWindowsClipboard::getWindow() const
|
|
{
|
|
return m_window;
|
|
}
|
|
|
|
Atom
|
|
CXWindowsClipboard::getSelection() const
|
|
{
|
|
return m_selection;
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::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_DEBUG "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
|
|
CXWindowsClipboard::add(EFormat format, const CString& data)
|
|
{
|
|
assert(m_open);
|
|
assert(m_owner);
|
|
|
|
log((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
|
|
|
|
m_data[format] = data;
|
|
m_added[format] = true;
|
|
|
|
// FIXME -- set motif clipboard item?
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::open(Time time) const
|
|
{
|
|
assert(!m_open);
|
|
|
|
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
|
|
CXWindowsClipboard::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
|
|
CXWindowsClipboard::getTime() const
|
|
{
|
|
checkCache();
|
|
return m_timeOwned;
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::has(EFormat format) const
|
|
{
|
|
assert(m_open);
|
|
|
|
fillCache();
|
|
return m_added[format];
|
|
}
|
|
|
|
CString
|
|
CXWindowsClipboard::get(EFormat format) const
|
|
{
|
|
assert(m_open);
|
|
|
|
fillCache();
|
|
return m_data[format];
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::clearConverters()
|
|
{
|
|
for (ConverterList::iterator index = m_converters.begin();
|
|
index != m_converters.end(); ++index) {
|
|
delete *index;
|
|
}
|
|
m_converters.clear();
|
|
}
|
|
|
|
IXWindowsClipboardConverter*
|
|
CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
|
|
{
|
|
IXWindowsClipboardConverter* converter = NULL;
|
|
for (ConverterList::const_iterator index = m_converters.begin();
|
|
index != m_converters.end(); ++index) {
|
|
converter = *index;
|
|
if (converter->getAtom() == target) {
|
|
break;
|
|
}
|
|
}
|
|
if (converter == NULL) {
|
|
log((CLOG_DEBUG1 " no converter for target %d", target));
|
|
return NULL;
|
|
}
|
|
|
|
// optionally skip already handled targets
|
|
if (onlyIfNotAdded) {
|
|
if (m_added[converter->getFormat()]) {
|
|
log((CLOG_DEBUG1 " skipping handled format %d", converter->getFormat()));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return converter;
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::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
|
|
CXWindowsClipboard::clearCache() const
|
|
{
|
|
const_cast<CXWindowsClipboard*>(this)->doClearCache();
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::doClearCache()
|
|
{
|
|
m_checkCache = false;
|
|
m_cached = false;
|
|
for (SInt32 index = 0; index < kNumFormats; ++index) {
|
|
m_data[index] = "";
|
|
m_added[index] = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::fillCache() const
|
|
{
|
|
// get the selection data if not already cached
|
|
checkCache();
|
|
if (!m_cached) {
|
|
const_cast<CXWindowsClipboard*>(this)->doFillCache();
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::doFillCache()
|
|
{
|
|
if (m_motif) {
|
|
motifFillCache();
|
|
}
|
|
else {
|
|
icccmFillCache();
|
|
}
|
|
m_checkCache = false;
|
|
m_cached = true;
|
|
m_cacheTime = m_timeOwned;
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::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.
|
|
const Atom atomTargets = m_atomTargets;
|
|
Atom target;
|
|
CString data;
|
|
if (!icccmGetSelection(atomTargets, &target, &data) ||
|
|
target != m_atomAtom) {
|
|
log((CLOG_DEBUG1 "selection doesn't support TARGETS"));
|
|
data = "";
|
|
|
|
target = XA_STRING;
|
|
data.append(reinterpret_cast<char*>(&target), sizeof(target));
|
|
}
|
|
|
|
// try each converter in order (because they're in order of
|
|
// preference).
|
|
const Atom* targets = reinterpret_cast<const Atom*>(data.data());
|
|
const UInt32 numTargets = data.size() / sizeof(Atom);
|
|
for (ConverterList::const_iterator index = m_converters.begin();
|
|
index != m_converters.end(); ++index) {
|
|
IXWindowsClipboardConverter* converter = *index;
|
|
|
|
// skip already handled targets
|
|
if (m_added[converter->getFormat()]) {
|
|
continue;
|
|
}
|
|
|
|
// see if atom is in target list
|
|
Atom target = None;
|
|
for (UInt32 i = 0; i < numTargets; ++i) {
|
|
if (converter->getAtom() == targets[i]) {
|
|
target = targets[i];
|
|
break;
|
|
}
|
|
}
|
|
if (target == None) {
|
|
continue;
|
|
}
|
|
|
|
// get the data
|
|
Atom actualTarget;
|
|
CString targetData;
|
|
if (!icccmGetSelection(target, &actualTarget, &targetData)) {
|
|
log((CLOG_DEBUG1 " no data for target %d", target));
|
|
continue;
|
|
}
|
|
|
|
// add to clipboard and note we've done it
|
|
IClipboard::EFormat format = converter->getFormat();
|
|
m_data[format] = converter->toIClipboard(targetData);
|
|
m_added[format] = true;
|
|
log((CLOG_DEBUG " added format %d for target %d", format, target));
|
|
}
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::icccmGetSelection(Atom target,
|
|
Atom* actualTarget, CString* data) const
|
|
{
|
|
assert(actualTarget != NULL);
|
|
assert(data != NULL);
|
|
|
|
// request data conversion
|
|
CICCCMGetClipboard getter(m_window, m_time, m_atomData);
|
|
if (!getter.readClipboard(m_display, m_selection,
|
|
target, actualTarget, data)) {
|
|
log((CLOG_DEBUG1 "can't get data for selection target %d", target));
|
|
logc(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner"));
|
|
return false;
|
|
}
|
|
else if (*actualTarget == None) {
|
|
log((CLOG_DEBUG1 "selection conversion failed for target %d", target));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
IClipboard::Time
|
|
CXWindowsClipboard::icccmGetTime() const
|
|
{
|
|
Atom actualTarget;
|
|
CString data;
|
|
if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) &&
|
|
actualTarget == m_atomInteger) {
|
|
Time time = *reinterpret_cast<const Time*>(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
|
|
CXWindowsClipboard::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 = CXWindowsUtil::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
|
|
CXWindowsClipboard::motifUnlockClipboard() const
|
|
{
|
|
log((CLOG_DEBUG1 "unlocked motif clipboard"));
|
|
|
|
// fail if we don't own the lock
|
|
Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
|
|
if (lockOwner != m_window) {
|
|
return;
|
|
}
|
|
|
|
// release lock
|
|
Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
|
|
XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::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;
|
|
SInt32 format;
|
|
CString data;
|
|
Window root = RootWindow(m_display, DefaultScreen(m_display));
|
|
if (!CXWindowsUtil::getWindowProperty(m_display, root,
|
|
m_atomMotifClipHeader,
|
|
&data, &target, &format, False)) {
|
|
return false;
|
|
}
|
|
|
|
// check the owner window against the current clipboard owner
|
|
const CMotifClipHeader* header =
|
|
reinterpret_cast<const CMotifClipHeader*>(data.data());
|
|
if (data.size() >= sizeof(CMotifClipHeader) &&
|
|
header->m_id == kMotifClipHeader) {
|
|
if (header->m_selectionOwner == owner) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::motifFillCache()
|
|
{
|
|
log((CLOG_DEBUG "Motif fill clipboard %d", m_id));
|
|
|
|
// get the Motif clipboard header property from the root window
|
|
Atom target;
|
|
SInt32 format;
|
|
CString data;
|
|
Window root = RootWindow(m_display, DefaultScreen(m_display));
|
|
if (!CXWindowsUtil::getWindowProperty(m_display, root,
|
|
m_atomMotifClipHeader,
|
|
&data, &target, &format, False)) {
|
|
return;
|
|
}
|
|
|
|
// check that the header is okay
|
|
const CMotifClipHeader* header =
|
|
reinterpret_cast<const CMotifClipHeader*>(data.data());
|
|
if (data.size() < sizeof(CMotifClipHeader) ||
|
|
header->m_id != kMotifClipHeader ||
|
|
header->m_numItems < 1) {
|
|
return;
|
|
}
|
|
|
|
// get the Motif item property from the root window
|
|
char name[18 + 20];
|
|
sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item);
|
|
Atom atomItem = XInternAtom(m_display, name, False);
|
|
data = "";
|
|
if (!CXWindowsUtil::getWindowProperty(m_display, root,
|
|
atomItem, &data,
|
|
&target, &format, False)) {
|
|
return;
|
|
}
|
|
|
|
// check that the item is okay
|
|
const CMotifClipItem* item =
|
|
reinterpret_cast<const CMotifClipItem*>(data.data());
|
|
if (data.size() < sizeof(CMotifClipItem) ||
|
|
item->m_id != kMotifClipItem ||
|
|
item->m_numFormats - item->m_numDeletedFormats < 1) {
|
|
return;
|
|
}
|
|
|
|
// format list is after static item structure elements
|
|
const SInt32 numFormats = item->m_numFormats - item->m_numDeletedFormats;
|
|
const SInt32* formats = reinterpret_cast<const SInt32*>(item->m_size +
|
|
reinterpret_cast<const char*>(data.data()));
|
|
|
|
// get the available formats
|
|
typedef std::map<Atom, CString> CMotifFormatMap;
|
|
CMotifFormatMap motifFormats;
|
|
for (SInt32 i = 0; i < numFormats; ++i) {
|
|
// get Motif format property from the root window
|
|
sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]);
|
|
Atom atomFormat = XInternAtom(m_display, name, False);
|
|
CString data;
|
|
if (!CXWindowsUtil::getWindowProperty(m_display, root,
|
|
atomFormat, &data,
|
|
&target, &format, False)) {
|
|
continue;
|
|
}
|
|
|
|
// check that the format is okay
|
|
const CMotifClipFormat* motifFormat =
|
|
reinterpret_cast<const CMotifClipFormat*>(data.data());
|
|
if (data.size() < sizeof(CMotifClipFormat) ||
|
|
motifFormat->m_id != kMotifClipFormat ||
|
|
motifFormat->m_length < 0 ||
|
|
motifFormat->m_type == None ||
|
|
motifFormat->m_deleted != 0) {
|
|
continue;
|
|
}
|
|
|
|
// save it
|
|
motifFormats.insert(std::make_pair(motifFormat->m_type, data));
|
|
}
|
|
const UInt32 numMotifFormats = motifFormats.size();
|
|
|
|
// 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) {
|
|
IXWindowsClipboardConverter* converter = *index;
|
|
|
|
// skip already handled targets
|
|
if (m_added[converter->getFormat()]) {
|
|
continue;
|
|
}
|
|
|
|
// see if atom is in target list
|
|
CMotifFormatMap::const_iterator index =
|
|
motifFormats.find(converter->getAtom());
|
|
if (index == motifFormats.end()) {
|
|
continue;
|
|
}
|
|
|
|
// get format
|
|
const CMotifClipFormat* motifFormat =
|
|
reinterpret_cast<const CMotifClipFormat*>(
|
|
index->second.data());
|
|
const Atom target = motifFormat->m_type;
|
|
|
|
// get the data (finally)
|
|
Atom actualTarget;
|
|
CString targetData;
|
|
if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) {
|
|
log((CLOG_DEBUG1 " no data for target %d", target));
|
|
continue;
|
|
}
|
|
|
|
// add to clipboard and note we've done it
|
|
IClipboard::EFormat format = converter->getFormat();
|
|
m_data[format] = converter->toIClipboard(targetData);
|
|
m_added[format] = true;
|
|
log((CLOG_DEBUG " added format %d for target %d", format, target));
|
|
}
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format,
|
|
Atom* actualTarget, CString* 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.
|
|
char name[18 + 20];
|
|
sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data);
|
|
Atom target = XInternAtom(m_display, name, False);
|
|
Window root = RootWindow(m_display, DefaultScreen(m_display));
|
|
return CXWindowsUtil::getWindowProperty(m_display, root,
|
|
target, data,
|
|
actualTarget, NULL, False);
|
|
}
|
|
|
|
IClipboard::Time
|
|
CXWindowsClipboard::motifGetTime() const
|
|
{
|
|
return icccmGetTime();
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::insertMultipleReply(Window requestor,
|
|
::Time time, Atom property)
|
|
{
|
|
// get the requested targets
|
|
Atom target;
|
|
SInt32 format;
|
|
CString data;
|
|
if (!CXWindowsUtil::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
|
|
const Atom* targets = reinterpret_cast<const Atom*>(data.data());
|
|
const UInt32 numTargets = data.size() / sizeof(Atom);
|
|
|
|
// add replies for each target
|
|
bool changed = false;
|
|
for (UInt32 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
|
|
static const Atom none = None;
|
|
data.replace(i * sizeof(Atom), sizeof(Atom),
|
|
reinterpret_cast<const char*>(&none),
|
|
sizeof(Atom));
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
// update the targets property if we changed it
|
|
if (changed) {
|
|
CXWindowsUtil::setWindowProperty(m_display, requestor,
|
|
property, data.data(), data.size(),
|
|
target, format);
|
|
}
|
|
|
|
// add reply for MULTIPLE request
|
|
insertReply(new CReply(requestor, m_atomMultiple,
|
|
time, property, CString(), None, 32));
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::insertReply(CReply* reply)
|
|
{
|
|
assert(reply != NULL);
|
|
|
|
// 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.count(reply->m_requestor) == 0);
|
|
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;
|
|
CXWindowsUtil::CErrorLock 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
|
|
CXWindowsClipboard::pushReplies()
|
|
{
|
|
// send the first reply for each window if that reply hasn't
|
|
// been sent yet.
|
|
for (CReplyMap::iterator index = m_replies.begin();
|
|
index != m_replies.end(); ++index) {
|
|
assert(!index->second.empty());
|
|
if (!index->second.front()->m_replied) {
|
|
pushReplies(index, index->second, index->second.begin());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::pushReplies(CReplyMap::iterator mapIndex,
|
|
CReplyList& replies, CReplyList::iterator index)
|
|
{
|
|
CReply* 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()) {
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
Window requestor = mapIndex->first;
|
|
XSelectInput(m_display, requestor, m_eventMasks[requestor]);
|
|
m_replies.erase(mapIndex);
|
|
m_eventMasks.erase(requestor);
|
|
}
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::sendReply(CReply* reply)
|
|
{
|
|
assert(reply != NULL);
|
|
|
|
// 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 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 size = reply->m_data.size();
|
|
if (!CXWindowsUtil::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 size = reply->m_data.size() - reply->m_ptr;
|
|
if (size > maxRequestSize)
|
|
size = maxRequestSize;
|
|
|
|
// send it
|
|
if (!CXWindowsUtil::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) {
|
|
CXWindowsUtil::CErrorLock 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;
|
|
CXWindowsUtil::setWindowProperty(m_display,
|
|
reply->m_requestor, reply->m_property,
|
|
&dummy,
|
|
0,
|
|
reply->m_type, reply->m_format);
|
|
|
|
// wait for delete notify
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// send notification if we haven't yet
|
|
if (!reply->m_replied) {
|
|
log((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
|
|
reply->m_replied = true;
|
|
|
|
// HACK -- work around apparent bug in lesstif, which doesn't
|
|
// wait around for the SelectionNotify then gets confused when
|
|
// it sees it the next time it requests the selection. if it
|
|
// looks like a lesstif requestor window then don't send the
|
|
// SelectionNotify. it looks like a lesstif requestor if:
|
|
// it has a _MOTIF_CLIP_LOCK_ACCESS_VALID property
|
|
// it does not have a GDK_SELECTION property
|
|
CString dummy;
|
|
if (m_id != kClipboardClipboard ||
|
|
!CXWindowsUtil::getWindowProperty(m_display,
|
|
reply->m_requestor,
|
|
m_atomMotifClipAccess,
|
|
&dummy, NULL, NULL, False) ||
|
|
CXWindowsUtil::getWindowProperty(m_display,
|
|
reply->m_requestor,
|
|
m_atomGDKSelection,
|
|
&dummy, NULL, NULL, False)) {
|
|
sendNotify(reply->m_requestor, m_selection,
|
|
reply->m_target, reply->m_property,
|
|
reply->m_time);
|
|
}
|
|
}
|
|
|
|
// wait for delete notify
|
|
return false;
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::clearReplies()
|
|
{
|
|
for (CReplyMap::iterator index = m_replies.begin();
|
|
index != m_replies.end(); ++index) {
|
|
clearReplies(index->second);
|
|
}
|
|
m_replies.clear();
|
|
m_eventMasks.clear();
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::clearReplies(CReplyList& replies)
|
|
{
|
|
for (CReplyList::iterator index = replies.begin();
|
|
index != replies.end(); ++index) {
|
|
delete *index;
|
|
}
|
|
replies.clear();
|
|
}
|
|
|
|
void
|
|
CXWindowsClipboard::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;
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
XSendEvent(m_display, requestor, False, 0, &event);
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::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 = CXWindowsUtil::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
|
|
CXWindowsClipboard::getTargetsData(CString& data, int* format) const
|
|
{
|
|
assert(format != NULL);
|
|
|
|
// add standard targets
|
|
Atom atom;
|
|
atom = m_atomTargets;
|
|
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
|
|
atom = m_atomMultiple;
|
|
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
|
|
atom = m_atomTimestamp;
|
|
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
|
|
|
|
// add targets we can convert to
|
|
for (ConverterList::const_iterator index = m_converters.begin();
|
|
index != m_converters.end(); ++index) {
|
|
IXWindowsClipboardConverter* converter = *index;
|
|
|
|
// skip formats we don't have
|
|
if (m_added[converter->getFormat()]) {
|
|
atom = converter->getAtom();
|
|
data.append(reinterpret_cast<char*>(&atom), sizeof(Atom));
|
|
}
|
|
}
|
|
|
|
*format = 32;
|
|
return m_atomAtom;
|
|
}
|
|
|
|
Atom
|
|
CXWindowsClipboard::getTimestampData(CString& data, int* format) const
|
|
{
|
|
assert(format != NULL);
|
|
|
|
assert(sizeof(m_timeOwned) == 4);
|
|
checkCache();
|
|
data.append(reinterpret_cast<const char*>(&m_timeOwned), 4);
|
|
*format = 32;
|
|
return m_atomInteger;
|
|
}
|
|
|
|
|
|
//
|
|
// CXWindowsClipboard::CICCCMGetClipboard
|
|
//
|
|
|
|
CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(
|
|
Window requestor, Time time, Atom property) :
|
|
m_requestor(requestor),
|
|
m_time(time),
|
|
m_property(property),
|
|
m_incr(false),
|
|
m_failed(false),
|
|
m_done(false),
|
|
m_reading(false),
|
|
m_data(NULL),
|
|
m_actualTarget(NULL),
|
|
m_error(false)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
|
|
Atom selection, Atom target, Atom* actualTarget, CString* data)
|
|
{
|
|
assert(actualTarget != NULL);
|
|
assert(data != NULL);
|
|
|
|
log((CLOG_DEBUG1 "request selection=%d, target=%d, window=%x", selection, target, m_requestor));
|
|
|
|
// 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;
|
|
CStopwatch timeout(true);
|
|
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)) {
|
|
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 {
|
|
CThread::sleep(0.01);
|
|
}
|
|
}
|
|
|
|
// put unprocessed events back
|
|
for (UInt32 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", m_failed ? "failed" : "succeeded"));
|
|
return !m_failed;
|
|
}
|
|
|
|
bool
|
|
CXWindowsClipboard::CICCCMGetClipboard::processEvent(
|
|
Display* display, 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) {
|
|
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 CString::size_type oldSize = m_data->size();
|
|
if (!CXWindowsUtil::getWindowProperty(display, m_requestor,
|
|
m_property, m_data, &target, NULL, 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 == XInternAtom(display, "INCR", False)) {
|
|
if (m_incr) {
|
|
m_failed = true;
|
|
m_error = true;
|
|
}
|
|
else if (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 %d", target));
|
|
*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 %d", target));
|
|
*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;
|
|
}
|
|
|
|
|
|
//
|
|
// CXWindowsClipboard::CReply
|
|
//
|
|
|
|
CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) :
|
|
m_requestor(requestor),
|
|
m_target(target),
|
|
m_time(time),
|
|
m_property(None),
|
|
m_replied(false),
|
|
m_done(false),
|
|
m_data(),
|
|
m_type(None),
|
|
m_format(32),
|
|
m_ptr(0)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time,
|
|
Atom property, const CString& data, Atom type, int format) :
|
|
m_requestor(requestor),
|
|
m_target(target),
|
|
m_time(time),
|
|
m_property(property),
|
|
m_replied(false),
|
|
m_done(false),
|
|
m_data(data),
|
|
m_type(type),
|
|
m_format(format),
|
|
m_ptr(0)
|
|
{
|
|
// do nothing
|
|
}
|