00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "CXWindowsClipboard.h"
00020 #include "CXWindowsClipboardTextConverter.h"
00021 #include "CXWindowsClipboardUCS2Converter.h"
00022 #include "CXWindowsClipboardUTF8Converter.h"
00023 #include "CXWindowsClipboardHTMLConverter.h"
00024 #include "CXWindowsClipboardBMPConverter.h"
00025 #include "CXWindowsUtil.h"
00026 #include "CThread.h"
00027 #include "CLog.h"
00028 #include "CStopwatch.h"
00029 #include "CArch.h"
00030 #include "stdvector.h"
00031 #include <cstdio>
00032 #include <X11/Xatom.h>
00033
00034
00035
00036
00037
00038 CXWindowsClipboard::CXWindowsClipboard(Display* display,
00039 Window window, ClipboardID id) :
00040 m_display(display),
00041 m_window(window),
00042 m_id(id),
00043 m_open(false),
00044 m_time(0),
00045 m_owner(false),
00046 m_timeOwned(0),
00047 m_timeLost(0)
00048 {
00049
00050 m_atomTargets = XInternAtom(m_display, "TARGETS", False);
00051 m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False);
00052 m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False);
00053 m_atomInteger = XInternAtom(m_display, "INTEGER", False);
00054 m_atomAtom = XInternAtom(m_display, "ATOM", False);
00055 m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False);
00056 m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False);
00057 m_atomINCR = XInternAtom(m_display, "INCR", False);
00058 m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False);
00059 m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False);
00060 m_atomMotifClipAccess = XInternAtom(m_display,
00061 "_MOTIF_CLIP_LOCK_ACCESS_VALID", False);
00062 m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False);
00063
00064
00065 switch (id) {
00066 case kClipboardClipboard:
00067 m_selection = XInternAtom(m_display, "CLIPBOARD", False);
00068 break;
00069
00070 case kClipboardSelection:
00071 default:
00072 m_selection = XA_PRIMARY;
00073 break;
00074 }
00075
00076
00077 m_converters.push_back(new CXWindowsClipboardHTMLConverter(m_display,
00078 "text/html"));
00079 m_converters.push_back(new CXWindowsClipboardBMPConverter(m_display));
00080 m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
00081 "text/plain;charset=UTF-8"));
00082 m_converters.push_back(new CXWindowsClipboardUTF8Converter(m_display,
00083 "UTF8_STRING"));
00084 m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
00085 "text/plain;charset=ISO-10646-UCS-2"));
00086 m_converters.push_back(new CXWindowsClipboardUCS2Converter(m_display,
00087 "text/unicode"));
00088 m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
00089 "text/plain"));
00090 m_converters.push_back(new CXWindowsClipboardTextConverter(m_display,
00091 "STRING"));
00092
00093
00094 clearCache();
00095 }
00096
00097 CXWindowsClipboard::~CXWindowsClipboard()
00098 {
00099 clearReplies();
00100 clearConverters();
00101 }
00102
00103 void
00104 CXWindowsClipboard::lost(Time time)
00105 {
00106 LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time));
00107 if (m_owner) {
00108 m_owner = false;
00109 m_timeLost = time;
00110 clearCache();
00111 }
00112 }
00113
00114 void
00115 CXWindowsClipboard::addRequest(Window owner, Window requestor,
00116 Atom target, ::Time time, Atom property)
00117 {
00118
00119
00120 bool success = false;
00121 if (owner == m_window) {
00122 LOG((CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection, CXWindowsUtil::atomToString(m_display, target).c_str(), requestor, CXWindowsUtil::atomToString(m_display, property).c_str()));
00123 if (wasOwnedAtTime(time)) {
00124 if (target == m_atomMultiple) {
00125
00126
00127 if (property != None) {
00128 success = insertMultipleReply(requestor, time, property);
00129 }
00130 }
00131 else {
00132 addSimpleRequest(requestor, target, time, property);
00133
00134
00135 success = true;
00136 }
00137 }
00138 else {
00139 LOG((CLOG_DEBUG1 "failed, not owned at time %d", time));
00140 }
00141 }
00142
00143 if (!success) {
00144
00145 LOG((CLOG_DEBUG1 "failed"));
00146 insertReply(new CReply(requestor, target, time));
00147 }
00148
00149
00150 pushReplies();
00151 }
00152
00153 bool
00154 CXWindowsClipboard::addSimpleRequest(Window requestor,
00155 Atom target, ::Time time, Atom property)
00156 {
00157
00158
00159
00160 if (property == None) {
00161 property = target;
00162 }
00163
00164
00165 CString data;
00166 Atom type = None;
00167 int format = 0;
00168 if (target == m_atomTargets) {
00169 type = getTargetsData(data, &format);
00170 }
00171 else if (target == m_atomTimestamp) {
00172 type = getTimestampData(data, &format);
00173 }
00174 else {
00175 IXWindowsClipboardConverter* converter = getConverter(target);
00176 if (converter != NULL) {
00177 IClipboard::EFormat clipboardFormat = converter->getFormat();
00178 if (m_added[clipboardFormat]) {
00179 try {
00180 data = converter->fromIClipboard(m_data[clipboardFormat]);
00181 format = converter->getDataSize();
00182 type = converter->getAtom();
00183 }
00184 catch (...) {
00185
00186 }
00187 }
00188 }
00189 }
00190
00191 if (type != None) {
00192
00193 LOG((CLOG_DEBUG1 "success"));
00194 insertReply(new CReply(requestor, target, time,
00195 property, data, type, format));
00196 return true;
00197 }
00198 else {
00199
00200 LOG((CLOG_DEBUG1 "failed"));
00201 insertReply(new CReply(requestor, target, time));
00202 return false;
00203 }
00204 }
00205
00206 bool
00207 CXWindowsClipboard::processRequest(Window requestor,
00208 ::Time , Atom property)
00209 {
00210 CReplyMap::iterator index = m_replies.find(requestor);
00211 if (index == m_replies.end()) {
00212
00213 return false;
00214 }
00215 LOG((CLOG_DEBUG1 "received property %s delete from 0x08%x", CXWindowsUtil::atomToString(m_display, property).c_str(), requestor));
00216
00217
00218
00219 CReplyList& replies = index->second;
00220 for (CReplyList::iterator index2 = replies.begin();
00221 index2 != replies.end(); ++index2) {
00222 CReply* reply = *index2;
00223 if (reply->m_replied && reply->m_property == property) {
00224
00225
00226 pushReplies(index, replies, index2);
00227 return true;
00228 }
00229 }
00230
00231 return false;
00232 }
00233
00234 bool
00235 CXWindowsClipboard::destroyRequest(Window requestor)
00236 {
00237 CReplyMap::iterator index = m_replies.find(requestor);
00238 if (index == m_replies.end()) {
00239
00240 return false;
00241 }
00242
00243
00244 clearReplies(index->second);
00245 m_replies.erase(index);
00246
00247
00248
00249
00250 return true;
00251 }
00252
00253 Window
00254 CXWindowsClipboard::getWindow() const
00255 {
00256 return m_window;
00257 }
00258
00259 Atom
00260 CXWindowsClipboard::getSelection() const
00261 {
00262 return m_selection;
00263 }
00264
00265 bool
00266 CXWindowsClipboard::empty()
00267 {
00268 assert(m_open);
00269
00270 LOG((CLOG_DEBUG "empty clipboard %d", m_id));
00271
00272
00273 XSetSelectionOwner(m_display, m_selection, m_window, m_time);
00274 if (XGetSelectionOwner(m_display, m_selection) != m_window) {
00275 LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id));
00276 return false;
00277 }
00278
00279
00280
00281 clearCache();
00282 m_cached = true;
00283
00284
00285
00286
00287
00288 m_timeOwned = m_time;
00289 m_timeLost = 0;
00290
00291
00292 m_owner = true;
00293 LOG((CLOG_DEBUG "grabbed clipboard %d", m_id));
00294
00295 return true;
00296 }
00297
00298 void
00299 CXWindowsClipboard::add(EFormat format, const CString& data)
00300 {
00301 assert(m_open);
00302 assert(m_owner);
00303
00304 LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format));
00305
00306 m_data[format] = data;
00307 m_added[format] = true;
00308
00309
00310 }
00311
00312 bool
00313 CXWindowsClipboard::open(Time time) const
00314 {
00315 assert(!m_open);
00316
00317 LOG((CLOG_DEBUG "open clipboard %d", m_id));
00318
00319
00320 m_motif = false;
00321
00322
00323 if (m_id == kClipboardClipboard) {
00324 if (!motifLockClipboard()) {
00325 return false;
00326 }
00327
00328
00329
00330 m_motif = motifOwnsClipboard();
00331 LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not "));
00332 if (!m_motif) {
00333 motifUnlockClipboard();
00334 }
00335 }
00336
00337
00338 m_open = true;
00339 m_time = time;
00340
00341
00342 m_checkCache = true;
00343
00344 return true;
00345 }
00346
00347 void
00348 CXWindowsClipboard::close() const
00349 {
00350 assert(m_open);
00351
00352 LOG((CLOG_DEBUG "close clipboard %d", m_id));
00353
00354
00355 if (m_motif) {
00356 motifUnlockClipboard();
00357 }
00358
00359 m_motif = false;
00360 m_open = false;
00361 }
00362
00363 IClipboard::Time
00364 CXWindowsClipboard::getTime() const
00365 {
00366 checkCache();
00367 return m_timeOwned;
00368 }
00369
00370 bool
00371 CXWindowsClipboard::has(EFormat format) const
00372 {
00373 assert(m_open);
00374
00375 fillCache();
00376 return m_added[format];
00377 }
00378
00379 CString
00380 CXWindowsClipboard::get(EFormat format) const
00381 {
00382 assert(m_open);
00383
00384 fillCache();
00385 return m_data[format];
00386 }
00387
00388 void
00389 CXWindowsClipboard::clearConverters()
00390 {
00391 for (ConverterList::iterator index = m_converters.begin();
00392 index != m_converters.end(); ++index) {
00393 delete *index;
00394 }
00395 m_converters.clear();
00396 }
00397
00398 IXWindowsClipboardConverter*
00399 CXWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const
00400 {
00401 IXWindowsClipboardConverter* converter = NULL;
00402 for (ConverterList::const_iterator index = m_converters.begin();
00403 index != m_converters.end(); ++index) {
00404 converter = *index;
00405 if (converter->getAtom() == target) {
00406 break;
00407 }
00408 }
00409 if (converter == NULL) {
00410 LOG((CLOG_DEBUG1 " no converter for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00411 return NULL;
00412 }
00413
00414
00415 if (onlyIfNotAdded) {
00416 if (m_added[converter->getFormat()]) {
00417 LOG((CLOG_DEBUG1 " skipping handled format %d", converter->getFormat()));
00418 return NULL;
00419 }
00420 }
00421
00422 return converter;
00423 }
00424
00425 void
00426 CXWindowsClipboard::checkCache() const
00427 {
00428 if (!m_checkCache) {
00429 return;
00430 }
00431 m_checkCache = false;
00432
00433
00434
00435 if (m_motif) {
00436 m_timeOwned = motifGetTime();
00437 }
00438 else {
00439 m_timeOwned = icccmGetTime();
00440 }
00441
00442
00443 if (m_timeOwned == 0) {
00444 m_timeOwned = m_time;
00445 }
00446
00447
00448 if (m_timeOwned != m_cacheTime) {
00449 clearCache();
00450 }
00451 }
00452
00453 void
00454 CXWindowsClipboard::clearCache() const
00455 {
00456 const_cast<CXWindowsClipboard*>(this)->doClearCache();
00457 }
00458
00459 void
00460 CXWindowsClipboard::doClearCache()
00461 {
00462 m_checkCache = false;
00463 m_cached = false;
00464 for (SInt32 index = 0; index < kNumFormats; ++index) {
00465 m_data[index] = "";
00466 m_added[index] = false;
00467 }
00468 }
00469
00470 void
00471 CXWindowsClipboard::fillCache() const
00472 {
00473
00474 checkCache();
00475 if (!m_cached) {
00476 const_cast<CXWindowsClipboard*>(this)->doFillCache();
00477 }
00478 }
00479
00480 void
00481 CXWindowsClipboard::doFillCache()
00482 {
00483 if (m_motif) {
00484 motifFillCache();
00485 }
00486 else {
00487 icccmFillCache();
00488 }
00489 m_checkCache = false;
00490 m_cached = true;
00491 m_cacheTime = m_timeOwned;
00492 }
00493
00494 void
00495 CXWindowsClipboard::icccmFillCache()
00496 {
00497 LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id));
00498
00499
00500
00501
00502
00503 const Atom atomTargets = m_atomTargets;
00504 Atom target;
00505 CString data;
00506 if (!icccmGetSelection(atomTargets, &target, &data) ||
00507 (target != m_atomAtom && target != m_atomTargets)) {
00508 LOG((CLOG_DEBUG1 "selection doesn't support TARGETS"));
00509 data = "";
00510 CXWindowsUtil::appendAtomData(data, XA_STRING);
00511 }
00512
00513 CXWindowsUtil::convertAtomProperty(data);
00514 const Atom* targets = reinterpret_cast<const Atom*>(data.data());
00515 const UInt32 numTargets = data.size() / sizeof(Atom);
00516 LOG((CLOG_DEBUG " available targets: %s", CXWindowsUtil::atomsToString(m_display, targets, numTargets).c_str()));
00517
00518
00519
00520 for (ConverterList::const_iterator index = m_converters.begin();
00521 index != m_converters.end(); ++index) {
00522 IXWindowsClipboardConverter* converter = *index;
00523
00524
00525 if (m_added[converter->getFormat()]) {
00526 continue;
00527 }
00528
00529
00530 Atom target = None;
00531
00532
00533
00534 target = converter->getAtom();
00535
00536
00537
00538
00539
00540
00541
00542
00543 if (target == None) {
00544 continue;
00545 }
00546
00547
00548 Atom actualTarget;
00549 CString targetData;
00550 if (!icccmGetSelection(target, &actualTarget, &targetData)) {
00551 LOG((CLOG_DEBUG1 " no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00552 continue;
00553 }
00554
00555
00556 IClipboard::EFormat format = converter->getFormat();
00557 m_data[format] = converter->toIClipboard(targetData);
00558 m_added[format] = true;
00559 LOG((CLOG_DEBUG " added format %d for target %s (%u %s)", format, CXWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes"));
00560 }
00561 }
00562
00563 bool
00564 CXWindowsClipboard::icccmGetSelection(Atom target,
00565 Atom* actualTarget, CString* data) const
00566 {
00567 assert(actualTarget != NULL);
00568 assert(data != NULL);
00569
00570
00571 CICCCMGetClipboard getter(m_window, m_time, m_atomData);
00572 if (!getter.readClipboard(m_display, m_selection,
00573 target, actualTarget, data)) {
00574 LOG((CLOG_DEBUG1 "can't get data for selection target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00575 LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner"));
00576 return false;
00577 }
00578 else if (*actualTarget == None) {
00579 LOG((CLOG_DEBUG1 "selection conversion failed for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00580 return false;
00581 }
00582 return true;
00583 }
00584
00585 IClipboard::Time
00586 CXWindowsClipboard::icccmGetTime() const
00587 {
00588 Atom actualTarget;
00589 CString data;
00590 if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) &&
00591 actualTarget == m_atomInteger) {
00592 Time time = *reinterpret_cast<const Time*>(data.data());
00593 LOG((CLOG_DEBUG1 "got ICCCM time %d", time));
00594 return time;
00595 }
00596 else {
00597
00598 LOG((CLOG_DEBUG1 "can't get ICCCM time"));
00599 return 0;
00600 }
00601 }
00602
00603 bool
00604 CXWindowsClipboard::motifLockClipboard() const
00605 {
00606
00607 Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
00608 if (lockOwner != None) {
00609 LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
00610 return false;
00611 }
00612
00613
00614
00615
00616
00617 Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
00618 XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time);
00619 lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
00620 if (lockOwner != m_window) {
00621 LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner));
00622 return false;
00623 }
00624
00625 LOG((CLOG_DEBUG1 "locked motif clipboard"));
00626 return true;
00627 }
00628
00629 void
00630 CXWindowsClipboard::motifUnlockClipboard() const
00631 {
00632 LOG((CLOG_DEBUG1 "unlocked motif clipboard"));
00633
00634
00635 Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock);
00636 if (lockOwner != m_window) {
00637 return;
00638 }
00639
00640
00641 Time time = CXWindowsUtil::getCurrentTime(m_display, m_window);
00642 XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time);
00643 }
00644
00645 bool
00646 CXWindowsClipboard::motifOwnsClipboard() const
00647 {
00648
00649
00650
00651
00652 Window owner = XGetSelectionOwner(m_display, m_selection);
00653 if (owner == None) {
00654 return false;
00655 }
00656
00657
00658 Atom target;
00659 SInt32 format;
00660 CString data;
00661 Window root = RootWindow(m_display, DefaultScreen(m_display));
00662 if (!CXWindowsUtil::getWindowProperty(m_display, root,
00663 m_atomMotifClipHeader,
00664 &data, &target, &format, False)) {
00665 return false;
00666 }
00667
00668
00669 const CMotifClipHeader* header =
00670 reinterpret_cast<const CMotifClipHeader*>(data.data());
00671 if (data.size() >= sizeof(CMotifClipHeader) &&
00672 header->m_id == kMotifClipHeader) {
00673 if (static_cast<Window>(header->m_selectionOwner) == owner) {
00674 return true;
00675 }
00676 }
00677
00678 return false;
00679 }
00680
00681 void
00682 CXWindowsClipboard::motifFillCache()
00683 {
00684 LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id));
00685
00686
00687 Atom target;
00688 SInt32 format;
00689 CString data;
00690 Window root = RootWindow(m_display, DefaultScreen(m_display));
00691 if (!CXWindowsUtil::getWindowProperty(m_display, root,
00692 m_atomMotifClipHeader,
00693 &data, &target, &format, False)) {
00694 return;
00695 }
00696
00697
00698 const CMotifClipHeader* header =
00699 reinterpret_cast<const CMotifClipHeader*>(data.data());
00700 if (data.size() < sizeof(CMotifClipHeader) ||
00701 header->m_id != kMotifClipHeader ||
00702 header->m_numItems < 1) {
00703 return;
00704 }
00705
00706
00707 char name[18 + 20];
00708 sprintf(name, "_MOTIF_CLIP_ITEM_%d", header->m_item);
00709 Atom atomItem = XInternAtom(m_display, name, False);
00710 data = "";
00711 if (!CXWindowsUtil::getWindowProperty(m_display, root,
00712 atomItem, &data,
00713 &target, &format, False)) {
00714 return;
00715 }
00716
00717
00718 const CMotifClipItem* item =
00719 reinterpret_cast<const CMotifClipItem*>(data.data());
00720 if (data.size() < sizeof(CMotifClipItem) ||
00721 item->m_id != kMotifClipItem ||
00722 item->m_numFormats - item->m_numDeletedFormats < 1) {
00723 return;
00724 }
00725
00726
00727 const SInt32 numFormats = item->m_numFormats - item->m_numDeletedFormats;
00728 const SInt32* formats = reinterpret_cast<const SInt32*>(item->m_size +
00729 reinterpret_cast<const char*>(data.data()));
00730
00731
00732 typedef std::map<Atom, CString> CMotifFormatMap;
00733 CMotifFormatMap motifFormats;
00734 for (SInt32 i = 0; i < numFormats; ++i) {
00735
00736 sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]);
00737 Atom atomFormat = XInternAtom(m_display, name, False);
00738 CString data;
00739 if (!CXWindowsUtil::getWindowProperty(m_display, root,
00740 atomFormat, &data,
00741 &target, &format, False)) {
00742 continue;
00743 }
00744
00745
00746 const CMotifClipFormat* motifFormat =
00747 reinterpret_cast<const CMotifClipFormat*>(data.data());
00748 if (data.size() < sizeof(CMotifClipFormat) ||
00749 motifFormat->m_id != kMotifClipFormat ||
00750 motifFormat->m_length < 0 ||
00751 motifFormat->m_type == None ||
00752 motifFormat->m_deleted != 0) {
00753 continue;
00754 }
00755
00756
00757 motifFormats.insert(std::make_pair(motifFormat->m_type, data));
00758 }
00759
00760
00761
00762
00763 for (ConverterList::const_iterator index = m_converters.begin();
00764 index != m_converters.end(); ++index) {
00765 IXWindowsClipboardConverter* converter = *index;
00766
00767
00768 if (m_added[converter->getFormat()]) {
00769 continue;
00770 }
00771
00772
00773 CMotifFormatMap::const_iterator index2 =
00774 motifFormats.find(converter->getAtom());
00775 if (index2 == motifFormats.end()) {
00776 continue;
00777 }
00778
00779
00780 const CMotifClipFormat* motifFormat =
00781 reinterpret_cast<const CMotifClipFormat*>(
00782 index2->second.data());
00783 const Atom target = motifFormat->m_type;
00784
00785
00786 Atom actualTarget;
00787 CString targetData;
00788 if (!motifGetSelection(motifFormat, &actualTarget, &targetData)) {
00789 LOG((CLOG_DEBUG1 " no data for target %s", CXWindowsUtil::atomToString(m_display, target).c_str()));
00790 continue;
00791 }
00792
00793
00794 IClipboard::EFormat format = converter->getFormat();
00795 m_data[format] = converter->toIClipboard(targetData);
00796 m_added[format] = true;
00797 LOG((CLOG_DEBUG " added format %d for target %s", format, CXWindowsUtil::atomToString(m_display, target).c_str()));
00798 }
00799 }
00800
00801 bool
00802 CXWindowsClipboard::motifGetSelection(const CMotifClipFormat* format,
00803 Atom* actualTarget, CString* data) const
00804 {
00805
00806
00807
00808 if (!motifOwnsClipboard()) {
00809 return icccmGetSelection(format->m_type, actualTarget, data);
00810 }
00811
00812
00813
00814
00815
00816
00817 char name[18 + 20];
00818 sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data);
00819 Atom target = XInternAtom(m_display, name, False);
00820 Window root = RootWindow(m_display, DefaultScreen(m_display));
00821 return CXWindowsUtil::getWindowProperty(m_display, root,
00822 target, data,
00823 actualTarget, NULL, False);
00824 }
00825
00826 IClipboard::Time
00827 CXWindowsClipboard::motifGetTime() const
00828 {
00829 return icccmGetTime();
00830 }
00831
00832 bool
00833 CXWindowsClipboard::insertMultipleReply(Window requestor,
00834 ::Time time, Atom property)
00835 {
00836
00837 Atom target;
00838 SInt32 format;
00839 CString data;
00840 if (!CXWindowsUtil::getWindowProperty(m_display, requestor,
00841 property, &data, &target, &format, False)) {
00842
00843 return false;
00844 }
00845
00846
00847 if (format != 32 || target != m_atomAtomPair) {
00848 return false;
00849 }
00850
00851
00852 CXWindowsUtil::convertAtomProperty(data);
00853 const Atom* targets = reinterpret_cast<const Atom*>(data.data());
00854 const UInt32 numTargets = data.size() / sizeof(Atom);
00855
00856
00857 bool changed = false;
00858 for (UInt32 i = 0; i < numTargets; i += 2) {
00859 const Atom target = targets[i + 0];
00860 const Atom property = targets[i + 1];
00861 if (!addSimpleRequest(requestor, target, time, property)) {
00862
00863 CXWindowsUtil::replaceAtomData(data, i, None);
00864 changed = true;
00865 }
00866 }
00867
00868
00869 if (changed) {
00870 CXWindowsUtil::setWindowProperty(m_display, requestor,
00871 property, data.data(), data.size(),
00872 target, format);
00873 }
00874
00875
00876 insertReply(new CReply(requestor, m_atomMultiple,
00877 time, property, CString(), None, 32));
00878
00879 return true;
00880 }
00881
00882 void
00883 CXWindowsClipboard::insertReply(CReply* reply)
00884 {
00885 assert(reply != NULL);
00886
00887
00888
00889
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900 const bool newWindow = (m_replies.count(reply->m_requestor) == 0);
00901 m_replies[reply->m_requestor].push_back(reply);
00902
00903
00904
00905
00906 if (newWindow) {
00907
00908 bool error = false;
00909 {
00910 CXWindowsUtil::CErrorLock lock(m_display, &error);
00911
00912
00913 XWindowAttributes attr;
00914 XGetWindowAttributes(m_display, reply->m_requestor, &attr);
00915 m_eventMasks[reply->m_requestor] = attr.your_event_mask;
00916
00917
00918 XSelectInput(m_display, reply->m_requestor, attr.your_event_mask |
00919 StructureNotifyMask | PropertyChangeMask);
00920 }
00921
00922
00923 if (error) {
00924 m_replies.erase(reply->m_requestor);
00925 delete reply;
00926 }
00927 }
00928 }
00929
00930 void
00931 CXWindowsClipboard::pushReplies()
00932 {
00933
00934
00935 for (CReplyMap::iterator index = m_replies.begin();
00936 index != m_replies.end(); ) {
00937 assert(!index->second.empty());
00938 if (!index->second.front()->m_replied) {
00939 pushReplies(index, index->second, index->second.begin());
00940 }
00941 else {
00942 ++index;
00943 }
00944 }
00945 }
00946
00947 void
00948 CXWindowsClipboard::pushReplies(CReplyMap::iterator& mapIndex,
00949 CReplyList& replies, CReplyList::iterator index)
00950 {
00951 CReply* reply = *index;
00952 while (sendReply(reply)) {
00953
00954
00955 index = replies.erase(index);
00956 delete reply;
00957 if (index == replies.end()) {
00958 break;
00959 }
00960 reply = *index;
00961 }
00962
00963
00964
00965 if (replies.empty()) {
00966 CXWindowsUtil::CErrorLock lock(m_display);
00967 Window requestor = mapIndex->first;
00968 XSelectInput(m_display, requestor, m_eventMasks[requestor]);
00969 m_replies.erase(mapIndex++);
00970 m_eventMasks.erase(requestor);
00971 }
00972 else {
00973 ++mapIndex;
00974 }
00975 }
00976
00977 bool
00978 CXWindowsClipboard::sendReply(CReply* reply)
00979 {
00980 assert(reply != NULL);
00981
00982
00983 if (reply->m_done) {
00984 LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
00985 return true;
00986 }
00987
00988
00989 bool failed = (reply->m_property == None);
00990 if (!failed) {
00991 LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
00992
00993
00994
00995 const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display);
00996 const bool useINCR = (reply->m_data.size() > maxRequestSize);
00997
00998
00999 if (useINCR && !reply->m_replied) {
01000 UInt32 size = reply->m_data.size();
01001 if (!CXWindowsUtil::setWindowProperty(m_display,
01002 reply->m_requestor, reply->m_property,
01003 &size, 4, m_atomINCR, 32)) {
01004 failed = true;
01005 }
01006 }
01007
01008
01009 else {
01010
01011 UInt32 size = reply->m_data.size() - reply->m_ptr;
01012 if (size > maxRequestSize)
01013 size = maxRequestSize;
01014
01015
01016 if (!CXWindowsUtil::setWindowProperty(m_display,
01017 reply->m_requestor, reply->m_property,
01018 reply->m_data.data() + reply->m_ptr,
01019 size,
01020 reply->m_type, reply->m_format)) {
01021 failed = true;
01022 }
01023 else {
01024 reply->m_ptr += size;
01025
01026
01027
01028 reply->m_done = (size == 0 || !useINCR);
01029 }
01030 }
01031 }
01032
01033
01034
01035
01036
01037
01038
01039 if (failed) {
01040 LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
01041 reply->m_done = true;
01042 if (reply->m_property != None) {
01043 CXWindowsUtil::CErrorLock lock(m_display);
01044 XDeleteProperty(m_display, reply->m_requestor, reply->m_property);
01045 }
01046
01047 if (!reply->m_replied) {
01048 sendNotify(reply->m_requestor, m_selection,
01049 reply->m_target, None,
01050 reply->m_time);
01051
01052
01053 return true;
01054 }
01055 else {
01056 static const char dummy = 0;
01057 CXWindowsUtil::setWindowProperty(m_display,
01058 reply->m_requestor, reply->m_property,
01059 &dummy,
01060 0,
01061 reply->m_type, reply->m_format);
01062
01063
01064 return false;
01065 }
01066 }
01067
01068
01069 if (!reply->m_replied) {
01070 LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property));
01071 reply->m_replied = true;
01072
01073
01074
01075
01076
01077 if (CLOG->getFilter() >= kDEBUG2) {
01078 CXWindowsUtil::CErrorLock lock(m_display);
01079 int n;
01080 Atom* props = XListProperties(m_display, reply->m_requestor, &n);
01081 LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor));
01082 for (int i = 0; i < n; ++i) {
01083 Atom target;
01084 CString data;
01085 char* name = XGetAtomName(m_display, props[i]);
01086 if (!CXWindowsUtil::getWindowProperty(m_display,
01087 reply->m_requestor,
01088 props[i], &data, &target, NULL, False)) {
01089 LOG((CLOG_DEBUG2 " %s: <can't read property>", name));
01090 }
01091 else {
01092
01093
01094 static const char* hex = "0123456789abcdef";
01095 for (CString::size_type j = 0; j < data.size(); ++j) {
01096 if (data[j] < 32 || data[j] > 126) {
01097 CString tmp;
01098 tmp.reserve(data.size() * 3);
01099 for (j = 0; j < data.size(); ++j) {
01100 unsigned char v = (unsigned char)data[j];
01101 tmp += hex[v >> 16];
01102 tmp += hex[v & 15];
01103 tmp += ' ';
01104 }
01105 data = tmp;
01106 break;
01107 }
01108 }
01109 char* type = XGetAtomName(m_display, target);
01110 LOG((CLOG_DEBUG2 " %s (%s): %s", name, type, data.c_str()));
01111 if (type != NULL) {
01112 XFree(type);
01113 }
01114 }
01115 if (name != NULL) {
01116 XFree(name);
01117 }
01118 }
01119 if (props != NULL) {
01120 XFree(props);
01121 }
01122 }
01123
01124 sendNotify(reply->m_requestor, m_selection,
01125 reply->m_target, reply->m_property,
01126 reply->m_time);
01127 }
01128
01129
01130 return false;
01131 }
01132
01133 void
01134 CXWindowsClipboard::clearReplies()
01135 {
01136 for (CReplyMap::iterator index = m_replies.begin();
01137 index != m_replies.end(); ++index) {
01138 clearReplies(index->second);
01139 }
01140 m_replies.clear();
01141 m_eventMasks.clear();
01142 }
01143
01144 void
01145 CXWindowsClipboard::clearReplies(CReplyList& replies)
01146 {
01147 for (CReplyList::iterator index = replies.begin();
01148 index != replies.end(); ++index) {
01149 delete *index;
01150 }
01151 replies.clear();
01152 }
01153
01154 void
01155 CXWindowsClipboard::sendNotify(Window requestor,
01156 Atom selection, Atom target, Atom property, Time time)
01157 {
01158 XEvent event;
01159 event.xselection.type = SelectionNotify;
01160 event.xselection.display = m_display;
01161 event.xselection.requestor = requestor;
01162 event.xselection.selection = selection;
01163 event.xselection.target = target;
01164 event.xselection.property = property;
01165 event.xselection.time = time;
01166 CXWindowsUtil::CErrorLock lock(m_display);
01167 XSendEvent(m_display, requestor, False, 0, &event);
01168 }
01169
01170 bool
01171 CXWindowsClipboard::wasOwnedAtTime(::Time time) const
01172 {
01173
01174 checkCache();
01175 if (m_timeOwned == 0) {
01176 return false;
01177 }
01178
01179
01180
01181
01182
01183 Time lost = m_timeLost;
01184 if (m_timeLost == 0) {
01185 if (time == CurrentTime) {
01186 return true;
01187 }
01188 else {
01189 lost = CXWindowsUtil::getCurrentTime(m_display, m_window);
01190 }
01191 }
01192 else {
01193 if (time == CurrentTime) {
01194 return false;
01195 }
01196 }
01197
01198
01199 Time duration = lost - m_timeOwned;
01200 Time when = time - m_timeOwned;
01201 return ( when <= duration);
01202 }
01203
01204 Atom
01205 CXWindowsClipboard::getTargetsData(CString& data, int* format) const
01206 {
01207 assert(format != NULL);
01208
01209
01210 CXWindowsUtil::appendAtomData(data, m_atomTargets);
01211 CXWindowsUtil::appendAtomData(data, m_atomMultiple);
01212 CXWindowsUtil::appendAtomData(data, m_atomTimestamp);
01213
01214
01215 for (ConverterList::const_iterator index = m_converters.begin();
01216 index != m_converters.end(); ++index) {
01217 IXWindowsClipboardConverter* converter = *index;
01218
01219
01220 if (m_added[converter->getFormat()]) {
01221 CXWindowsUtil::appendAtomData(data, converter->getAtom());
01222 }
01223 }
01224
01225 *format = 32;
01226 return m_atomAtom;
01227 }
01228
01229 Atom
01230 CXWindowsClipboard::getTimestampData(CString& data, int* format) const
01231 {
01232 assert(format != NULL);
01233
01234 checkCache();
01235 CXWindowsUtil::appendTimeData(data, m_timeOwned);
01236 *format = 32;
01237 return m_atomInteger;
01238 }
01239
01240
01241
01242
01243
01244
01245 CXWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard(
01246 Window requestor, Time time, Atom property) :
01247 m_requestor(requestor),
01248 m_time(time),
01249 m_property(property),
01250 m_incr(false),
01251 m_failed(false),
01252 m_done(false),
01253 m_reading(false),
01254 m_data(NULL),
01255 m_actualTarget(NULL),
01256 m_error(false)
01257 {
01258
01259 }
01260
01261 CXWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard()
01262 {
01263
01264 }
01265
01266 bool
01267 CXWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display,
01268 Atom selection, Atom target, Atom* actualTarget, CString* data)
01269 {
01270 assert(actualTarget != NULL);
01271 assert(data != NULL);
01272
01273 LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", CXWindowsUtil::atomToString(display, selection).c_str(), CXWindowsUtil::atomToString(display, target).c_str(), m_requestor));
01274
01275 m_atomNone = XInternAtom(display, "NONE", False);
01276 m_atomIncr = XInternAtom(display, "INCR", False);
01277
01278
01279 m_actualTarget = actualTarget;
01280 m_data = data;
01281
01282
01283 *m_actualTarget = None;
01284 *m_data = "";
01285
01286
01287 XDeleteProperty(display, m_requestor, m_property);
01288
01289
01290 XWindowAttributes attr;
01291 XGetWindowAttributes(display, m_requestor, &attr);
01292 XSelectInput(display, m_requestor,
01293 attr.your_event_mask | PropertyChangeMask);
01294
01295
01296 XConvertSelection(display, selection, target,
01297 m_property, m_requestor, m_time);
01298
01299
01300 XSync(display, False);
01301
01302
01303
01304
01305
01306
01307 XEvent xevent;
01308 std::vector<XEvent> events;
01309 CStopwatch timeout(true);
01310 static const double s_timeout = 0.25;
01311 bool noWait = false;
01312 while (!m_done && !m_failed) {
01313
01314 if (timeout.getTime() >= s_timeout) {
01315 m_failed = true;
01316 break;
01317 }
01318
01319
01320 if (noWait || XPending(display) > 0) {
01321 while (!m_done && !m_failed && (noWait || XPending(display) > 0)) {
01322 XNextEvent(display, &xevent);
01323 if (!processEvent(display, &xevent)) {
01324
01325 events.push_back(xevent);
01326 }
01327 else {
01328
01329 timeout.reset();
01330
01331
01332
01333
01334
01335 noWait = true;
01336 }
01337 }
01338 }
01339 else {
01340 ARCH->sleep(0.01);
01341 }
01342 }
01343
01344
01345 for (UInt32 i = events.size(); i > 0; --i) {
01346 XPutBackEvent(display, &events[i - 1]);
01347 }
01348
01349
01350 XSelectInput(display, m_requestor, attr.your_event_mask);
01351
01352
01353 LOG((CLOG_DEBUG1 "request %s", m_failed ? "failed" : "succeeded"));
01354 return !m_failed;
01355 }
01356
01357 bool
01358 CXWindowsClipboard::CICCCMGetClipboard::processEvent(
01359 Display* display, XEvent* xevent)
01360 {
01361
01362 switch (xevent->type) {
01363 case DestroyNotify:
01364 if (xevent->xdestroywindow.window == m_requestor) {
01365 m_failed = true;
01366 return true;
01367 }
01368
01369
01370 return false;
01371
01372 case SelectionNotify:
01373 if (xevent->xselection.requestor == m_requestor) {
01374
01375 if (xevent->xselection.property == None ||
01376 xevent->xselection.property == m_atomNone) {
01377 m_done = true;
01378 return true;
01379 }
01380
01381
01382 else if (xevent->xselection.property == m_property) {
01383 m_reading = true;
01384 break;
01385 }
01386 }
01387
01388
01389 return false;
01390
01391 case PropertyNotify:
01392
01393 if (xevent->xproperty.window == m_requestor &&
01394 xevent->xproperty.atom == m_property &&
01395 xevent->xproperty.state == PropertyNewValue) {
01396 if (!m_reading) {
01397
01398 return true;
01399 }
01400 break;
01401 }
01402
01403
01404 return false;
01405
01406 default:
01407
01408 return false;
01409 }
01410
01411
01412 Atom target;
01413 const CString::size_type oldSize = m_data->size();
01414 if (!CXWindowsUtil::getWindowProperty(display, m_requestor,
01415 m_property, m_data, &target, NULL, True)) {
01416
01417 m_failed = true;
01418 return true;
01419 }
01420
01421
01422
01423
01424 if (target == m_atomIncr) {
01425 if (m_incr) {
01426 m_failed = true;
01427 m_error = true;
01428 }
01429 else if (m_data->size() == oldSize) {
01430 m_failed = true;
01431 m_error = true;
01432 }
01433 else {
01434 m_incr = true;
01435
01436
01437 *m_data = "";
01438 }
01439 }
01440
01441
01442 else if (m_incr) {
01443
01444 if (oldSize == 0) {
01445 LOG((CLOG_DEBUG1 " INCR first chunk, target %s", CXWindowsUtil::atomToString(display, target).c_str()));
01446 *m_actualTarget = target;
01447 }
01448
01449
01450 else {
01451 if (target != *m_actualTarget) {
01452 LOG((CLOG_WARN " INCR target mismatch"));
01453 m_failed = true;
01454 m_error = true;
01455 }
01456 }
01457
01458
01459 if (m_data->size() == oldSize) {
01460 LOG((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size()));
01461 m_done = true;
01462 }
01463 }
01464
01465
01466 else {
01467 LOG((CLOG_DEBUG1 " target %s", CXWindowsUtil::atomToString(display, target).c_str()));
01468 *m_actualTarget = target;
01469 m_done = true;
01470 }
01471
01472
01473 LOGC(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size()));
01474 return true;
01475 }
01476
01477
01478
01479
01480
01481
01482 CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time) :
01483 m_requestor(requestor),
01484 m_target(target),
01485 m_time(time),
01486 m_property(None),
01487 m_replied(false),
01488 m_done(false),
01489 m_data(),
01490 m_type(None),
01491 m_format(32),
01492 m_ptr(0)
01493 {
01494
01495 }
01496
01497 CXWindowsClipboard::CReply::CReply(Window requestor, Atom target, ::Time time,
01498 Atom property, const CString& data, Atom type, int format) :
01499 m_requestor(requestor),
01500 m_target(target),
01501 m_time(time),
01502 m_property(property),
01503 m_replied(false),
01504 m_done(false),
01505 m_data(data),
01506 m_type(type),
01507 m_format(format),
01508 m_ptr(0)
01509 {
01510
01511 }