From ebe79c28c30ad328dcff88b95e7863e885af36f9 Mon Sep 17 00:00:00 2001 From: dscho Date: Sun, 8 Mar 2009 18:33:08 +0000 Subject: [PATCH] Clipboard support for SDLvncviewer The clipboard support has only been tested on Linux so far. Signed-off-by: Johannes Schindelin --- ChangeLog | 3 + client_examples/Makefile.am | 8 +- client_examples/SDLvncviewer.c | 20 ++ client_examples/scrap.c | 558 +++++++++++++++++++++++++++++++++ client_examples/scrap.h | 18 ++ 5 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 client_examples/scrap.c create mode 100644 client_examples/scrap.h diff --git a/ChangeLog b/ChangeLog index 82a6662..24483cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2009-03-08 Johannes E. Schindelin + * client_examples/SDLvncviewer.c: support clipboard operations + 2009-03-07 Johannes E. Schindelin * client_examples/SDLvncviewer.c: force releasing Alt keys whenr losing focus. This helps when you switch windows by pressing diff --git a/client_examples/Makefile.am b/client_examples/Makefile.am index 9eed084..fa71a50 100644 --- a/client_examples/Makefile.am +++ b/client_examples/Makefile.am @@ -19,8 +19,14 @@ SDLVIEWER=SDLvncviewer SDLvncviewer_CFLAGS=$(SDL_CFLAGS) +SDLvncviewer_SOURCES=SDLvncviewer.c scrap.c + +if HAVE_X +X11_LIB=-lX11 +endif + # thanks to autoconf, this looks ugly -SDLvncviewer_LDADD=$(LDADD) $(SDL_LIBS) +SDLvncviewer_LDADD=$(LDADD) $(SDL_LIBS) $(X11_LIB) endif noinst_PROGRAMS=ppmtest $(SDLVIEWER) $(FFMPEG_CLIENT) backchannel diff --git a/client_examples/SDLvncviewer.c b/client_examples/SDLvncviewer.c index 6fe1a17..0042284 100644 --- a/client_examples/SDLvncviewer.c +++ b/client_examples/SDLvncviewer.c @@ -1,5 +1,6 @@ #include #include +#include "scrap.h" struct { int sdl; int rfb; } buttonMapping[]={ {1, rfbButton1Mask}, @@ -397,6 +398,17 @@ static void handleSDLEvent(rfbClient *cl, SDL_Event *e) leftAltKeyDown = FALSE; rfbClientLog("released left Alt key\n"); } + + if (e->active.gain && lost_scrap()) { + static char *data = NULL; + static int len = 0; + get_scrap(T('T', 'E', 'X', 'T'), &len, &data); + if (len) + SendClientCutText(cl, data, len); + } + break; + case SDL_SYSWMEVENT: + clipboard_filter(&e); break; case SDL_VIDEORESIZE: setRealDimension(cl, e->resize.w, e->resize.h); @@ -406,6 +418,11 @@ static void handleSDLEvent(rfbClient *cl, SDL_Event *e) } } +static void got_selection(rfbClient *cl, const char *text, int len) +{ + put_scrap(T('T', 'E', 'X', 'T'), len, text); +} + #ifdef mac #define main SDLmain #endif @@ -443,9 +460,12 @@ int main(int argc,char** argv) { cl->GotFrameBufferUpdate=update; cl->HandleKeyboardLedState=kbd_leds; cl->HandleTextChat=text_chat; + cl->GotXCutText = got_selection; if(!rfbInitClient(cl,&argc,argv)) return 1; + init_scrap(); + while(1) { if(SDL_PollEvent(&e)) handleSDLEvent(cl, &e); diff --git a/client_examples/scrap.c b/client_examples/scrap.c new file mode 100644 index 0000000..c28800c --- /dev/null +++ b/client_examples/scrap.c @@ -0,0 +1,558 @@ +/* Handle clipboard text and data in arbitrary formats */ + +#include +#include + +#ifdef WIN32 +#include +#include +#else +#include +#include +#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); +} diff --git a/client_examples/scrap.h b/client_examples/scrap.h new file mode 100644 index 0000000..647bd74 --- /dev/null +++ b/client_examples/scrap.h @@ -0,0 +1,18 @@ +/* Handle clipboard text and data in arbitrary formats */ + +/* Miscellaneous defines */ +#define T(A, B, C, D) (int)((A<<24)|(B<<16)|(C<<8)|(D<<0)) + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +extern int init_scrap(void); +extern int lost_scrap(void); +extern void put_scrap(int type, int srclen, const char *src); +extern void get_scrap(int type, int *dstlen, char **dst); +extern int clipboard_filter(const SDL_Event *event); + +#ifdef __cplusplus +} +#endif /* __cplusplus */