mirror of
				https://github.com/gnif/LookingGlass.git
				synced 2025-10-31 12:42:02 +00:00 
			
		
		
		
	[client] spice: implement full clipboard guest copy support
This commit is contained in:
		| @@ -18,25 +18,187 @@ Place, Suite 330, Boston, MA 02111-1307 USA | |||||||
| */ | */ | ||||||
|  |  | ||||||
| #include "lg-clipboard.h" | #include "lg-clipboard.h" | ||||||
| #include <stdbool.h> | #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() | static const char * x11_cb_getName() | ||||||
| { | { | ||||||
|   return "X11"; |   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; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void x11_cb_free() | 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 = | const LG_Clipboard LGC_X11 = | ||||||
| { | { | ||||||
|   .getName = x11_cb_getName, |   .getName = x11_cb_getName, | ||||||
|   .init    = x11_cb_init, |   .init    = x11_cb_init, | ||||||
|   .free    = x11_cb_free |   .free    = x11_cb_free, | ||||||
|  |   .wmevent = x11_cb_wmevent, | ||||||
|  |   .notice  = x11_cb_notice | ||||||
| }; | }; | ||||||
| @@ -20,16 +20,36 @@ Place, Suite 330, Boston, MA 02111-1307 USA | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  | #include <SDL2/SDL.h> | ||||||
|  | #include <SDL2/SDL_syswm.h> | ||||||
|  |  | ||||||
|  | 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 const char * (* LG_ClipboardGetName)(); | ||||||
| typedef bool         (* LG_ClipboardInit)(); | typedef bool         (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo); | ||||||
| typedef void         (* LG_ClipboardFree)(); | 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 | typedef struct LG_Clipboard | ||||||
| { | { | ||||||
|   LG_ClipboardGetName getName; |   LG_ClipboardGetName getName; | ||||||
|   LG_ClipboardInit    init; |   LG_ClipboardInit    init; | ||||||
|   LG_ClipboardFree    free; |   LG_ClipboardFree    free; | ||||||
|  |   LG_ClipboardWMEvent wmevent; | ||||||
|  |   LG_ClipboardNotice  notice; | ||||||
| } | } | ||||||
| LG_Clipboard; | LG_Clipboard; | ||||||
| @@ -64,6 +64,9 @@ struct AppState | |||||||
|   bool                 lgrResize; |   bool                 lgrResize; | ||||||
|  |  | ||||||
|   const LG_Clipboard * lgc; |   const LG_Clipboard * lgc; | ||||||
|  |   SpiceDataType        cbType; | ||||||
|  |   LG_ClipboardReplyFn  cbReplyFn; | ||||||
|  |   void               * cbReplyData; | ||||||
|  |  | ||||||
|   SDL_Window         * window; |   SDL_Window         * window; | ||||||
|   int                  shmFD; |   int                  shmFD; | ||||||
| @@ -489,15 +492,37 @@ static inline const uint32_t mapScancode(SDL_Scancode scancode) | |||||||
|   return ps2; |   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) | void spiceClipboardNotice(const SpiceDataType type) | ||||||
| { | { | ||||||
|   // we only support text data for now |   if (!state.lgc || !state.lgc->notice) | ||||||
|   if (type == SPICE_DATA_TEXT) |     return; | ||||||
|     spice_clipboard_request(type); |  | ||||||
|  |   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) | void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size) | ||||||
|  | { | ||||||
|  |   if (type == SPICE_DATA_TEXT) | ||||||
|   { |   { | ||||||
|     // dos2unix |     // dos2unix | ||||||
|     uint8_t * p = buffer; |     uint8_t * p = buffer; | ||||||
| @@ -508,7 +533,9 @@ void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t siz | |||||||
|         *p++ = c; |         *p++ = c; | ||||||
|     } |     } | ||||||
|     *p = '\0'; |     *p = '\0'; | ||||||
|   SDL_SetClipboardText((char *)buffer); |   } | ||||||
|  |  | ||||||
|  |   state.cbReplyFn(state.cbReplyData, type, buffer, size); | ||||||
| } | } | ||||||
|  |  | ||||||
| int eventFilter(void * userdata, SDL_Event * event) | int eventFilter(void * userdata, SDL_Event * event) | ||||||
| @@ -548,6 +575,13 @@ int eventFilter(void * userdata, SDL_Event * event) | |||||||
|  |  | ||||||
|   switch(event->type) |   switch(event->type) | ||||||
|   { |   { | ||||||
|  |     case SDL_SYSWMEVENT: | ||||||
|  |     { | ||||||
|  |       if (state.lgc && state.lgc->wmevent) | ||||||
|  |         state.lgc->wmevent(event->syswm.msg); | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     case SDL_MOUSEMOTION: |     case SDL_MOUSEMOTION: | ||||||
|     { |     { | ||||||
|       if ( |       if ( | ||||||
| @@ -893,18 +927,6 @@ int run() | |||||||
|     return 1; |     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) |   if (params.fullscreen) | ||||||
|     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); |     SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); | ||||||
|  |  | ||||||
| @@ -943,6 +965,8 @@ int run() | |||||||
|         (unsigned char *)&value, |         (unsigned char *)&value, | ||||||
|         1 |         1 | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|  |       state.lgc = &LGC_X11; | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); |     DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); | ||||||
| @@ -955,6 +979,16 @@ int run() | |||||||
|     return -1; |     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; |   SDL_Cursor *cursor = NULL; | ||||||
|   if (params.hideMouse) |   if (params.hideMouse) | ||||||
|   { |   { | ||||||
|   | |||||||
| @@ -1026,12 +1026,6 @@ bool spice_agent_process(uint32_t dataSize) | |||||||
|         if (msg.type == VD_AGENT_CLIPBOARD) |         if (msg.type == VD_AGENT_CLIPBOARD) | ||||||
|         { |         { | ||||||
|           DEBUG_PROTO("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) |           if (spice.cbBuffer) | ||||||
|           { |           { | ||||||
|             DEBUG_ERROR("cbBuffer was never freed"); |             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_PNG : spice.cbType = SPICE_DATA_PNG ; break; | ||||||
|           case VD_AGENT_CLIPBOARD_IMAGE_BMP : spice.cbType = SPICE_DATA_BMP ; 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_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: |           default: | ||||||
|             DEBUG_WARN("Unknown clipboard data type: %u", types[0]); |             DEBUG_WARN("Unknown clipboard data type: %u", types[0]); | ||||||
|             return true; |             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_PNG : req.type = VD_AGENT_CLIPBOARD_IMAGE_PNG ; break; | ||||||
|     case SPICE_DATA_BMP : req.type = VD_AGENT_CLIPBOARD_IMAGE_BMP ; 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_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: |     default: | ||||||
|       DEBUG_ERROR("invalid clipboard data type requested"); |       DEBUG_ERROR("invalid clipboard data type requested"); | ||||||
|       return false; |       return false; | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ typedef enum SpiceDataType | |||||||
|   SPICE_DATA_PNG, |   SPICE_DATA_PNG, | ||||||
|   SPICE_DATA_BMP, |   SPICE_DATA_BMP, | ||||||
|   SPICE_DATA_TIFF, |   SPICE_DATA_TIFF, | ||||||
|   SPICE_DATA_JPG |   SPICE_DATA_JPEG | ||||||
| } | } | ||||||
| SpiceDataType; | SpiceDataType; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Geoffrey McRae
					Geoffrey McRae