/* Handle clipboard text and data in arbitrary formats */ #include <stdio.h> #include <limits.h> #ifdef WIN32 #include <SDL.h> #include <SDL_syswm.h> #else #include <SDL/SDL.h> #include <SDL/SDL_syswm.h> #endif #include "scrap.h" #include "rfb/rfbconfig.h" /* Determine what type of clipboard we are using */ #if defined(__unix__) && !defined(__QNXNTO__) && defined(LIBVNCSERVER_HAVE_X11) #define X11_SCRAP #elif defined(__WIN32__) #define WIN_SCRAP #elif defined(__QNXNTO__) #define QNX_SCRAP #else #warning Unknown window manager for clipboard handling #endif /* scrap type */ /* System dependent data types */ #if defined(X11_SCRAP) typedef Atom scrap_type; static Atom XA_TARGETS, XA_TEXT, XA_COMPOUND_TEXT, XA_UTF8_STRING; #elif defined(WIN_SCRAP) typedef UINT scrap_type; #elif defined(QNX_SCRAP) typedef uint32_t scrap_type; #define Ph_CL_TEXT T('T', 'E', 'X', 'T') #else typedef int scrap_type; #endif /* scrap type */ /* System dependent variables */ #if defined(X11_SCRAP) static Display *SDL_Display; static Window SDL_Window; static void (*Lock_Display)(void); static void (*Unlock_Display)(void); static Atom XA_UTF8_STRING; #elif defined(WIN_SCRAP) static HWND SDL_Window; #elif defined(QNX_SCRAP) static unsigned short InputGroup; #endif /* scrap type */ #define FORMAT_PREFIX "SDL_scrap_0x" static scrap_type convert_format(int type) { switch (type) { case T('T', 'E', 'X', 'T'): #if defined(X11_SCRAP) return XA_UTF8_STRING ? XA_UTF8_STRING : XA_STRING; #elif defined(WIN_SCRAP) return CF_TEXT; #elif defined(QNX_SCRAP) return Ph_CL_TEXT; #endif /* scrap type */ default: { char format[sizeof(FORMAT_PREFIX)+8+1]; sprintf(format, "%s%08lx", FORMAT_PREFIX, (unsigned long)type); #if defined(X11_SCRAP) return XInternAtom(SDL_Display, format, False); #elif defined(WIN_SCRAP) return RegisterClipboardFormat(format); #endif /* scrap type */ } } } /* Convert internal data to scrap format */ static int convert_data(int type, char *dst, const char *src, int srclen) { int dstlen; dstlen = 0; switch (type) { case T('T', 'E', 'X', 'T'): if (dst) { while (--srclen >= 0) { #if defined(__unix__) if (*src == '\r') { *dst++ = '\n'; ++dstlen; } else #elif defined(__WIN32__) if (*src == '\r') { *dst++ = '\r'; ++dstlen; *dst++ = '\n'; ++dstlen; } else #endif { *dst++ = *src; ++dstlen; } ++src; } *dst = '\0'; ++dstlen; } else { while (--srclen >= 0) { #if defined(__unix__) if (*src == '\r') ++dstlen; else #elif defined(__WIN32__) if (*src == '\r') { ++dstlen; ++dstlen; } else #endif { ++dstlen; } ++src; } ++dstlen; } break; default: if (dst) { *(int *)dst = srclen; dst += sizeof(int); memcpy(dst, src, srclen); } dstlen = sizeof(int)+srclen; break; } return(dstlen); } /* Convert scrap data to internal format */ static int convert_scrap(int type, char *dst, char *src, int srclen) { int dstlen; dstlen = 0; switch (type) { case T('T', 'E', 'X', 'T'): { if (srclen == 0) srclen = strlen(src); if (dst) { while (--srclen >= 0) { #if defined(__WIN32__) if (*src == '\r') /* drop extraneous '\r' */; else #endif if (*src == '\n') { *dst++ = '\r'; ++dstlen; } else { *dst++ = *src; ++dstlen; } ++src; } *dst = '\0'; ++dstlen; } else { while (--srclen >= 0) { #if defined(__WIN32__) /* drop extraneous '\r' */; if (*src != '\r') #endif ++dstlen; ++src; } ++dstlen; } break; } default: dstlen = *(int *)src; if (dst) memcpy(dst, src + sizeof(int), srclen ? srclen - sizeof(int) : dstlen); break; } return dstlen; } int init_scrap(void) { SDL_SysWMinfo info; int retval; /* Grab the window manager specific information */ retval = -1; SDL_SetError("SDL is not running on known window manager"); SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info)) { /* Save the information for later use */ #if defined(X11_SCRAP) if (info.subsystem == SDL_SYSWM_X11) { SDL_Display = info.info.x11.display; SDL_Window = info.info.x11.window; Lock_Display = info.info.x11.lock_func; Unlock_Display = info.info.x11.unlock_func; /* Enable the special window hook events */ SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); SDL_SetEventFilter(clipboard_filter); XA_TARGETS = XInternAtom(SDL_Display, "TARGETS", False); XA_TEXT = XInternAtom(SDL_Display, "TEXT", False); XA_COMPOUND_TEXT = XInternAtom(SDL_Display, "COMPOUND_TEXT", False); XA_UTF8_STRING = XInternAtom(SDL_Display, "UTF8_STRING", False); retval = 0; } else SDL_SetError("SDL is not running on X11"); #elif defined(WIN_SCRAP) SDL_Window = info.window; retval = 0; #elif defined(QNX_SCRAP) InputGroup = PhInputGroup(NULL); retval = 0; #endif /* scrap type */ } return(retval); } int lost_scrap(void) { int retval; #if defined(X11_SCRAP) if (Lock_Display) Lock_Display(); retval = (XGetSelectionOwner(SDL_Display, XA_PRIMARY) != SDL_Window); if (Unlock_Display) Unlock_Display(); #elif defined(WIN_SCRAP) retval = (GetClipboardOwner() != SDL_Window); #elif defined(QNX_SCRAP) retval = (PhInputGroup(NULL) != InputGroup); #endif /* scrap type */ return(retval); } void put_scrap(int type, int srclen, const char *src) { scrap_type format; int dstlen; char *dst; format = convert_format(type); dstlen = convert_data(type, NULL, src, srclen); #if defined(X11_SCRAP) dst = (char *)malloc(dstlen); if (dst != NULL) { if (Lock_Display) Lock_Display(); convert_data(type, dst, src, srclen); XChangeProperty(SDL_Display, DefaultRootWindow(SDL_Display), XA_CUT_BUFFER0, format, 8, PropModeReplace, (unsigned char *)dst, dstlen); free(dst); if (lost_scrap()) XSetSelectionOwner(SDL_Display, XA_PRIMARY, SDL_Window, CurrentTime); if (Unlock_Display) Unlock_Display(); } #elif defined(WIN_SCRAP) if (OpenClipboard(SDL_Window)) { HANDLE hMem; hMem = GlobalAlloc((GMEM_MOVEABLE|GMEM_DDESHARE), dstlen); if (hMem != NULL) { dst = (char *)GlobalLock(hMem); convert_data(type, dst, src, srclen); GlobalUnlock(hMem); EmptyClipboard(); SetClipboardData(format, hMem); } CloseClipboard(); } #elif defined(QNX_SCRAP) #if (_NTO_VERSION < 620) /* before 6.2.0 releases */ #define PhClipboardHdr PhClipHeader #endif { PhClipboardHdr clheader = { Ph_CLIPBOARD_TYPE_TEXT, 0, NULL }; int* cldata; int status; dst = (char *)malloc(dstlen+4); if (dst != NULL) { cldata = (int*)dst; *cldata = type; convert_data(type, dst+4, src, srclen); clheader.data = dst; #if (_NTO_VERSION < 620) /* before 6.2.0 releases */ if (dstlen > 65535) /* maximum photon clipboard size :(*/ clheader.length = 65535; else #endif clheader.length = dstlen+4; status = PhClipboardCopy(InputGroup, 1, &clheader); if (status == -1) fprintf(stderr, "Photon: copy to clipboard failed!\n"); free(dst); } } #endif /* scrap type */ } void get_scrap(int type, int *dstlen, char **dst) { scrap_type format; *dstlen = 0; format = convert_format(type); #if defined(X11_SCRAP) { Window owner; Atom selection; Atom seln_type; int seln_format; unsigned long nbytes; unsigned long overflow; char *src; if (Lock_Display) Lock_Display(); owner = XGetSelectionOwner(SDL_Display, XA_PRIMARY); if (Unlock_Display) Unlock_Display(); if ((owner == None) || (owner == SDL_Window)) { owner = DefaultRootWindow(SDL_Display); selection = XA_CUT_BUFFER0; } else { int selection_response = 0; SDL_Event event; owner = SDL_Window; if (Lock_Display) Lock_Display(); selection = XInternAtom(SDL_Display, "SDL_SELECTION", False); XConvertSelection(SDL_Display, XA_PRIMARY, format, selection, owner, CurrentTime); if (Unlock_Display) Unlock_Display(); while (!selection_response) { SDL_WaitEvent(&event); if (event.type == SDL_SYSWMEVENT) { XEvent xevent = event.syswm.msg->event.xevent; if ((xevent.type == SelectionNotify) && (xevent.xselection.requestor == owner)) selection_response = 1; } } } if (Lock_Display) Lock_Display(); if (XGetWindowProperty(SDL_Display, owner, selection, 0, INT_MAX/4, False, format, &seln_type, &seln_format, &nbytes, &overflow, (unsigned char **)&src) == Success) { if (seln_type == format) { *dstlen = convert_scrap(type, NULL, src, nbytes); *dst = (char *)realloc(*dst, *dstlen); if (*dst == NULL) *dstlen = 0; else convert_scrap(type, *dst, src, nbytes); } XFree(src); } } if (Unlock_Display) Unlock_Display(); #elif defined(WIN_SCRAP) if (IsClipboardFormatAvailable(format) && OpenClipboard(SDL_Window)) { HANDLE hMem; char *src; hMem = GetClipboardData(format); if (hMem != NULL) { src = (char *)GlobalLock(hMem); *dstlen = convert_scrap(type, NULL, src, 0); *dst = (char *)realloc(*dst, *dstlen); if (*dst == NULL) *dstlen = 0; else convert_scrap(type, *dst, src, 0); GlobalUnlock(hMem); } CloseClipboard(); } #elif defined(QNX_SCRAP) #if (_NTO_VERSION < 620) /* before 6.2.0 releases */ { void* clhandle; PhClipHeader* clheader; int* cldata; clhandle = PhClipboardPasteStart(InputGroup); if (clhandle != NULL) { clheader = PhClipboardPasteType(clhandle, Ph_CLIPBOARD_TYPE_TEXT); if (clheader != NULL) { cldata = clheader->data; if ((clheader->length>4) && (*cldata == type)) { *dstlen = convert_scrap(type, NULL, (char*)clheader->data+4, clheader->length-4); *dst = (char *)realloc(*dst, *dstlen); if (*dst == NULL) *dstlen = 0; else convert_scrap(type, *dst, (char*)clheader->data+4, clheader->length-4); } } PhClipboardPasteFinish(clhandle); } } #else /* 6.2.0 and 6.2.1 and future releases */ { void* clhandle; PhClipboardHdr* clheader; int* cldata; clheader=PhClipboardRead(InputGroup, Ph_CLIPBOARD_TYPE_TEXT); if (clheader!=NULL) { cldata=clheader->data; if ((clheader->length>4) && (*cldata==type)) { *dstlen = convert_scrap(type, NULL, (char*)clheader->data+4, clheader->length-4); *dst = (char *)realloc(*dst, *dstlen); if (*dst == NULL) *dstlen = 0; else convert_scrap(type, *dst, (char*)clheader->data+4, clheader->length-4); } } } #endif #endif /* scrap type */ } int clipboard_filter(const SDL_Event *event) { #if defined(X11_SCRAP) /* Post all non-window manager specific events */ if (event->type != SDL_SYSWMEVENT) return(1); /* Handle window-manager specific clipboard events */ switch (event->syswm.msg->event.xevent.type) { /* Copy the selection from XA_CUT_BUFFER0 to the requested property */ case SelectionRequest: { XSelectionRequestEvent *req; XEvent sevent; int seln_format; unsigned long nbytes; unsigned long overflow; unsigned char *seln_data; req = &event->syswm.msg->event.xevent.xselectionrequest; if (req->target == XA_TARGETS) { Atom supported[] = { XA_TEXT, XA_COMPOUND_TEXT, XA_UTF8_STRING, XA_TARGETS, XA_STRING }; XEvent response; XChangeProperty(SDL_Display, req->requestor, req->property, req->target, 32, PropModeReplace, (unsigned char*)supported, sizeof(supported) / sizeof(supported[0])); response.xselection.property=None; response.xselection.type= SelectionNotify; response.xselection.display= req->display; response.xselection.requestor= req->requestor; response.xselection.selection=req->selection; response.xselection.target= req->target; response.xselection.time = req->time; XSendEvent (SDL_Display, req->requestor,0,0,&response); XFlush (SDL_Display); return 1; } sevent.xselection.type = SelectionNotify; sevent.xselection.display = req->display; sevent.xselection.selection = req->selection; sevent.xselection.target = None; sevent.xselection.property = req->property; sevent.xselection.requestor = req->requestor; sevent.xselection.time = req->time; if (XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display), XA_CUT_BUFFER0, 0, INT_MAX/4, False, req->target, &sevent.xselection.target, &seln_format, &nbytes, &overflow, &seln_data) == Success) { if (sevent.xselection.target == req->target) { if (sevent.xselection.target == XA_STRING && nbytes > 0 && seln_data[nbytes-1] == '\0') --nbytes; XChangeProperty(SDL_Display, req->requestor, req->property, sevent.xselection.target, seln_format, PropModeReplace, seln_data, nbytes); sevent.xselection.property = req->property; } XFree(seln_data); } XSendEvent(SDL_Display,req->requestor,False,0,&sevent); XSync(SDL_Display, False); break; } } /* Post the event for X11 clipboard reading above */ #endif /* X11_SCRAP */ return(1); }