#include "CXWindowsScreen.h" #include "CThread.h" #include "CLock.h" #include "TMethodJob.h" #include "CLog.h" #include "CString.h" #include "CStopwatch.h" #include #include #include #include #include // // CXWindowsScreen // static const UInt32 kMaxRequestSize = 4096; CXWindowsScreen::CXWindowsScreen() : m_display(NULL), m_root(None), m_w(0), m_h(0), m_stop(false) { // do nothing } CXWindowsScreen::~CXWindowsScreen() { assert(m_display == NULL); } void CXWindowsScreen::openDisplay() { assert(m_display == NULL); // open the display log((CLOG_DEBUG "XOpenDisplay(%s)", "NULL")); m_display = XOpenDisplay(NULL); // FIXME -- allow non-default if (m_display == NULL) throw int(5); // FIXME -- make exception for this // get default screen m_screen = DefaultScreen(m_display); Screen* screen = ScreenOfDisplay(m_display, m_screen); // get screen size m_w = WidthOfScreen(screen); m_h = HeightOfScreen(screen); log((CLOG_INFO "display size: %dx%d", m_w, m_h)); // get the root window m_root = RootWindow(m_display, m_screen); // 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_atomAtom = XInternAtom(m_display, "ATOM", False); m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False); m_atomInteger = XInternAtom(m_display, "INTEGER", False); m_atomData = XInternAtom(m_display, "DESTINATION", False); m_atomINCR = XInternAtom(m_display, "INCR", False); m_atomString = XInternAtom(m_display, "STRING", False); m_atomText = XInternAtom(m_display, "TEXT", False); m_atomCompoundText = XInternAtom(m_display, "COMPOUND_TEXT", False); m_atomSynergyTime = XInternAtom(m_display, "SYNERGY_TIME", False); // clipboard atoms m_atomClipboard[kClipboardClipboard] = XInternAtom(m_display, "CLIPBOARD", False); m_atomClipboard[kClipboardSelection] = XA_PRIMARY; // let subclass prep display onOpenDisplay(); } void CXWindowsScreen::closeDisplay() { assert(m_display != NULL); // let subclass close down display onCloseDisplay(); // clear out the clipboard request lists for (ClipboardID id = 0; id < kClipboardEnd; ++id) { CClipboardInfo& clipboard = m_clipboards[id]; for (CRequestMap::iterator index = clipboard.m_requests.begin(); index != clipboard.m_requests.end(); ++index) { CRequestList* list = index->second; for (CRequestList::iterator index2 = list->begin(); index2 != list->end(); ++index2) { delete *index2; } delete list; } clipboard.m_requests.clear(); } // close the display XCloseDisplay(m_display); m_display = NULL; log((CLOG_DEBUG "closed display")); } int CXWindowsScreen::getScreen() const { assert(m_display != NULL); return m_screen; } Window CXWindowsScreen::getRoot() const { assert(m_display != NULL); return m_root; } void CXWindowsScreen::getScreenSize( SInt32* w, SInt32* h) const { assert(m_display != NULL); assert(w != NULL && h != NULL); *w = m_w; *h = m_h; } Cursor CXWindowsScreen::createBlankCursor() const { // this seems just a bit more complicated than really necessary // get the closet cursor size to 1x1 unsigned int w, h; XQueryBestCursor(m_display, m_root, 1, 1, &w, &h); // make bitmap data for cursor of closet size. since the cursor // is blank we can use the same bitmap for shape and mask: all // zeros. const int size = ((w + 7) >> 3) * h; char* data = new char[size]; memset(data, 0, size); // make bitmap Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h); // need an arbitrary color for the cursor XColor color; color.pixel = 0; color.red = color.green = color.blue = 0; color.flags = DoRed | DoGreen | DoBlue; // make cursor from bitmap Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap, &color, &color, 0, 0); // don't need bitmap or the data anymore delete[] data; XFreePixmap(m_display, bitmap); return cursor; } bool CXWindowsScreen::getEvent(XEvent* xevent) const { // wait for an event in a cancellable way and don't lock the // display while we're waiting. m_mutex.lock(); while (!m_stop && XPending(m_display) == 0) { m_mutex.unlock(); CThread::sleep(0.05); m_mutex.lock(); } if (m_stop) { m_mutex.unlock(); return false; } else { XNextEvent(m_display, xevent); m_mutex.unlock(); return true; } } void CXWindowsScreen::doStop() { CLock lock(&m_mutex); m_stop = true; } ClipboardID CXWindowsScreen::getClipboardID(Atom selection) { for (ClipboardID id = 0; id < kClipboardEnd; ++id) if (selection == m_atomClipboard[id]) return id; return kClipboardEnd; } bool CXWindowsScreen::lostClipboard( Atom selection, Time timestamp) { for (ClipboardID id = 0; id < kClipboardEnd; ++id) { if (selection == m_atomClipboard[id]) { // note the time CLock lock(&m_mutex); m_clipboards[id].m_lostClipboard = timestamp; log((CLOG_INFO "lost clipboard %d ownership at %d", id, timestamp)); return true; } } return false; } bool CXWindowsScreen::setDisplayClipboard( ClipboardID id, const IClipboard* clipboard, Window requestor, Time timestamp) { CLock lock(&m_mutex); XSetSelectionOwner(m_display, m_atomClipboard[id], requestor, timestamp); if (XGetSelectionOwner(m_display, m_atomClipboard[id]) == requestor) { // we got the selection log((CLOG_INFO "grabbed clipboard at %d", timestamp)); m_clipboards[id].m_lostClipboard = CurrentTime; if (clipboard != NULL) { // save clipboard to serve requests CClipboard::copy(&m_clipboards[id].m_clipboard, clipboard, timestamp); } else { // clear clipboard if (m_clipboards[id].m_clipboard.open(timestamp)) { m_clipboards[id].m_clipboard.close(); } } return true; } return false; } void CXWindowsScreen::getDisplayClipboard( ClipboardID id, IClipboard* clipboard, Window requestor, Time timestamp) const { assert(clipboard != NULL); assert(requestor != None); // FIXME -- don't update clipboard object if clipboard hasn't changed // clear the clipboard object if (!clipboard->open(timestamp)) return; // block others from using the display while we get the clipboard. // in particular, this prevents the event thread from stealing the // selection notify event we're expecting. CLock lock(&m_mutex); Atom selection = m_atomClipboard[id]; // ask the selection for all the formats it has. some owners return // the TARGETS atom and some the ATOM atom when TARGETS is requested. Atom format; CString targets; if (getDisplayClipboard(selection, m_atomTargets, requestor, timestamp, &format, &targets) && (format == m_atomTargets || format == XA_ATOM)) { // get each target (that we can interpret). some owners return // some targets multiple times in the list so don't try to get // those multiple times. const Atom* targetAtoms = reinterpret_cast(targets.data()); const SInt32 numTargets = targets.size() / sizeof(Atom); std::set clipboardFormats; std::set targets; log((CLOG_INFO "getting selection %d with %d targets", id, numTargets)); for (SInt32 i = 0; i < numTargets; ++i) { Atom format = targetAtoms[i]; log((CLOG_DEBUG1 " source target %d", format)); // skip already handled targets if (targets.count(format) > 0) { log((CLOG_DEBUG1 " skipping handled target %d", format)); continue; } // mark this target as done targets.insert(format); // determine the expected clipboard format IClipboard::EFormat expectedFormat = getFormat(format); // if we can use the format and we haven't already retrieved // it then get it if (expectedFormat == IClipboard::kNumFormats) { log((CLOG_DEBUG1 " no format for target", format)); continue; } if (clipboardFormats.count(expectedFormat) > 0) { log((CLOG_DEBUG1 " skipping handled format %d", expectedFormat)); continue; } CString data; if (!getDisplayClipboard(selection, format, requestor, timestamp, &format, &data)) { log((CLOG_DEBUG1 " no data for target", format)); continue; } // use the actual format, not the expected IClipboard::EFormat actualFormat = getFormat(format); if (actualFormat == IClipboard::kNumFormats) { log((CLOG_DEBUG1 " no format for target", format)); continue; } if (clipboardFormats.count(actualFormat) > 0) { log((CLOG_DEBUG1 " skipping handled format %d", actualFormat)); continue; } // add to clipboard and note we've done it clipboard->add(actualFormat, data); clipboardFormats.insert(actualFormat); log((CLOG_INFO " added format %d for target %d", actualFormat, format)); } } else { // non-ICCCM conforming selection owner. try TEXT format. // FIXME log((CLOG_DEBUG1 "selection doesn't support TARGETS, format is %d", format)); } // done with clipboard clipboard->close(); } bool CXWindowsScreen::getDisplayClipboard( Atom selection, Atom type, Window requestor, Time timestamp, Atom* outputType, CString* outputData) const { assert(outputType != NULL); assert(outputData != NULL); // FIXME -- this doesn't work to retrieve Motif selections. // delete data property XDeleteProperty(m_display, requestor, m_atomData); // request data conversion XConvertSelection(m_display, selection, type, m_atomData, requestor, timestamp); // wait for the selection notify event. can't just mask out other // events because X stupidly doesn't provide a mask for selection // events, so we use a predicate to find our event. we also set // a time limit for a response so we're not screwed by a bad // clipboard owner. CStopwatch timer(true); XEvent xevent; while (XCheckIfEvent(m_display, &xevent, &CXWindowsScreen::findSelectionNotify, (XPointer)&requestor) != True) { // return false if we've timed-out if (timer.getTime() >= 0.2) return false; // wait a bit CThread::sleep(0.05); } assert(xevent.type == SelectionNotify); assert(xevent.xselection.requestor == requestor); // make sure the transfer worked Atom property = xevent.xselection.property; if (property == None) { // cannot convert *outputType = type; log((CLOG_DEBUG "selection conversion failed for %d", type)); return false; } // get the data and discard the property SInt32 datumSize; CString data; bool okay = getData(requestor, property, outputType, &datumSize, &data); XDeleteProperty(m_display, requestor, property); // fail if we couldn't get the data if (!okay) { log((CLOG_DEBUG "can't get data for selection format %d", type)); return false; } // handle INCR type specially. it means we'll be receiving the data // piecemeal so we just loop until we've collected all the data. if (*outputType == m_atomINCR) { log((CLOG_DEBUG1 "selection data for format %d is incremental", type)); // the data is a lower bound on the amount of data to be // transferred. use it as a hint to size our buffer. UInt32 size; switch (datumSize) { case 8: size = *(reinterpret_cast(data.data())); break; case 16: size = *(reinterpret_cast(data.data())); break; case 32: size = *(reinterpret_cast(data.data())); break; default: assert(0 && "invalid datum size"); } // empty the buffer and reserve the lower bound data.erase(); data.reserve(size); // look for property notify events with the following CPropertyNotifyInfo filter; filter.m_window = requestor; filter.m_property = property; // now enter the INCR loop bool error = false; *outputType = (Atom)0; for (;;) { // wait for more data while (XCheckIfEvent(m_display, &xevent, &CXWindowsScreen::findPropertyNotify, (XPointer)&filter) != True) { // wait a bit CThread::sleep(0.05); } assert(xevent.type == PropertyNotify); assert(xevent.xproperty.window == requestor); assert(xevent.xproperty.atom == property); // get the additional data then delete the property to // ask the clipboard owner for the next chunk. Atom newType; CString newData; okay = getData(requestor, property, &newType, NULL, &newData); XDeleteProperty(m_display, requestor, property); // transfer has failed if we can't get the data if (!okay) error = true; // a zero length property means we got the last chunk if (newData.size() == 0) break; // if this is the first chunk then save the type. otherwise // note that the new type is the same as the first chunk's // type. if they're not the the clipboard owner is busted // but we have to continue the transfer because there's no // way to cancel it. if (*outputType == (Atom)0) *outputType = newType; else if (*outputType != newType) error = true; // append the data data += newData; } // if there was an error we could say the transferred failed // but we'll be liberal in what we accept. if (error) { log((CLOG_WARN "ICCCM violation by clipboard owner")); // return false; } } *outputData = data; return true; } bool CXWindowsScreen::getData( Window window, Atom property, Atom* type, SInt32* datumSize, CString* data) const { assert(type != NULL); assert(data != NULL); // clear out any existing data data->erase(); // read the property long offset = 0; long length = 8192 / 4; for (;;) { // get more data int actualDatumSize; unsigned long numItems, bytesLeft; unsigned char* rawData; const int result = XGetWindowProperty(m_display, window, property, offset, length, False, AnyPropertyType, type, &actualDatumSize, &numItems, &bytesLeft, &rawData); if (result != Success) { // failed return false; } // save datum size if (datumSize != NULL) *datumSize = (SInt32)actualDatumSize; const SInt32 bytesPerDatum = (SInt32)actualDatumSize / 8; // advance read pointer. since we can only read at offsets that // are multiples of 4 byte we take care to write multiples of 4 // bytes to data, except when we've retrieved the last chunk. SInt32 quadCount = (numItems * bytesPerDatum) / 4; offset += quadCount; // append data if (bytesLeft == 0) data->append((char*)rawData, bytesPerDatum * numItems); else data->append((char*)rawData, 4 * quadCount); // done with returned data XFree(rawData); // done if no data is left if (bytesLeft == 0) return true; } } IClipboard::EFormat CXWindowsScreen::getFormat(Atom src) const { // FIXME -- handle more formats (especially mime-type-like formats // and various character encodings like unicode). if (src == XA_STRING || src == m_atomText || src == m_atomCompoundText) return IClipboard::kText; return IClipboard::kNumFormats; } Bool CXWindowsScreen::findSelectionNotify( Display*, XEvent* xevent, XPointer arg) { Window requestor = *reinterpret_cast(arg); return (xevent->type == SelectionNotify && xevent->xselection.requestor == requestor) ? True : False; } Bool CXWindowsScreen::findPropertyNotify( Display*, XEvent* xevent, XPointer arg) { CPropertyNotifyInfo* filter = reinterpret_cast(arg); return (xevent->type == PropertyNotify && xevent->xproperty.window == filter->m_window && xevent->xproperty.atom == filter->m_property && xevent->xproperty.state == PropertyNewValue) ? True : False; } void CXWindowsScreen::addClipboardRequest( Window owner, Window requestor, Atom selection, Atom target, Atom property, Time time) { bool success = false; // see if it's a selection we know about ClipboardID id; for (id = 0; id < kClipboardEnd; ++id) if (selection == m_atomClipboard[id]) break; // mutex the display CLock lock(&m_mutex); // a request for multiple targets is special if (id != kClipboardEnd) { // check time we own the selection against the requested time if (!wasOwnedAtTime(id, owner, time)) { // fail for time we didn't own selection } else if (target == m_atomMultiple) { // add a multiple request if (property != None) { success = sendClipboardMultiple(id, requestor, property, time); } } else { // handle remaining request formats success = sendClipboardData(id, requestor, target, property, time); } } // send success or failure sendNotify(requestor, selection, target, success ? property : None, time); } void CXWindowsScreen::processClipboardRequest( Window requestor, Atom property, Time /*time*/) { CLock lock(&m_mutex); // check every clipboard for (ClipboardID id = 0; id < kClipboardEnd; ++id) { CClipboardInfo& clipboard = m_clipboards[id]; // find the request list CRequestMap::iterator index = clipboard.m_requests.find(requestor); if (index == clipboard.m_requests.end()) { // this clipboard isn't servicing this requestor window continue; } CRequestList* list = index->second; assert(list != NULL); // find the property in the list CRequestList::iterator index2; for (index2 = list->begin(); index2 != list->end(); ++index2) { if ((*index2)->m_property == property) { break; } } if (index2 == list->end()) { // this clipboard isn't waiting on this property continue; } CClipboardRequest* request = *index2; assert(request != NULL); // compute amount of data to send assert(request->m_sent <= request->m_data.size()); UInt32 count = request->m_data.size() - request->m_sent; if (count > kMaxRequestSize) { // limit maximum chunk size count = kMaxRequestSize; // make it a multiple of the size count &= ~((request->m_size >> 3) - 1); } // send more data // FIXME -- handle Alloc errors (by returning false) XChangeProperty(m_display, request->m_requestor, request->m_property, request->m_type, request->m_size, PropModeReplace, reinterpret_cast( request->m_data.data() + request->m_sent), count / (request->m_size >> 3)); // account for sent data request->m_sent += count; // if we sent zero bytes then we're done sending this data. remove // it from the list and, if the list is empty, the list from the // map. also stop watching the requestor for events. if (count == 0) { list->erase(index2); delete request; if (list->empty()) { clipboard.m_requests.erase(index); delete list; } XSelectInput(m_display, requestor, getEventMask(requestor)); } // request has been serviced break; } } void CXWindowsScreen::destroyClipboardRequest( Window requestor) { CLock lock(&m_mutex); // check every clipboard for (ClipboardID id = 0; id < kClipboardEnd; ++id) { CClipboardInfo& clipboard = m_clipboards[id]; // find the request list CRequestMap::iterator index = clipboard.m_requests.find(requestor); if (index == clipboard.m_requests.end()) { continue; } CRequestList* list = index->second; assert(list != NULL); // destroy every request in the list for (CRequestList::iterator index2 = list->begin(); index2 != list->end(); ++index2) { delete *index2; } // remove and destroy the list clipboard.m_requests.erase(index); delete list; } // note -- we don't stop watching the window for events because // we're called in response to the window being destroyed. } bool CXWindowsScreen::sendClipboardData( ClipboardID id, Window requestor, Atom target, Atom property, Time time) { if (target == m_atomTargets) { return sendClipboardTargets(id, requestor, property, time); } else if (target == m_atomTimestamp) { return sendClipboardTimestamp(id, requestor, property, time); } else { // compute the type and size for the requested target and // convert the data from the clipboard. Atom type = None; int size = 0; CString data; if (target == m_atomText || target == m_atomString) { if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) { type = m_atomString; size = 8; data = m_clipboards[id].m_clipboard.get(IClipboard::kText); } } // fail if we don't recognize or can't handle the target if (type == None || size == 0) { return false; } if (data.size() > kMaxRequestSize) { log((CLOG_DEBUG1 "handling clipboard request for %d as INCR", target)); // get the appropriate list, creating it if necessary CRequestList* list = m_clipboards[id].m_requests[requestor]; if (list == NULL) { list = new CRequestList; m_clipboards[id].m_requests[requestor] = list; } // create request object CClipboardRequest* request = new CClipboardRequest; request->m_data = data; request->m_sent = 0; request->m_requestor = requestor; request->m_property = property; request->m_type = type; request->m_size = size; // add request to request list list->push_back(request); // start watching requestor for property changes and // destruction, in addition to other events required by // the subclass. XSelectInput(m_display, requestor, getEventMask(requestor) | StructureNotifyMask | PropertyChangeMask); // FIXME -- handle Alloc errors (by returning false) // set property to INCR const UInt32 zero = 0; XChangeProperty(m_display, requestor, property, m_atomINCR, 8 * sizeof(zero), PropModeReplace, reinterpret_cast(&zero), 1); } else { log((CLOG_DEBUG1 "handling clipboard request for %d", target)); // FIXME -- handle Alloc errors (by returning false) XChangeProperty(m_display, requestor, property, type, size, PropModeReplace, reinterpret_cast( data.data()), data.size() / (size >> 3)); } return true; } return false; } bool CXWindowsScreen::sendClipboardMultiple( ClipboardID id, Window requestor, Atom property, Time time) { log((CLOG_DEBUG1 "handling clipboard request for MULTIPLE")); // get the list of requested formats Atom type; SInt32 size; CString data; if (!getData(requestor, property, &type, &size, &data)) { type = 0; } // we only handle atom pair type bool success = false; if (type == m_atomAtomPair) { // check each format, replacing ones we can't do with None. set // the property for each to the requested data (for small requests) // or INCR (for large requests). bool updated = false; UInt32 numRequests = data.size() / (2 * sizeof(Atom)); for (UInt32 index = 0; index < numRequests; ++index) { // get request info const Atom* request = reinterpret_cast(data.data()); const Atom target = request[2 * index + 0]; const Atom property = request[2 * index + 1]; // handle target if (property != None) { if (!sendClipboardData(id, requestor, target, property, time)) { // couldn't handle target. change property to None. const Atom none = None; data.replace((2 * index + 1) * sizeof(Atom), sizeof(Atom), reinterpret_cast(&none), sizeof(none)); updated = true; } else { success = true; } } } // update property if we changed it if (updated) { // FIXME -- handle Alloc errors (by returning false) XChangeProperty(m_display, requestor, property, m_atomAtomPair, 8 * sizeof(Atom), PropModeReplace, reinterpret_cast( data.data()), data.length()); } } // send notify sendNotify(requestor, m_atomClipboard[id], m_atomMultiple, success ? property : None, time); return success; } bool CXWindowsScreen::sendClipboardTargets( ClipboardID id, Window requestor, Atom property, Time /*time*/) { log((CLOG_DEBUG1 "handling request for TARGETS")); // count the number of targets, plus TARGETS and MULTIPLE SInt32 numTargets = 2; if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) { numTargets += 2; } // construct response Atom* response = new Atom[numTargets]; SInt32 count = 0; response[count++] = m_atomTargets; response[count++] = m_atomMultiple; if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) { response[count++] = m_atomText; response[count++] = m_atomString; } // send response (we assume we can transfer the entire list at once) // FIXME -- handle Alloc errors (by returning false) XChangeProperty(m_display, requestor, property, m_atomAtom, 8 * sizeof(Atom), PropModeReplace, reinterpret_cast(response), count); // done with our response delete[] response; return true; } bool CXWindowsScreen::sendClipboardTimestamp( ClipboardID id, Window requestor, Atom property, Time /*time*/) { log((CLOG_DEBUG1 "handling clipboard request for TIMESTAMP")); // FIXME -- handle Alloc errors (by returning false) Time time = m_clipboards[id].m_clipboard.getTime(); XChangeProperty(m_display, requestor, property, m_atomInteger, 32, PropModeReplace, reinterpret_cast(time), 1); return true; } void CXWindowsScreen::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; XSendEvent(m_display, requestor, False, 0, &event); } bool CXWindowsScreen::wasOwnedAtTime( ClipboardID id, Window window, Time time) const { const CClipboardInfo& clipboard = m_clipboards[id]; // not owned if we've never owned the selection if (clipboard.m_clipboard.getTime() == CurrentTime) 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_lostClipboard as the end time. Time lost = clipboard.m_lostClipboard; if (lost == CurrentTime) if (time == CurrentTime) return true; else lost = getCurrentTimeNoLock(window); else if (time == CurrentTime) return false; // compare time to range Time duration = clipboard.m_lostClipboard - clipboard.m_clipboard.getTime(); Time when = time - clipboard.m_clipboard.getTime(); return (/*when >= 0 &&*/ when < duration); } Time CXWindowsScreen::getCurrentTime(Window window) const { CLock lock(&m_mutex); return getCurrentTimeNoLock(window); } Time CXWindowsScreen::getCurrentTimeNoLock( Window window) const { // select property events on window // note -- this will break clipboard transfer if used on a requestor // window, so don't do that. XSelectInput(m_display, window, getEventMask(window) | PropertyChangeMask); // do a zero-length append to get the current time unsigned char dummy; XChangeProperty(m_display, window, m_atomSynergyTime, m_atomInteger, 8, PropModeAppend, &dummy, 0); // look for property notify events with the following CPropertyNotifyInfo filter; filter.m_window = window; filter.m_property = m_atomSynergyTime; // wait for reply XEvent xevent; while (XCheckIfEvent(m_display, &xevent, &CXWindowsScreen::findPropertyNotify, (XPointer)&filter) != True) { // wait a bit CThread::sleep(0.05); } assert(xevent.type == PropertyNotify); assert(xevent.xproperty.window == window); assert(xevent.xproperty.atom == m_atomSynergyTime); // restore event mask XSelectInput(m_display, window, getEventMask(window)); return xevent.xproperty.time; } // // CXWindowsScreen::CClipboardInfo // CXWindowsScreen::CClipboardInfo::CClipboardInfo() : m_clipboard(), m_lostClipboard(CurrentTime), m_requests() { // do nothing } // // CXWindowsScreen::CDisplayLock // CXWindowsScreen::CDisplayLock::CDisplayLock(const CXWindowsScreen* screen) : m_mutex(&screen->m_mutex), m_display(screen->m_display) { assert(m_display != NULL); m_mutex->lock(); } CXWindowsScreen::CDisplayLock::~CDisplayLock() { m_mutex->unlock(); } CXWindowsScreen::CDisplayLock::operator Display*() const { return m_display; }