This is an automated email from the git hooks/post-receive script. x2go pushed a change to branch master in repository x2gokdrive. from df8f0b2 patches.xorg/1.18.4/xorg-server-configure-ac.patch: correctly add -pthread and -lpthread references *after* the pkgconfig checks execute. new 3d6295b Move selection functionality to separate thread. Use XCB API to manage selections. The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Summary of changes: 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(-) -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gokdrive.git
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