This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository x2gokdrive. commit 3d6295b9f0163e87df733a2490bbbe7d81b66f6d Author: Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> Date: Thu Aug 20 15:45:02 2020 -0500 Move selection functionality to separate thread. Use XCB API to manage selections. --- debian/changelog | 2 + x2gokdriveinit.c | 2 + x2gokdriveremote.c | 362 ++++++++------ x2gokdriveremote.h | 75 +-- x2gokdriveselection.c | 1252 +++++++++++++++++++++++++++++-------------------- x2gokdriveselection.h | 18 +- 6 files changed, 1052 insertions(+), 659 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3ca47a9..152b81b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -9,6 +9,8 @@ x2gokdrive (0.0.0.1-0x2go1) UNRELEASED; urgency=medium * Calculate screen dimensions (in mm) according to dpi value. Default DPI (Xorg) is 75. User can set DPI using -dpi command line option. + - Move selection functionality to separate thread. Use XCB API to manage + selections. [ Mihai Moldovan ] * Initial release: diff --git a/x2gokdriveinit.c b/x2gokdriveinit.c index bfad59f..f37e90b 100644 --- a/x2gokdriveinit.c +++ b/x2gokdriveinit.c @@ -256,6 +256,8 @@ ddxProcessArgument(int argc, char **argv, int i) /* compat with nxagent */ return 1; } + else if (argv[i][0] == ':') + remote_set_display_name(argv[i]); return KdProcessArgument(argc, argv, i); } diff --git a/x2gokdriveremote.c b/x2gokdriveremote.c index ac524f0..7732deb 100644 --- a/x2gokdriveremote.c +++ b/x2gokdriveremote.c @@ -528,17 +528,44 @@ int send_deleted_cursors(void) return sent; } -int send_selection(int sel, char* data, uint32_t length, uint32_t format) +int send_output_selection(outputChunk* chunk) +{ + //client supports extended selections + if(remoteVars.selstruct.clientSupportsExetndedSelection) + { + //send extended selection + return send_selection_chunk(chunk->selection, chunk->data, chunk->size, chunk->mimeData, chunk->firstChunk, chunk->lastChunk, chunk->compressed, chunk->totalSize); + } + else + { + //older client doesn't support only uncompressed datas in single chunk + //not sending chunk in other case + if(!chunk->compressed && chunk->firstChunk && chunk->lastChunk) + { + return send_selection_chunk(chunk->selection, chunk->data, chunk->size, chunk->mimeData, TRUE, TRUE, FALSE, chunk->size); + } + EPHYR_DBG("Client doesn't support extended selection, not sending this chunk"); + } + return 0; +} + +int send_selection_chunk(int sel, unsigned char* data, uint32_t length, uint32_t format, BOOL first, BOOL last, BOOL compressed, uint32_t total) { unsigned char buffer[56] = {0}; _X_UNUSED int ln = 0; int l = 0; int sent = 0; - *((uint32_t*)buffer)=SELECTION; - *((uint32_t*)buffer+1)=sel; - *((uint32_t*)buffer+2)=format; - *((uint32_t*)buffer+3)=length; + + *((uint32_t*)buffer)=SELECTION; //0 + *((uint32_t*)buffer+1)=sel; //4 + *((uint32_t*)buffer+2)=format; //8 + *((uint32_t*)buffer+3)=length; //16 + *((uint32_t*)buffer+4)=first; //20 + *((uint32_t*)buffer+5)=last; //24 + *((uint32_t*)buffer+6)=compressed; //28 + *((uint32_t*)buffer+7)=total; //28 + // #warning check this ln=write(remoteVars.clientsock,buffer,56); @@ -1470,7 +1497,7 @@ void *send_frame_thread (void *threadid) remoteVars.client_connected=TRUE; - if(!remoteVars.first_sendqueue_element && !remoteVars.firstCursor) + if(!remoteVars.first_sendqueue_element && !remoteVars.firstCursor && !remoteVars.selstruct.firstOutputChunk) { /* sleep if frame queue is empty */ pthread_cond_wait(&remoteVars.have_sendqueue_cond, &remoteVars.sendqueue_mutex); @@ -1478,6 +1505,30 @@ void *send_frame_thread (void *threadid) /* mutex is locked on this point */ + //only send output selection chunks if there are no frames and cursors in the queue + //selections can take a lot of bandwidth and have less priority + if(remoteVars.selstruct.firstOutputChunk && !remoteVars.first_sendqueue_element && !remoteVars.firstCursor) + { + //get chunk from queue + outputChunk* chunk=remoteVars.selstruct.firstOutputChunk; + remoteVars.selstruct.firstOutputChunk=(outputChunk*)chunk->next; + if(!remoteVars.selstruct.firstOutputChunk) + { + remoteVars.selstruct.lastOutputChunk=NULL; + } + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); + //send chunk + send_output_selection(chunk); + //free chunk and it's data + if(chunk->data) + { + free(chunk->data); + } +// EPHYR_DBG(" REMOVE CHUNK %p %p %p", remoteVars.selstruct.firstOutputChunk, remoteVars.selstruct.lastOutputChunk, chunk); + free(chunk); + pthread_mutex_lock(&remoteVars.sendqueue_mutex); + } + if(remoteVars.firstCursor) { /* get cursor from queue, delete it from queue, unlock mutex and send cursor. After sending free cursor */ @@ -1499,40 +1550,6 @@ void *send_frame_thread (void *threadid) pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } - pthread_mutex_lock(&remoteVars.sendqueue_mutex); - if(remoteVars.selstruct.clipboard.changed) - { - size_t sz=remoteVars.selstruct.clipboard.size; - char* data=malloc(sz); - int format = 0; - - memcpy(data, remoteVars.selstruct.clipboard.data, sz); - remoteVars.selstruct.clipboard.changed=FALSE; - format=remoteVars.selstruct.clipboard.mimeData; - pthread_mutex_unlock(&remoteVars.sendqueue_mutex); - send_selection(CLIPBOARD,data,sz, format); - free(data); - } - else - pthread_mutex_unlock(&remoteVars.sendqueue_mutex); - - pthread_mutex_lock(&remoteVars.sendqueue_mutex); - if(remoteVars.selstruct.selection.changed) - { - size_t sz=remoteVars.selstruct.selection.size; - char* data=malloc(sz); - int format = 0; - - memcpy(data, remoteVars.selstruct.selection.data, sz); - remoteVars.selstruct.selection.changed=FALSE; - format=remoteVars.selstruct.selection.mimeData; - pthread_mutex_unlock(&remoteVars.sendqueue_mutex); - send_selection(PRIMARY, data, sz, format); - free(data); - } - else - pthread_mutex_unlock(&remoteVars.sendqueue_mutex); - pthread_mutex_lock(&remoteVars.sendqueue_mutex); if(remoteVars.first_sendqueue_element) { @@ -1637,6 +1654,25 @@ void *send_frame_thread (void *threadid) pthread_exit(0); } +/* warning! sendqueue_mutex should be locked by thread calling this function! */ +void clear_output_selection(void) +{ + outputChunk* chunk=remoteVars.selstruct.firstOutputChunk; + outputChunk* prev_chunk; + + while(chunk) + { + prev_chunk=chunk; + chunk=(outputChunk*)chunk->next; + if(prev_chunk->data) + { + free(prev_chunk->data); + } + free(prev_chunk); + } + remoteVars.selstruct.firstOutputChunk=remoteVars.selstruct.lastOutputChunk=NULL; +} + /* warning! sendqueue_mutex should be locked by thread calling this function! */ static void clear_send_queue(void) @@ -1797,10 +1833,132 @@ void disconnect_client(void) clear_send_queue(); clear_frame_cache(0); freeCursors(); + clear_output_selection(); pthread_cond_signal(&remoteVars.have_sendqueue_cond); pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } + +void readInputSelectionBuffer(char* buff) +{ + //read th rest of the chunk data + + inputBuffer* selbuff = &remoteVars.selstruct.inSelection[remoteVars.selstruct.readingInputBuffer]; + + int leftToRead=selbuff->currentChunkSize - selbuff->currentChunkBytesReady; + + + int l=(leftToRead < EVLENGTH)?leftToRead:EVLENGTH; + + //lock input selection + pthread_mutex_lock(&remoteVars.selstruct.inMutex); + + //copy data to selection + memcpy(selbuff->data+selbuff->bytesReady, buff, l); + selbuff->bytesReady+=l; + selbuff->currentChunkBytesReady+=l; + + if(selbuff->bytesReady==selbuff->size) + { + //selection buffer received completely +// EPHYR_DBG("READY Selection %d, MIME %d, Read %d from %d", remoteVars.selstruct.readingInputBuffer, selbuff->mimeData, selbuff->bytesReady, selbuff->size); + //send notify to system that we are using selection + own_selection(remoteVars.selstruct.readingInputBuffer); + + } + if(selbuff->currentChunkBytesReady==selbuff->currentChunkSize) + { + //selection chunk received completely, next event will start with event header +// EPHYR_DBG("READY Selection Chunk, read %d",selbuff->currentChunkSize); + remoteVars.selstruct.readingInputBuffer=-1; + } + //unlock selection + pthread_mutex_unlock(&remoteVars.selstruct.inMutex); +} + +void readInputSelectionHeader(char* buff) +{ + + //read the input selection event. + //The event represents one chunk of imput selection + //it has a header and some data of the chunk + + uint32_t size, totalSize; + uint8_t destination, mime; + inputBuffer* selbuff = NULL; + BOOL firstChunk=FALSE, lastChunk=FALSE; + BOOL compressed=FALSE; + uint32_t headerSize=10; + uint32_t l; + + size=*((uint32_t*)buff+1); + destination=*((uint8_t*)buff+8); + mime=*((uint8_t*)buff+9); + + //if client supports ext selection, read extended header + if(remoteVars.selstruct.clientSupportsExetndedSelection) + { + headerSize=17; + firstChunk=*((uint8_t*)buff + 10); + lastChunk=*((uint8_t*)buff + 11); + compressed=*((uint8_t*)buff + 12); + totalSize=*( (uint32_t*) (buff+13)); + } + else + { + compressed=FALSE; + lastChunk=firstChunk=TRUE; + totalSize=size; + } + +// EPHYR_DBG("HAVE NEW INCOMING SELECTION Chunk: sel %d size %d mime %d",destination, size, mime); + + //lock selection + pthread_mutex_lock(&remoteVars.selstruct.inMutex); + + remoteVars.selstruct.readingInputBuffer=-1; + + selbuff = &remoteVars.selstruct.inSelection[destination]; + if(firstChunk) + { + //if it's first chunk, initialize our selection buffer + if(selbuff->size && selbuff->data) + { + free(selbuff->data); + } + selbuff->size=totalSize; + selbuff->mimeData=mime; + selbuff->data=malloc(totalSize); + selbuff->bytesReady=0; + } + + //read the selection data from header + l=(size < EVLENGTH-headerSize)?size:(EVLENGTH-headerSize); + memcpy(selbuff->data+selbuff->bytesReady, buff+headerSize, l); + + selbuff->bytesReady+=l; + + selbuff->currentChunkBytesReady=l; + selbuff->currentChunkSize=size; + + if(selbuff->size == selbuff->bytesReady) + { + //Selection is completed +// EPHYR_DBG("READY INCOMING SELECTION for %d",destination); + //own the selection + own_selection(destination); + } + + if(selbuff->currentChunkBytesReady != selbuff->currentChunkSize) + { + // we didn't recieve complete chunk yet, next event will have data + remoteVars.selstruct.readingInputBuffer=destination; +// EPHYR_DBG("READ INCOMING BUFFER %d, read %d from %d", destination, selbuff->bytesReady, selbuff->size); + } + //unlock selection + pthread_mutex_unlock(&remoteVars.selstruct.inMutex); +} + void clientReadNotify(int fd, int ready, void *data) { @@ -1839,38 +1997,9 @@ clientReadNotify(int fd, int ready, void *data) { char* buff=remoteVars.eventBuffer+i*EVLENGTH; - if(remoteVars.selstruct.readingInputBuffer) + if(remoteVars.selstruct.readingInputBuffer != -1) { - int leftToRead=remoteVars.selstruct.inBuffer.size - remoteVars.selstruct.inBuffer.position; - int chunk=(leftToRead < EVLENGTH)?leftToRead:EVLENGTH; - - memcpy(remoteVars.selstruct.inBuffer.data+remoteVars.selstruct.inBuffer.position, buff, chunk); - remoteVars.selstruct.inBuffer.position+=chunk; - if(! (remoteVars.selstruct.inBuffer.position < remoteVars.selstruct.inBuffer.size)) - { - inputBuffer* selbuff; - if(remoteVars.selstruct.inBuffer.target==PRIMARY) - { - selbuff=&remoteVars.selstruct.inSelection; - } - else - { - selbuff=&remoteVars.selstruct.inClipboard; - } - if(selbuff->data) - free(selbuff->data); - selbuff->target=remoteVars.selstruct.inBuffer.target; - selbuff->data=remoteVars.selstruct.inBuffer.data; - remoteVars.selstruct.inBuffer.data=NULL; - selbuff->size=remoteVars.selstruct.inBuffer.size; - remoteVars.selstruct.inBuffer.size=0; - selbuff->mimeData=remoteVars.selstruct.inBuffer.mimeData; - remoteVars.selstruct.readingInputBuffer=FALSE; - EPHYR_DBG("READY TARGET %d, MIME %d, Read %d from %d",remoteVars.selstruct.inBuffer.target, selbuff->mimeData, - remoteVars.selstruct.inBuffer.position, selbuff->size); - own_selection(selbuff->target); - } -// EPHYR_DBG("CHUNK IS DONE %d",remoteVars.selstruct.readingInputBuffer); + readInputSelectionBuffer(buff); } else { @@ -2034,47 +2163,7 @@ clientReadNotify(int fd, int ready, void *data) } case SELECTIONEVENT: { - uint32_t size; - uint8_t destination, mime; - inputBuffer* selbuff = NULL; - - size=*((uint32_t*)buff+1); - destination=*((uint8_t*)buff+8); - mime=*((uint8_t*)buff+9); - - EPHYR_DBG("HAVE NEW INCOMING SELECTION: %d %d %d",size, destination, mime); - - if(destination==CLIPBOARD) - selbuff=&(remoteVars.selstruct.inClipboard); - else - selbuff=&(remoteVars.selstruct.inSelection); - if(size < EVLENGTH-10) - { - if(selbuff->data) - free(selbuff->data); - selbuff->size=size; - selbuff->data=malloc(size); - selbuff->target=destination; - memcpy(selbuff->data, buff+10, size); - selbuff->mimeData=mime; - EPHYR_DBG("READY INCOMING SELECTION for %d",destination); - own_selection(selbuff->target); - - } - else - { - selbuff=&(remoteVars.selstruct.inBuffer); - if(selbuff->data) - free(selbuff->data); - selbuff->data=malloc(size); - memcpy(selbuff->data, buff+10, EVLENGTH-10); - selbuff->mimeData=mime; - selbuff->target=destination; - selbuff->position=EVLENGTH-10; - selbuff->size=size; - remoteVars.selstruct.readingInputBuffer=TRUE; - EPHYR_DBG("READ INCOMING BUFFER %d from %d",EVLENGTH-10, size); - } + readInputSelectionHeader(buff); break; } default: @@ -2201,6 +2290,15 @@ void terminateServer(int exitStatus) { pthread_cancel(remoteVars.send_thread_id); } + if(remoteVars.selstruct.selThreadId) + { + pthread_cancel(remoteVars.selstruct.selThreadId); + if(remoteVars.selstruct.xcbConnection) + { + xcb_disconnect(remoteVars.selstruct.xcbConnection); + } + pthread_mutex_destroy(&remoteVars.selstruct.inMutex); + } pthread_mutex_destroy(&remoteVars.mainimg_mutex); pthread_mutex_destroy(&remoteVars.sendqueue_mutex); @@ -2212,27 +2310,14 @@ void terminateServer(int exitStatus) free(remoteVars.second_buffer); } - if(remoteVars.selstruct.clipboard.data) - { - free(remoteVars.selstruct.clipboard.data); - } - - if(remoteVars.selstruct.selection.data) - { - free(remoteVars.selstruct.selection.data); - } - if(remoteVars.selstruct.inClipboard.data) + if(remoteVars.selstruct.inSelection[0].data) { - free(remoteVars.selstruct.inClipboard.data); + free(remoteVars.selstruct.inSelection[0].data); } - if(remoteVars.selstruct.inSelection.data) + if(remoteVars.selstruct.inSelection[1].data) { - free(remoteVars.selstruct.inSelection.data); - } - if(remoteVars.selstruct.inBuffer.data) - { - free(remoteVars.selstruct.inBuffer.data); + free(remoteVars.selstruct.inSelection[1].data); } setAgentState(TERMINATED); EPHYR_DBG("exit program with status %d", exitStatus); @@ -2372,6 +2457,7 @@ remote_init(void) remoteVars.jpegQuality=JPG_QUALITY; remoteVars.compression=DEFAULT_COMPRESSION; + remoteVars.selstruct.selectionMode = CLIP_BOTH; pthread_mutex_init(&remoteVars.mainimg_mutex, NULL); pthread_mutex_init(&remoteVars.sendqueue_mutex,NULL); @@ -3085,6 +3171,18 @@ uint32_t calculate_crc(uint32_t width, uint32_t height, int32_t dx, int32_t dy) return crc; } +void remote_set_display_name(const char* name) +{ + int max_len=256; + if(strlen(name)<max_len) + { + max_len=strlen(name); + } + strncpy(RemoteHostVars.displayName, name, max_len); + RemoteHostVars.displayName[max_len]='\0'; + EPHYR_DBG("DISPLAY name: %s",RemoteHostVars.displayName); +} + void * remote_screen_init(KdScreenInfo *screen, int x, int y, @@ -3097,10 +3195,10 @@ remote_screen_init(KdScreenInfo *screen, //but we need to reinstall it by the next screen init. EPHYR_DBG("REMOTE SCREEN INIT!!!!!!!!!!!!!!!!!!"); - if(remoteVars.selstruct.callBackInstalled) + if(remoteVars.selstruct.threadStarted) { - EPHYR_DBG("SKIPPING CALLBACK INSTALL"); - remoteVars.selstruct.callBackInstalled=FALSE; + EPHYR_DBG("SKIPPING Selection CALLBACK INSTALL"); +// remoteVars.selstruct.callBackInstalled=FALSE; } else { diff --git a/x2gokdriveremote.h b/x2gokdriveremote.h index c574035..a06e692 100644 --- a/x2gokdriveremote.h +++ b/x2gokdriveremote.h @@ -259,40 +259,55 @@ struct sendqueue_element struct sendqueue_element* next; }; +//input selection typedef struct { - unsigned char* data; - uint32_t size; - int mimeData; - uint32_t position; - int target; + unsigned char* data; //data + uint32_t size; //total size of selection + uint32_t bytesReady; //how many bytes already read + uint32_t currentChunkSize; //size of chunk we reading now; + uint32_t currentChunkBytesReady; //how many bytes of current chunk are ready; + enum SelectionMime mimeData; //UTF_STRING or PIXMAP + xcb_timestamp_t timestamp; //ts when we own selection + BOOL owner; //if we are the owners of selection }inputBuffer; + +//chunk of data with output selection typedef struct { - unsigned char* data; - uint32_t size; - int mimeData; - BOOL changed; - -}outputBuffer; + unsigned char* data; //data + uint32_t size; //size of chunk in B + enum SelectionMime mimeData; //UTF_STRING or PIXMAP (text or image) + BOOL compressed; // if chunk is compressed + BOOL firstChunk; // if it's a first chunk in selection + BOOL lastChunk; // if it's a last chunk in selection + enum SelectionType selection; //PRIMARY or CLIPBOARD + uint32_t totalSize; //the total size of the selection data + struct outputChunk* next; //next chunk in the queue +}outputChunk; typedef struct { - BOOL readingIncremental; - uint32_t incrementalPosition; - Window clipWinId; - WindowPtr clipWinPtr; - BOOL callBackInstalled; - unsigned char selectionMode; -//Output selection - outputBuffer clipboard; - outputBuffer selection; -//Input selection - BOOL readingInputBuffer; - inputBuffer inBuffer; - inputBuffer inSelection; - inputBuffer inClipboard; + unsigned long selThreadId; //id of selection thread + enum ClipboardMode selectionMode; //CLIP_NONE, CLIP_CLIENT, CLIP_SERVER, CLIP_BOTH + + //output selection members + uint32_t incrementalSize; //the total size of INCR selection we are currently reading + uint32_t incrementalSizeRead; //bytes already read + xcb_window_t clipWinId; // win id of clipboard window + xcb_connection_t* xcbConnection; //XCB connection + BOOL threadStarted; //if selection thread already started + BOOL clientSupportsExetndedSelection; //if client supports extended selection + xcb_atom_t incrAtom; //mime type of the incr selection we are reading + xcb_atom_t currentSelection; //selection we are currently reading + outputChunk* firstOutputChunk; //the first and last elements of the + outputChunk* lastOutputChunk; //queue of selection chunks + + //Input selection members + int readingInputBuffer; //which selection are reading input buffer at the moments: PRIMARY, CLIPBOARD or -1 if none + inputBuffer inSelection[2]; //PRIMARY an CLIPBOARD selection buffers + pthread_mutex_t inMutex; //mutex for synchronization of incoming selection }SelectionStructure; @@ -306,6 +321,7 @@ struct _remoteHostVars char stateFile[256]; char acceptAddr[256]; char cookie[33]; + char displayName[256]; int listenPort; int jpegQuality; uint32_t framenum; @@ -374,7 +390,11 @@ struct _remoteHostVars SelectionStructure selstruct; } RemoteHostVars; -int send_selection(int sel, char* data, uint32_t length, uint32_t mimeData); +int send_selection_chunk(int sel, unsigned char* data, uint32_t length, uint32_t format, BOOL first, BOOL last, BOOL compressed, uint32_t total); +int send_output_selection(outputChunk* chunk); + +void readInputSelectionBuffer(char* buff); +void readInputSelectionHeader(char* buff); #if XORG_VERSION_CURRENT < 11900000 void pollEvents(void); @@ -414,7 +434,7 @@ void clientReadNotify(int fd, int ready, void *data); void add_frame(uint32_t width, uint32_t height, int32_t x, int32_t y, uint32_t crc, uint32_t size); - +void clear_output_selection(void); void disconnect_client(void); @@ -432,6 +452,7 @@ int remote_init(void); void remote_selection_init(void); +void remote_set_display_name(const char* name); void *remote_screen_init(KdScreenInfo *screen, int x, int y, diff --git a/x2gokdriveselection.c b/x2gokdriveselection.c index 39129c6..e174aff 100644 --- a/x2gokdriveselection.c +++ b/x2gokdriveselection.c @@ -25,659 +25,913 @@ * */ +#include <xcb/xcb.h> +#include <xcb/xfixes.h> +#include <pthread.h> +#include <unistd.h> +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <png.h> + + #ifdef HAVE_CONFIG_H #include <dix-config.h> #if XORG_VERSION_CURRENT < 11999901 #include <kdrive-config.h> -#endif /* XORG_VERSION_CURRENT */ +#endif // XORG_VERSION_CURRENT #endif - #include "x2gokdriveselection.h" - -#include "selection.h" -#include <X11/Xatom.h> -#include "propertyst.h" -#include "xace.h" - static struct _remoteHostVars *remoteVars = NULL; -static Atom atomPrimary, atomClipboard, atomTargets, atomString, atomUTFString, atomTimestamp = {0}; -static Atom imageAtom = 0; -static Atom atomJPEG, atomJPG = {0}; - -static int (*proc_send_event_orig)(ClientPtr); -static int (*proc_convert_selection_orig)(ClientPtr); -static int (*proc_change_property_orig)(ClientPtr); +static xcb_atom_t ATOM_CLIPBOARD; -static int create_selection_window(void); - -int own_selection(int target) +uint32_t max_chunk(void) { - Selection *pSel = NULL; - SelectionInfoRec info = {0}; - Atom selection=atomPrimary; - int rc; + if(remoteVars->selstruct.clientSupportsExetndedSelection) + return 1024*100/4; //100KB + else + return 10*1024*1024/4; //10MB +} - if(remoteVars->selstruct.selectionMode == CLIP_SERVER || remoteVars->selstruct.selectionMode == CLIP_NONE) +int own_selection(enum SelectionType selection) +{ + xcb_atom_t sel=XCB_ATOM_PRIMARY; + if(remoteVars->selstruct.selectionMode == CLIP_NONE || remoteVars->selstruct.selectionMode == CLIP_SERVER) { - EPHYR_DBG("CLIENT selection disabled, not accepting selection event"); - return Success; + EPHYR_DBG("Client selection is disabled"); + return 0; } - - if(target==CLIPBOARD) - selection=atomClipboard; - - - rc = create_selection_window(); - - if (rc != Success) - return rc; - - rc = dixLookupSelection(&pSel, selection, serverClient, DixSetAttrAccess); - if (rc == Success) + if(selection!=PRIMARY) { - if (pSel->client && (pSel->client != serverClient)) - { - xEvent event = - { - .u.selectionClear.time = currentTime.milliseconds, - .u.selectionClear.window = pSel->window, - .u.selectionClear.atom = pSel->selection - }; - event.u.u.type = SelectionClear; - WriteEventsToClient(pSel->client, 1, &event); - } + sel=ATOM_CLIPBOARD; } - else if (rc == BadMatch) - { - pSel = dixAllocateObjectWithPrivates(Selection, PRIVATE_SELECTION); - if (!pSel) - return BadAlloc; + xcb_set_selection_owner(remoteVars->selstruct.xcbConnection, remoteVars->selstruct.clipWinId, sel, XCB_CURRENT_TIME); + xcb_flush(remoteVars->selstruct.xcbConnection); + remoteVars->selstruct.inSelection[selection].owner=TRUE; + remoteVars->selstruct.inSelection[selection].timestamp=XCB_CURRENT_TIME; + return 0; +} +void selection_init(struct _remoteHostVars *obj) +{ + remoteVars=obj; + return; +} - pSel->selection = selection; - rc = XaceHookSelectionAccess(serverClient, &pSel, - DixCreateAccess | DixSetAttrAccess); - if (rc != Success) - { - free(pSel); - return rc; - } +xcb_atom_t atom(const char* name) +{ + //get atom for the name, return 0 if not found + xcb_intern_atom_cookie_t cookie; + xcb_intern_atom_reply_t *reply; + xcb_atom_t a=0; - pSel->next = CurrentSelections; - CurrentSelections = pSel; + cookie = xcb_intern_atom(remoteVars->selstruct.xcbConnection, 0, strlen(name), name); + if ((reply = xcb_intern_atom_reply(remoteVars->selstruct.xcbConnection, cookie, NULL))) + { + a=reply->atom; +// EPHYR_DBG("The %s atom has ID %u", name, a); + free(reply); } - else - return rc; - - pSel->lastTimeChanged = currentTime; - pSel->window = remoteVars->selstruct.clipWinId; - pSel->pWin = remoteVars->selstruct.clipWinPtr; - pSel->client = serverClient; - - EPHYR_DBG("OWN selection: %s", NameForAtom(selection)); - - info.selection = pSel; - info.client = serverClient; - info.kind = SelectionSetOwner; - CallCallbacks(&SelectionCallback, &info); - - return Success; + return a; } - -static int create_selection_window(void) +char *atom_name(xcb_atom_t xatom) { - int result = -1; - - if(remoteVars->selstruct.clipWinPtr) - return Success; - - - remoteVars->selstruct.clipWinId = FakeClientID(0); - - remoteVars->selstruct.clipWinPtr = CreateWindow(remoteVars->selstruct.clipWinId, remoteVars->ephyrScreen->pScreen->root, - 0, 0, 100, 100, 0, InputOnly, - 0, NULL, 0, serverClient, - CopyFromParent, &result); - if (!remoteVars->selstruct.clipWinPtr) + //get name for atom, don't forget to free return value + char* name; + xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(remoteVars->selstruct.xcbConnection, xatom); + xcb_get_atom_name_reply_t *reply=xcb_get_atom_name_reply(remoteVars->selstruct.xcbConnection, cookie, NULL); + + if(!reply) + return NULL; + if(!reply->name_len) { - EPHYR_DBG("Can't create selection window"); - return result; + free(reply); + return NULL; } - - if (!AddResource(remoteVars->selstruct.clipWinPtr->drawable.id, RT_WINDOW, remoteVars->selstruct.clipWinPtr)) - return BadAlloc; - - EPHYR_DBG("Selection window created %d, %p",remoteVars->selstruct.clipWinId, remoteVars->selstruct.clipWinPtr); - return Success; + name=malloc( xcb_get_atom_name_name_length(reply)+1); + strncpy(name, xcb_get_atom_name_name(reply),xcb_get_atom_name_name_length(reply)); + name [xcb_get_atom_name_name_length(reply)]='\0'; + free(reply); + return name; } - -static void request_selection(Atom selection, Atom rtype) +static xcb_atom_t target_has_atom(xcb_atom_t* list, size_t size, const char *name) { - xEvent ev = {{{0}}}; - Selection *selPtr = NULL; - int rc = -1; + //check if the atom which represents "name" is in the list of supported mime types + size_t i = 0; + xcb_atom_t a=atom(name); + if(!a) + return 0; - rc = create_selection_window(); - if (rc != Success) + for (i = 0;i < size;i++) { - EPHYR_DBG("ERROR! Can't create selection Window"); - return; + if (list[i]==a) + return a; } - - /*EPHYR_DBG("Request: %s, %s", - * NameForAtom(selection), NameForAtom(rtype));*/ - - rc = dixLookupSelection(&selPtr, selection, serverClient, DixGetAttrAccess); - if (rc != Success) - return; - ev.u.u.type = SelectionRequest; - ev.u.selectionRequest.owner = selPtr->window; - ev.u.selectionRequest.time = currentTime.milliseconds; - ev.u.selectionRequest.requestor = remoteVars->selstruct.clipWinId; - ev.u.selectionRequest.selection = selection; - ev.u.selectionRequest.target = rtype; - ev.u.selectionRequest.property = rtype; - WriteEventsToClient(selPtr->client, 1, &ev); + return 0; } - - -static void selection_callback(CallbackListPtr *callbacks, - void * data, void * args) +static int is_string_atom( xcb_atom_t at) { - SelectionInfoRec *info = (SelectionInfoRec *) args; - - if (info->kind != SelectionSetOwner) - return; - if (info->client == serverClient) - return; - - /* - * EPHYR_DBG("Selection owner changed: %s", - * NameForAtom(info->selection->selection));*/ - - if ((info->selection->selection != atomPrimary) && - (info->selection->selection != atomClipboard)) - return; - - if(remoteVars->selstruct.selectionMode == CLIP_BOTH || remoteVars->selstruct.selectionMode == CLIP_SERVER) - request_selection(info->selection->selection, atomTargets); + //check if selection data is string/text + if(!at) + return 0; + if( at == atom("UTF8_STRING") || + at == atom("text/plain;charset=utf-8") || + at == atom("STRING") || + at == atom("TEXT") || + at == atom("text/plain")) + return 1; + return 0; } -static Atom find_atom_by_name(const char* name, const Atom list[], size_t size) +static int is_image_atom( xcb_atom_t at) { - for (int i = 0;i < size;i++) - { - if(!strcmp(name, NameForAtom (list[i]))) - { - EPHYR_DBG("Found IMAGE ATOM %s:%d", NameForAtom (list[i]), list[i]); - return list [i]; - } - } + //check if selection data is image + if(!at) + return 0; + if( at == atom("image/png") || + at == atom("image/xpm") || + at == atom("image/jpg") || + at == atom("image/jpeg") || + at == atom("PIXMAP") || + at == atom("image/bmp")) + return 1; return 0; } -static BOOL find_image_atom(const Atom list[], size_t size) +static xcb_atom_t best_atom_from_target(xcb_atom_t* list, size_t size) { - Atom at = {0}; - at=find_atom_by_name("image/jpg",list,size); - if(at) + //here we chose the best of supported formats for selection + xcb_atom_t a; + //selecting utf formats first + if((a=target_has_atom(list, size, "UTF8_STRING"))) + { +// EPHYR_DBG("selecting mime type UTF8_STRING"); + return a; + } + if((a=target_has_atom(list, size, "text/plain;charset=utf-8"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG("selecting mime type text/plain;charset=utf-8"); + return a; } - at=find_atom_by_name("image/jpeg",list,size); - if(at) + if((a=target_has_atom(list, size, "STRING"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG( "selecting mime type STRING"); + return a; } - at=find_atom_by_name("image/png",list,size); - if(at) + if((a=target_has_atom(list, size, "TEXT"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG( "selecting mime type TEXT"); + return a; } - at=find_atom_by_name("image/bmp",list,size); - if(at) + if((a=target_has_atom(list, size, "text/plain"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG( "selecting mime type text/plain"); + return a; } - at=find_atom_by_name("image/xpm",list,size); - if(at) + + //selecting loseless formats first + if((a=target_has_atom(list, size, "image/png"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG( "selecting mime type image/png"); + return a; } - at=find_atom_by_name("PIXMAP",list,size); - if(at) + if((a=target_has_atom(list, size, "image/xpm"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG( "selecting mime type image/xpm"); + return a; } - at=find_atom_by_name("image/ico",list,size); - if(at) + if((a=target_has_atom(list, size, "PIXMAP"))) { - imageAtom=at; - return TRUE; +// EPHYR_DBG( "selecting mime type PIXMAP"); + return a; } - - imageAtom=0; - return FALSE; - + if((a=target_has_atom(list, size, "image/bmp"))) + { +// EPHYR_DBG( "selecting mime type image/bmp"); + return a; + } + if((a=target_has_atom(list, size, "image/jpg"))) + { +// EPHYR_DBG( "selecting mime type image/jpg"); + return a; + } + if((a=target_has_atom(list, size, "image/jpeg"))) + { +// EPHYR_DBG( "selecting mime type image/jpeg"); + return a; + } + return 0; } -//static void listAtoms(const Atom list[], size_t size) -//{ -// size_t i; -// -// for (i = 0;i < size;i++) -// { -// EPHYR_DBG("%d:%s", list[i], NameForAtom( list[i])); -// } -//} - -static Bool prop_has_atom(Atom atom, const Atom list[], size_t size) +void request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t) { - size_t i = 0; - - for (i = 0;i < size;i++) { - if (list[i] == atom) - return TRUE; + //execute convert selection for primary or clipboard to get mimetypes or data (depends on target atom) + if(!t) + t=XCB_CURRENT_TIME; + if(property) + { + xcb_delete_property(remoteVars->selstruct.xcbConnection,remoteVars->selstruct.clipWinId,property); } - - return FALSE; + xcb_convert_selection(remoteVars->selstruct.xcbConnection,remoteVars->selstruct.clipWinId,selection, target, property, t); + xcb_flush(remoteVars->selstruct.xcbConnection); } -static void process_selection(Atom selection, Atom target, - Atom property, Atom requestor, - TimeStamp time) +void read_selection_property(xcb_atom_t selection, xcb_atom_t property) { - PropertyPtr prop; - int rc = -1; - - rc = dixLookupProperty(&prop, remoteVars->selstruct.clipWinPtr, property, - serverClient, DixReadAccess); - - if(rc== BadMatch) + xcb_atom_t* tg; + char* stype, *sprop; + xcb_atom_t data_atom; + unsigned int bytes_left, bytes_read=0; + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + outputChunk* chunk; + + + //request property which represents value of selection (data or mime types) + //get max 100K of data, we don't need to send more than that over network for perfomance reasons + cookie= xcb_get_property(remoteVars->selstruct.xcbConnection,FALSE, remoteVars->selstruct.clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, 0, max_chunk()); + reply=xcb_get_property_reply(remoteVars->selstruct.xcbConnection, cookie, NULL); + if(!reply) { - EPHYR_DBG("BAD MATCH!!!"); +// EPHYR_DBG( "NULL reply"); } - - if (rc != Success) - return; - - EPHYR_DBG("Selection notification for %s (target %s, property %s, type %s)", - NameForAtom(selection), NameForAtom(target), - NameForAtom(property), NameForAtom(prop->type)); - - if (target != property) - return; - - if (target == atomTargets) + else { - if (prop->format != 32) - return; - if (prop->type != XA_ATOM) - return; - -// listAtoms((const Atom*)prop->data, prop->size); - - - if(prop_has_atom(atomUTFString, (const Atom*)prop->data, prop->size)) - { - request_selection(selection, atomUTFString); - } - else if(prop_has_atom(atomString, (const Atom*)prop->data, prop->size)) + if(reply->type==XCB_NONE) { - request_selection(selection, atomString); +// EPHYR_DBG( "NONE reply"); } else { - /* requesting pixmap only for clipboard */ - if((selection == atomClipboard) && find_image_atom((const Atom*)prop->data, prop->size) && imageAtom) + //here we have type of data + +/* + stype=atom_name(reply->type); + sprop=atom_name(property); + EPHYR_DBG( "Property %s type %s, format %d, length %d", sprop, stype, reply->format, reply->length); + if(stype) + free(stype); + if(sprop) + free(sprop); +*/ + //need to read property incrementally + if(reply->type == atom("INCR")) + { + unsigned int sz=*((unsigned int*) xcb_get_property_value(reply)); +// EPHYR_DBG( "have incr property size: %d", sz); + remoteVars->selstruct.incrAtom=property; + remoteVars->selstruct.incrementalSize=sz; + remoteVars->selstruct.incrementalSizeRead=0; + + //deleteing property should tell the selection owner that we are ready for incremental reading of data + xcb_delete_property(remoteVars->selstruct.xcbConnection,remoteVars->selstruct.clipWinId, property); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(reply); + return; + } + //we have supported mime types in reply + if(reply->type == atom( "ATOM")) { - request_selection(selection, imageAtom); + if(reply->format!=32) + { + EPHYR_DBG( "wrong format for TARGETS"); + } + else + { +// EPHYR_DBG( "target supports %lu mime types",xcb_get_property_value_length(reply)/sizeof(xcb_atom_t)); + + /* + #warning debug + tg=(xcb_atom_t*) xcb_get_property_value(reply); + for(int i=0;i<xcb_get_property_value_length(reply)/sizeof(xcb_atom_t);++i) + { + stype=atom_name(tg[i]); + EPHYR_DBG("selection has target: %s, %d",stype, tg[i]); + if(stype) + free(stype); + } + //#debug + */ + + data_atom=0; + //get the best of supported mime types and request the selection in this format + data_atom=best_atom_from_target(xcb_get_property_value(reply), xcb_get_property_value_length(reply)/sizeof(xcb_atom_t)); + + xcb_delete_property( remoteVars->selstruct.xcbConnection, remoteVars->selstruct.clipWinId, property); + xcb_flush(remoteVars->selstruct.xcbConnection); + + if(data_atom) + request_selection_data( selection, data_atom, data_atom, 0); + else + { + EPHYR_DBG( "there are no supported mime types in the target"); + } + } } + else + { + //here we have selection as string or image + if(is_image_atom( reply->type) || is_string_atom( reply->type)) + { + //read property data in loop in the chunks with size (100KB) + do + { + bytes_left=reply->bytes_after; + //now we can access property data + + /*FILE* cp=fopen("/tmp/clip", "a"); + fwrite(xcb_get_property_value(reply),1, xcb_get_property_value_length(reply),cp); + fclose(cp);*/ + + + chunk=(outputChunk*) malloc(sizeof(outputChunk)); + + memset((void*)chunk,0,sizeof(outputChunk)); + + if(xcb_get_property_value_length(reply)) + { + chunk->size=xcb_get_property_value_length(reply); + chunk->data=(unsigned char*)malloc(chunk->size); + memcpy(chunk->data, xcb_get_property_value(reply),chunk->size); + } + + chunk->compressed=FALSE; + if(is_string_atom(property)) + chunk->mimeData=UTF_STRING; + else + chunk->mimeData=PIXMAP; + + chunk->selection=selection_from_atom(selection); + + + if(remoteVars->selstruct.incrementalSize && (remoteVars->selstruct.incrAtom==property)) + { + //this is the total size of our selection + chunk->totalSize=remoteVars->selstruct.incrementalSize; + //we are doing incremental reading + if(remoteVars->selstruct.incrementalSizeRead == 0) + { + //it's the first chunk + chunk->firstChunk=TRUE; + } + remoteVars->selstruct.incrementalSizeRead+=xcb_get_property_value_length(reply); + if(!bytes_left && ! bytes_read && !xcb_get_property_value_length(reply)) + { + //we got the property with 0 size it means that we recieved all data of incr property +// EPHYR_DBG("INCR Property done, read %d", remoteVars->selstruct.incrementalSizeRead); + remoteVars->selstruct.incrAtom=0; + remoteVars->selstruct.incrementalSize=0; + //it's the last chunk + chunk->lastChunk=TRUE; + } + } + else + { + //we are doing simple read + if(bytes_read==0) + { + //it's the first chunk + chunk->firstChunk=TRUE; + } + if(bytes_left==0) + { + //the last chunk + chunk->lastChunk=TRUE; + } + //total size of the selection + chunk->totalSize=xcb_get_property_value_length(reply)+bytes_left; + } + + bytes_read+=xcb_get_property_value_length(reply); +// EPHYR_DBG( "read chunk of selection - size %d, total read %d, left %d, first:%d, last:%d", xcb_get_property_value_length(reply), bytes_read, bytes_left, chunk->firstChunk, chunk->lastChunk); + + pthread_mutex_lock(&remoteVars->sendqueue_mutex); + //attach chunk to the end of output chunk queue + if(!remoteVars->selstruct.lastOutputChunk) + { + remoteVars->selstruct.lastOutputChunk=remoteVars->selstruct.firstOutputChunk=chunk; + } + else + { + remoteVars->selstruct.lastOutputChunk->next=(struct outputChunk*)chunk; + remoteVars->selstruct.lastOutputChunk=chunk; + } +// EPHYR_DBG(" ADD CHUNK %p %p %p", remoteVars->selstruct.firstOutputChunk, remoteVars->selstruct.lastOutputChunk, chunk); + pthread_cond_signal(&remoteVars->have_sendqueue_cond); + pthread_mutex_unlock(&remoteVars->sendqueue_mutex); + + if(bytes_left) + { + free(reply); + cookie= xcb_get_property(remoteVars->selstruct.xcbConnection, 0, remoteVars->selstruct.clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, bytes_read/4, max_chunk()); + reply=xcb_get_property_reply(remoteVars->selstruct.xcbConnection, cookie, NULL); + if(!reply) + { + //something is wrong + EPHYR_DBG("NULL reply"); + break; + } + } + //read in loop till no data left + }while(bytes_left); + } + else + { + stype=atom_name(reply->type); + EPHYR_DBG("Not supported mime type: %s, %d",stype, reply->type); + if(stype) + free(stype); + } + } + if(reply) + free(reply); + //if reading incr property this will say sel owner that we are ready for the next chunk of data + xcb_delete_property(remoteVars->selstruct.xcbConnection, remoteVars->selstruct.clipWinId, property); + xcb_flush(remoteVars->selstruct.xcbConnection); } } - else if (target == atomString || target == atomUTFString || (target==imageAtom && imageAtom) ) +} + +void process_selection_notify(xcb_generic_event_t *e) +{ + xcb_selection_notify_event_t *sel_event; + +// EPHYR_DBG("selection notify"); + sel_event=(xcb_selection_notify_event_t *)e; + + //processing the event which is reply for convert selection call + + remoteVars->selstruct.incrAtom=0; + remoteVars->selstruct.incrementalSize=0; + + if (sel_event->requestor != remoteVars->selstruct.clipWinId) { - int format=STRING; - if(target==atomUTFString) - { - format=UTF_STRING; - }else if(target == imageAtom) +// EPHYR_DBG("not our window"); + return; + } + else + { +// EPHYR_DBG("selection notify sel %d, target %d, property %d", sel_event->selection, sel_event->target, sel_event->property); + if(sel_event->property==XCB_NONE) { - format=PIXMAP; + EPHYR_DBG( "NO SELECTION"); } - /* read incrementinal data only for clipboard */ - if(!strcmp( NameForAtom(prop->type), "INCR") && selection==atomClipboard) + else { - EPHYR_DBG("GOT INCR PROPERTY: %d",*((int*)prop->data)); - remoteVars->selstruct.readingIncremental=TRUE; - pthread_mutex_lock(&remoteVars->sendqueue_mutex); - - remoteVars->selstruct.clipboard.size=*((int*)prop->data); - remoteVars->selstruct.clipboard.data=malloc(remoteVars->selstruct.clipboard.size); - remoteVars->selstruct.clipboard.mimeData=format; - remoteVars->selstruct.incrementalPosition=0; - pthread_mutex_unlock(&remoteVars->sendqueue_mutex); - - DeleteProperty(serverClient,remoteVars->selstruct.clipWinPtr, property); - return; + remoteVars->selstruct.currentSelection=sel_event->selection; + //read property + read_selection_property(remoteVars->selstruct.currentSelection, sel_event->property); } + } +} - if (prop->format != 8) - return; - if (prop->type != atomString && prop->type != atomUTFString && prop->type!=imageAtom) - return; +void process_property_notify(xcb_generic_event_t *e) +{ + xcb_property_notify_event_t *pn; - pthread_mutex_lock(&remoteVars->sendqueue_mutex); +// EPHYR_DBG("property notify"); - remoteVars->selstruct.readingIncremental=FALSE; - if(selection==atomClipboard || selection ==atomPrimary) + pn = (xcb_property_notify_event_t *)e; + if (pn->window != remoteVars->selstruct.clipWinId) + { +// EPHYR_DBG("not our window"); + return; + } +// EPHYR_DBG("property %d, state %d ", pn->atom, pn->state); + if(pn->state==XCB_PROPERTY_NEW_VALUE) + { +// EPHYR_DBG( "NEW VALUE"); + if(remoteVars->selstruct.incrAtom==pn->atom && remoteVars->selstruct.incrementalSize) { - outputBuffer* buff; - if(selection==atomClipboard) - buff=&remoteVars->selstruct.clipboard; - else - buff=&remoteVars->selstruct.selection; - if(buff->data) - { - free(buff->data); - } - buff->size=prop->size; - buff->data=malloc(buff->size); - memcpy(buff->data, prop->data, prop->size); - buff->changed=TRUE; - buff->mimeData=format; -// EPHYR_DBG("Have new Clipboard %s %d",remoteVars->selstruct.clipboard, remoteVars->selstruct.clipboardSize); + //we recieveing the selection data incrementally, let's read a next chunk +// EPHYR_DBG("reading incr property %d", pn->atom); + read_selection_property(remoteVars->selstruct.currentSelection, pn->atom); } - pthread_cond_signal(&remoteVars->have_sendqueue_cond); - pthread_mutex_unlock(&remoteVars->sendqueue_mutex); } - DeleteProperty(serverClient,remoteVars->selstruct.clipWinPtr, property); + if(pn->state==XCB_PROPERTY_DELETE) + { +// EPHYR_DBG( "DELETE"); + } + if(pn->state==XCB_PROPERTY_NOTIFY) + { +// EPHYR_DBG( "NOTIFY"); + } } - - -#define SEND_EVENT_BIT 0x80 - -static int send_event(ClientPtr client) +void process_selection_owner_notify(xcb_generic_event_t *e) { - REQUEST(xSendEventReq); - REQUEST_SIZE_MATCH(xSendEventReq); - - stuff->event.u.u.type &= ~(SEND_EVENT_BIT); + enum SelectionType selection; + xcb_xfixes_selection_notify_event_t *notify_event=(xcb_xfixes_selection_notify_event_t *)e; - - if (stuff->event.u.u.type == SelectionNotify && - stuff->event.u.selectionNotify.requestor == remoteVars->selstruct.clipWinId) +// EPHYR_DBG("SEL OWNER notify, selection: %d, window: %d, owner: %d",notify_event->selection, notify_event->window, notify_event->owner); + if(notify_event->owner == remoteVars->selstruct.clipWinId) { - TimeStamp time; - time = ClientTimeToServerTime(stuff->event.u.selectionNotify.time); - - process_selection(stuff->event.u.selectionNotify.selection, - stuff->event.u.selectionNotify.target, - stuff->event.u.selectionNotify.property, - stuff->event.u.selectionNotify.requestor, - time); +// EPHYR_DBG("It's our own selection, ignoring"); + return; } - return proc_send_event_orig(client); -} - -static int convert_selection(ClientPtr client, Atom selection, - Atom target, Atom property, - Window requestor, CARD32 time) -{ - Selection *pSel = NULL; - WindowPtr pWin = {0}; - int rc = -1; - - Atom realProperty = {0}; - xEvent event = {{{0}}}; + if(remoteVars->selstruct.selectionMode == CLIP_NONE || remoteVars->selstruct.selectionMode == CLIP_CLIENT) + { + EPHYR_DBG("Server selection is disabled"); + return; + } - inputBuffer* buff=&remoteVars->selstruct.inSelection; - if(selection==atomClipboard) - buff=&remoteVars->selstruct.inClipboard; + //cancel all previous incr reading + remoteVars->selstruct.incrementalSize=remoteVars->selstruct.incrementalSizeRead=0; + remoteVars->selstruct.incrAtom=0; -// EPHYR_DBG("Selection request for %s (type %s)", NameForAtom(selection), NameForAtom(target)); - rc = dixLookupSelection(&pSel, selection, client, DixGetAttrAccess); - if (rc != Success) - return rc; + selection=selection_from_atom(notify_event->selection); - rc = dixLookupWindow(&pWin, requestor, client, DixSetAttrAccess); - if (rc != Success) - return rc; + //we are not owners of this selction anymore + remoteVars->selstruct.inSelection[selection].owner=FALSE; - if (property != None) - realProperty = property; - else - realProperty = target; + //get supported mime types + request_selection_data( notify_event->selection, atom( "TARGETS"), atom( "TARGETS"), 0); +} - if (target == atomTargets) +static +void *selection_thread (void* id) +{ + xcb_screen_t *screen; + uint response_type; + xcb_generic_event_t *e=NULL; + uint32_t mask = 0; + uint32_t values[2]; + xcb_generic_error_t *error = 0; + const xcb_query_extension_reply_t *reply; + xcb_xfixes_query_version_cookie_t xfixes_query_cookie; + xcb_xfixes_query_version_reply_t *xfixes_query; + + + /* Create the window */ + remoteVars->selstruct.xcbConnection = xcb_connect (RemoteHostVars.displayName, NULL); + if(xcb_connection_has_error(remoteVars->selstruct.xcbConnection)) + { + EPHYR_DBG("Warning! can't create XCB connection to display %s, selections exchange between client and server will be disabled", RemoteHostVars.displayName); + remoteVars->selstruct.xcbConnection=0; + pthread_exit(0); + return NULL; + } + screen = xcb_setup_roots_iterator (xcb_get_setup (remoteVars->selstruct.xcbConnection)).data; + remoteVars->selstruct.clipWinId = xcb_generate_id (remoteVars->selstruct.xcbConnection); + mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + values[0] = screen->white_pixel; + values[1] = XCB_EVENT_MASK_PROPERTY_CHANGE; + + ATOM_CLIPBOARD=atom("CLIPBOARD"); + + //create window which will recieve selection events and provide remote selection to X-clients + xcb_create_window (remoteVars->selstruct.xcbConnection, + XCB_COPY_FROM_PARENT, + remoteVars->selstruct.clipWinId, + screen->root, + 0, 0, + 1, 1, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + screen->root_visual, + mask, values); + + xcb_flush(remoteVars->selstruct.xcbConnection); + //check if we have xfixes, we need it to recieve selection owner events + reply = xcb_get_extension_data(remoteVars->selstruct.xcbConnection, &xcb_xfixes_id); + if (reply && reply->present) { - Atom string_targets[] = { atomTargets, atomTimestamp, atomUTFString }; - Atom pixmap_targets[] = { atomTargets, atomTimestamp, atomJPEG, atomJPG }; - if(buff->mimeData == PIXMAP) + xfixes_query_cookie = xcb_xfixes_query_version(remoteVars->selstruct.xcbConnection, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); + xfixes_query = xcb_xfixes_query_version_reply (remoteVars->selstruct.xcbConnection, xfixes_query_cookie, &error); + if (!xfixes_query || error || xfixes_query->major_version < 2) { - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - XA_ATOM, 32, PropModeReplace, - sizeof(pixmap_targets)/sizeof(pixmap_targets[0]), - pixmap_targets, TRUE); + free(error); } else { - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - XA_ATOM, 32, PropModeReplace, - sizeof(string_targets)/sizeof(string_targets[0]), - string_targets, TRUE); + //we'll recieve sel owner events for primary amd clipboard + mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER; + xcb_xfixes_select_selection_input_checked(remoteVars->selstruct.xcbConnection,remoteVars->selstruct.clipWinId, XCB_ATOM_PRIMARY, mask); + xcb_xfixes_select_selection_input_checked(remoteVars->selstruct.xcbConnection, remoteVars->selstruct.clipWinId, ATOM_CLIPBOARD, mask); } - if (rc != Success) - return rc; + free(xfixes_query); } - else if (target == atomTimestamp) - { - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - XA_INTEGER, 32, PropModeReplace, 1, - &pSel->lastTimeChanged.milliseconds, - TRUE); - if (rc != Success) - return rc; - } - else if (target == atomUTFString || target == atomJPEG || target == atomJPG ) - { - rc = dixChangeWindowProperty(serverClient, pWin, realProperty, - target, 8, PropModeReplace, - buff->size, buff->data, TRUE); - if (rc != Success) - return rc; - } - else + xcb_flush(remoteVars->selstruct.xcbConnection); + + // event loop + while ((e = xcb_wait_for_event(remoteVars->selstruct.xcbConnection))) { - return BadMatch; + response_type = e->response_type & ~0x80; + + //we notified that selection is changed in primary or clipboard + if (response_type == reply->first_event + XCB_XFIXES_SELECTION_NOTIFY) + { + process_selection_owner_notify(e); + } + else + { + //we notified that property is changed + if (response_type == XCB_PROPERTY_NOTIFY) + { + process_property_notify(e); + } + //we got reply to our selection request (mime types or data) + else if (response_type == XCB_SELECTION_NOTIFY) + { + process_selection_notify(e); + } + else if (response_type == XCB_SELECTION_REQUEST) + { + process_selection_request(e); + } + else + { +// EPHYR_DBG("not processing this event %d ", response_type); + } + } + free(e); + xcb_flush(remoteVars->selstruct.xcbConnection); } - event.u.u.type = SelectionNotify; - event.u.selectionNotify.time = time; - event.u.selectionNotify.requestor = requestor; - event.u.selectionNotify.selection = selection; - event.u.selectionNotify.target = target; - event.u.selectionNotify.property = property; - WriteEventsToClient(client, 1, &event); - return Success; + pthread_exit(0); + return NULL; } -static int proc_convert_selection(ClientPtr client) +void install_selection_callbacks(void) { - Bool paramsOkay; - WindowPtr pWin = {0}; - Selection *pSel = NULL; - int rc = -1; - - REQUEST(xConvertSelectionReq); - REQUEST_SIZE_MATCH(xConvertSelectionReq); - rc = dixLookupWindow(&pWin, stuff->requestor, client, DixSetAttrAccess); - if (rc != Success) - return rc; - paramsOkay = ValidAtom(stuff->selection) && ValidAtom(stuff->target); - paramsOkay &= (stuff->property == None) || ValidAtom(stuff->property); - if (!paramsOkay) - { - client->errorValue = stuff->property; - return BadAtom; - } - - rc = dixLookupSelection(&pSel, stuff->selection, client, DixReadAccess); - if (rc == Success && pSel->client == serverClient && pSel->window == remoteVars->selstruct.clipWinId) - { - rc = convert_selection(client, stuff->selection, - stuff->target, stuff->property, - stuff->requestor, stuff->time); - if (rc != Success) - { - xEvent event; - memset(&event, 0, sizeof(xEvent)); - event.u.u.type = SelectionNotify; - event.u.selectionNotify.time = stuff->time; - event.u.selectionNotify.requestor = stuff->requestor; - event.u.selectionNotify.selection = stuff->selection; - event.u.selectionNotify.target = stuff->target; - event.u.selectionNotify.property = None; - WriteEventsToClient(client, 1, &event); - } + int ret; + if(remoteVars->selstruct.threadStarted) + return; + remoteVars->selstruct.threadStarted=TRUE; - return Success; + ret = pthread_create(&(remoteVars->selstruct.selThreadId), NULL, selection_thread, (void *)remoteVars->selstruct.selThreadId); + if (ret) + { + EPHYR_DBG("ERROR; return code from pthread_create() is %d", ret); + remoteVars->selstruct.selThreadId=0; } - - return proc_convert_selection_orig(client); + else + pthread_mutex_init(&remoteVars->selstruct.inMutex, NULL); + return; } - -static int proc_change_property(ClientPtr client) +void process_selection_request(xcb_generic_event_t *e) { - int rc = -1; - BOOL incRead; - PropertyPtr prop = {0}; + xcb_selection_request_event_t *req=(xcb_selection_request_event_t*)e; + char *asel, *atar, *aprop; + enum SelectionType sel=selection_from_atom(req->selection); + xcb_atom_t property=req->property; + xcb_atom_t target=req->target; - REQUEST(xChangePropertyReq); + xcb_selection_notify_event_t* event= (xcb_selection_notify_event_t*)calloc(32, 1); + event->response_type = XCB_SELECTION_NOTIFY; + event->requestor = req->requestor; + event->selection = req->selection; + event->target = req->target; + event->property = XCB_NONE; + event->time = req->time; - REQUEST_AT_LEAST_SIZE(xChangePropertyReq); - rc=proc_change_property_orig(client); + if(property == XCB_NONE) + property=target; - if(rc!=Success) - return rc; +/* + asel= atom_name(req->selection); + atar= atom_name(req->target); + aprop=atom_name(req->property); + + EPHYR_DBG("selection request for %s %s %s ", asel, atar, aprop); + if(asel) + free(asel); + if(aprop) + free(aprop); + if(atar) + free(atar); +*/ + + //synchronize with main thread + pthread_mutex_lock(&remoteVars->selstruct.inMutex); + if(! remoteVars->selstruct.inSelection[sel].owner) + { + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + //we don't own this selection + EPHYR_DBG("not our selection"); + xcb_send_event(remoteVars->selstruct.xcbConnection, FALSE, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(event); + return; + } - pthread_mutex_lock(&remoteVars->sendqueue_mutex); - incRead=remoteVars->selstruct.readingIncremental; - pthread_mutex_unlock(&remoteVars->sendqueue_mutex); - + if(remoteVars->selstruct.inSelection[sel].timestamp > req->time) + { + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + //selection changed after request + EPHYR_DBG("requested selection doesn't exist anymore"); + xcb_send_event(remoteVars->selstruct.xcbConnection, FALSE, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(event); + return; + } + if(req->target==atom("TIMESTAMP")) + { + event->property=property; +// EPHYR_DBG("requested TIMESTAMP"); + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, + property, XCB_ATOM_INTEGER, 32, 1, &remoteVars->selstruct.inSelection[sel].timestamp); - if(stuff->window == remoteVars->selstruct.clipWinId && incRead && - ((imageAtom && imageAtom==stuff->type)||(stuff->type == atomUTFString) || (stuff->type == atomString)) ) - EPHYR_DBG("HAVE NEW DATA for %d: %s %s", stuff->window, NameForAtom(stuff->property), NameForAtom(stuff->type)); + } + else if(req->target==atom("TARGETS")) + { + event->property=property; +// EPHYR_DBG("requested TARGETS"); + send_mime_types(req); + } else - return rc; - + { + event->property=send_data(req); + } + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + xcb_send_event(remoteVars->selstruct.xcbConnection, FALSE, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(event); +} +enum SelectionType selection_from_atom(xcb_atom_t selection) +{ + if(selection == XCB_ATOM_PRIMARY) + return PRIMARY; + return CLIPBOARD; - rc = dixLookupProperty(&prop, remoteVars->selstruct.clipWinPtr, stuff->property, - serverClient, DixReadAccess); +} - if(rc== BadMatch) - { - EPHYR_DBG("BAD MATCH!!!"); - return Success; - } +void send_mime_types(xcb_selection_request_event_t* req) +{ + //inmutex is locked in the caller function + //send supported targets + enum SelectionType sel=selection_from_atom(req->selection); - if (rc != Success) - return rc; + //we'll have max 7 mimetypes, don't forget to change this dimension if adding new mimetypes + xcb_atom_t targets[7]; + xcb_atom_t a; + uint32_t mcount=0; - EPHYR_DBG("Have %d bytes for %s ", - prop->size, NameForAtom(stuff->property)); + if((a=atom("TARGETS"))) + targets[mcount++]=a; + if((a=atom("TIMESTAMP"))) + targets[mcount++]=a; - pthread_mutex_lock(&remoteVars->sendqueue_mutex); - if(prop->size==0) + if(remoteVars->selstruct.inSelection[sel].mimeData==PIXMAP) { - EPHYR_DBG("READ %d FROM %d", remoteVars->selstruct.incrementalPosition, remoteVars->selstruct.clipboard.size); - remoteVars->selstruct.readingIncremental=FALSE; - if(remoteVars->selstruct.incrementalPosition == remoteVars->selstruct.clipboard.size) + //return PNG mime, if our data in PNG format, otherwise return JPEG + if((a=atom("image/png")) && is_png(remoteVars->selstruct.inSelection[sel].data, remoteVars->selstruct.inSelection[sel].size)) + { + //our buffer is PNG file + targets[mcount++]=a; + } + else { - remoteVars->selstruct.clipboard.changed=TRUE; - pthread_cond_signal(&remoteVars->have_sendqueue_cond); + if((a=atom("image/jpg"))) + targets[mcount++]=a; + if((a=atom("image/jpeg"))) + targets[mcount++]=a; } } else { - memcpy(remoteVars->selstruct.clipboard.data+remoteVars->selstruct.incrementalPosition, prop->data, prop->size); - remoteVars->selstruct.incrementalPosition+=prop->size; + if((a=atom("UTF8_STRING"))) + targets[mcount++]=a; + if((a=atom("text/plain;charset=utf-8"))) + targets[mcount++]=a; + if((a=atom("STRING"))) + targets[mcount++]=a; + if((a=atom("TEXT"))) + targets[mcount++]=a; + if((a=atom("text/plain"))) + targets[mcount++]=a; } - pthread_mutex_unlock(&remoteVars->sendqueue_mutex); - + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, req->property, XCB_ATOM_ATOM, + 32, mcount, (const void *)targets); + xcb_flush(remoteVars->selstruct.xcbConnection); +} - DeleteProperty(serverClient,remoteVars->selstruct.clipWinPtr, stuff->property); +xcb_atom_t send_data(xcb_selection_request_event_t* req) +{ + //inmutex is locked in the caller function + //send data + enum SelectionType sel=selection_from_atom(req->selection); + char *starget; + xcb_atom_t png=atom("image/png"); + xcb_atom_t jpg=atom("image/jpg"); + xcb_atom_t jpeg=atom("image/jpeg"); - return rc; + if(remoteVars->selstruct.inSelection[sel].mimeData==UTF_STRING) + { + //if it's one of supported text formats send without convertion + if(is_string_atom(req->target)) + { +// EPHYR_DBG("sending UTF text"); + return set_data_property(req, remoteVars->selstruct.inSelection[sel].data, remoteVars->selstruct.inSelection[sel].size); + } + else + { + starget=atom_name(req->target); + EPHYR_DBG("unsupported property requested: %s",starget); + if(starget) + free(starget); + return XCB_NONE; + } + } + else + { + if(!is_image_atom(req->target)) + { + starget=atom_name(req->target); + EPHYR_DBG("unsupported property requested: %s",starget); + if(starget) + free(starget); + return XCB_NONE; + } +/* + starget=atom_name(req->target); + EPHYR_DBG("requested %s",starget); + if(starget) + free(starget); +*/ + + //TODO: implement convertion between different image formats + if(is_png(remoteVars->selstruct.inSelection[sel].data, remoteVars->selstruct.inSelection[sel].size)) + { + if(req->target!=png) + { + starget=atom_name(req->target); + EPHYR_DBG("unsupported property requested: %s",starget); + if(starget) + free(starget); + return XCB_NONE; + } + } + else + { + if((req->target!=jpg)&&(req->target!=jpeg)) + { + starget=atom_name(req->target); + EPHYR_DBG("unsupported property requested: %s",starget); + if(starget) + free(starget); + return XCB_NONE; + } + } + return set_data_property(req, remoteVars->selstruct.inSelection[sel].data, remoteVars->selstruct.inSelection[sel].size); + } + return XCB_NONE; } -void selection_init(struct _remoteHostVars *obj) +xcb_atom_t set_data_property(xcb_selection_request_event_t* req, unsigned char* data, uint32_t size) { - EPHYR_DBG("INITIALIZING selections"); - remoteVars=obj; - proc_convert_selection_orig = ProcVector[X_ConvertSelection]; - proc_send_event_orig = ProcVector[X_SendEvent]; - proc_change_property_orig = ProcVector[X_ChangeProperty]; - ProcVector[X_ConvertSelection] = proc_convert_selection; - ProcVector[X_SendEvent] = send_event; - ProcVector[X_ChangeProperty] = proc_change_property; - remoteVars->selstruct.callBackInstalled=TRUE; - install_selection_callbacks(); -} + //inmutex locked in parent thread + //set data to window property + //change when implemented + BOOL support_incr=FALSE; -void install_selection_callbacks(void) -{ - if(remoteVars->selstruct.selectionMode == CLIP_CLIENT || remoteVars->selstruct.selectionMode == CLIP_NONE) + xcb_atom_t xtsel=atom("_XT_SELECTION_0"); + xcb_atom_t qtsel=atom("_QT_SELECTION"); + //this types of application not supporting incr selection + if(req->property == xtsel|| req->property == qtsel ) { - EPHYR_DBG("SERVER CLIPBOARD disabled, not installing callbacks"); - return; + EPHYR_DBG("property %d doesn't support INCR",req->property); + support_incr=FALSE; } - atomPrimary = MakeAtom("PRIMARY", 7, TRUE); - atomClipboard = MakeAtom("CLIPBOARD", 9, TRUE); - - atomTargets = MakeAtom("TARGETS", 7, TRUE); - atomTimestamp = MakeAtom("TIMESTAMP", 9, TRUE); - atomString = MakeAtom("STRING", 6, TRUE); - atomUTFString = MakeAtom("UTF8_STRING", 11, TRUE); - - atomJPEG = MakeAtom("image/jpeg",487,TRUE); - atomJPG = MakeAtom("image/jpg",488,TRUE); + //check if we are sending incr + if(size < xcb_get_maximum_request_length(remoteVars->selstruct.xcbConnection) * 4 - 24) + { +// EPHYR_DBG( "sending %d bytes, property %d, target %d", size, req->property, req->target); - remoteVars->selstruct.clipWinPtr=0; + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target, + 8, size, (const void *)data); - /* try to dele callback to avaid double call */ - DeleteCallback(&SelectionCallback, selection_callback, 0); + xcb_flush(remoteVars->selstruct.xcbConnection); + return req->property; + } - if (!AddCallback(&SelectionCallback, selection_callback, 0)) - FatalError("Failed to install callback\n"); - else - EPHYR_DBG("Selection callback installed"); + EPHYR_DBG("data is too big"); + return XCB_NONE; +} +BOOL is_png(unsigned char* data, uint32_t size) +{ + if( size<8) + return FALSE; + return !png_sig_cmp(data, 0, 8); } diff --git a/x2gokdriveselection.h b/x2gokdriveselection.h index 3827ffa..7ec8d8a 100644 --- a/x2gokdriveselection.h +++ b/x2gokdriveselection.h @@ -30,8 +30,24 @@ #include "x2gokdriveremote.h" +uint32_t max_chunk(void); + void selection_init(struct _remoteHostVars *obj); void install_selection_callbacks(void); -int own_selection(int target); +int own_selection(enum SelectionType selection); + +xcb_atom_t atom(const char* name); +char *atom_name(xcb_atom_t xatom); +void request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t); +void read_selection_property(xcb_atom_t selection, xcb_atom_t property); +void process_selection_notify(xcb_generic_event_t *e); +void process_property_notify(xcb_generic_event_t *e); +void process_selection_owner_notify(xcb_generic_event_t *e); +void process_selection_request(xcb_generic_event_t *e); +void send_mime_types(xcb_selection_request_event_t* req); +enum SelectionType selection_from_atom(xcb_atom_t selection); +xcb_atom_t send_data(xcb_selection_request_event_t* req); +xcb_atom_t set_data_property(xcb_selection_request_event_t* req, unsigned char* data, uint32_t size); +BOOL is_png(unsigned char* data, uint32_t size); #endif /* X2GOKDRIVESELECTION_H */ -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gokdrive.git