Sync utf8 clipboard support

This commit is contained in:
Lauri Kasanen
2021-04-12 12:38:24 +03:00
parent 04461b9d4c
commit a1cf454f06
34 changed files with 1794 additions and 181 deletions

View File

@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2011-2015 Pierre Ossman for Cendio AB
* Copyright 2011-2019 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -226,3 +226,35 @@ int vncIsTCPPortUsed(int port)
}
return 0;
}
char* vncConvertLF(const char* src, size_t bytes)
{
try {
return convertLF(src, bytes);
} catch (...) {
return NULL;
}
}
char* vncLatin1ToUTF8(const char* src, size_t bytes)
{
try {
return latin1ToUTF8(src, bytes);
} catch (...) {
return NULL;
}
}
char* vncUTF8ToLatin1(const char* src, size_t bytes)
{
try {
return utf8ToLatin1(src, bytes);
} catch (...) {
return NULL;
}
}
void vncStrFree(char* str)
{
strFree(str);
}

View File

@@ -1,5 +1,5 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2011-2015 Pierre Ossman for Cendio AB
* Copyright 2011-2019 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -50,6 +50,13 @@ void vncListParams(int width, int nameWidth);
int vncGetSocketPort(int fd);
int vncIsTCPPortUsed(int port);
char* vncConvertLF(const char* src, size_t bytes);
char* vncLatin1ToUTF8(const char* src, size_t bytes);
char* vncUTF8ToLatin1(const char* src, size_t bytes);
void vncStrFree(char* str);
#ifdef __cplusplus
}
#endif

View File

