Added support for multiple clipboards. This is mainly to

support both PRIMARY and CLIPBOARD selections on X windows.
This commit is contained in:
crs 2002-04-27 14:19:53 +00:00
parent 428166fe85
commit f9170eb139
18 changed files with 468 additions and 251 deletions

View File

@ -85,14 +85,14 @@ void CClient::run(const CNetworkAddress& serverAddress)
}
}
void CClient::onClipboardChanged()
void CClient::onClipboardChanged(ClipboardID id)
{
log((CLOG_DEBUG "sending clipboard changed"));
log((CLOG_DEBUG "sending clipboard %d changed", id));
CLock lock(&m_mutex);
if (m_output != NULL) {
// m_output can be NULL if the screen calls this method
// before we've gotten around to connecting to the server.
CProtocolUtil::writef(m_output, kMsgCClipboard);
CProtocolUtil::writef(m_output, kMsgCClipboard, id);
}
}
@ -318,7 +318,12 @@ void CClient::onLeave()
void CClient::onGrabClipboard()
{
m_screen->grabClipboard();
ClipboardID id;
{
CLock lock(&m_mutex);
CProtocolUtil::readf(m_input, kMsgCClipboard + 4, &id);
}
m_screen->grabClipboard(id);
}
void CClient::onScreenSaver()
@ -345,45 +350,47 @@ void CClient::onQueryInfo()
void CClient::onQueryClipboard()
{
// parse message
ClipboardID id;
UInt32 seqNum;
CClipboard clipboard;
{
CLock lock(&m_mutex);
CProtocolUtil::readf(m_input, kMsgQClipboard + 4, &seqNum);
CProtocolUtil::readf(m_input, kMsgQClipboard + 4, &id, &seqNum);
}
log((CLOG_DEBUG "received query clipboard seqnum=%d", seqNum));
log((CLOG_DEBUG "received query clipboard %d seqnum=%d", id, seqNum));
// get screen's clipboard data
m_screen->getClipboard(&clipboard);
m_screen->getClipboard(id, &clipboard);
// marshall the data
CString data = clipboard.marshall();
// send it
log((CLOG_DEBUG "sending clipboard seqnum=%d, size=%d", seqNum, data.size()));
log((CLOG_DEBUG "sending clipboard %d seqnum=%d, size=%d", id, seqNum, data.size()));
{
CLock lock(&m_mutex);
CProtocolUtil::writef(m_output, kMsgDClipboard, seqNum, &data);
CProtocolUtil::writef(m_output, kMsgDClipboard, id, seqNum, &data);
}
}
void CClient::onSetClipboard()
{
ClipboardID id;
CString data;
{
// parse message
UInt32 seqNum;
CLock lock(&m_mutex);
CProtocolUtil::readf(m_input, kMsgDClipboard + 4, &seqNum, &data);
CProtocolUtil::readf(m_input, kMsgDClipboard + 4, &id, &seqNum, &data);
}
log((CLOG_DEBUG "received clipboard size=%d", data.size()));
log((CLOG_DEBUG "received clipboard %d size=%d", id, data.size()));
// unmarshall
CClipboard clipboard;
clipboard.unmarshall(data);
// set screen's clipboard
m_screen->setClipboard(&clipboard);
m_screen->setClipboard(id, &clipboard);
}
void CClient::onKeyDown()

View File

@ -4,6 +4,7 @@
#include "CMutex.h"
#include "CString.h"
#include "BasicTypes.h"
#include "ClipboardTypes.h"
class CNetworkAddress;
class IInputStream;
@ -20,7 +21,7 @@ class CClient {
void run(const CNetworkAddress& serverAddress);
// handle events on client's screen
void onClipboardChanged();
void onClipboardChanged(ClipboardID);
// accessors

View File

@ -63,7 +63,8 @@ void CXWindowsSecondaryScreen::run()
// selection owner. report that to the server.
if (lostClipboard(xevent.xselectionclear.selection,
xevent.xselectionclear.time)) {
m_client->onClipboardChanged();
m_client->onClipboardChanged(getClipboardID(
xevent.xselectionclear.selection));
}
break;
@ -260,16 +261,16 @@ void CXWindowsSecondaryScreen::mouseWheel(SInt32)
}
void CXWindowsSecondaryScreen::setClipboard(
const IClipboard* clipboard)
ClipboardID id, const IClipboard* clipboard)
{
// FIXME -- don't use CurrentTime
setDisplayClipboard(clipboard, m_window, CurrentTime);
setDisplayClipboard(id, clipboard, m_window, CurrentTime);
}
void CXWindowsSecondaryScreen::grabClipboard()
void CXWindowsSecondaryScreen::grabClipboard(ClipboardID id)
{
// FIXME -- don't use CurrentTime
setDisplayClipboard(NULL, m_window, CurrentTime);
setDisplayClipboard(id, NULL, m_window, CurrentTime);
}
void CXWindowsSecondaryScreen::getSize(
@ -284,10 +285,10 @@ SInt32 CXWindowsSecondaryScreen::getJumpZoneSize() const
}
void CXWindowsSecondaryScreen::getClipboard(
IClipboard* clipboard) const
ClipboardID id, IClipboard* clipboard) const
{
// FIXME -- don't use CurrentTime
getDisplayClipboard(clipboard, m_window, CurrentTime);
getDisplayClipboard(id, clipboard, m_window, CurrentTime);
}
void CXWindowsSecondaryScreen::onOpenDisplay()

View File

