From 56a91f20671402a8c6f60c40e5e4e18f7978c740 Mon Sep 17 00:00:00 2001 From: Jon Turney Date: Wed, 18 Nov 2015 21:27:23 +0000 Subject: [PATCH] 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 --- hw/xwin/winclipboard/internal.h | 6 +- hw/xwin/winclipboard/thread.c | 3 + hw/xwin/winclipboard/wndproc.c | 14 +- hw/xwin/winclipboard/xevents.c | 481 +++++++++++++++++------------- hw/xwin/winclipboard/xwinclip.man | 7 +- 5 files changed, 290 insertions(+), 221 deletions(-) diff --git a/hw/xwin/winclipboard/internal.h b/hw/xwin/winclipboard/internal.h index 2e6b22db3..766a836ae 100644 --- a/hw/xwin/winclipboard/internal.h +++ b/hw/xwin/winclipboard/internal.h @@ -1,4 +1,3 @@ - /* *Copyright (C) 2003-2004 Harold L Hunt II All Rights Reserved. * @@ -38,7 +37,7 @@ /* Windows headers */ #include -#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 diff --git a/hw/xwin/winclipboard/thread.c b/hw/xwin/winclipboard/thread.c index fa57ada80..8be6991cc 100644 --- a/hw/xwin/winclipboard/thread.c +++ b/hw/xwin/winclipboard/thread.c @@ -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) { diff --git a/hw/xwin/winclipboard/wndproc.c b/hw/xwin/winclipboard/wndproc.c index c8d0064bd..990febced 100644 --- a/hw/xwin/winclipboard/wndproc.c +++ b/hw/xwin/winclipboard/wndproc.c @@ -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 }, }; diff --git a/hw/xwin/winclipboard/xevents.c b/hw/xwin/winclipboard/xevents.c index d4ea97fc3..61d805011 100644 --- a/hw/xwin/winclipboard/xevents.c +++ b/hw/xwin/winclipboard/xevents.c @@ -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: diff --git a/hw/xwin/winclipboard/xwinclip.man b/hw/xwin/winclipboard/xwinclip.man index 73dfc8a94..f9e0d3bfb 100644 --- a/hw/xwin/winclipboard/xwinclip.man +++ b/hw/xwin/winclipboard/xwinclip.man @@ -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.