hw/xwin: Implement INCR protocol for X clipboard -> Windows clipboard

Also, relax the timeout mechanism so it allows 1 second between events,
rather than 1 second for the entire transfer, as transfers of large
pastes can take more than 1 second.

Also, prefer UTF8_STRING encoding to COMPOUND_TEXT encoding
This commit is contained in:
Jon Turney 2015-11-18 21:27:23 +00:00
parent 4f95d87d66
commit 56a91f2067
5 changed files with 290 additions and 221 deletions

View File

@ -1,4 +1,3 @@
/*
*Copyright (C) 2003-2004 Harold L Hunt II All Rights Reserved.
*
@ -38,7 +37,7 @@
/* Windows headers */
#include <X11/Xwindows.h>
#define WIN_XEVENTS_SUCCESS 0
#define WIN_XEVENTS_SUCCESS 0 // more like 'CONTINUE'
#define WIN_XEVENTS_FAILED 1
#define WIN_XEVENTS_NOTIFY_DATA 3
#define WIN_XEVENTS_NOTIFY_TARGETS 4
@ -77,6 +76,7 @@ typedef struct
Atom atomUTF8String;
Atom atomCompoundText;
Atom atomTargets;
Atom atomIncr;
} ClipboardAtoms;
/* Modern clipboard API functions */
@ -111,6 +111,8 @@ typedef struct
{
Bool fUseUnicode;
Atom *targetList;
unsigned char *incr;
unsigned long int incrsize;
} ClipboardConversionData;
int

View File

