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