Files
barrier/synergy/CXWindowsScreen.cpp
crs f15c9df85b added platform independent clipboard transfer stuff
clipboard owner support (MS windows done, X windows partial)
added key transfer on ms windows
mutex fixes in CClient (had race conditions)
faster debug output in ms windows
changed temporary screen name to "secondary"
network fixes on ms windows (poll returned wrong result)
fixed transparent cursor on ms windows
2001-11-25 18:32:41 +00:00

621 lines
16 KiB
C++

#include "CXWindowsScreen.h"
#include "CThread.h"
#include "CLock.h"
#include "TMethodJob.h"
#include "CLog.h"
#include "CString.h"
#include <string.h>
#include <assert.h>
#include <X11/X.h>
#include <X11/Xatom.h>
#include <set>
//
// CXWindowsScreen
//
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_atomData = XInternAtom(m_display, "DESTINATION", False);
m_atomINCR = XInternAtom(m_display, "INCR", False);
m_atomText = XInternAtom(m_display, "TEXT", False);
m_atomCompoundText = XInternAtom(m_display, "COMPOUND_TEXT", False);
// let subclass prep display
onOpenDisplay();
}
void CXWindowsScreen::closeDisplay()
{
assert(m_display != NULL);
// let subclass close down display
onCloseDisplay();
// 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;
}
bool CXWindowsScreen::setDisplayClipboard(
const IClipboard* clipboard,
Window requestor, Time timestamp)
{
CLock lock(&m_mutex);
XSetSelectionOwner(m_display, XA_PRIMARY, requestor, timestamp);
if (XGetSelectionOwner(m_display, XA_PRIMARY) == requestor) {
// we got the selection
log((CLOG_DEBUG "grabbed clipboard"));
if (clipboard != NULL) {
// save clipboard to serve requests
CClipboard::copy(&m_clipboard, clipboard);
}
else {
// clear clipboard
if (m_clipboard.open()) {
m_clipboard.close();
}
}
return true;
}
return false;
}
void CXWindowsScreen::getDisplayClipboard(
IClipboard* clipboard,
Window requestor, Time timestamp) const
{
assert(clipboard != NULL);
assert(requestor != None);
// clear the clipboard object
if (!clipboard->open())
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);
// use PRIMARY selection as the "clipboard"
Atom selection = XA_PRIMARY;
// 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<const Atom*>(targets.data());
const SInt32 numTargets = targets.size() / sizeof(Atom);
std::set<IClipboard::EFormat> clipboardFormats;
std::set<Atom> targets;
log((CLOG_DEBUG "selection has %d targets", numTargets));
for (SInt32 i = 0; i < numTargets; ++i) {
Atom format = targetAtoms[i];
log((CLOG_DEBUG " source target %d", format));
// skip already handled targets
if (targets.count(format) > 0) {
log((CLOG_DEBUG " 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_DEBUG " no format for target", format));
continue;
}
if (clipboardFormats.count(expectedFormat) > 0) {
log((CLOG_DEBUG " skipping handled format %d", expectedFormat));
continue;
}
CString data;
if (!getDisplayClipboard(selection, format,
requestor, timestamp, &format, &data)) {
log((CLOG_DEBUG " no data for target", format));
continue;
}
// use the actual format, not the expected
IClipboard::EFormat actualFormat = getFormat(format);
if (actualFormat == IClipboard::kNumFormats) {
log((CLOG_DEBUG " no format for target", format));
continue;
}
if (clipboardFormats.count(actualFormat) > 0) {
log((CLOG_DEBUG " skipping handled format %d", actualFormat));
continue;
}
// add to clipboard and note we've done it
clipboard->add(actualFormat, data);
clipboardFormats.insert(actualFormat);
}
}
else {
// non-ICCCM conforming selection owner. try TEXT format.
// FIXME
log((CLOG_DEBUG "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);
// 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.
XEvent xevent;
while (XCheckIfEvent(m_display, &xevent,
&CXWindowsScreen::findSelectionNotify,
(XPointer)&requestor) != True) {
// 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_DEBUG "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<const UInt8*>(data.data()));
break;
case 16:
size = *(reinterpret_cast<const UInt16*>(data.data()));
break;
case 32:
size = *(reinterpret_cast<const UInt32*>(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
PropertyNotifyInfo 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 = *((Window*)arg);
return (xevent->type == SelectionNotify &&
xevent->xselection.requestor == requestor) ? True : False;
}
Bool CXWindowsScreen::findPropertyNotify(
Display*, XEvent* xevent, XPointer arg)
{
PropertyNotifyInfo* filter = (PropertyNotifyInfo*)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)
{
// mutex the display
CLock lock(&m_mutex);
// check the target
IClipboard::EFormat format = IClipboard::kNumFormats;
if (selection == XA_PRIMARY) {
if (target == m_atomTargets) {
// send the list of available targets
sendClipboardTargetsNotify(requestor, property, time);
return;
}
else if (target == m_atomMultiple) {
// add a multiple request
if (property != None) {
addClipboardMultipleRequest(requestor, property, time);
return;
}
}
else {
format = getFormat(target);
}
}
// if we can't handle the format then send a failure notification
if (format == IClipboard::kNumFormats) {
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 = None;
event.xselection.time = time;
XSendEvent(m_display, requestor, False, 0, &event);
return;
}
// we could process short requests without adding to the request
// queue but we'll keep things simple by doing all requests the
// same way.
// FIXME
}
void CXWindowsScreen::sendClipboardTargetsNotify(
Window requestor,
Atom property, Time time)
{
// construct response
// FIXME
// set property
// FIXME
// send notification
XEvent event;
event.xselection.type = SelectionNotify;
event.xselection.display = m_display;
event.xselection.requestor = requestor;
event.xselection.selection = XA_PRIMARY;
event.xselection.target = m_atomTargets;
event.xselection.property = property;
event.xselection.time = time;
XSendEvent(m_display, requestor, False, 0, &event);
}
void CXWindowsScreen::processClipboardRequest(
Window requestor,
Atom property, Time time)
{
// FIXME
}
void CXWindowsScreen::addClipboardRequest(
Window requestor,
Atom property, Time time,
IClipboard::EFormat format)
{
// FIXME
}
void CXWindowsScreen::addClipboardMultipleRequest(
Window requestor,
Atom property, Time time)
{
// FIXME
}
//
// 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;
}