@ -199,6 +199,7 @@ winClipboardProc(Bool fUseUnicode, char *szDisplay)
atoms.atomUTF8String = XInternAtom (pDisplay, "UTF8_STRING", False);
atoms.atomCompoundText = XInternAtom (pDisplay, "COMPOUND_TEXT", False);
atoms.atomTargets = XInternAtom (pDisplay, "TARGETS", False);
atoms.atomIncr = XInternAtom (pDisplay, "INCR", False);
/* Create a messaging window */
iWindow = XCreateSimpleWindow(pDisplay,
@ -265,6 +266,8 @@ winClipboardProc(Bool fUseUnicode, char *szDisplay)
}
data.fUseUnicode = fUseUnicode;
data.incr = NULL;
data.incrsize = 0;
/* Loop for events */
while (1) {

View File

@ -73,9 +73,8 @@ winProcessXEventsTimeout(HWND hwnd, Window iWindow, Display * pDisplay,
int iConnNumber;
struct timeval tv;
int iReturn;
DWORD dwStopTime = GetTickCount() + iTimeoutSec * 1000;
winDebug("winProcessXEventsTimeout () - pumping X events for %d seconds\n",
winDebug("winProcessXEventsTimeout () - pumping X events, timeout %d seconds\n",
iTimeoutSec);
/* Get our connection number */
@ -104,11 +103,9 @@ winProcessXEventsTimeout(HWND hwnd, Window iWindow, Display * pDisplay,
FD_SET(iConnNumber, &fdsRead);
/* Adjust timeout */
remainingTime = dwStopTime - GetTickCount();
remainingTime = iTimeoutSec * 1000;
tv.tv_sec = remainingTime / 1000;
tv.tv_usec = (remainingTime % 1000) * 1000;
winDebug("winProcessXEventsTimeout () - %ld milliseconds left\n",
remainingTime);
/* Break out if no time left */
if (remainingTime <= 0)
@ -494,6 +491,9 @@ winClipboardWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
/* Process X events */
data.fUseUnicode = fConvertToUnicode;
data.incr = NULL;
data.incrsize = 0;
iReturn = winProcessXEventsTimeout(hwnd,
iWindow,
pDisplay,
@ -517,10 +517,10 @@ winClipboardWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
struct target_priority target_priority_table[] =
{
{ atoms->atomCompoundText, 0 },
#ifdef X_HAVE_UTF8_STRING
{ atoms->atomUTF8String, 1 },
{ atoms->atomUTF8String, 0 },
#endif
{ atoms->atomCompoundText, 1 },
{ XA_STRING, 2 },
};

View File

@ -184,6 +184,271 @@ winClipboardSelectionNotifyTargets(HWND hwnd, Window iWindow, Display *pDisplay,
return WIN_XEVENTS_NOTIFY_TARGETS;
}
static int
winClipboardSelectionNotifyData(HWND hwnd, Window iWindow, Display *pDisplay, ClipboardConversionData *data, ClipboardAtoms *atoms)
{
Atom encoding;
int format;
unsigned long int nitems;
unsigned long int after;
unsigned char *value;
XTextProperty xtpText = { 0 };
Bool fSetClipboardData = TRUE;
int iReturn;
char **ppszTextList = NULL;
int iCount;
char *pszReturnData = NULL;
wchar_t *pwszUnicodeStr = NULL;
HGLOBAL hGlobal = NULL;
char *pszConvertData = NULL;
char *pszGlobalData = NULL;
/* Retrieve the selection data and delete the property */
iReturn = XGetWindowProperty(pDisplay,
iWindow,
atoms->atomLocalProperty,
0,
INT_MAX,
True,
AnyPropertyType,
&encoding,
&format,
&nitems,
&after,
&value);
if (iReturn != Success) {
ErrorF("winClipboardFlushXEvents - SelectionNotify - "
"XGetWindowProperty () failed, aborting: %d\n", iReturn);
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
{
char *pszAtomName = NULL;
winDebug("SelectionNotify - returned data %lu left %lu\n", nitems, after);
pszAtomName = XGetAtomName(pDisplay, encoding);
winDebug("Notify atom name %s\n", pszAtomName);
XFree(pszAtomName);
pszAtomName = NULL;
}
/* INCR reply indicates the start of a incremental transfer */
if (encoding == atoms->atomIncr) {
winDebug("winClipboardSelectionNotifyData: starting INCR, anticipated size %d\n", *(int *)value);
data->incrsize = 0;
data->incr = malloc(*(int *)value);
// XXX: if malloc failed, we have an error
return WIN_XEVENTS_SUCCESS;
}
else if (data->incr) {
/* If an INCR transfer is in progress ... */
if (nitems == 0) {
winDebug("winClipboardSelectionNotifyData: ending INCR, actual size %ld\n", data->incrsize);
/* a zero-length property indicates the end of the data */
xtpText.value = data->incr;
xtpText.encoding = encoding;
xtpText.format = format; // XXX: The type of the converted selection is the type of the first partial property. The remaining partial properties must have the same type.
xtpText.nitems = data->incrsize;
}
else {
/* Otherwise, continue appending the INCR data */
winDebug("winClipboardSelectionNotifyData: INCR, %ld bytes\n", nitems);
data->incr = realloc(data->incr, data->incrsize + nitems);
memcpy(data->incr + data->incrsize, value, nitems);
data->incrsize = data->incrsize + nitems;
return WIN_XEVENTS_SUCCESS;
}
}
else {
/* Otherwise, the data is just contained in the property */
winDebug("winClipboardSelectionNotifyData: non-INCR, %ld bytes\n", nitems);
xtpText.value = value;
xtpText.encoding = encoding;
xtpText.format = format;
xtpText.nitems = nitems;
}
if (data->fUseUnicode) {
#ifdef X_HAVE_UTF8_STRING
/* Convert the text property to a text list */
iReturn = Xutf8TextPropertyToTextList(pDisplay,
&xtpText,
&ppszTextList, &iCount);
#endif
}
else {
iReturn = XmbTextPropertyToTextList(pDisplay,
&xtpText,
&ppszTextList, &iCount);
}
if (iReturn == Success || iReturn > 0) {
/* Conversion succeeded or some unconvertible characters */
if (ppszTextList != NULL) {
int i;
int iReturnDataLen = 0;
for (i = 0; i < iCount; i++) {
iReturnDataLen += strlen(ppszTextList[i]);
}
pszReturnData = malloc(iReturnDataLen + 1);
pszReturnData[0] = '\0';
for (i = 0; i < iCount; i++) {
strcat(pszReturnData, ppszTextList[i]);
}
}
else {
ErrorF("winClipboardFlushXEvents - SelectionNotify - "
"X*TextPropertyToTextList list_return is NULL.\n");
pszReturnData = malloc(1);
pszReturnData[0] = '\0';
}
}
else {
ErrorF("winClipboardFlushXEvents - SelectionNotify - "
"X*TextPropertyToTextList returned: ");
switch (iReturn) {
case XNoMemory:
ErrorF("XNoMemory\n");
break;
case XLocaleNotSupported:
ErrorF("XLocaleNotSupported\n");
break;
case XConverterNotFound:
ErrorF("XConverterNotFound\n");
break;
default:
ErrorF("%d\n", iReturn);
break;
}
pszReturnData = malloc(1);
pszReturnData[0] = '\0';
}
if (ppszTextList)
XFreeStringList(ppszTextList);
ppszTextList = NULL;
/* Free the data returned from XGetWindowProperty */
XFree(value);
value = NULL;
nitems = 0;
/* Free any INCR data */
if (data->incr) {
free(data->incr);
data->incr = NULL;
data->incrsize = 0;
}
/* Convert the X clipboard string to DOS format */
winClipboardUNIXtoDOS(&pszReturnData, strlen(pszReturnData));
if (data->fUseUnicode) {
/* Find out how much space needed to convert MBCS to Unicode */
int iUnicodeLen = MultiByteToWideChar(CP_UTF8,
0,
pszReturnData, -1, NULL, 0);
/* NOTE: iUnicodeLen includes space for null terminator */
pwszUnicodeStr = malloc(sizeof(wchar_t) * iUnicodeLen);
if (!pwszUnicodeStr) {
ErrorF("winClipboardFlushXEvents - SelectionNotify "
"malloc failed for pwszUnicodeStr, aborting.\n");
/* Abort */
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
/* Do the actual conversion */
MultiByteToWideChar(CP_UTF8,
0,
pszReturnData,
-1, pwszUnicodeStr, iUnicodeLen);
/* Allocate global memory for the X clipboard data */
hGlobal = GlobalAlloc(GMEM_MOVEABLE,
sizeof(wchar_t) * iUnicodeLen);
}
else {
int iConvertDataLen = 0;
pszConvertData = strdup(pszReturnData);
iConvertDataLen = strlen(pszConvertData) + 1;
/* Allocate global memory for the X clipboard data */
hGlobal = GlobalAlloc(GMEM_MOVEABLE, iConvertDataLen);
}
free(pszReturnData);
/* Check that global memory was allocated */
if (!hGlobal) {
ErrorF("winClipboardFlushXEvents - SelectionNotify "
"GlobalAlloc failed, aborting: %08x\n", (unsigned int)GetLastError());
/* Abort */
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
/* Obtain a pointer to the global memory */
pszGlobalData = GlobalLock(hGlobal);
if (pszGlobalData == NULL) {
ErrorF("winClipboardFlushXEvents - Could not lock global "
"memory for clipboard transfer\n");
/* Abort */
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
/* Copy the returned string into the global memory */
if (data->fUseUnicode) {
wcscpy((wchar_t *)pszGlobalData, pwszUnicodeStr);
free(pwszUnicodeStr);
pwszUnicodeStr = NULL;
}
else {
strcpy(pszGlobalData, pszConvertData);
free(pszConvertData);
pszConvertData = NULL;
}
/* Release the pointer to the global memory */
GlobalUnlock(hGlobal);
pszGlobalData = NULL;
/* Push the selection data to the Windows clipboard */
if (data->fUseUnicode)
SetClipboardData(CF_UNICODETEXT, hGlobal);
else
SetClipboardData(CF_TEXT, hGlobal);
/* Flag that SetClipboardData has been called */
fSetClipboardData = FALSE;
/*
* NOTE: Do not try to free pszGlobalData, it is owned by
* Windows after the call to SetClipboardData ().
*/
winClipboardFlushXEvents_SelectionNotify_Done:
/* Free allocated resources */
if (ppszTextList)
XFreeStringList(ppszTextList);
if (value) {
XFree(value);
value = NULL;
nitems = 0;
}
free(pszConvertData);
free(pwszUnicodeStr);
if (hGlobal && pszGlobalData)
GlobalUnlock(hGlobal);
if (fSetClipboardData) {
SetClipboardData(CF_UNICODETEXT, NULL);
SetClipboardData(CF_TEXT, NULL);
}
return WIN_XEVENTS_NOTIFY_DATA;
}
/*
* Process any pending X events
*/
@ -193,7 +458,6 @@ winClipboardFlushXEvents(HWND hwnd,
Window iWindow, Display * pDisplay, ClipboardConversionData *data, ClipboardAtoms *atoms)
{
Atom atomClipboard = atoms->atomClipboard;
Atom atomLocalProperty = atoms->atomLocalProperty;
Atom atomUTF8String = atoms->atomUTF8String;
Atom atomCompoundText = atoms->atomCompoundText;
Atom atomTargets = atoms->atomTargets;
@ -203,20 +467,14 @@ winClipboardFlushXEvents(HWND hwnd,
XTextProperty xtpText = { 0 };
XEvent event;
XSelectionEvent eventSelection;
unsigned long ulReturnBytesLeft;
char *pszReturnData = NULL;
char *pszGlobalData = NULL;
int iReturn;
HGLOBAL hGlobal = NULL;
XICCEncodingStyle xiccesStyle;
char *pszConvertData = NULL;
char *pszTextList[2] = { NULL };
int iCount;
char **ppszTextList = NULL;
wchar_t *pwszUnicodeStr = NULL;
Bool fAbort = FALSE;
Bool fCloseClipboard = FALSE;
Bool fSetClipboardData = TRUE;
/* Get the next event - will not block because one is ready */
XNextEvent(pDisplay, &event);
@ -569,214 +827,19 @@ winClipboardFlushXEvents(HWND hwnd,
return winClipboardSelectionNotifyTargets(hwnd, iWindow, pDisplay, data, atoms);
}
/* Retrieve the selection data and delete the property */
iReturn = XGetWindowProperty(pDisplay,
iWindow,
atomLocalProperty,
0,
INT_MAX,
True,
AnyPropertyType,
&xtpText.encoding,
&xtpText.format,
&xtpText.nitems,
&ulReturnBytesLeft, &xtpText.value);
if (iReturn != Success) {
ErrorF("winClipboardFlushXEvents - SelectionNotify - "
"XGetWindowProperty () failed, aborting: %d\n", iReturn);
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
{
char *pszAtomName = NULL;
winDebug("SelectionNotify - returned data %lu left %lu\n",
xtpText.nitems, ulReturnBytesLeft);
pszAtomName = XGetAtomName(pDisplay, xtpText.encoding);
winDebug("Notify atom name %s\n", pszAtomName);
XFree(pszAtomName);
pszAtomName = NULL;
}
if (data->fUseUnicode) {
#ifdef X_HAVE_UTF8_STRING
/* Convert the text property to a text list */
iReturn = Xutf8TextPropertyToTextList(pDisplay,
&xtpText,
&ppszTextList, &iCount);
#endif
}
else {
iReturn = XmbTextPropertyToTextList(pDisplay,
&xtpText,
&ppszTextList, &iCount);
}
if (iReturn == Success || iReturn > 0) {
/* Conversion succeeded or some unconvertible characters */
if (ppszTextList != NULL) {
int i;
int iReturnDataLen = 0;
for (i = 0; i < iCount; i++) {
iReturnDataLen += strlen(ppszTextList[i]);
}
pszReturnData = malloc(iReturnDataLen + 1);
pszReturnData[0] = '\0';
for (i = 0; i < iCount; i++) {
strcat(pszReturnData, ppszTextList[i]);
}
}
else {
ErrorF("winClipboardFlushXEvents - SelectionNotify - "
"X*TextPropertyToTextList list_return is NULL.\n");
pszReturnData = malloc(1);
pszReturnData[0] = '\0';
}
}
else {
ErrorF("winClipboardFlushXEvents - SelectionNotify - "
"X*TextPropertyToTextList returned: ");
switch (iReturn) {
case XNoMemory:
ErrorF("XNoMemory\n");
break;
case XLocaleNotSupported:
ErrorF("XLocaleNotSupported\n");
break;
case XConverterNotFound:
ErrorF("XConverterNotFound\n");
break;
default:
ErrorF("%d\n", iReturn);
break;
}
pszReturnData = malloc(1);
pszReturnData[0] = '\0';
}
/* Free the data returned from XGetWindowProperty */
if (ppszTextList)
XFreeStringList(ppszTextList);
ppszTextList = NULL;
XFree(xtpText.value);
xtpText.value = NULL;
xtpText.nitems = 0;
/* Convert the X clipboard string to DOS format */
winClipboardUNIXtoDOS(&pszReturnData, strlen(pszReturnData));
if (data->fUseUnicode) {
/* Find out how much space needed to convert MBCS to Unicode */
int iUnicodeLen = MultiByteToWideChar(CP_UTF8,
0,
pszReturnData, -1, NULL, 0);
/* NOTE: iUnicodeLen includes space for null terminator */
pwszUnicodeStr = malloc(sizeof(wchar_t) * iUnicodeLen);
if (!pwszUnicodeStr) {
ErrorF("winClipboardFlushXEvents - SelectionNotify "
"malloc failed for pwszUnicodeStr, aborting.\n");
/* Abort */
fAbort = TRUE;
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
/* Do the actual conversion */
MultiByteToWideChar(CP_UTF8,
0,
pszReturnData,
-1, pwszUnicodeStr, iUnicodeLen);
/* Allocate global memory for the X clipboard data */
hGlobal = GlobalAlloc(GMEM_MOVEABLE,
sizeof(wchar_t) * iUnicodeLen);
}
else {
int iConvertDataLen = 0;
pszConvertData = strdup(pszReturnData);
iConvertDataLen = strlen(pszConvertData) + 1;
/* Allocate global memory for the X clipboard data */
hGlobal = GlobalAlloc(GMEM_MOVEABLE, iConvertDataLen);
}
free(pszReturnData);
/* Check that global memory was allocated */
if (!hGlobal) {
ErrorF("winClipboardFlushXEvents - SelectionNotify "
"GlobalAlloc failed, aborting: %08x\n", (unsigned int)GetLastError());
/* Abort */
fAbort = TRUE;
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
/* Obtain a pointer to the global memory */
pszGlobalData = GlobalLock(hGlobal);
if (pszGlobalData == NULL) {
ErrorF("winClipboardFlushXEvents - Could not lock global "
"memory for clipboard transfer\n");
/* Abort */
fAbort = TRUE;
goto winClipboardFlushXEvents_SelectionNotify_Done;
}
/* Copy the returned string into the global memory */
if (data->fUseUnicode) {
wcscpy((wchar_t *)pszGlobalData, pwszUnicodeStr);
free(pwszUnicodeStr);
pwszUnicodeStr = NULL;
}
else {
strcpy(pszGlobalData, pszConvertData);
free(pszConvertData);
pszConvertData = NULL;
}
/* Release the pointer to the global memory */
GlobalUnlock(hGlobal);
pszGlobalData = NULL;
/* Push the selection data to the Windows clipboard */
if (data->fUseUnicode)
SetClipboardData(CF_UNICODETEXT, hGlobal);
else
SetClipboardData(CF_TEXT, hGlobal);
/* Flag that SetClipboardData has been called */
fSetClipboardData = FALSE;
/*
* NOTE: Do not try to free pszGlobalData, it is owned by
* Windows after the call to SetClipboardData ().
*/
winClipboardFlushXEvents_SelectionNotify_Done:
/* Free allocated resources */
if (ppszTextList)
XFreeStringList(ppszTextList);
if (xtpText.value) {
XFree(xtpText.value);
xtpText.value = NULL;
xtpText.nitems = 0;
}
free(pszConvertData);
free(pwszUnicodeStr);
if (hGlobal && pszGlobalData)
GlobalUnlock(hGlobal);
if (fSetClipboardData) {
SetClipboardData(CF_UNICODETEXT, NULL);
SetClipboardData(CF_TEXT, NULL);
}
return WIN_XEVENTS_NOTIFY_DATA;
return winClipboardSelectionNotifyData(hwnd, iWindow, pDisplay, data, atoms);
case SelectionClear:
winDebug("SelectionClear - doing nothing\n");
break;
case PropertyNotify:
/* If INCR is in progress, collect the data */
if (data->incr &&
(event.xproperty.atom == atoms->atomLocalProperty) &&
(event.xproperty.state == PropertyNewValue))
return winClipboardSelectionNotifyData(hwnd, iWindow, pDisplay, data, atoms);
break;
case MappingNotify:

View File

@ -39,8 +39,8 @@ XWin(1)
.SH BUGS
Only text clipboard contents are supported.
The INCR (Incrememntal transfer) clipboard protocol for clipboard contents larger than the maximum size of an
X request is not supported.
The INCR (Incremental transfer) clipboard protocol for clipboard contents larger than the maximum size of an X
request (approximately 256K) is only supported for X -> Windows clipboard transfers.
Some X clients, notably ones written in Tcl/Tk, do not re-assert ownership of the PRIMARY selection or update
it's timestamp when it's contents change, which currently prevents \fIxwinclip\fP from correctly noticing that
@ -49,7 +49,8 @@ the PRIMARY selection's contents have changed.
Windows clipboard rendering is synchronous in the WM_RENDER*FORMAT message (that is, we must have placed the
contents onto the clipboard by the time we return from processing this message), but we must wait for the X
client which owns the selection to convert the selection to our requested format. This is currently achieved
using a fixed timeout of one second.
using a fixed timeout. After requesting conversion of the selection, if no events are received from the X
client which owns the selection for one second, the conversion is assumed to have failed.
The XWin(1) server should indicate somehow (by placing an atom on the root window?) that it is running with it's
internal clipboard integration enabled, and xwinclip should notice this and exit with an appropriate error.