@ -24,11 +24,11 @@ class CXWindowsSecondaryScreen : public CXWindowsScreen, public ISecondaryScreen
virtual void mouseUp(ButtonID);
virtual void mouseMove(SInt32 xAbsolute, SInt32 yAbsolute);
virtual void mouseWheel(SInt32 delta);
virtual void setClipboard(const IClipboard*);
virtual void grabClipboard();
virtual void setClipboard(ClipboardID, const IClipboard*);
virtual void grabClipboard(ClipboardID);
virtual void getSize(SInt32* width, SInt32* height) const;
virtual SInt32 getJumpZoneSize() const;
virtual void getClipboard(IClipboard*) const;
virtual void getClipboard(ClipboardID, IClipboard*) const;
protected:
// CXWindowsScreen overrides

View File

@ -43,9 +43,7 @@ else { wait(0); exit(1); }
CServer::CServer() : m_primary(NULL),
m_active(NULL),
m_primaryInfo(NULL),
m_clipboardSeqNum(0),
m_clipboardReady(false)
m_primaryInfo(NULL)
{
m_socketFactory = NULL;
m_securityFactory = NULL;
@ -150,14 +148,16 @@ void CServer::setInfo(const CString& client,
log((CLOG_NOTE "client \"%s\" size=%dx%d zone=%d", client.c_str(), w, h, zoneSize));
}
void CServer::grabClipboard()
void CServer::grabClipboard(ClipboardID id)
{
grabClipboard(m_primaryInfo->m_name);
grabClipboard(id, m_primaryInfo->m_name);
}
void CServer::grabClipboard(const CString& client)
void CServer::grabClipboard(
ClipboardID id, const CString& client)
{
CLock lock(&m_mutex);
ClipboardInfo& clipboard = m_clipboards[id];
// client must be connected
CScreenList::iterator index = m_screens.find(client);
@ -165,74 +165,77 @@ void CServer::grabClipboard(const CString& client)
throw XBadClient();
}
log((CLOG_NOTE "client \"%s\" grabbed clipboard from \"%s\"", client.c_str(), m_clipboardOwner.c_str()));
log((CLOG_NOTE "client \"%s\" grabbed clipboard %d from \"%s\"", client.c_str(), id, clipboard.m_clipboardOwner.c_str()));
// save the clipboard owner
m_clipboardOwner = client;
clipboard.m_clipboardOwner = client;
// mark client as having the clipboard data
index->second->m_gotClipboard = true;
index->second->m_gotClipboard[id] = true;
// tell all other clients to take ownership of clipboard and mark
// them as not having the data yet.
for (index = m_screens.begin(); index != m_screens.end(); ++index) {
if (index->first != client) {
CScreenInfo* info = index->second;
info->m_gotClipboard = false;
info->m_gotClipboard[id] = false;
if (info->m_protocol == NULL) {
m_primary->grabClipboard();
m_primary->grabClipboard(id);
}
else {
info->m_protocol->sendGrabClipboard();
info->m_protocol->sendGrabClipboard(id);
}
}
}
// increment the clipboard sequence number so we can identify the
// clipboard query's response.
++m_clipboardSeqNum;
++clipboard.m_clipboardSeqNum;
// begin getting the clipboard data
if (m_active->m_protocol == NULL) {
// get clipboard immediately from primary screen
m_primary->getClipboard(&m_clipboard);
m_clipboardData = m_clipboard.marshall();
m_clipboardReady = true;
m_primary->getClipboard(id, &clipboard.m_clipboard);
clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
clipboard.m_clipboardReady = true;
}
else {
// clear out the clipboard since existing data is now out of date.
if (m_clipboard.open()) {
m_clipboard.close();
if (clipboard.m_clipboard.open()) {
clipboard.m_clipboard.close();
}
m_clipboardReady = false;
clipboard.m_clipboardReady = false;
// send request but don't wait for reply
m_active->m_protocol->sendQueryClipboard(m_clipboardSeqNum);
m_active->m_protocol->sendQueryClipboard(id,
clipboard.m_clipboardSeqNum);
}
}
void CServer::setClipboard(
void CServer::setClipboard(ClipboardID id,
UInt32 seqNum, const CString& data)
{
// update the clipboard if the sequence number matches
CLock lock(&m_mutex);
if (seqNum == m_clipboardSeqNum) {
ClipboardInfo& clipboard = m_clipboards[id];
if (seqNum == clipboard.m_clipboardSeqNum) {
// unmarshall into our clipboard buffer
m_clipboardData = data;
m_clipboard.unmarshall(m_clipboardData);
m_clipboardReady = true;
clipboard.m_clipboardData = data;
clipboard.m_clipboard.unmarshall(clipboard.m_clipboardData);
clipboard.m_clipboardReady = true;
// if the active client doesn't have the clipboard data
// (and it won't unless the client is the one sending us
// the data) then send the data now.
if (!m_active->m_gotClipboard) {
if (!m_active->m_gotClipboard[id]) {
if (m_active->m_protocol == NULL) {
m_primary->setClipboard(&m_clipboard);
m_primary->setClipboard(id, &clipboard.m_clipboard);
}
else {
m_active->m_protocol->sendClipboard(m_clipboardData);
m_active->m_protocol->sendClipboard(id,
clipboard.m_clipboardData);
}
m_active->m_gotClipboard = true;
m_active->m_gotClipboard[id] = true;
}
}
}
@ -509,14 +512,18 @@ void CServer::switchScreen(CScreenInfo* dst,
}
// send the clipboard data if we haven't done so yet
if (m_clipboardReady && !m_active->m_gotClipboard) {
if (m_active->m_protocol == NULL) {
m_primary->setClipboard(&m_clipboard);
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
ClipboardInfo& clipboard = m_clipboards[id];
if (clipboard.m_clipboardReady && !m_active->m_gotClipboard[id]) {
if (m_active->m_protocol == NULL) {
m_primary->setClipboard(id, &clipboard.m_clipboard);
}
else {
m_active->m_protocol->sendClipboard(id,
clipboard.m_clipboardData);
}
m_active->m_gotClipboard[id] = true;
}
else {
m_active->m_protocol->sendClipboard(m_clipboardData);
}
m_active->m_gotClipboard = true;
}
}
else {
@ -919,10 +926,13 @@ void CServer::openPrimaryScreen()
// set the clipboard owner to the primary screen and then get the
// current clipboard data.
m_primary->getClipboard(&m_clipboard);
m_clipboardData = m_clipboard.marshall();
m_clipboardReady = true;
m_clipboardOwner = m_active->m_name;
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
ClipboardInfo& clipboard = m_clipboards[id];
m_primary->getClipboard(id, &clipboard.m_clipboard);
clipboard.m_clipboardData = clipboard.m_clipboard.marshall();
clipboard.m_clipboardReady = true;
clipboard.m_clipboardOwner = m_active->m_name;
}
}
void CServer::closePrimaryScreen()
@ -1075,13 +1085,28 @@ CServer::CScreenInfo::CScreenInfo(const CString& name,
m_name(name),
m_protocol(protocol),
m_width(0), m_height(0),
m_zoneSize(0),
m_gotClipboard(false)
m_zoneSize(0)
{
// do nothing
for (ClipboardID id = 0; id < kClipboardEnd; ++id)
m_gotClipboard[id] = false;
}
CServer::CScreenInfo::~CScreenInfo()
{
// do nothing
}
//
// CServer::ClipboardInfo
//
CServer::ClipboardInfo::ClipboardInfo() :
m_clipboard(),
m_clipboardData(),
m_clipboardOwner(),
m_clipboardSeqNum(0),
m_clipboardReady(false)
{
// do nothing
}

View File

@ -1,6 +1,7 @@
#ifndef CSERVER_H
#define CSERVER_H
#include "ClipboardTypes.h"
#include "KeyTypes.h"
#include "MouseTypes.h"
#include "CScreenMap.h"
@ -43,13 +44,14 @@ class CServer {
bool onMouseMovePrimary(SInt32 x, SInt32 y);
void onMouseMoveSecondary(SInt32 dx, SInt32 dy);
void onMouseWheel(SInt32 delta);
void grabClipboard();
void grabClipboard(ClipboardID);
// handle messages from clients
void setInfo(const CString& clientName,
SInt32 w, SInt32 h, SInt32 zoneSize);
void grabClipboard(const CString& clientName);
void setClipboard(UInt32 seqNum, const CString& data);
void grabClipboard(ClipboardID, const CString& clientName);
void setClipboard(ClipboardID,
UInt32 seqNum, const CString& data);
// accessors
@ -95,7 +97,7 @@ class CServer {
IServerProtocol* m_protocol;
SInt32 m_width, m_height;
SInt32 m_zoneSize;
bool m_gotClipboard;
bool m_gotClipboard[kClipboardEnd];
};
// change the active screen
@ -146,6 +148,17 @@ class CServer {
private:
typedef std::list<CThread*> CThreadList;
typedef std::map<CString, CScreenInfo*> CScreenList;
class ClipboardInfo {
public:
ClipboardInfo();
public:
CClipboard m_clipboard;
CString m_clipboardData;
CString m_clipboardOwner;
UInt32 m_clipboardSeqNum;
bool m_clipboardReady;
};
CMutex m_mutex;
@ -165,11 +178,7 @@ class CServer {
CScreenMap m_screenMap;
CClipboard m_clipboard;
CString m_clipboardData;
CString m_clipboardOwner;
UInt32 m_clipboardSeqNum;
bool m_clipboardReady;
ClipboardInfo m_clipboards[kClipboardEnd];
};
#endif

View File

@ -33,9 +33,9 @@ class CServerProtocol : public IServerProtocol {
virtual void sendClose() = 0;
virtual void sendEnter(SInt32 xAbs, SInt32 yAbs) = 0;
virtual void sendLeave() = 0;
virtual void sendClipboard(const CString&) = 0;
virtual void sendGrabClipboard() = 0;
virtual void sendQueryClipboard(UInt32 seqNum) = 0;
virtual void sendClipboard(ClipboardID, const CString&) = 0;
virtual void sendGrabClipboard(ClipboardID) = 0;
virtual void sendQueryClipboard(ClipboardID, UInt32 seqNum) = 0;
virtual void sendScreenSaver(bool on) = 0;
virtual void sendKeyDown(KeyID, KeyModifierMask) = 0;
virtual void sendKeyRepeat(KeyID, KeyModifierMask, SInt32 count) = 0;

View File

@ -103,22 +103,24 @@ void CServerProtocol1_0::sendLeave()
CProtocolUtil::writef(getOutputStream(), kMsgCLeave);
}
void CServerProtocol1_0::sendClipboard(const CString& data)
void CServerProtocol1_0::sendClipboard(
ClipboardID id, const CString& data)
{
log((CLOG_INFO "send clipboard to \"%s\" size=%d", getClient().c_str(), data.size()));
CProtocolUtil::writef(getOutputStream(), kMsgDClipboard, 0, &data);
log((CLOG_INFO "send clipboard %d to \"%s\" size=%d", id, getClient().c_str(), data.size()));
CProtocolUtil::writef(getOutputStream(), kMsgDClipboard, id, 0, &data);
}
void CServerProtocol1_0::sendGrabClipboard()
void CServerProtocol1_0::sendGrabClipboard(ClipboardID id)
{
log((CLOG_INFO "send grab clipboard to \"%s\"", getClient().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCClipboard);
log((CLOG_INFO "send grab clipboard %d to \"%s\"", id, getClient().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCClipboard, id);
}
void CServerProtocol1_0::sendQueryClipboard(UInt32 seqNum)
void CServerProtocol1_0::sendQueryClipboard(
ClipboardID id, UInt32 seqNum)
{
log((CLOG_INFO "query clipboard to \"%s\"", getClient().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgQClipboard, seqNum);
log((CLOG_INFO "query clipboard %d to \"%s\"", id, getClient().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgQClipboard, id, seqNum);
}
void CServerProtocol1_0::sendScreenSaver(bool on)
@ -194,15 +196,18 @@ void CServerProtocol1_0::recvInfo()
void CServerProtocol1_0::recvClipboard()
{
ClipboardID id;
UInt32 seqNum;
CString data;
CProtocolUtil::readf(getInputStream(), kMsgDClipboard + 4, &seqNum, &data);
log((CLOG_INFO "received client \"%s\" clipboard seqnum=%d, size=%d", getClient().c_str(), seqNum, data.size()));
getServer()->setClipboard(seqNum, data);
CProtocolUtil::readf(getInputStream(), kMsgDClipboard + 4, &id, &seqNum, &data);
log((CLOG_INFO "received client \"%s\" clipboard %d seqnum=%d, size=%d", getClient().c_str(), id, seqNum, data.size()));
getServer()->setClipboard(id, seqNum, data);
}
void CServerProtocol1_0::recvGrabClipboard()
{
log((CLOG_INFO "received client \"%s\" grabbed clipboard", getClient().c_str()));
getServer()->grabClipboard(getClient());
ClipboardID id;
CProtocolUtil::readf(getInputStream(), kMsgCClipboard + 4, &id);
log((CLOG_INFO "received client \"%s\" grabbed clipboard %d", getClient().c_str(), id));
getServer()->grabClipboard(id, getClient());
}

View File

@ -18,9 +18,9 @@ class CServerProtocol1_0 : public CServerProtocol {
virtual void sendClose();
virtual void sendEnter(SInt32 xAbs, SInt32 yAbs);
virtual void sendLeave();
virtual void sendClipboard(const CString&);
virtual void sendGrabClipboard();
virtual void sendQueryClipboard(UInt32 seqNum);
virtual void sendClipboard(ClipboardID, const CString&);
virtual void sendGrabClipboard(ClipboardID);
virtual void sendQueryClipboard(ClipboardID, UInt32 seqNum);
virtual void sendScreenSaver(bool on);
virtual void sendKeyDown(KeyID, KeyModifierMask);
virtual void sendKeyRepeat(KeyID, KeyModifierMask, SInt32 count);

View File

@ -137,7 +137,8 @@ void CXWindowsPrimaryScreen::run()
// selection owner. report that to the server.
if (lostClipboard(xevent.xselectionclear.selection,
xevent.xselectionclear.time)) {
m_server->grabClipboard();
m_server->grabClipboard(getClipboardID(
xevent.xselectionclear.selection));
}
break;
@ -270,7 +271,7 @@ void CXWindowsPrimaryScreen::leave()
assert(result != GrabNotViewable);
if (result != GrabSuccess) {
log((CLOG_DEBUG "waiting to grab pointer"));
CThread::sleep(0.25);
CThread::sleep(0.1);
}
} while (result != GrabSuccess);
log((CLOG_DEBUG "grabbed pointer"));
@ -283,7 +284,7 @@ void CXWindowsPrimaryScreen::leave()
// back off to avoid grab deadlock
XUngrabPointer(display, CurrentTime);
log((CLOG_DEBUG "ungrabbed pointer, waiting to grab keyboard"));
CThread::sleep(0.25);
CThread::sleep(0.1);
}
} while (result != GrabSuccess);
log((CLOG_DEBUG "grabbed keyboard"));
@ -323,16 +324,16 @@ void CXWindowsPrimaryScreen::warpCursorNoLock(
}
void CXWindowsPrimaryScreen::setClipboard(
const IClipboard* clipboard)
ClipboardID id, const IClipboard* clipboard)
{
// FIXME -- don't use CurrentTime
setDisplayClipboard(clipboard, m_window, CurrentTime);
setDisplayClipboard(id, clipboard, m_window, CurrentTime);
}
void CXWindowsPrimaryScreen::grabClipboard()
void CXWindowsPrimaryScreen::grabClipboard(ClipboardID id)
{
// FIXME -- don't use CurrentTime
setDisplayClipboard(NULL, m_window, CurrentTime);
setDisplayClipboard(id, NULL, m_window, CurrentTime);
}
void CXWindowsPrimaryScreen::getSize(
@ -347,10 +348,10 @@ SInt32 CXWindowsPrimaryScreen::getJumpZoneSize() const
}
void CXWindowsPrimaryScreen::getClipboard(
IClipboard* clipboard) const
ClipboardID id, IClipboard* clipboard) const
{
// FIXME -- don't use CurrentTime
getDisplayClipboard(clipboard, m_window, CurrentTime);
getDisplayClipboard(id, clipboard, m_window, CurrentTime);
}
void CXWindowsPrimaryScreen::onOpenDisplay()

View File

@ -19,11 +19,11 @@ class CXWindowsPrimaryScreen : public CXWindowsScreen, public IPrimaryScreen {
virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute);
virtual void leave();
virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute);
virtual void setClipboard(const IClipboard*);
virtual void grabClipboard();
virtual void setClipboard(ClipboardID, const IClipboard*);
virtual void grabClipboard(ClipboardID);
virtual void getSize(SInt32* width, SInt32* height) const;
virtual SInt32 getJumpZoneSize() const;
virtual void getClipboard(IClipboard*) const;
virtual void getClipboard(ClipboardID, IClipboard*) const;
protected:
// CXWindowsScreen overrides

View File

@ -14,7 +14,6 @@
// CXWindowsScreen
//
static const Atom kClipboardSelection = XA_PRIMARY;
static const UInt32 kMaxRequestSize = 4096;
CXWindowsScreen::CXWindowsScreen() :
@ -66,6 +65,11 @@ void CXWindowsScreen::openDisplay()
m_atomText = XInternAtom(m_display, "TEXT", False);
m_atomCompoundText = XInternAtom(m_display, "COMPOUND_TEXT", False);
// clipboard atoms
m_atomClipboard[kClipboardClipboard] =
XInternAtom(m_display, "CLIPBOARD", False);
m_atomClipboard[kClipboardSelection] = XA_PRIMARY;
// let subclass prep display
onOpenDisplay();
}
@ -78,16 +82,19 @@ void CXWindowsScreen::closeDisplay()
onCloseDisplay();
// clear out the clipboard request lists
for (CRequestMap::iterator index = m_requests.begin();
index != m_requests.end(); ++index) {
CRequestList* list = index->second;
for (CRequestList::iterator index2 = list->begin();
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
ClipboardInfo& 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 *index2;
}
delete list;
}
delete list;
clipboard.m_requests.clear();
}
m_requests.clear();
// close the display
XCloseDisplay(m_display);
@ -179,39 +186,50 @@ void CXWindowsScreen::doStop()
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)
{
if (selection == kClipboardSelection) {
// note the time
CLock lock(&m_mutex);
m_lostClipboard = timestamp;
log((CLOG_INFO "lost clipboard ownership at %d", timestamp));
return true;
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, kClipboardSelection, requestor, timestamp);
if (XGetSelectionOwner(m_display, kClipboardSelection) == requestor) {
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_gotClipboard = timestamp;
m_clipboards[id].m_gotClipboard = timestamp;
if (clipboard != NULL) {
// save clipboard to serve requests
CClipboard::copy(&m_clipboard, clipboard);
CClipboard::copy(&m_clipboards[id].m_clipboard, clipboard);
}
else {
// clear clipboard
if (m_clipboard.open()) {
m_clipboard.close();
if (m_clipboards[id].m_clipboard.open()) {
m_clipboards[id].m_clipboard.close();
}
}
@ -222,6 +240,7 @@ bool CXWindowsScreen::setDisplayClipboard(
}
void CXWindowsScreen::getDisplayClipboard(
ClipboardID id,
IClipboard* clipboard,
Window requestor, Time timestamp) const
{
@ -236,7 +255,7 @@ void CXWindowsScreen::getDisplayClipboard(
// in particular, this prevents the event thread from stealing the
// selection notify event we're expecting.
CLock lock(&m_mutex);
Atom selection = kClipboardSelection;
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.
@ -541,10 +560,13 @@ void CXWindowsScreen::addClipboardRequest(
Atom selection, Atom target,
Atom property, Time time)
{
// we can only own kClipboardSelection
if (selection != kClipboardSelection) {
// see if it's a selection we know about
ClipboardID id;
for (id = 0; id < kClipboardEnd; ++id)
if (selection == m_atomClipboard[id])
break;
if (id == kClipboardEnd)
return;
}
// mutex the display
CLock lock(&m_mutex);
@ -554,16 +576,16 @@ void CXWindowsScreen::addClipboardRequest(
if (target == m_atomMultiple) {
// add a multiple request
if (property != None) {
success = sendClipboardMultiple(requestor, property, time);
success = sendClipboardMultiple(id, requestor, property, time);
}
}
else {
// handle remaining request formats
success = sendClipboardData(requestor, target, property, time);
success = sendClipboardData(id, requestor, target, property, time);
}
// send success or failure
sendNotify(requestor, target, success ? property : None, time);
sendNotify(id, requestor, target, success ? property : None, time);
}
void CXWindowsScreen::processClipboardRequest(
@ -572,62 +594,71 @@ void CXWindowsScreen::processClipboardRequest(
{
CLock lock(&m_mutex);
// find the request list
CRequestMap::iterator index = m_requests.find(requestor);
if (index == m_requests.end()) {
return;
}
CRequestList* list = index->second;
assert(list != NULL);
// check every clipboard
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
ClipboardInfo& clipboard = m_clipboards[id];
// find the property in the list
CRequestList::iterator index2;
for (index2 = list->begin(); index2 != list->end(); ++index2) {
if ((*index2)->m_property == property) {
break;
// 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;
}
}
if (index2 == list->end()) {
log((CLOG_WARN "received property event on unexpected property"));
return;
}
CClipboardRequest* request = *index2;
assert(request != NULL);
CRequestList* list = index->second;
assert(list != 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;
// 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);
// make it a multiple of the size
count &= ~((request->m_size >> 3) - 1);
}
// 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;
// send more data
// FIXME -- handle Alloc errors (by returning false)
XChangeProperty(m_display, request->m_requestor, request->m_property,
// 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<const unsigned char*>(
request->m_data.data() + request->m_sent),
count / (request->m_size >> 3));
// account for sent data
request->m_sent += count;
// 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()) {
m_requests.erase(index);
delete list;
// 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, NoEventMask);
}
XSelectInput(m_display, requestor, NoEventMask);
// request has been serviced
break;
}
}
@ -636,37 +667,43 @@ void CXWindowsScreen::destroyClipboardRequest(
{
CLock lock(&m_mutex);
// find the request list
CRequestMap::iterator index = m_requests.find(requestor);
if (index == m_requests.end()) {
return;
}
CRequestList* list = index->second;
assert(list != NULL);
// check every clipboard
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
ClipboardInfo& clipboard = m_clipboards[id];
// destroy every request in the list
for (CRequestList::iterator index2 = list->begin();
// 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;
}
delete *index2;
}
// remove and destroy the list
m_requests.erase(index);
delete list;
// 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(requestor, property, time);
return sendClipboardTargets(id, requestor, property, time);
}
else if (target == m_atomTimestamp) {
return sendClipboardTimestamp(requestor, property, time);
return sendClipboardTimestamp(id, requestor, property, time);
}
else {
// compute the type and size for the requested target and
@ -675,10 +712,10 @@ bool CXWindowsScreen::sendClipboardData(
int size = 0;
CString data;
if (target == m_atomText || target == m_atomString) {
if (m_clipboard.has(IClipboard::kText)) {
if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) {
type = m_atomString;
size = 8;
data = m_clipboard.get(IClipboard::kText);
data = m_clipboards[id].m_clipboard.get(IClipboard::kText);
}
}
@ -691,10 +728,10 @@ bool CXWindowsScreen::sendClipboardData(
log((CLOG_DEBUG "handling clipboard request for %d as INCR", target));
// get the appropriate list, creating it if necessary
CRequestList* list = m_requests[requestor];
CRequestList* list = m_clipboards[id].m_requests[requestor];
if (list == NULL) {
list = new CRequestList;
m_requests[requestor] = list;
m_clipboards[id].m_requests[requestor] = list;
}
// create request object
@ -718,7 +755,8 @@ bool CXWindowsScreen::sendClipboardData(
// set property to INCR
const UInt32 zero = 0;
XChangeProperty(m_display, requestor, property,
m_atomINCR, 8 * sizeof(zero),
m_atomINCR,
8 * sizeof(zero),
PropModeReplace,
reinterpret_cast<const unsigned char*>(&zero),
1);
@ -730,7 +768,8 @@ bool CXWindowsScreen::sendClipboardData(
XChangeProperty(m_display, requestor, property,
type, size,
PropModeReplace,
reinterpret_cast<const unsigned char*>(data.data()),
reinterpret_cast<const unsigned char*>(
data.data()),
data.size() / (size >> 3));
}
return true;
@ -739,6 +778,7 @@ bool CXWindowsScreen::sendClipboardData(
}
bool CXWindowsScreen::sendClipboardMultiple(
ClipboardID id,
Window requestor,
Atom property, Time time)
{
@ -768,7 +808,7 @@ bool CXWindowsScreen::sendClipboardMultiple(
// handle target
if (property != None) {
if (!sendClipboardData(requestor, target, property, time)) {
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),
@ -786,15 +826,18 @@ bool CXWindowsScreen::sendClipboardMultiple(
if (updated) {
// FIXME -- handle Alloc errors (by returning false)
XChangeProperty(m_display, requestor, property,
m_atomAtomPair, 8 * sizeof(Atom),
m_atomAtomPair,
8 * sizeof(Atom),
PropModeReplace,
reinterpret_cast<const unsigned char*>(data.data()),
reinterpret_cast<const unsigned char*>(
data.data()),
data.length());
}
// send notify if any format was successful
if (success) {
sendNotify(requestor, m_atomMultiple, success ? property : None, time);
sendNotify(id, requestor, m_atomMultiple,
success ? property : None, time);
return true;
}
@ -802,6 +845,7 @@ bool CXWindowsScreen::sendClipboardMultiple(
}
bool CXWindowsScreen::sendClipboardTargets(
ClipboardID id,
Window requestor,
Atom property, Time /*time*/)
{
@ -809,7 +853,7 @@ bool CXWindowsScreen::sendClipboardTargets(
// count the number of targets, plus TARGETS and MULTIPLE
SInt32 numTargets = 2;
if (m_clipboard.has(IClipboard::kText)) {
if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) {
numTargets += 2;
}
@ -818,7 +862,7 @@ bool CXWindowsScreen::sendClipboardTargets(
SInt32 count = 0;
response[count++] = m_atomTargets;
response[count++] = m_atomMultiple;
if (m_clipboard.has(IClipboard::kText)) {
if (m_clipboards[id].m_clipboard.has(IClipboard::kText)) {
response[count++] = m_atomText;
response[count++] = m_atomString;
}
@ -826,7 +870,8 @@ bool CXWindowsScreen::sendClipboardTargets(
// 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),
m_atomAtom,
8 * sizeof(Atom),
PropModeReplace,
reinterpret_cast<unsigned char*>(response),
count);
@ -838,6 +883,7 @@ bool CXWindowsScreen::sendClipboardTargets(
}
bool CXWindowsScreen::sendClipboardTimestamp(
ClipboardID id,
Window requestor,
Atom property, Time /*time*/)
{
@ -845,22 +891,24 @@ bool CXWindowsScreen::sendClipboardTimestamp(
// FIXME -- handle Alloc errors (by returning false)
XChangeProperty(m_display, requestor, property,
m_atomInteger, 8 * sizeof(m_gotClipboard),
m_atomInteger,
8 * sizeof(m_clipboards[id].m_gotClipboard),
PropModeReplace,
reinterpret_cast<unsigned char*>(&m_gotClipboard),
reinterpret_cast<unsigned char*>(
&m_clipboards[id].m_gotClipboard),
1);
return true;
}
void CXWindowsScreen::sendNotify(
Window requestor, Atom target,
Atom property, Time time)
ClipboardID id, Window requestor,
Atom target, Atom property, Time time)
{
XEvent event;
event.xselection.type = SelectionNotify;
event.xselection.display = m_display;
event.xselection.requestor = requestor;
event.xselection.selection = kClipboardSelection;
event.xselection.selection = m_atomClipboard[id];
event.xselection.target = target;
event.xselection.property = property;
event.xselection.time = time;

View File

@ -4,6 +4,7 @@
#include "CClipboard.h"
#include "CMutex.h"
#include "BasicTypes.h"
#include "ClipboardTypes.h"
#include <X11/Xlib.h>
#include <map>
#include <list>
@ -56,19 +57,25 @@ class CXWindowsScreen {
// cause getEvent() to return false immediately and forever after
void doStop();
// determine the clipboard from the X selection. returns
// kClipboardEnd if no such clipboard.
ClipboardID getClipboardID(Atom selection);
// call when we lose the clipboard ownership (i.e. when we receive
// a SelectionClear event). returns true iff we've actually lost
// a selection we care about.
bool lostClipboard(Atom selection, Time timestamp);
// set the contents of the clipboard (i.e. primary selection)
bool setDisplayClipboard(const IClipboard* clipboard,
bool setDisplayClipboard(ClipboardID,
const IClipboard* clipboard,
Window requestor, Time timestamp);
// copy the clipboard contents to clipboard. requestor must be a
// valid window; it will be used to receive the transfer. timestamp
// should be the timestamp of the provoking event and not CurrentTime.
void getDisplayClipboard(IClipboard* clipboard,
void getDisplayClipboard(ClipboardID,
IClipboard* clipboard,
Window requestor, Time timestamp) const;
// add a selection request to the request list
@ -120,18 +127,31 @@ class CXWindowsScreen {
static Bool findPropertyNotify(Display*,
XEvent* xevent, XPointer arg);
bool sendClipboardData(Window requestor, Atom target,
bool sendClipboardData(ClipboardID, Window requestor,
Atom target, Atom property, Time time);
bool sendClipboardMultiple(ClipboardID, Window requestor,
Atom property, Time time);
bool sendClipboardMultiple(Window requestor,
bool sendClipboardTargets(ClipboardID, Window requestor,
Atom property, Time time);
bool sendClipboardTargets(Window requestor,
Atom property, Time time);
bool sendClipboardTimestamp(Window requestor,
Atom property, Time time);
void sendNotify(Window requestor, Atom target,
bool sendClipboardTimestamp(ClipboardID, Window requestor,
Atom property, Time time);
void sendNotify(ClipboardID, Window requestor,
Atom target, Atom property, Time time);
private:
class ClipboardInfo {
public:
// the contents of the clipboard
CClipboard m_clipboard;
// when we got the clipboard and when we lost it
Time m_gotClipboard;
Time m_lostClipboard;
// the request queues
CRequestMap m_requests;
};
Display* m_display;
int m_screen;
Window m_root;
@ -150,7 +170,11 @@ class CXWindowsScreen {
Atom m_atomString;
Atom m_atomText;
Atom m_atomCompoundText;
Atom m_atomClipboard[kClipboardEnd];
// clipboard info
ClipboardInfo m_clipboards[kClipboardEnd];
/*
// the contents of our selection
CClipboard m_clipboard;
@ -160,6 +184,7 @@ class CXWindowsScreen {
// the request queues
CRequestMap m_requests;
*/
// X is not thread safe
CMutex m_mutex;

21
synergy/ClipboardTypes.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef CLIPBOARDTYPES_H
#define CLIPBOARDTYPES_H
#include "BasicTypes.h"
// type to hold a clipboard identifier
typedef UInt8 ClipboardID;
// clipboard identifiers. kClipboardClipboard is what is normally
// considered the clipboard (e.g. the cut/copy/paste menu items
// affect it). kClipboardSelection is the selection on those
// platforms that can treat the selection as a clipboard (e.g. X
// windows). clipboard identifiers must be sequential starting
// at zero.
static const ClipboardID kClipboardClipboard = 0;
static const ClipboardID kClipboardSelection = 1;
// the number of clipboards (i.e. one greater than the last clipboard id)
static const ClipboardID kClipboardEnd = 2;
#endif

View File

@ -3,6 +3,7 @@
#include "IInterface.h"
#include "BasicTypes.h"
#include "ClipboardTypes.h"
class CServer;
class IClipboard;
@ -49,7 +50,7 @@ class IPrimaryScreen : public IInterface {
// set the screen's clipboard contents. this is usually called
// soon after an enter().
virtual void setClipboard(const IClipboard*) = 0;
virtual void setClipboard(ClipboardID, const IClipboard*) = 0;
/*
// show or hide the screen saver
@ -57,7 +58,7 @@ class IPrimaryScreen : public IInterface {
*/
// synergy should own the clipboard
virtual void grabClipboard() = 0;
virtual void grabClipboard(ClipboardID) = 0;
// accessors
@ -75,7 +76,7 @@ class IPrimaryScreen : public IInterface {
virtual SInt32 getJumpZoneSize() const = 0;
// get the screen's clipboard contents
virtual void getClipboard(IClipboard*) const = 0;
virtual void getClipboard(ClipboardID, IClipboard*) const = 0;
};
#endif

View File

@ -3,6 +3,7 @@
#include "IInterface.h"
#include "BasicTypes.h"
#include "ClipboardTypes.h"
#include "KeyTypes.h"
#include "MouseTypes.h"
@ -52,7 +53,7 @@ class ISecondaryScreen : public IInterface {
// set the screen's clipboard contents. this is usually called
// soon after an enter().
virtual void setClipboard(const IClipboard*) = 0;
virtual void setClipboard(ClipboardID, const IClipboard*) = 0;
/*
// show or hide the screen saver
@ -60,7 +61,7 @@ class ISecondaryScreen : public IInterface {
*/
// take ownership of clipboard
virtual void grabClipboard() = 0;
virtual void grabClipboard(ClipboardID) = 0;
// accessors
@ -71,7 +72,7 @@ class ISecondaryScreen : public IInterface {
virtual SInt32 getJumpZoneSize() const = 0;
// get the screen's clipboard contents
virtual void getClipboard(IClipboard*) const = 0;
virtual void getClipboard(ClipboardID, IClipboard*) const = 0;
};
#endif

View File

@ -1,6 +1,7 @@
#ifndef ISERVERPROTOCOL_H
#define ISERVERPROTOCOL_H
#include "ClipboardTypes.h"
#include "KeyTypes.h"
#include "MouseTypes.h"
#include "IInterface.h"
@ -25,9 +26,9 @@ class IServerProtocol : public IInterface {
virtual void sendClose() = 0;
virtual void sendEnter(SInt32 xAbs, SInt32 yAbs) = 0;
virtual void sendLeave() = 0;
virtual void sendClipboard(const CString&) = 0;
virtual void sendGrabClipboard() = 0;
virtual void sendQueryClipboard(UInt32 seqNum) = 0;
virtual void sendClipboard(ClipboardID, const CString&) = 0;
virtual void sendGrabClipboard(ClipboardID) = 0;
virtual void sendQueryClipboard(ClipboardID, UInt32 seqNum) = 0;
virtual void sendScreenSaver(bool on) = 0;
virtual void sendKeyDown(KeyID, KeyModifierMask) = 0;
virtual void sendKeyRepeat(KeyID, KeyModifierMask, SInt32 count) = 0;

View File

@ -7,30 +7,101 @@
static const SInt32 kMajorVersion = 0;
static const SInt32 kMinorVersion = 1;
// message codes (trailing NUL is not part of code). codes are
// grouped into:
// commands -- request an action, no reply expected
// queries -- request info
// data -- send info
// errors -- notify of error
static const char kMsgCClose[] = "CBYE"; // server
static const char kMsgCEnter[] = "CINN%2i%2i"; // server
static const char kMsgCLeave[] = "COUT"; // server
static const char kMsgCClipboard[] = "CCLP"; // server/client
static const char kMsgCScreenSaver[] = "CSEC%1i"; // server
//
// message codes (trailing NUL is not part of code). in comments, $n
// refers to the n'th argument (counting from one). message codes are
// always 4 bytes optionally followed by message specific parameters.
//
static const char kMsgDKeyDown[] = "DKDN%2i%2i"; // server
static const char kMsgDKeyRepeat[] = "DKRP%2i%2i%2i"; // server
static const char kMsgDKeyUp[] = "DKUP%2i%2i"; // server
static const char kMsgDMouseDown[] = "DMDN%1i"; // server
static const char kMsgDMouseUp[] = "DMUP%1i"; // server
static const char kMsgDMouseMove[] = "DMMV%2i%2i"; // server
static const char kMsgDMouseWheel[] = "DMWM%2i"; // server
static const char kMsgDClipboard[] = "DCLP%4i%s"; // server/client
static const char kMsgDInfo[] = "DINF%2i%2i%2i"; // client
//
// command codes
//
static const char kMsgQClipboard[] = "QCLP%4i"; // server
static const char kMsgQInfo[] = "QINF"; // server
// close connection; primary -> secondary
static const char kMsgCClose[] = "CBYE";
// enter screen: primary -> secondary
// entering screen at screen position $1 = x, $2 = y. x,y are
// absolute screen coordinates.
static const char kMsgCEnter[] = "CINN%2i%2i";
// leave screen: primary -> secondary
// leaving screen
static const char kMsgCLeave[] = "COUT";
// grab clipboard: primary <-> secondary
// sent by screen when some other app on that screen grabs a
// clipboard. $1 = the clipboard identifier.
static const char kMsgCClipboard[] = "CCLP%1i";
// screensaver change: primary -> secondary
// screensaver on primary has started ($1 == 1) or closed ($1 == 0)
static const char kMsgCScreenSaver[] = "CSEC%1i";
//
// data codes
//
// key pressed: primary -> secondary
// $1 = KeyID, $2 = KeyModifierMask
static const char kMsgDKeyDown[] = "DKDN%2i%2i";
// key auto-repeat: primary -> secondary
// $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats
static const char kMsgDKeyRepeat[] = "DKRP%2i%2i%2i";
// key released: primary -> secondary
// $1 = KeyID, $2 = KeyModifierMask
static const char kMsgDKeyUp[] = "DKUP%2i%2i";
// mouse button pressed: primary -> secondary
// $1 = ButtonID
static const char kMsgDMouseDown[] = "DMDN%1i";
// mouse button released: primary -> secondary
// $1 = ButtonID
static const char kMsgDMouseUp[] = "DMUP%1i";
// mouse moved: primary -> secondary
// $1 = x, $2 = y. x,y are absolute screen coordinates.
static const char kMsgDMouseMove[] = "DMMV%2i%2i";
// mouse button pressed: primary -> secondary
// $1 = delta
static const char kMsgDMouseWheel[] = "DMWM%2i";
// clipboard data: primary <-> secondary
// $2 = sequence number, $3 = clipboard data. the sequence number
// is 0 when sent by the primary. the secondary sends this message
// in response to a kMsgQClipboard and uses the sequence number from
// that message. $1 = clipboard identifier.
static const char kMsgDClipboard[] = "DCLP%1i%4i%s";
// client data: seconary -> primary
// $1 = seconary screen width in pixels, $2 = screen height, $3 =
// size of warp zone.
static const char kMsgDInfo[] = "DINF%2i%2i%2i";
//
// query codes
//
// query clipboard: primary -> secondary
// $2 = sequence number. the sequence number is an arbitrary value
// used by primary to identify the kMsgDClipboard response to a
// query. $1 = clipboard identifier.
static const char kMsgQClipboard[] = "QCLP%1i%4i";
// query screen info: primary -> secondary
// client should reply with a kMsgDInfo.
static const char kMsgQInfo[] = "QINF";
//
// error codes
//
static const char kMsgEIncompatible[] = "EICV";