/* *Copyright (C) 1994-2000 The XFree86 Project, Inc. All Rights Reserved. *Copyright (C) 2003-2004 Harold L Hunt II All Rights Reserved. *Copyright (C) Colin Harrison 2005-2008 * *Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the *"Software"), to deal in the Software without restriction, including *without limitation the rights to use, copy, modify, merge, publish, *distribute, sublicense, and/or sell copies of the Software, and to *permit persons to whom the Software is furnished to do so, subject to *the following conditions: * *The above copyright notice and this permission notice shall be *included in all copies or substantial portions of the Software. * *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND *NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR *ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION *WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * *Except as contained in this notice, the name of the copyright holder(s) *and author(s) shall not be used in advertising or otherwise to promote *the sale, use or other dealings in this Software without prior written *authorization from the copyright holder(s) and author(s). * * Authors: Harold L Hunt II * Colin Harrison */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include /* Local headers */ #include "xevents.h" #include "textconv.h" #include "debug.h" /* * Constants */ #define CLIP_NUM_SELECTIONS 2 #define CLIP_OWN_NONE ((unsigned int)-1) #define CLIP_OWN_PRIMARY 0 #define CLIP_OWN_CLIPBOARD 1 /* * Global variables */ extern int xfixes_event_base; /* * Local variables */ static Window s_iOwners[CLIP_NUM_SELECTIONS] = {None, None}; static const char *szSelectionNames[CLIP_NUM_SELECTIONS] = { "PRIMARY", "CLIPBOARD"}; static unsigned int lastOwnedSelectionIndex = CLIP_OWN_NONE; static Atom atomClipboard = None; static void MonitorSelection(XFixesSelectionNotifyEvent *e, unsigned int i) { /* Look for owned -> not owned transition */ if (None == e->owner && None != s_iOwners[i]) { unsigned int other_index; winDebug("MonitorSelection - %s - Going from owned to not owned.\n", szSelectionNames[i]); /* If this selection is not owned, the other monitored selection must be the most recently owned, if it is owned at all */ if (i == CLIP_OWN_PRIMARY) other_index = CLIP_OWN_CLIPBOARD; if (i == CLIP_OWN_CLIPBOARD) other_index = CLIP_OWN_PRIMARY; if (None != s_iOwners[other_index]) lastOwnedSelectionIndex = other_index; else lastOwnedSelectionIndex = CLIP_OWN_NONE; } /* Save last owned selection */ if (None != e->owner) { lastOwnedSelectionIndex = i; } /* Save new selection owner or None */ s_iOwners[i] = e->owner; winDebug("MonitorSelection - %s - Now owned by XID %x\n", szSelectionNames[i], e->owner); } Atom GetLastOwnedSelectionAtom(void) { if (lastOwnedSelectionIndex == CLIP_OWN_NONE) return None; if (lastOwnedSelectionIndex == CLIP_OWN_PRIMARY) return XA_PRIMARY; if (lastOwnedSelectionIndex == CLIP_OWN_CLIPBOARD) return atomClipboard; return None; } /* * Process any pending X events */ int FlushXEvents (HWND hwnd, Atom atomLocalProperty, Atom atomUTF8String, Atom atomCompoundText, Atom atomTargets, Window iWindow, Display *pDisplay, Bool fUseUnicode) { atomClipboard = XInternAtom (pDisplay, "CLIPBOARD", False); /* Process all pending events */ while (XPending(pDisplay)) { XTextProperty xtpText = { 0, 0, 0, 0 }; XEvent event; XSelectionEvent eventSelection; unsigned long ulReturnBytesLeft; char *pszReturnData = NULL; char *pszGlobalData = NULL; int iReturn; HGLOBAL hGlobal = NULL; XICCEncodingStyle xiccesStyle; int iConvertDataLen = 0; char *pszConvertData = NULL; char *pszTextList[2] = { NULL }; int iCount; char **ppszTextList = NULL; wchar_t *pwszUnicodeStr = NULL; int iUnicodeLen = 0; int iReturnDataLen = 0; Bool fAbort = FALSE; Bool fCloseClipboard = FALSE; Bool fSetClipboardData = TRUE; /* Get the next event - will not block because one is ready */ XNextEvent(pDisplay, &event); /* Branch on the event type */ switch (event.type) { /* * SelectionRequest */ case SelectionRequest: { char *pszAtomName = NULL; winDebug("SelectionRequest - target %d\n", event.xselectionrequest.target); pszAtomName = XGetAtomName(pDisplay, event.xselectionrequest.target); winDebug("SelectionRequest - Target atom name %s\n", pszAtomName); XFree(pszAtomName); pszAtomName = NULL; } /* Abort if invalid target type */ if (event.xselectionrequest.target != XA_STRING && event.xselectionrequest.target != atomUTF8String && event.xselectionrequest.target != atomCompoundText && event.xselectionrequest.target != atomTargets) { /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } /* Handle targets type of request */ if (event.xselectionrequest.target == atomTargets) { Atom atomTargetArr[] = { atomTargets, atomCompoundText, atomUTF8String, XA_STRING }; winDebug("SelectionRequest - populating targets\n"); /* Try to change the property */ iReturn = XChangeProperty(pDisplay, event.xselectionrequest.requestor, event.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (unsigned char *) atomTargetArr, (sizeof(atomTargetArr) / sizeof(atomTargetArr[0]))); if (iReturn == BadAlloc || iReturn == BadAtom || iReturn == BadMatch || iReturn == BadValue || iReturn == BadWindow) { winError("FlushXEvents - SelectionRequest - " "XChangeProperty failed: %d\n", iReturn); } /* Setup selection notify xevent */ eventSelection.type = SelectionNotify; eventSelection.send_event = True; eventSelection.display = pDisplay; eventSelection.requestor = event.xselectionrequest.requestor; eventSelection.selection = event.xselectionrequest.selection; eventSelection.target = event.xselectionrequest.target; eventSelection.property = event.xselectionrequest.property; eventSelection.time = event.xselectionrequest.time; /* * Notify the requesting window that * the operation has completed */ iReturn = XSendEvent(pDisplay, eventSelection.requestor, False, 0L, (XEvent *) &eventSelection); if (iReturn == BadValue || iReturn == BadWindow) { winError("FlushXEvents - SelectionRequest - " "XSendEvent () failed\n"); } break; } /* Close clipboard if we have it open already */ if (GetOpenClipboardWindow() == hwnd) { CloseClipboard(); } /* Access the clipboard */ if (!OpenClipboard(hwnd)) { winError("FlushXEvents - SelectionRequest - " "OpenClipboard () failed: %08lx\n", GetLastError()); /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } /* Indicate that clipboard was opened */ fCloseClipboard = TRUE; /* Check that clipboard format is available */ if (fUseUnicode && !IsClipboardFormatAvailable(CF_UNICODETEXT)) { static int count; /* Hack to stop acroread spamming the log */ static HWND lasthwnd; /* I've not seen any other client get here repeatedly? */ if (hwnd != lasthwnd) count = 0; count++; if (count < 6) winError("FlushXEvents - CF_UNICODETEXT is not " "available from Win32 clipboard. Aborting %d.\n", count); lasthwnd = hwnd; /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } else if (!fUseUnicode && !IsClipboardFormatAvailable(CF_TEXT)) { winError("FlushXEvents - CF_TEXT is not " "available from Win32 clipboard. Aborting.\n"); /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } /* Setup the string style */ if (event.xselectionrequest.target == XA_STRING) xiccesStyle = XStringStyle; #ifdef X_HAVE_UTF8_STRING else if (event.xselectionrequest.target == atomUTF8String) xiccesStyle = XUTF8StringStyle; #endif else if (event.xselectionrequest.target == atomCompoundText) xiccesStyle = XCompoundTextStyle; else xiccesStyle = XStringStyle; /* Get a pointer to the clipboard text, in desired format */ if (fUseUnicode) { /* Retrieve clipboard data */ hGlobal = GetClipboardData(CF_UNICODETEXT); } else { /* Retrieve clipboard data */ hGlobal = GetClipboardData(CF_TEXT); } if (!hGlobal) { winError("FlushXEvents - SelectionRequest - " "GetClipboardData () failed: %08lx\n", GetLastError()); /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } pszGlobalData = (char *) GlobalLock(hGlobal); /* Convert the Unicode string to UTF8 (MBCS) */ if (fUseUnicode) { iConvertDataLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) pszGlobalData, -1, NULL, 0, NULL, NULL); /* NOTE: iConvertDataLen includes space for null terminator */ pszConvertData = (char *) malloc(iConvertDataLen); WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) pszGlobalData, -1, pszConvertData, iConvertDataLen, NULL, NULL); } else { pszConvertData = strdup(pszGlobalData); iConvertDataLen = strlen(pszConvertData) + 1; } /* Convert DOS string to UNIX string */ DOStoUNIX(pszConvertData, strlen(pszConvertData)); /* Setup our text list */ pszTextList[0] = pszConvertData; pszTextList[1] = NULL; /* Initialize the text property */ xtpText.value = NULL; xtpText.nitems = 0; /* Create the text property from the text list */ if (fUseUnicode) { #ifdef X_HAVE_UTF8_STRING iReturn = Xutf8TextListToTextProperty(pDisplay, pszTextList, 1, xiccesStyle, &xtpText); #endif } else { iReturn = XmbTextListToTextProperty(pDisplay, pszTextList, 1, xiccesStyle, &xtpText); } if (iReturn == XNoMemory || iReturn == XLocaleNotSupported) { winError("FlushXEvents - SelectionRequest - " "X*TextListToTextProperty failed: %d\n", iReturn); /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } /* Free the converted string */ free(pszConvertData); pszConvertData = NULL; /* Copy the clipboard text to the requesting window */ iReturn = XChangeProperty(pDisplay, event.xselectionrequest.requestor, event.xselectionrequest.property, event.xselectionrequest.target, 8, PropModeReplace, xtpText.value, xtpText.nitems); if (iReturn == BadAlloc || iReturn == BadAtom || iReturn == BadMatch || iReturn == BadValue || iReturn == BadWindow) { winError("FlushXEvents - SelectionRequest - " "XChangeProperty failed: %d\n", iReturn); /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } /* Release the clipboard data */ GlobalUnlock(hGlobal); pszGlobalData = NULL; fCloseClipboard = FALSE; CloseClipboard(); /* Clean up */ XFree(xtpText.value); xtpText.value = NULL; xtpText.nitems = 0; /* Setup selection notify event */ eventSelection.type = SelectionNotify; eventSelection.send_event = True; eventSelection.display = pDisplay; eventSelection.requestor = event.xselectionrequest.requestor; eventSelection.selection = event.xselectionrequest.selection; eventSelection.target = event.xselectionrequest.target; eventSelection.property = event.xselectionrequest.property; eventSelection.time = event.xselectionrequest.time; /* Notify the requesting window that the operation has completed */ iReturn = XSendEvent(pDisplay, eventSelection.requestor, False, 0L, (XEvent *) &eventSelection); if (iReturn == BadValue || iReturn == BadWindow) { winError("FlushXEvents - SelectionRequest - " "XSendEvent () failed\n"); /* Abort */ fAbort = TRUE; goto FlushXEvents_SelectionRequest_Done; } FlushXEvents_SelectionRequest_Done: /* Free allocated resources */ if (xtpText.value) { XFree(xtpText.value); xtpText.value = NULL; xtpText.nitems = 0; } free(pszConvertData); if (hGlobal && pszGlobalData) GlobalUnlock(hGlobal); /* * Send a SelectionNotify event to the requesting * client when we abort. */ if (fAbort) { winDebug("SelectionRequest - aborting\n"); /* Setup selection notify event */ eventSelection.type = SelectionNotify; eventSelection.send_event = True; eventSelection.display = pDisplay; eventSelection.requestor = event.xselectionrequest.requestor; eventSelection.selection = event.xselectionrequest.selection; eventSelection.target = event.xselectionrequest.target; eventSelection.property = None; eventSelection.time = event.xselectionrequest.time; /* Notify the requesting window that the operation is complete */ iReturn = XSendEvent(pDisplay, eventSelection.requestor, False, 0L, (XEvent *) &eventSelection); if (iReturn == BadValue || iReturn == BadWindow) { /* * Should not be a problem if XSendEvent fails because * the client may simply have exited. */ winError("FlushXEvents - SelectionRequest - " "XSendEvent () failed for abort event.\n"); } } /* Close clipboard if it was opened */ if (fCloseClipboard) { fCloseClipboard = FALSE; CloseClipboard(); } break; /* * SelectionNotify */ case SelectionNotify: winDebug("FlushXEvents - SelectionNotify\n"); { char *pszAtomName; pszAtomName = XGetAtomName(pDisplay, event.xselection.selection); winDebug ("FlushXEvents - SelectionNotify - ATOM: %s\n", pszAtomName); XFree(pszAtomName); } /* * Request conversion of UTF8 and CompoundText targets. */ if (event.xselection.property == None) { if (event.xselection.target == XA_STRING) { winDebug("FlushXEvents - SelectionNotify - " "XA_STRING\n"); return WIN_XEVENTS_CONVERT; } else if (event.xselection.target == atomUTF8String) { winDebug("FlushXEvents - SelectionNotify - " "Requesting conversion of UTF8 target.\n"); XConvertSelection(pDisplay, event.xselection.selection, XA_STRING, atomLocalProperty, iWindow, CurrentTime); /* Process the ConvertSelection event */ XFlush(pDisplay); return WIN_XEVENTS_CONVERT; } #ifdef X_HAVE_UTF8_STRING else if (event.xselection.target == atomCompoundText) { winDebug("FlushXEvents - SelectionNotify - " "Requesting conversion of CompoundText target.\n"); XConvertSelection(pDisplay, event.xselection.selection, atomUTF8String, atomLocalProperty, iWindow, CurrentTime); /* Process the ConvertSelection event */ XFlush(pDisplay); return WIN_XEVENTS_CONVERT; } #endif else { winError("FlushXEvents - SelectionNotify - " "Unknown format. Cannot request conversion, " "aborting.\n"); break; } } case SelectionClear: winDebug("SelectionClear - doing nothing\n"); break; case PropertyNotify: { char *pszAtomName; pszAtomName = XGetAtomName(pDisplay, event.xproperty.atom); winDebug("FlushXEvents - PropertyNotify - ATOM: %s\n", pszAtomName); XFree(pszAtomName); } if (event.xproperty.atom != atomLocalProperty) break; /* Retrieve the size of the stored data */ iReturn = XGetWindowProperty(pDisplay, iWindow, atomLocalProperty, 0, 0, /* Don't get data, just size */ False, AnyPropertyType, &xtpText.encoding, &xtpText.format, &xtpText.nitems, &ulReturnBytesLeft, &xtpText.value); if (iReturn != Success) { winError("FlushXEvents - PropertyNotify - " "XGetWindowProperty () failed, aborting: %d\n", iReturn); break; } winDebug("PropertyNotify - returned data %d left %d\n", xtpText.nitems, ulReturnBytesLeft); /* Request the selection data */ iReturn = XGetWindowProperty(pDisplay, iWindow, atomLocalProperty, 0, ulReturnBytesLeft, False, AnyPropertyType, &xtpText.encoding, &xtpText.format, &xtpText.nitems, &ulReturnBytesLeft, &xtpText.value); if (iReturn != Success) { winError("FlushXEvents - PropertyNotify - " "XGetWindowProperty () failed, aborting: %d\n", iReturn); break; } { char *pszAtomName = NULL; winDebug("PropertyNotify - returned data %d left %d\n", xtpText.nitems, ulReturnBytesLeft); pszAtomName = XGetAtomName(pDisplay, xtpText.encoding); winDebug("PropertyNotify - encoding atom name %s\n", pszAtomName); XFree(pszAtomName); pszAtomName = NULL; } if (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; 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 { winError("FlushXEvents - PropertyNotify - " "X*TextPropertyToTextList list_return is NULL.\n"); pszReturnData = malloc(1); pszReturnData[0] = '\0'; } } else { winError("FlushXEvents - PropertyNotify - " "X*TextPropertyToTextList returned: "); switch (iReturn) { case XNoMemory: winError("XNoMemory\n"); break; case XLocaleNotSupported: winError("XLocaleNotSupported\n"); break; case XConverterNotFound: winError("XConverterNotFound\n"); break; default: winError("%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 */ UNIXtoDOS(&pszReturnData, strlen(pszReturnData)); if (fUseUnicode) { /* Find out how much space needed to convert MBCS to Unicode */ iUnicodeLen = MultiByteToWideChar(CP_UTF8, 0, pszReturnData, -1, NULL, 0); /* Allocate memory for the Unicode string */ pwszUnicodeStr = (wchar_t *) malloc(sizeof(wchar_t) * (iUnicodeLen + 1)); if (!pwszUnicodeStr) { winError("FlushXEvents - PropertyNotify " "malloc failed for pwszUnicodeStr, aborting.\n"); /* Abort */ fAbort = TRUE; goto FlushXEvents_PropertyNotify_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 + 1)); } else { 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) { winError("FlushXEvents - PropertyNotify " "GlobalAlloc failed, aborting: %ld\n", GetLastError()); /* Abort */ fAbort = TRUE; goto FlushXEvents_PropertyNotify_Done; } /* Obtain a pointer to the global memory */ pszGlobalData = GlobalLock(hGlobal); if (pszGlobalData == NULL) { winError("FlushXEvents - Could not lock global " "memory for clipboard transfer\n"); /* Abort */ fAbort = TRUE; goto FlushXEvents_PropertyNotify_Done; } /* Copy the returned string into the global memory */ if (fUseUnicode) { memcpy(pszGlobalData, pwszUnicodeStr, sizeof(wchar_t) * (iUnicodeLen + 1)); 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 (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 (). */ FlushXEvents_PropertyNotify_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; case MappingNotify: break; default: if (event.type == XFixesSetSelectionOwnerNotify + xfixes_event_base) { XFixesSelectionNotifyEvent *e = (XFixesSelectionNotifyEvent *)&event; winDebug("FlushXEvents - XFixesSetSelectionOwnerNotify\n"); /* Save selection owners for monitored selections, ignore other selections */ if (e->selection == XA_PRIMARY) { MonitorSelection(e, CLIP_OWN_PRIMARY); } else if (e->selection == atomClipboard) { MonitorSelection(e, CLIP_OWN_CLIPBOARD); } else break; /* Selection is being disowned */ if (e->owner == None) { winDebug("winProcSetSelectionOwner - No window, returning.\n"); break; } /* XXX: there are all kinds of wacky edge cases we might need here: - we own windows clipboard, but neither PRIMARY nor CLIPBOARD have an owner, so we should disown it? - root window is taking ownership? */ /* If we are the owner of the most recently owned selection, don't go all recursive :) */ if ((lastOwnedSelectionIndex != CLIP_OWN_NONE) && (s_iOwners[lastOwnedSelectionIndex] == iWindow)) { winDebug("winProcSetSelectionOwner - Ownership changed to us, aborting.\n"); break; } /* Close clipboard if we have it open already (possible? correct??) */ if (GetOpenClipboardWindow() == hwnd) { CloseClipboard(); } /* Access the Windows clipboard */ if (!OpenClipboard(hwnd)) { winError("winProcSetSelectionOwner - OpenClipboard () failed: %08x\n", (int) GetLastError()); break; } /* Take ownership of the Windows clipboard */ if (!EmptyClipboard()) { winError("winProcSetSelectionOwner - EmptyClipboard () failed: %08x\n", (int) GetLastError()); break; } /* Advertise regular text and unicode */ SetClipboardData(CF_UNICODETEXT, NULL); SetClipboardData(CF_TEXT, NULL); /* Release the clipboard */ if (!CloseClipboard()) { winError("winProcSetSelectionOwner - CloseClipboard () failed: %08x\n", (int) GetLastError()); break; } } /* XFixesSelectionWindowDestroyNotifyMask */ /* XFixesSelectionClientCloseNotifyMask */ else { winError("FlushXEvents - unexpected event type %d\n", event.type); } break; } } return WIN_XEVENTS_SUCCESS; }