diff --git a/client/clipboard/x11.c b/client/clipboard/x11.c index 142460ef..a32ca6e3 100644 --- a/client/clipboard/x11.c +++ b/client/clipboard/x11.c @@ -18,25 +18,187 @@ Place, Suite 330, Boston, MA 02111-1307 USA */ #include "lg-clipboard.h" -#include +#include "debug.h" + +struct state +{ + Display * display; + Window window; + Atom aSelection; + Atom aTargets; + Atom aTypes[LG_CLIPBOARD_DATA_MAX]; + LG_ClipboardRequestFn requestFn; + LG_ClipboardData type; +}; + +static struct state * this = NULL; + +static const char * atomTypes[] = +{ + "UTF8_STRING", + "image/png", + "image/bmp", + "image/tiff", + "image/jpeg" +}; static const char * x11_cb_getName() { return "X11"; } -static bool x11_cb_init() +static bool x11_cb_init(SDL_SysWMinfo * wminfo) { + // final sanity check + if (wminfo->subsystem != SDL_SYSWM_X11) + { + DEBUG_ERROR("wrong subsystem"); + return false; + } + + this = (struct state *)malloc(sizeof(struct state)); + memset(this, 0, sizeof(struct state)); + + this->display = wminfo->info.x11.display; + this->window = wminfo->info.x11.window; + this->aSelection = XInternAtom(this->display, "CLIPBOARD", False); + this->aTargets = XInternAtom(this->display, "TARGETS" , False); + + for(int i = 0; i < LG_CLIPBOARD_DATA_MAX; ++i) + { + this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False); + if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue) + { + DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]); + free(this); + this = NULL; + return false; + } + } + + // we need the raw X events + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + return true; } static void x11_cb_free() { + free(this); + this = NULL; +} + +static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size) +{ + XEvent *s = (XEvent *)opaque; + + XChangeProperty( + this->display , + s->xselection.requestor, + s->xselection.property , + s->xselection.target , + 8, + PropModeReplace, + data, + size); + + XSendEvent(this->display, s->xselection.requestor, 0, 0, s); + XFlush(this->display); + free(s); +} + +static void x11_cb_wmevent(SDL_SysWMmsg * msg) +{ + XEvent e = msg->msg.x11.event; + + if (e.type == SelectionRequest) + { + XEvent * s = (XEvent *)malloc(sizeof(XEvent)); + s->xselection.type = SelectionNotify; + s->xselection.requestor = e.xselectionrequest.requestor; + s->xselection.selection = e.xselectionrequest.selection; + s->xselection.target = e.xselectionrequest.target; + s->xselection.property = e.xselectionrequest.property; + s->xselection.time = e.xselectionrequest.time; + + if (!this->requestFn) + { + s->xselection.property = None; + XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s); + XFlush(this->display); + free(s); + return; + } + + // target list requested + if (e.xselectionrequest.target == this->aTargets) + { + Atom targets[2]; + targets[0] = this->aTargets; + targets[1] = this->aTypes[this->type]; + + XChangeProperty( + e.xselectionrequest.display, + e.xselectionrequest.requestor, + e.xselectionrequest.property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)targets, + sizeof(targets) / sizeof(Atom)); + + XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s); + XFlush(this->display); + free(s); + return; + } + + // look to see if we can satisfy the data type + for(int i = 0; i < LG_CLIPBOARD_DATA_MAX; ++i) + if (this->aTypes[i] == e.xselectionrequest.target && this->type == i) + { + // request the data + this->requestFn(x11_cb_reply_fn, s); + return; + } + + DEBUG_INFO("Unable to copy \"%s\" to \"%s\" type", + atomTypes[this->type], + XGetAtomName(this->display, e.xselectionrequest.target)); + + // report no data + s->xselection.property = None; + XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s); + XFlush(this->display); + } + + if (e.type == SelectionNotify) + { + DEBUG_WARN("FIXME: SelectionNotify"); + return; + } + + if (e.type == SelectionClear) + { + DEBUG_WARN("FIXME: SelectionClear"); + return; + } +} + +static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type) +{ + this->requestFn = requestFn; + this->type = type; + XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime); + XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime); + XFlush(this->display); } const LG_Clipboard LGC_X11 = { .getName = x11_cb_getName, .init = x11_cb_init, - .free = x11_cb_free + .free = x11_cb_free, + .wmevent = x11_cb_wmevent, + .notice = x11_cb_notice }; \ No newline at end of file diff --git a/client/lg-clipboard.h b/client/lg-clipboard.h index 1bbef75b..13fb5291 100644 --- a/client/lg-clipboard.h +++ b/client/lg-clipboard.h @@ -20,16 +20,36 @@ Place, Suite 330, Boston, MA 02111-1307 USA #pragma once #include +#include +#include +typedef enum LG_ClipboardData +{ + LG_CLIPBOARD_DATA_TEXT = 0, + LG_CLIPBOARD_DATA_PNG, + LG_CLIPBOARD_DATA_BMP, + LG_CLIPBOARD_DATA_TIFF, + LG_CLIPBOARD_DATA_JPEG, + + LG_CLIPBOARD_DATA_MAX // enum max, not a data type +} +LG_ClipboardData; + +typedef void (* LG_ClipboardReplyFn )(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size); +typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque); typedef const char * (* LG_ClipboardGetName)(); -typedef bool (* LG_ClipboardInit)(); +typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo); typedef void (* LG_ClipboardFree)(); +typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg); +typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type); typedef struct LG_Clipboard { LG_ClipboardGetName getName; LG_ClipboardInit init; LG_ClipboardFree free; + LG_ClipboardWMEvent wmevent; + LG_ClipboardNotice notice; } LG_Clipboard; \ No newline at end of file diff --git a/client/main.c b/client/main.c index 31f4c329..2c751514 100644 --- a/client/main.c +++ b/client/main.c @@ -64,6 +64,9 @@ struct AppState bool lgrResize; const LG_Clipboard * lgc; + SpiceDataType cbType; + LG_ClipboardReplyFn cbReplyFn; + void * cbReplyData; SDL_Window * window; int shmFD; @@ -489,26 +492,50 @@ static inline const uint32_t mapScancode(SDL_Scancode scancode) return ps2; } +void clipboardRequestFn(const LG_ClipboardReplyFn replyFn, void * opaque) +{ + state.cbReplyData = opaque; + state.cbReplyFn = replyFn; + + spice_clipboard_request(state.cbType); +} void spiceClipboardNotice(const SpiceDataType type) { - // we only support text data for now - if (type == SPICE_DATA_TEXT) - spice_clipboard_request(type); + if (!state.lgc || !state.lgc->notice) + return; + + state.cbType = type; + + LG_ClipboardData t; + switch(type) + { + case LG_CLIPBOARD_DATA_TEXT: t = SPICE_DATA_TEXT; break; + case LG_CLIPBOARD_DATA_PNG : t = SPICE_DATA_PNG ; break; + case LG_CLIPBOARD_DATA_BMP : t = SPICE_DATA_BMP ; break; + case LG_CLIPBOARD_DATA_TIFF: t = SPICE_DATA_TIFF; break; + case LG_CLIPBOARD_DATA_JPEG: t = SPICE_DATA_JPEG; break; + } + + state.lgc->notice(clipboardRequestFn, t); } void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size) { - // dos2unix - uint8_t * p = buffer; - for(uint32_t i = 0; i < size; ++i) + if (type == SPICE_DATA_TEXT) { - uint8_t c = buffer[i]; - if (c != '\r') - *p++ = c; + // dos2unix + uint8_t * p = buffer; + for(uint32_t i = 0; i < size; ++i) + { + uint8_t c = buffer[i]; + if (c != '\r') + *p++ = c; + } + *p = '\0'; } - *p = '\0'; - SDL_SetClipboardText((char *)buffer); + + state.cbReplyFn(state.cbReplyData, type, buffer, size); } int eventFilter(void * userdata, SDL_Event * event) @@ -548,6 +575,13 @@ int eventFilter(void * userdata, SDL_Event * event) switch(event->type) { + case SDL_SYSWMEVENT: + { + if (state.lgc && state.lgc->wmevent) + state.lgc->wmevent(event->syswm.msg); + return 0; + } + case SDL_MOUSEMOTION: { if ( @@ -893,18 +927,6 @@ int run() return 1; } - // choose a clipboard api - for(unsigned int i = 0; i < LG_CLIPBOARD_COUNT; ++i) - { - const LG_Clipboard * cb = LG_Clipboards[i]; - if (cb->init()) - { - state.lgc = cb; - DEBUG_INFO("Using Clipboard: %s", cb->getName()); - break; - } - } - if (params.fullscreen) SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); @@ -943,6 +965,8 @@ int run() (unsigned char *)&value, 1 ); + + state.lgc = &LGC_X11; } } else { DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); @@ -955,6 +979,16 @@ int run() return -1; } + if (state.lgc) + { + DEBUG_INFO("Using Clipboard: %s", state.lgc->getName()); + if (!state.lgc->init(&wminfo)) + { + DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway"); + state.lgc = NULL; + } + } + SDL_Cursor *cursor = NULL; if (params.hideMouse) { diff --git a/client/spice/spice.c b/client/spice/spice.c index 5595cafe..45bd597d 100644 --- a/client/spice/spice.c +++ b/client/spice/spice.c @@ -1026,12 +1026,6 @@ bool spice_agent_process(uint32_t dataSize) if (msg.type == VD_AGENT_CLIPBOARD) { DEBUG_PROTO("VD_AGENT_CLIPBOARD"); - if (type != VD_AGENT_CLIPBOARD_UTF8_TEXT) - { - DEBUG_ERROR("for some reason we were sent a non text clipboard, this shouldn't happen"); - return false; - } - if (spice.cbBuffer) { DEBUG_ERROR("cbBuffer was never freed"); @@ -1089,7 +1083,7 @@ bool spice_agent_process(uint32_t dataSize) case VD_AGENT_CLIPBOARD_IMAGE_PNG : spice.cbType = SPICE_DATA_PNG ; break; case VD_AGENT_CLIPBOARD_IMAGE_BMP : spice.cbType = SPICE_DATA_BMP ; break; case VD_AGENT_CLIPBOARD_IMAGE_TIFF: spice.cbType = SPICE_DATA_TIFF; break; - case VD_AGENT_CLIPBOARD_IMAGE_JPG : spice.cbType = SPICE_DATA_JPG ; break; + case VD_AGENT_CLIPBOARD_IMAGE_JPG : spice.cbType = SPICE_DATA_JPEG; break; default: DEBUG_WARN("Unknown clipboard data type: %u", types[0]); return true; @@ -1483,7 +1477,7 @@ bool spice_clipboard_request(SpiceDataType type) case SPICE_DATA_PNG : req.type = VD_AGENT_CLIPBOARD_IMAGE_PNG ; break; case SPICE_DATA_BMP : req.type = VD_AGENT_CLIPBOARD_IMAGE_BMP ; break; case SPICE_DATA_TIFF: req.type = VD_AGENT_CLIPBOARD_IMAGE_TIFF; break; - case SPICE_DATA_JPG : req.type = VD_AGENT_CLIPBOARD_IMAGE_JPG ; break; + case SPICE_DATA_JPEG: req.type = VD_AGENT_CLIPBOARD_IMAGE_JPG ; break; default: DEBUG_ERROR("invalid clipboard data type requested"); return false; diff --git a/client/spice/spice.h b/client/spice/spice.h index 5967c33c..5fe77b46 100644 --- a/client/spice/spice.h +++ b/client/spice/spice.h @@ -27,7 +27,7 @@ typedef enum SpiceDataType SPICE_DATA_PNG, SPICE_DATA_BMP, SPICE_DATA_TIFF, - SPICE_DATA_JPG + SPICE_DATA_JPEG } SpiceDataType;