mirror of
				https://github.com/gnif/LookingGlass.git
				synced 2025-10-31 04:31:57 +00:00 
			
		
		
		
	[client] initial host to client clipboard sync working
This commit is contained in:
		| @@ -16,6 +16,7 @@ pkg_check_modules(PKGCONFIG REQUIRED | ||||
| 	spice-protocol | ||||
| 	fontconfig | ||||
| 	x11 | ||||
| 	xfixes | ||||
| 	wayland-egl | ||||
| 	libconfig | ||||
| 	nettle | ||||
|   | ||||
| @@ -20,15 +20,27 @@ Place, Suite 330, Boston, MA 02111-1307 USA | ||||
| #include "lg-clipboard.h" | ||||
| #include "debug.h" | ||||
|  | ||||
| #include <X11/extensions/Xfixes.h> | ||||
|  | ||||
| struct state | ||||
| { | ||||
|   Display             * display; | ||||
|   Window                window; | ||||
|   Atom                  aSelection; | ||||
|   Atom                  aCurSelection; | ||||
|   Atom                  aTargets; | ||||
|   Atom                  aTypes[LG_CLIPBOARD_DATA_MAX]; | ||||
|   Atom                  aSelData; | ||||
|   Atom                  aIncr; | ||||
|   Atom                  aTypes[LG_CLIPBOARD_DATA_NONE]; | ||||
|   LG_ClipboardReleaseFn releaseFn; | ||||
|   LG_ClipboardRequestFn requestFn; | ||||
|   LG_ClipboardNotifyFn  notifyFn; | ||||
|   LG_ClipboardDataFn    dataFn; | ||||
|   LG_ClipboardData      type; | ||||
|  | ||||
|   // XFixes vars | ||||
|   int eventBase; | ||||
|   int errorBase; | ||||
| }; | ||||
|  | ||||
| static struct state * this = NULL; | ||||
| @@ -47,7 +59,11 @@ static const char * x11_cb_getName() | ||||
|   return "X11"; | ||||
| } | ||||
|  | ||||
| static bool x11_cb_init(SDL_SysWMinfo * wminfo) | ||||
| static bool x11_cb_init( | ||||
|     SDL_SysWMinfo         * wminfo, | ||||
|     LG_ClipboardReleaseFn   releaseFn, | ||||
|     LG_ClipboardNotifyFn    notifyFn, | ||||
|     LG_ClipboardDataFn      dataFn) | ||||
| { | ||||
|   // final sanity check | ||||
|   if (wminfo->subsystem != SDL_SYSWM_X11) | ||||
| @@ -59,12 +75,18 @@ static bool x11_cb_init(SDL_SysWMinfo * wminfo) | ||||
|   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); | ||||
|   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); | ||||
|   this->aSelData      = XInternAtom(this->display, "SEL_DATA" , False); | ||||
|   this->aIncr         = XInternAtom(this->display, "INCR"     , False); | ||||
|   this->aCurSelection = BadValue; | ||||
|   this->releaseFn     = releaseFn; | ||||
|   this->notifyFn      = notifyFn; | ||||
|   this->dataFn        = dataFn; | ||||
|  | ||||
|   for(int i = 0; i < LG_CLIPBOARD_DATA_MAX; ++i) | ||||
|   for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) | ||||
|   { | ||||
|     this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False); | ||||
|     if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue) | ||||
| @@ -79,6 +101,18 @@ static bool x11_cb_init(SDL_SysWMinfo * wminfo) | ||||
|   // we need the raw X events | ||||
|   SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); | ||||
|  | ||||
|   // use xfixes to get clipboard change notifications | ||||
|   if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase)) | ||||
|   { | ||||
|     DEBUG_ERROR("failed to initialize xfixes"); | ||||
|     free(this); | ||||
|     this = NULL; | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   XFixesSelectSelectionInput(this->display,DefaultRootWindow(this->display), XA_PRIMARY      , XFixesSetSelectionOwnerNotifyMask); | ||||
|   XFixesSelectSelectionInput(this->display,DefaultRootWindow(this->display), this->aSelection, XFixesSetSelectionOwnerNotifyMask); | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| @@ -154,7 +188,7 @@ static void x11_cb_wmevent(SDL_SysWMmsg * msg) | ||||
|     } | ||||
|  | ||||
|     // look to see if we can satisfy the data type | ||||
|     for(int i = 0; i < LG_CLIPBOARD_DATA_MAX; ++i) | ||||
|     for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) | ||||
|       if (this->aTypes[i] == e.xselectionrequest.target && this->type == i) | ||||
|       { | ||||
|         // request the data | ||||
| @@ -172,15 +206,113 @@ static void x11_cb_wmevent(SDL_SysWMmsg * msg) | ||||
|     XFlush(this->display); | ||||
|   } | ||||
|  | ||||
|   if (e.type == SelectionNotify) | ||||
|   if (e.type == SelectionClear && ( | ||||
|         e.xselectionclear.selection == XA_PRIMARY || | ||||
|         e.xselectionclear.selection == this->aSelection) | ||||
|      ) | ||||
|   { | ||||
|     DEBUG_WARN("FIXME: SelectionNotify"); | ||||
|     this->aCurSelection = BadValue; | ||||
|     this->releaseFn(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (e.type == SelectionClear) | ||||
|   // if someone selected data | ||||
|   if (e.type == this->eventBase + XFixesSelectionNotify && ( | ||||
|         ((XFixesSelectionNotifyEvent *)&e)->selection == XA_PRIMARY || | ||||
|         ((XFixesSelectionNotifyEvent *)&e)->selection == this->aSelection) | ||||
|      ) | ||||
|   { | ||||
|     DEBUG_WARN("FIXME: SelectionClear"); | ||||
|     // ask for the data type | ||||
|     XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e; | ||||
|  | ||||
|     // remember which selection we are working with | ||||
|     this->aCurSelection = sne->selection; | ||||
|  | ||||
|     XConvertSelection( | ||||
|         this->display, | ||||
|         sne->selection, | ||||
|         this->aTargets, | ||||
|         this->aTargets, | ||||
|         this->window, | ||||
|         CurrentTime); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (e.type == SelectionNotify) | ||||
|   { | ||||
|     if (e.xselection.property == None) | ||||
|       return; | ||||
|  | ||||
|     Atom type; | ||||
|     int format; | ||||
|     unsigned long itemCount, after; | ||||
|     unsigned char *data; | ||||
|  | ||||
|     XGetWindowProperty( | ||||
|         this->display, | ||||
|         this->window, | ||||
|         e.xselection.property, | ||||
|         0, ~0L, // start and length | ||||
|         True  , // delete the property | ||||
|         AnyPropertyType, | ||||
|         &type, | ||||
|         &format, | ||||
|         &itemCount, | ||||
|         &after, | ||||
|         &data); | ||||
|  | ||||
|     // the target list | ||||
|     if (e.xselection.property == this->aTargets) | ||||
|     { | ||||
|       // the format is 32-bit and we must have data | ||||
|       // this is technically incorrect however as it's | ||||
|       // an array of padded 64-bit values | ||||
|       if (!data || format != 32) | ||||
|       { | ||||
|         this->notifyFn(LG_CLIPBOARD_DATA_NONE); | ||||
|         if (data) | ||||
|           XFree(data); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       // see if we support any of the targets listed | ||||
|       const uint64_t * targets = (const uint64_t *)data; | ||||
|       for(unsigned long i = 0; i < itemCount; ++i) | ||||
|       { | ||||
|         for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n) | ||||
|           if (this->aTypes[n] == targets[i]) | ||||
|           { | ||||
|             // we have a match, so send the notification | ||||
|             this->notifyFn(n); | ||||
|             XFree(data); | ||||
|             return; | ||||
|           } | ||||
|       } | ||||
|  | ||||
|       // no matches | ||||
|       this->notifyFn(LG_CLIPBOARD_DATA_NONE); | ||||
|       XFree(data); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (format == this->aIncr) | ||||
|     { | ||||
|       DEBUG_WARN("fixme: large paste buffers are not yet supported"); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) | ||||
|         if (this->aTypes[i] == type) | ||||
|         { | ||||
|           this->dataFn(i, data, itemCount); | ||||
|           XFree(data); | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|       DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type)); | ||||
|     } | ||||
|  | ||||
|     XFree(data); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
| @@ -194,11 +326,35 @@ static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type | ||||
|   XFlush(this->display); | ||||
| } | ||||
|  | ||||
| static void x11_cb_release() | ||||
| { | ||||
|   this->requestFn = NULL; | ||||
|   XSetSelectionOwner(this->display, XA_PRIMARY      , None, CurrentTime); | ||||
|   XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime); | ||||
|   XFlush(this->display); | ||||
| } | ||||
|  | ||||
| static void x11_cb_request(LG_ClipboardData type) | ||||
| { | ||||
|   if (this->aCurSelection == BadValue) | ||||
|     return; | ||||
|  | ||||
|   XConvertSelection( | ||||
|       this->display, | ||||
|       this->aCurSelection, | ||||
|       this->aTypes[type], | ||||
|       this->aSelData, | ||||
|       this->window, | ||||
|       CurrentTime); | ||||
| } | ||||
|  | ||||
| const LG_Clipboard LGC_X11 = | ||||
| { | ||||
|   .getName = x11_cb_getName, | ||||
|   .init    = x11_cb_init, | ||||
|   .free    = x11_cb_free, | ||||
|   .wmevent = x11_cb_wmevent, | ||||
|   .notice  = x11_cb_notice | ||||
|   .notice  = x11_cb_notice, | ||||
|   .release = x11_cb_release, | ||||
|   .request = x11_cb_request | ||||
| }; | ||||
| @@ -31,18 +31,23 @@ typedef enum LG_ClipboardData | ||||
|   LG_CLIPBOARD_DATA_TIFF, | ||||
|   LG_CLIPBOARD_DATA_JPEG, | ||||
|  | ||||
|   LG_CLIPBOARD_DATA_MAX // enum max, not a data type | ||||
|   LG_CLIPBOARD_DATA_NONE // 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_ClipboardReplyFn  )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size); | ||||
| typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque); | ||||
| typedef void (* LG_ClipboardReleaseFn)(); | ||||
| typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type); | ||||
| typedef void (* LG_ClipboardDataFn  )(const LG_ClipboardData type, uint8_t * data, size_t size); | ||||
|  | ||||
| typedef const char * (* LG_ClipboardGetName)(); | ||||
| typedef bool         (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo); | ||||
| typedef bool         (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn); | ||||
| typedef void         (* LG_ClipboardFree)(); | ||||
| typedef void         (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg); | ||||
| typedef void         (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type); | ||||
| typedef void         (* LG_ClipboardRelease)(); | ||||
| typedef void         (* LG_ClipboardRequest)(LG_ClipboardData type); | ||||
|  | ||||
| typedef struct LG_Clipboard | ||||
| { | ||||
| @@ -51,5 +56,7 @@ typedef struct LG_Clipboard | ||||
|   LG_ClipboardFree    free; | ||||
|   LG_ClipboardWMEvent wmevent; | ||||
|   LG_ClipboardNotice  notice; | ||||
|   LG_ClipboardRelease release; | ||||
|   LG_ClipboardRequest request; | ||||
| } | ||||
| LG_Clipboard; | ||||
| @@ -492,7 +492,60 @@ static inline const uint32_t mapScancode(SDL_Scancode scancode) | ||||
|   return ps2; | ||||
| } | ||||
|  | ||||
| void clipboardRequestFn(const LG_ClipboardReplyFn replyFn, void * opaque) | ||||
| static LG_ClipboardData spice_type_to_clipboard_type(const SpiceDataType type) | ||||
| { | ||||
|   switch(type) | ||||
|   { | ||||
|     case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break; | ||||
|     case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break; | ||||
|     case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break; | ||||
|     case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break; | ||||
|     case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break; | ||||
|     default: | ||||
|       DEBUG_ERROR("invalid spice data type"); | ||||
|       return LG_CLIPBOARD_DATA_NONE; | ||||
|   } | ||||
| } | ||||
|  | ||||
| static SpiceDataType clipboard_type_to_spice_type(const LG_ClipboardData type) | ||||
| { | ||||
|   switch(type) | ||||
|   { | ||||
|     case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break; | ||||
|     case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break; | ||||
|     case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break; | ||||
|     case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break; | ||||
|     case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break; | ||||
|     default: | ||||
|       DEBUG_ERROR("invalid clipboard data type"); | ||||
|       return SPICE_DATA_NONE; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void clipboardRelease() | ||||
| { | ||||
|   spice_clipboard_release(); | ||||
|  | ||||
|   // another application just took the clipboard ownership | ||||
| } | ||||
|  | ||||
| void clipboardNotify(const LG_ClipboardData type) | ||||
| { | ||||
|   if (type == LG_CLIPBOARD_DATA_NONE) | ||||
|   { | ||||
|     spice_clipboard_release(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   spice_clipboard_grab(clipboard_type_to_spice_type(type)); | ||||
| } | ||||
|  | ||||
| void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size) | ||||
| { | ||||
|   spice_clipboard_data(clipboard_type_to_spice_type(type), data, (uint32_t)size); | ||||
| } | ||||
|  | ||||
| void clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque) | ||||
| { | ||||
|   state.cbReplyData = opaque; | ||||
|   state.cbReplyFn   = replyFn; | ||||
| @@ -506,18 +559,7 @@ void spiceClipboardNotice(const SpiceDataType type) | ||||
|     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); | ||||
|   state.lgc->notice(clipboardRequest, spice_type_to_clipboard_type(type)); | ||||
| } | ||||
|  | ||||
| void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size) | ||||
| @@ -538,6 +580,18 @@ void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t siz | ||||
|   state.cbReplyFn(state.cbReplyData, type, buffer, size); | ||||
| } | ||||
|  | ||||
| void spiceClipboardRelease() | ||||
| { | ||||
|   if (state.lgc && state.lgc->release) | ||||
|     state.lgc->release(); | ||||
| } | ||||
|  | ||||
| void spiceClipboardRequest(const SpiceDataType type) | ||||
| { | ||||
|   if (state.lgc && state.lgc->request) | ||||
|     state.lgc->request(spice_type_to_clipboard_type(type)); | ||||
| } | ||||
|  | ||||
| int eventFilter(void * userdata, SDL_Event * event) | ||||
| { | ||||
|   static bool serverMode   = false; | ||||
| @@ -982,7 +1036,7 @@ int run() | ||||
|   if (state.lgc) | ||||
|   { | ||||
|     DEBUG_INFO("Using Clipboard: %s", state.lgc->getName()); | ||||
|     if (!state.lgc->init(&wminfo)) | ||||
|     if (!state.lgc->init(&wminfo, clipboardRelease, clipboardNotify, clipboardData)) | ||||
|     { | ||||
|       DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway"); | ||||
|       state.lgc = NULL; | ||||
| @@ -1022,7 +1076,11 @@ int run() | ||||
|  | ||||
|     if (params.useSpice) | ||||
|     { | ||||
|       spice_set_on_clipboard_cb(spiceClipboardNotice, spiceClipboardData); | ||||
|       spice_set_clipboard_cb( | ||||
|           spiceClipboardNotice, | ||||
|           spiceClipboardData, | ||||
|           spiceClipboardRelease, | ||||
|           spiceClipboardRequest); | ||||
|       if (!spice_connect(params.spiceHost, params.spicePort, "")) | ||||
|       { | ||||
|         DEBUG_ERROR("Failed to connect to spice server"); | ||||
|   | ||||
| @@ -116,13 +116,17 @@ struct Spice | ||||
|   bool cbSupported; | ||||
|   bool cbSelection; | ||||
|  | ||||
|   bool                 cbGrabbed; | ||||
|   SpiceDataType        cbType; | ||||
|   uint8_t *            cbBuffer; | ||||
|   uint32_t             cbRemain; | ||||
|   uint32_t             cbSize; | ||||
|   SpiceClipboardNotice cbNoticeFn; | ||||
|   SpiceClipboardData   cbDataFn; | ||||
|   // clipboard variables | ||||
|   bool                  cbAgentGrabbed; | ||||
|   bool                  cbClientGrabbed; | ||||
|   SpiceDataType         cbType; | ||||
|   uint8_t *             cbBuffer; | ||||
|   uint32_t              cbRemain; | ||||
|   uint32_t              cbSize; | ||||
|   SpiceClipboardNotice  cbNoticeFn; | ||||
|   SpiceClipboardData    cbDataFn; | ||||
|   SpiceClipboardRelease cbReleaseFn; | ||||
|   SpiceClipboardRequest cbRequestFn; | ||||
| }; | ||||
|  | ||||
| // globals | ||||
| @@ -150,6 +154,10 @@ bool spice_agent_connect  (); | ||||
| bool spice_agent_send_caps(bool request); | ||||
| void spice_agent_on_clipboard(); | ||||
|  | ||||
| // utility functions | ||||
| static uint32_t spice_type_to_agent_type(SpiceDataType type); | ||||
| static SpiceDataType agent_type_to_spice_type(uint32_t type); | ||||
|  | ||||
| // thread safe read/write methods | ||||
| bool spice_write_msg       (struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size); | ||||
| bool spice_agent_write_msg (uint32_t type, const void * buffer, const ssize_t size); | ||||
| @@ -207,6 +215,9 @@ void spice_disconnect() | ||||
|   spice.cbBuffer = NULL; | ||||
|   spice.cbRemain = 0; | ||||
|   spice.cbSize   = 0; | ||||
|  | ||||
|   spice.cbAgentGrabbed  = false; | ||||
|   spice.cbClientGrabbed = false; | ||||
| } | ||||
|  | ||||
| // ============================================================================ | ||||
| @@ -1008,7 +1019,9 @@ bool spice_agent_process(uint32_t dataSize) | ||||
|       if (msg.type == VD_AGENT_CLIPBOARD_RELEASE) | ||||
|       { | ||||
|         DEBUG_PROTO("VD_AGENT_CLIPBOARD_RELEASE"); | ||||
|         spice.cbGrabbed = false; | ||||
|         spice.cbAgentGrabbed = false; | ||||
|         if (spice.cbReleaseFn) | ||||
|           spice.cbReleaseFn(); | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
| @@ -1058,6 +1071,8 @@ bool spice_agent_process(uint32_t dataSize) | ||||
|         else | ||||
|         { | ||||
|           DEBUG_PROTO("VD_AGENT_CLIPBOARD_REQUEST"); | ||||
|           if (spice.cbRequestFn) | ||||
|             spice.cbRequestFn(agent_type_to_spice_type(type)); | ||||
|           return true; | ||||
|         } | ||||
|       } | ||||
| @@ -1077,19 +1092,8 @@ bool spice_agent_process(uint32_t dataSize) | ||||
|         // there is zero documentation on the types field, it might be a bitfield | ||||
|         // but for now we are going to assume it's not. | ||||
|  | ||||
|         switch(types[0]) | ||||
|         { | ||||
|           case VD_AGENT_CLIPBOARD_UTF8_TEXT : spice.cbType = SPICE_DATA_TEXT; 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_TIFF: spice.cbType = SPICE_DATA_TIFF; 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; | ||||
|         } | ||||
|  | ||||
|         spice.cbGrabbed = true; | ||||
|         spice.cbType         = agent_type_to_spice_type(types[0]); | ||||
|         spice.cbAgentGrabbed = true; | ||||
|         if (spice.cbSelection) | ||||
|         { | ||||
|           // Windows doesnt support this, so until it's needed there is no point messing with it | ||||
| @@ -1166,11 +1170,14 @@ bool spice_agent_write_msg(uint32_t type, const void * buffer, const ssize_t siz | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (spice_write_nl(&spice.scMain, buffer, size) != size) | ||||
|   if (buffer && size) | ||||
|   { | ||||
|     LG_UNLOCK(spice.scMain.lock); | ||||
|     DEBUG_ERROR("failed to write agent data payload"); | ||||
|     return false; | ||||
|     if (spice_write_nl(&spice.scMain, buffer, size) != size) | ||||
|     { | ||||
|       LG_UNLOCK(spice.scMain.lock); | ||||
|       DEBUG_ERROR("failed to write agent data payload"); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   LG_UNLOCK(spice.scMain.lock); | ||||
| @@ -1455,11 +1462,43 @@ bool spice_mouse_release(uint32_t button) | ||||
|  | ||||
| // ============================================================================ | ||||
|  | ||||
| static uint32_t spice_type_to_agent_type(SpiceDataType type) | ||||
| { | ||||
|   switch(type) | ||||
|   { | ||||
|     case SPICE_DATA_TEXT: return VD_AGENT_CLIPBOARD_UTF8_TEXT ; break; | ||||
|     case SPICE_DATA_PNG : return VD_AGENT_CLIPBOARD_IMAGE_PNG ; break; | ||||
|     case SPICE_DATA_BMP : return VD_AGENT_CLIPBOARD_IMAGE_BMP ; break; | ||||
|     case SPICE_DATA_TIFF: return VD_AGENT_CLIPBOARD_IMAGE_TIFF; break; | ||||
|     case SPICE_DATA_JPEG: return VD_AGENT_CLIPBOARD_IMAGE_JPG ; break; | ||||
|     default: | ||||
|       DEBUG_ERROR("unsupported spice data type specified"); | ||||
|       return VD_AGENT_CLIPBOARD_NONE; | ||||
|   } | ||||
| } | ||||
|  | ||||
| static SpiceDataType agent_type_to_spice_type(uint32_t type) | ||||
| { | ||||
|   switch(type) | ||||
|   { | ||||
|     case VD_AGENT_CLIPBOARD_UTF8_TEXT : return SPICE_DATA_TEXT; break; | ||||
|     case VD_AGENT_CLIPBOARD_IMAGE_PNG : return SPICE_DATA_PNG ; break; | ||||
|     case VD_AGENT_CLIPBOARD_IMAGE_BMP : return SPICE_DATA_BMP ; break; | ||||
|     case VD_AGENT_CLIPBOARD_IMAGE_TIFF: return SPICE_DATA_TIFF; break; | ||||
|     case VD_AGENT_CLIPBOARD_IMAGE_JPG : return SPICE_DATA_JPEG; break; | ||||
|     default: | ||||
|       DEBUG_ERROR("unsupported agent data type specified"); | ||||
|       return SPICE_DATA_NONE; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // ============================================================================ | ||||
|  | ||||
| bool spice_clipboard_request(SpiceDataType type) | ||||
| { | ||||
|   VDAgentClipboardRequest req; | ||||
|  | ||||
|   if (!spice.cbGrabbed) | ||||
|   if (!spice.cbAgentGrabbed) | ||||
|   { | ||||
|     DEBUG_ERROR("the agent has not grabbed any data yet"); | ||||
|     return false; | ||||
| @@ -1471,18 +1510,7 @@ bool spice_clipboard_request(SpiceDataType type) | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   switch(type) | ||||
|   { | ||||
|     case SPICE_DATA_TEXT: req.type = VD_AGENT_CLIPBOARD_UTF8_TEXT ; 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_TIFF: req.type = VD_AGENT_CLIPBOARD_IMAGE_TIFF; break; | ||||
|     case SPICE_DATA_JPEG: req.type = VD_AGENT_CLIPBOARD_IMAGE_JPG ; break; | ||||
|     default: | ||||
|       DEBUG_ERROR("invalid clipboard data type requested"); | ||||
|       return false; | ||||
|   } | ||||
|  | ||||
|   req.type = spice_type_to_agent_type(type); | ||||
|   if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_REQUEST, &req, sizeof(req))) | ||||
|   { | ||||
|     DEBUG_ERROR("failed to request clipboard data"); | ||||
| @@ -1494,18 +1522,124 @@ bool spice_clipboard_request(SpiceDataType type) | ||||
|  | ||||
| // ============================================================================ | ||||
|  | ||||
| bool spice_set_on_clipboard_cb(SpiceClipboardNotice cbNoticeFn, SpiceClipboardData cbDataFn) | ||||
| bool spice_set_clipboard_cb(SpiceClipboardNotice cbNoticeFn, SpiceClipboardData cbDataFn, SpiceClipboardRelease cbReleaseFn, SpiceClipboardRequest cbRequestFn) | ||||
| { | ||||
|   if ((cbNoticeFn && !cbDataFn) || (cbDataFn && !cbNoticeFn)) | ||||
|   { | ||||
|     DEBUG_ERROR("Clipboard callback notice and data callbacks must be specified"); | ||||
|     DEBUG_ERROR("clipboard callback notice and data callbacks must be specified"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   spice.cbNoticeFn = cbNoticeFn; | ||||
|   spice.cbDataFn   = cbDataFn; | ||||
|   spice.cbNoticeFn  = cbNoticeFn; | ||||
|   spice.cbDataFn    = cbDataFn; | ||||
|   spice.cbReleaseFn = cbReleaseFn; | ||||
|   spice.cbRequestFn = cbRequestFn; | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // ============================================================================ | ||||
| // ============================================================================ | ||||
|  | ||||
| bool spice_clipboard_grab(SpiceDataType type) | ||||
| { | ||||
|   // release first if we have grabbed previously | ||||
|   if (spice.cbClientGrabbed && !spice_clipboard_release()) | ||||
|     return false; | ||||
|  | ||||
|   if (type == SPICE_DATA_NONE) | ||||
|   { | ||||
|     DEBUG_ERROR("grab type is invalid"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (spice.cbSelection) | ||||
|   { | ||||
|     uint8_t req[8] = { VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD }; | ||||
|     ((uint32_t*)req)[1] = spice_type_to_agent_type(type); | ||||
|  | ||||
|     if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_GRAB, req, sizeof(req))) | ||||
|     { | ||||
|       DEBUG_ERROR("failed to grab the clipboard"); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     spice.cbClientGrabbed = true; | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   uint32_t req = spice_type_to_agent_type(type); | ||||
|   if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_GRAB, &req, sizeof(req))) | ||||
|   { | ||||
|     DEBUG_ERROR("failed to grab the clipboard"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   spice.cbClientGrabbed = true; | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // ============================================================================ | ||||
|  | ||||
| bool spice_clipboard_release() | ||||
| { | ||||
|   // check if if there is anything to release first | ||||
|   if (!spice.cbClientGrabbed) | ||||
|     return true; | ||||
|  | ||||
|   if (spice.cbSelection) | ||||
|   { | ||||
|     uint8_t req[4] = { VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD }; | ||||
|     if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_RELEASE, req, sizeof(req))) | ||||
|     { | ||||
|       DEBUG_ERROR("failed to release the clipboard"); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     spice.cbClientGrabbed = false; | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|    if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD_RELEASE, NULL, 0)) | ||||
|    { | ||||
|      DEBUG_ERROR("failed to release the clipboard"); | ||||
|      return false; | ||||
|    } | ||||
|  | ||||
|    spice.cbClientGrabbed = false; | ||||
|    return true; | ||||
| } | ||||
|  | ||||
| // ============================================================================ | ||||
|  | ||||
| bool spice_clipboard_data(SpiceDataType type, uint8_t * data, size_t size) | ||||
| { | ||||
|   uint8_t * buffer; | ||||
|   uint8_t bufSize; | ||||
|  | ||||
|   if (spice.cbSelection) | ||||
|   { | ||||
|     bufSize                = 8 + size; | ||||
|     buffer                 = (uint8_t *)malloc(bufSize); | ||||
|     buffer[0]              = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD; | ||||
|     buffer[1]              = buffer[2] = buffer[3] = 0; | ||||
|     ((uint32_t*)buffer)[1] = spice_type_to_agent_type(type); | ||||
|     memcpy(buffer + 8, data, size); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     bufSize                = 4 + size; | ||||
|     buffer                 = (uint8_t *)malloc(bufSize); | ||||
|     ((uint32_t*)buffer)[0] = spice_type_to_agent_type(type); | ||||
|     memcpy(buffer + 4, data, size); | ||||
|   } | ||||
|  | ||||
|   if (!spice_agent_write_msg(VD_AGENT_CLIPBOARD, buffer, bufSize)) | ||||
|   { | ||||
|     DEBUG_ERROR("failed to write the clipboard data"); | ||||
|     free(buffer); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   free(buffer); | ||||
|   return true; | ||||
| } | ||||
| @@ -27,12 +27,16 @@ typedef enum SpiceDataType | ||||
|   SPICE_DATA_PNG, | ||||
|   SPICE_DATA_BMP, | ||||
|   SPICE_DATA_TIFF, | ||||
|   SPICE_DATA_JPEG | ||||
|   SPICE_DATA_JPEG, | ||||
|  | ||||
|   SPICE_DATA_NONE | ||||
| } | ||||
| SpiceDataType; | ||||
|  | ||||
| typedef void (*SpiceClipboardNotice)(const SpiceDataType type); | ||||
| typedef void (*SpiceClipboardData  )(const SpiceDataType type, uint8_t * buffer, uint32_t size); | ||||
| typedef void (*SpiceClipboardNotice )(const SpiceDataType type); | ||||
| typedef void (*SpiceClipboardData   )(const SpiceDataType type, uint8_t * buffer, uint32_t size); | ||||
| typedef void (*SpiceClipboardRelease)(); | ||||
| typedef void (*SpiceClipboardRequest)(const SpiceDataType type); | ||||
|  | ||||
| bool spice_connect(const char * host, const unsigned short port, const char * password); | ||||
| void spice_disconnect(); | ||||
| @@ -48,6 +52,13 @@ bool spice_mouse_press   (uint32_t button); | ||||
| bool spice_mouse_release (uint32_t button); | ||||
|  | ||||
| bool spice_clipboard_request(SpiceDataType type); | ||||
| bool spice_clipboard_grab   (SpiceDataType type); | ||||
| bool spice_clipboard_release(); | ||||
| bool spice_clipboard_data   (SpiceDataType type, uint8_t * data, size_t size); | ||||
|  | ||||
| /* events */ | ||||
| bool spice_set_on_clipboard_cb(SpiceClipboardNotice cbNoticeFn, SpiceClipboardData cbDataFn); | ||||
| bool spice_set_clipboard_cb( | ||||
|     SpiceClipboardNotice  cbNoticeFn, | ||||
|     SpiceClipboardData    cbDataFn, | ||||
|     SpiceClipboardRelease cbReleaseFn, | ||||
|     SpiceClipboardRequest cbRequestFn); | ||||
		Reference in New Issue
	
	Block a user
	 Geoffrey McRae
					Geoffrey McRae