@@ -176,6 +176,33 @@ XserverDesktop::queryConnection(network::Socket* sock,
return rfb::VNCServerST::PENDING;
}
void XserverDesktop::requestClipboard()
{
try {
server->requestClipboard();
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::requestClipboard: %s",e.str());
}
}
void XserverDesktop::announceClipboard(bool available)
{
try {
server->announceClipboard(available);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::announceClipboard: %s",e.str());
}
}
void XserverDesktop::sendClipboardData(const char* data)
{
try {
server->sendClipboardData(data);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::sendClipboardData: %s",e.str());
}
}
void XserverDesktop::bell()
{
server->bell();
@@ -186,15 +213,6 @@ void XserverDesktop::setLEDState(unsigned int state)
server->setLEDState(state);
}
void XserverDesktop::serverCutText(const char* str, int len)
{
try {
server->serverCutText(str, len);
} catch (rdr::Exception& e) {
vlog.error("XserverDesktop::serverCutText: %s",e.str());
}
}
void XserverDesktop::setDesktopName(const char* name)
{
try {
@@ -435,11 +453,6 @@ void XserverDesktop::pointerEvent(const Point& pos, int buttonMask,
vncPointerButtonAction(buttonMask, skipClick, skipRelease);
}
void XserverDesktop::clientCutText(const char* str, int len)
{
vncClientCutText(str, len);
}
unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height,
const rfb::ScreenSet& layout)
{
@@ -453,6 +466,21 @@ unsigned int XserverDesktop::setScreenLayout(int fb_width, int fb_height,
return ::setScreenLayout(fb_width, fb_height, layout, &outputIdMap);
}
void XserverDesktop::handleClipboardRequest()
{
vncHandleClipboardRequest();
}
void XserverDesktop::handleClipboardAnnounce(bool available)
{
vncHandleClipboardAnnounce(available);
}
void XserverDesktop::handleClipboardData(const char* data_)
{
vncHandleClipboardData(data_);
}
void XserverDesktop::grabRegion(const rfb::Region& region)
{
if (directFbptr)

View File

@@ -60,9 +60,11 @@ public:
void unblockUpdates();
void setFramebuffer(int w, int h, void* fbptr, int stride);
void refreshScreenLayout();
void requestClipboard();
void announceClipboard(bool available);
void sendClipboardData(const char* data);
void bell();
void setLEDState(unsigned int state);
void serverCutText(const char* str, int len);
void setDesktopName(const char* name);
void setCursor(int width, int height, int hotX, int hotY,
const unsigned char *rgbaData);
@@ -90,9 +92,11 @@ public:
virtual void pointerEvent(const rfb::Point& pos, int buttonMask,
const bool skipClick, const bool skipRelease);
virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
virtual void clientCutText(const char* str, int len);
virtual unsigned int setScreenLayout(int fb_width, int fb_height,
const rfb::ScreenSet& layout);
virtual void handleClipboardRequest();
virtual void handleClipboardAnnounce(bool available);
virtual void handleClipboardData(const char* data);
// rfb::PixelBuffer callbacks
virtual void grabRegion(const rfb::Region& r);

View File

@@ -315,10 +315,22 @@ void vncUpdateDesktopName(void)
desktop[scr]->setDesktopName(desktopName);
}
void vncServerCutText(const char *text, size_t len)
void vncRequestClipboard(void)
{
for (int scr = 0; scr < vncGetScreenCount(); scr++)
desktop[scr]->serverCutText(text, len);
desktop[scr]->requestClipboard();
}
void vncAnnounceClipboard(int available)
{
for (int scr = 0; scr < vncGetScreenCount(); scr++)
desktop[scr]->announceClipboard(available);
}
void vncSendClipboardData(const char* data)
{
for (int scr = 0; scr < vncGetScreenCount(); scr++)
desktop[scr]->sendClipboardData(data);
}
int vncConnectClient(const char *addr)

View File

@@ -60,7 +60,9 @@ int vncGetSendPrimary(void);
void vncUpdateDesktopName(void);
void vncServerCutText(const char *text, size_t len);
void vncRequestClipboard(void);
void vncAnnounceClipboard(int available);
void vncSendClipboardData(const char* data);
int vncConnectClient(const char *addr);

View File

@@ -1,4 +1,4 @@
/* Copyright 2016 Pierre Ossman for Cendio AB
/* Copyright 2016-2019 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -47,15 +47,34 @@ static Atom xaTARGETS, xaTIMESTAMP, xaSTRING, xaTEXT, xaUTF8_STRING;
static WindowPtr pWindow;
static Window wid;
static char* clientCutText;
static int clientCutTextLen;
static Bool probing;
static Atom activeSelection = None;
struct VncDataTarget {
ClientPtr client;
Atom selection;
Atom target;
Atom property;
Window requestor;
CARD32 time;
struct VncDataTarget* next;
};
static struct VncDataTarget* vncDataTargetHead;
static int vncCreateSelectionWindow(void);
static int vncOwnSelection(Atom selection);
static int vncConvertSelection(ClientPtr client, Atom selection,
Atom target, Atom property,
Window requestor, CARD32 time,
const char* data);
static int vncProcConvertSelection(ClientPtr client);
static void vncSelectionRequest(Atom selection, Atom target);
static int vncProcSendEvent(ClientPtr client);
static void vncSelectionCallback(CallbackListPtr *callbacks,
void * data, void * args);
static void vncClientStateCallback(CallbackListPtr * l,
void * d, void * p);
static int (*origProcConvertSelection)(ClientPtr);
static int (*origProcSendEvent)(ClientPtr);
@@ -80,34 +99,99 @@ void vncSelectionInit(void)
if (!AddCallback(&SelectionCallback, vncSelectionCallback, 0))
FatalError("Add VNC SelectionCallback failed\n");
if (!AddCallback(&ClientStateCallback, vncClientStateCallback, 0))
FatalError("Add VNC ClientStateCallback failed\n");
}
void vncClientCutText(const char* str, int len)
void vncHandleClipboardRequest(void)
{
int rc;
if (clientCutText != NULL)
free(clientCutText);
clientCutText = malloc(len);
if (clientCutText == NULL) {
LOG_ERROR("Could not allocate clipboard buffer");
DeleteWindowFromAnySelections(pWindow);
if (activeSelection == None) {
LOG_DEBUG("Got request for local clipboard although no clipboard is active");
return;
}
memcpy(clientCutText, str, len);
clientCutTextLen = len;
LOG_DEBUG("Got request for local clipboard, re-probing formats");
if (vncGetSetPrimary()) {
rc = vncOwnSelection(xaPRIMARY);
probing = FALSE;
vncSelectionRequest(activeSelection, xaTARGETS);
}
void vncHandleClipboardAnnounce(int available)
{
if (available) {
int rc;
LOG_DEBUG("Remote clipboard announced, grabbing local ownership");
if (vncGetSetPrimary()) {
rc = vncOwnSelection(xaPRIMARY);
if (rc != Success)
LOG_ERROR("Could not set PRIMARY selection");
}
rc = vncOwnSelection(xaCLIPBOARD);
if (rc != Success)
LOG_ERROR("Could not set PRIMARY selection");
}
LOG_ERROR("Could not set CLIPBOARD selection");
} else {
struct VncDataTarget* next;
vncOwnSelection(xaCLIPBOARD);
if (rc != Success)
LOG_ERROR("Could not set CLIPBOARD selection");
if (pWindow == NULL)
return;
LOG_DEBUG("Remote clipboard lost, removing local ownership");
DeleteWindowFromAnySelections(pWindow);
/* Abort any pending transfer */
while (vncDataTargetHead != NULL) {
xEvent event;
event.u.u.type = SelectionNotify;
event.u.selectionNotify.time = vncDataTargetHead->time;
event.u.selectionNotify.requestor = vncDataTargetHead->requestor;
event.u.selectionNotify.selection = vncDataTargetHead->selection;
event.u.selectionNotify.target = vncDataTargetHead->target;
event.u.selectionNotify.property = None;
WriteEventsToClient(vncDataTargetHead->client, 1, &event);
next = vncDataTargetHead->next;
free(vncDataTargetHead);
vncDataTargetHead = next;
}
}
}
void vncHandleClipboardData(const char* data)
{
struct VncDataTarget* next;
LOG_DEBUG("Got remote clipboard data, sending to X11 clients");
while (vncDataTargetHead != NULL) {
int rc;
xEvent event;
rc = vncConvertSelection(vncDataTargetHead->client,
vncDataTargetHead->selection,
vncDataTargetHead->target,
vncDataTargetHead->property,
vncDataTargetHead->requestor,
vncDataTargetHead->time,
data);
if (rc != Success) {
event.u.u.type = SelectionNotify;
event.u.selectionNotify.time = vncDataTargetHead->time;
event.u.selectionNotify.requestor = vncDataTargetHead->requestor;
event.u.selectionNotify.selection = vncDataTargetHead->selection;
event.u.selectionNotify.target = vncDataTargetHead->target;
event.u.selectionNotify.property = None;
WriteEventsToClient(vncDataTargetHead->client, 1, &event);
}
next = vncDataTargetHead->next;
free(vncDataTargetHead);
vncDataTargetHead = next;
}
}
static int vncCreateSelectionWindow(void)
@@ -195,7 +279,8 @@ static int vncOwnSelection(Atom selection)
static int vncConvertSelection(ClientPtr client, Atom selection,
Atom target, Atom property,
Window requestor, CARD32 time)
Window requestor, CARD32 time,
const char* data)
{
Selection *pSel;
WindowPtr pWin;
@@ -205,8 +290,13 @@ static int vncConvertSelection(ClientPtr client, Atom selection,
xEvent event;
LOG_DEBUG("Selection request for %s (type %s)",
NameForAtom(selection), NameForAtom(target));
if (data == NULL) {
LOG_DEBUG("Selection request for %s (type %s)",
NameForAtom(selection), NameForAtom(target));
} else {
LOG_DEBUG("Sending data for selection request for %s (type %s)",
NameForAtom(selection), NameForAtom(target));
}
rc = dixLookupSelection(&pSel, selection, client, DixGetAttrAccess);
if (rc != Success)
@@ -243,51 +333,59 @@ static int vncConvertSelection(ClientPtr client, Atom selection,
TRUE);
if (rc != Success)
return rc;
} else if ((target == xaSTRING) || (target == xaTEXT)) {
rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
XA_STRING, 8, PropModeReplace,
clientCutTextLen, clientCutText,
TRUE);
if (rc != Success)
return rc;
} else if (target == xaUTF8_STRING) {
unsigned char* buffer;
unsigned char* out;
size_t len;
} else {
if (data == NULL) {
struct VncDataTarget* vdt;
const unsigned char* in;
size_t in_len;
if ((target != xaSTRING) && (target != xaTEXT) &&
(target != xaUTF8_STRING))
return BadMatch;
buffer = malloc(clientCutTextLen*2);
if (buffer == NULL)
return BadAlloc;
vdt = calloc(1, sizeof(struct VncDataTarget));
if (vdt == NULL)
return BadAlloc;
out = buffer;
len = 0;
in = clientCutText;
in_len = clientCutTextLen;
while (in_len > 0) {
if (*in & 0x80) {
*out++ = 0xc0 | (*in >> 6);
*out++ = 0x80 | (*in & 0x3f);
len += 2;
in++;
in_len--;
vdt->client = client;
vdt->selection = selection;
vdt->target = target;
vdt->property = property;
vdt->requestor = requestor;
vdt->time = time;
vdt->next = vncDataTargetHead;
vncDataTargetHead = vdt;
LOG_DEBUG("Requesting clipboard data from client");
vncRequestClipboard();
return Success;
} else {
if ((target == xaSTRING) || (target == xaTEXT)) {
char* latin1;
latin1 = vncUTF8ToLatin1(data, (size_t)-1);
if (latin1 == NULL)
return BadAlloc;
rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
XA_STRING, 8, PropModeReplace,
strlen(latin1), latin1, TRUE);
vncStrFree(latin1);
if (rc != Success)
return rc;
} else if (target == xaUTF8_STRING) {
rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
xaUTF8_STRING, 8, PropModeReplace,
strlen(data), data, TRUE);
if (rc != Success)
return rc;
} else {
*out++ = *in++;
len++;
in_len--;
return BadMatch;
}
}
rc = dixChangeWindowProperty(serverClient, pWin, realProperty,
xaUTF8_STRING, 8, PropModeReplace,
len, buffer, TRUE);
free(buffer);
if (rc != Success)
return rc;
} else {
return BadMatch;
}
event.u.u.type = SelectionNotify;
@@ -326,7 +424,7 @@ static int vncProcConvertSelection(ClientPtr client)
pSel->window == wid) {
rc = vncConvertSelection(client, stuff->selection,
stuff->target, stuff->property,
stuff->requestor, stuff->time);
stuff->requestor, stuff->time, NULL);
if (rc != Success) {
xEvent event;
@@ -410,69 +508,61 @@ static void vncHandleSelection(Atom selection, Atom target,
if (prop->type != XA_ATOM)
return;
if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size))
vncSelectionRequest(selection, xaSTRING);
else if (vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size))
vncSelectionRequest(selection, xaUTF8_STRING);
if (probing) {
if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size) ||
vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size)) {
LOG_DEBUG("Compatible format found, notifying clients");
activeSelection = selection;
vncAnnounceClipboard(TRUE);
}
} else {
if (vncHasAtom(xaUTF8_STRING, (const Atom*)prop->data, prop->size))
vncSelectionRequest(selection, xaUTF8_STRING);
else if (vncHasAtom(xaSTRING, (const Atom*)prop->data, prop->size))
vncSelectionRequest(selection, xaSTRING);
}
} else if (target == xaSTRING) {
char* filtered;
char* utf8;
if (prop->format != 8)
return;
if (prop->type != xaSTRING)
return;
vncServerCutText(prop->data, prop->size);
} else if (target == xaUTF8_STRING) {
unsigned char* buffer;
unsigned char* out;
size_t len;
filtered = vncConvertLF(prop->data, prop->size);
if (filtered == NULL)
return;
const unsigned char* in;
size_t in_len;
utf8 = vncLatin1ToUTF8(filtered, (size_t)-1);
vncStrFree(filtered);
if (utf8 == NULL)
return;
LOG_DEBUG("Sending clipboard to clients (%d bytes)",
(int)strlen(utf8));
vncSendClipboardData(utf8);
vncStrFree(utf8);
} else if (target == xaUTF8_STRING) {
char *filtered;
if (prop->format != 8)
return;
if (prop->type != xaUTF8_STRING)
return;
buffer = malloc(prop->size);
if (buffer == NULL)
filtered = vncConvertLF(prop->data, prop->size);
if (filtered == NULL)
return;
out = buffer;
len = 0;
in = prop->data;
in_len = prop->size;
while (in_len > 0) {
if ((*in & 0x80) == 0x00) {
*out++ = *in++;
len++;
in_len--;
} else if ((*in & 0xe0) == 0xc0) {
unsigned ucs;
ucs = (*in++ & 0x1f) << 6;
in_len--;
if (in_len > 0) {
ucs |= (*in++ & 0x3f);
in_len--;
}
if (ucs <= 0xff)
*out++ = ucs;
else
*out++ = '?';
len++;
} else {
*out++ = '?';
len++;
do {
in++;
in_len--;
} while ((in_len > 0) && ((*in & 0xc0) == 0x80));
}
}
LOG_DEBUG("Sending clipboard to clients (%d bytes)",
(int)strlen(filtered));
vncServerCutText((const char*)buffer, len);
vncSendClipboardData(filtered);
free(buffer);
vncStrFree(filtered);
}
}
@@ -504,6 +594,12 @@ static void vncSelectionCallback(CallbackListPtr *callbacks,
{
SelectionInfoRec *info = (SelectionInfoRec *) args;
if (info->selection->selection == activeSelection) {
LOG_DEBUG("Local clipboard lost, notifying clients");
activeSelection = None;
vncAnnounceClipboard(FALSE);
}
if (info->kind != SelectionSetOwner)
return;
if (info->client == serverClient)
@@ -527,5 +623,25 @@ static void vncSelectionCallback(CallbackListPtr *callbacks,
!vncGetSendPrimary())
return;
LOG_DEBUG("Got clipboard notification, probing for formats");
probing = TRUE;
vncSelectionRequest(info->selection->selection, xaTARGETS);
}
static void vncClientStateCallback(CallbackListPtr * l,
void * d, void * p)
{
ClientPtr client = ((NewClientInfoRec*)p)->client;
if (client->clientState == ClientStateGone) {
struct VncDataTarget** nextPtr = &vncDataTargetHead;
for (struct VncDataTarget* cur = vncDataTargetHead; cur; cur = *nextPtr) {
if (cur->client == client) {
*nextPtr = cur->next;
free(cur);
continue;
}
nextPtr = &cur->next;
}
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2016 Pierre Ossman for Cendio AB
/* Copyright 2016-2019 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,7 +24,9 @@ extern "C" {
void vncSelectionInit(void);
void vncClientCutText(const char* str, int len);
void vncHandleClipboardRequest(void);
void vncHandleClipboardAnnounce(int available);
void vncHandleClipboardData(const char* data);
#ifdef __cplusplus
}