This is an automated email from the git hooks/post-receive script. x2go pushed a commit to branch master in repository x2gokdrive. commit aed1bdfa2e43fdf9fb3bdb576e8cb00d7e11aac3 Author: Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> Date: Wed Sep 30 11:36:04 2020 -0500 support sending and recieving selections on demand. Support reading and writing INCR properties. --- debian/changelog | 1 + x2gokdriveremote.c | 352 ++++++++++++++++++---- x2gokdriveremote.h | 93 ++++-- x2gokdriveselection.c | 787 +++++++++++++++++++++++++++++++++++++++++--------- x2gokdriveselection.h | 25 +- 5 files changed, 1047 insertions(+), 211 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3a442cf..9bfc70b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -14,6 +14,7 @@ x2gokdrive (0.0.0.1-0x2go1) UNRELEASED; urgency=medium - add xcb-xfixes to deps. - send a recive feature versions. - reinit client version on new connection and awaka sending thread when client version recieved. + - support sending and recieving selections on demand. Support reading and writing INCR properties. [ Mihai Moldovan ] * Initial release: diff --git a/x2gokdriveremote.c b/x2gokdriveremote.c index 210de85..0fd828a 100644 --- a/x2gokdriveremote.c +++ b/x2gokdriveremote.c @@ -36,6 +36,7 @@ #endif #include "x2gokdriveremote.h" #include "x2gokdriveselection.h" +#include <zlib.h> /* init it in OsInit() */ static struct _remoteHostVars remoteVars = {0}; @@ -45,6 +46,7 @@ static BOOL remoteInitialized=FALSE; void remote_selection_init(void) { + remoteVars.selstruct.readingInputBuffer=-1; selection_init(&remoteVars); } @@ -207,6 +209,7 @@ void remote_removeCursor(uint32_t serialNumber) struct sentCursor* prev = NULL; struct deletedCursor* dcur = NULL; + pthread_mutex_lock(&remoteVars.sendqueue_mutex); cur=remoteVars.sentCursorsHead; @@ -239,6 +242,7 @@ void remote_removeCursor(uint32_t serialNumber) remoteVars.first_deleted_cursor=remoteVars.last_deleted_cursor=dcur; } ++remoteVars.deletedcursor_list_size; + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } @@ -253,8 +257,10 @@ void remote_sendCursor(CursorPtr cursor) cframe->data=0; cframe->next=0; + pthread_mutex_lock(&remoteVars.sendqueue_mutex); cursorSent=isCursorSent(cursor->serialNumber); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); if(!cursorSent) { @@ -289,13 +295,17 @@ void remote_sendCursor(CursorPtr cursor) cframe->forG=cursor->foreGreen*255./65535.0; cframe->forB=cursor->foreBlue*255./65535.0; + pthread_mutex_lock(&remoteVars.sendqueue_mutex); addSentCursor(cursor->serialNumber); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } + pthread_mutex_lock(&remoteVars.sendqueue_mutex); addCursorToQueue(cframe); pthread_cond_signal(&remoteVars.have_sendqueue_cond); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } @@ -305,6 +315,7 @@ void remote_sendVersion(void) unsigned char buffer[56] = {0}; _X_UNUSED int l; + *((uint32_t*)buffer)=SERVERVERSION; //4B *((uint16_t*)buffer+2)=FEATURE_VERSION; EPHYR_DBG("Sending server version: %d", FEATURE_VERSION); @@ -312,6 +323,16 @@ void remote_sendVersion(void) remoteVars.server_version_sent=TRUE; } +void request_selection_from_client(enum SelectionType selection) +{ + unsigned char buffer[56] = {0}; + _X_UNUSED int l; + + *((uint32_t*)buffer)=DEMANDCLIENTSELECTION; //4B + *((uint16_t*)buffer+2)=(uint16_t)selection; + l=write(remoteVars.clientsock,buffer,56); + EPHYR_DBG("requesting selection from client"); +} static int32_t send_cursor(struct cursorFrame* cursor) @@ -542,19 +563,19 @@ int send_deleted_cursors(void) return sent; } -int send_output_selection(outputChunk* chunk) +int send_output_selection(struct 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); + return send_selection_chunk(chunk->selection, chunk->data, chunk->size, chunk->mimeData, chunk->firstChunk, chunk->lastChunk, chunk->compressed_size, 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) + if(!chunk->compressed_size && chunk->firstChunk && chunk->lastChunk) { return send_selection_chunk(chunk->selection, chunk->data, chunk->size, chunk->mimeData, TRUE, TRUE, FALSE, chunk->size); } @@ -563,7 +584,7 @@ int send_output_selection(outputChunk* 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) +int send_selection_chunk(int sel, unsigned char* data, uint32_t length, uint32_t format, BOOL first, BOOL last, uint32_t compressed, uint32_t total) { unsigned char buffer[56] = {0}; _X_UNUSED int ln = 0; @@ -578,12 +599,20 @@ int send_selection_chunk(int sel, unsigned char* data, uint32_t length, uint32_t *((uint32_t*)buffer+4)=first; //20 *((uint32_t*)buffer+5)=last; //24 *((uint32_t*)buffer+6)=compressed; //28 - *((uint32_t*)buffer+7)=total; //28 + *((uint32_t*)buffer+7)=total; //32 // #warning check this ln=write(remoteVars.clientsock,buffer,56); + //if the data is compressed, send "compressed" amount of bytes +// EPHYR_DBG("sending chunk. total %d, chunk %d, compressed %d", total, length, compressed); + if(compressed) + { + length=compressed; + } + + while(sent<length) { l=write(remoteVars.clientsock,data+sent,((length-sent)<MAXMSGSIZE)?(length-sent):MAXMSGSIZE); @@ -1339,6 +1368,7 @@ void sendMainImageFromSendThread(uint32_t width, uint32_t height, int32_t dx ,in EPHYR_DBG("sending mainImage"); } + pthread_mutex_lock(&remoteVars.mainimg_mutex); for(int j=0;j<9;++j) @@ -1387,6 +1417,7 @@ void sendMainImageFromSendThread(uint32_t width, uint32_t height, int32_t dx ,in send_frame(width, height,dx,dy,0,regions); } + pthread_mutex_unlock(&remoteVars.mainimg_mutex); free(regions[0].compressed_data); } @@ -1395,6 +1426,7 @@ static void *send_frame_thread (void *threadid) { long tid; + enum SelectionType r; tid = (long)threadid; EPHYR_DBG("Started sending thread: #%ld!\n", tid); @@ -1473,6 +1505,7 @@ void *send_frame_thread (void *threadid) + pthread_mutex_lock(&remoteVars.sendqueue_mutex); //only accept one client, close server socket shutdown(remoteVars.serversock, SHUT_RDWR); @@ -1482,8 +1515,7 @@ void *send_frame_thread (void *threadid) #endif /* XORG_VERSION_CURRENT */ remoteVars.client_connected=TRUE; remoteVars.server_version_sent=FALSE; - remoteVars.client_version=0; - remoteVars.client_os=0; + set_client_version(0,0); if(remoteVars.checkConnectionTimer) { TimerFree(remoteVars.checkConnectionTimer); @@ -1495,10 +1527,12 @@ void *send_frame_thread (void *threadid) remoteVars.data_copy=0; remoteVars.evBufferOffset=0; setAgentState(RUNNING); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); while(1) { + pthread_mutex_lock(&remoteVars.sendqueue_mutex); if(!remoteVars.client_connected) { @@ -1508,6 +1542,7 @@ void *send_frame_thread (void *threadid) #endif /* XORG_VERSION_CURRENT */ shutdown(remoteVars.clientsock, SHUT_RDWR); close(remoteVars.clientsock); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); break; } @@ -1535,12 +1570,13 @@ void *send_frame_thread (void *threadid) 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; + struct OutputChunk* chunk=remoteVars.selstruct.firstOutputChunk; + remoteVars.selstruct.firstOutputChunk=chunk->next; if(!remoteVars.selstruct.firstOutputChunk) { remoteVars.selstruct.lastOutputChunk=NULL; } + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); //send chunk send_output_selection(chunk); @@ -1551,9 +1587,28 @@ void *send_frame_thread (void *threadid) } // EPHYR_DBG(" REMOVE CHUNK %p %p %p", remoteVars.selstruct.firstOutputChunk, remoteVars.selstruct.lastOutputChunk, chunk); free(chunk); + pthread_mutex_lock(&remoteVars.sendqueue_mutex); } + //check if we need to request the selection from client + if(remoteVars.selstruct.requestSelection[PRIMARY] || remoteVars.selstruct.requestSelection[CLIPBOARD]) + { + for(r=PRIMARY; r<=CLIPBOARD; ++r) + { + if(remoteVars.selstruct.requestSelection[r]) + { + remoteVars.selstruct.requestSelection[r]=FALSE; + + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); + //send request for selection + request_selection_from_client(r); + + 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 */ @@ -1563,6 +1618,7 @@ void *send_frame_thread (void *threadid) remoteVars.firstCursor=remoteVars.firstCursor->next; else remoteVars.firstCursor=remoteVars.lastCursor=0; + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); send_cursor(cframe); if(cframe->data) @@ -1572,9 +1628,11 @@ void *send_frame_thread (void *threadid) } else { + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } + pthread_mutex_lock(&remoteVars.sendqueue_mutex); if(remoteVars.first_sendqueue_element) { @@ -1614,16 +1672,19 @@ void *send_frame_thread (void *threadid) uint32_t frame_height=frame->height; /* unlock sendqueue for main thread */ + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); send_frame(frame_width, frame_height, x, y, crc, frame->regions); } else { EPHYR_DBG("Sending main image or screen update"); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); sendMainImageFromSendThread(width, height, x, y); } + pthread_mutex_lock(&remoteVars.sendqueue_mutex); if(frame) { @@ -1665,11 +1726,14 @@ void *send_frame_thread (void *threadid) send_deleted_cursors(); } + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); remoteVars.framenum++; } else { + + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } } @@ -1682,13 +1746,13 @@ void *send_frame_thread (void *threadid) /* warning! sendqueue_mutex should be locked by thread calling this function! */ void clear_output_selection(void) { - outputChunk* chunk=remoteVars.selstruct.firstOutputChunk; - outputChunk* prev_chunk; + struct OutputChunk* chunk=remoteVars.selstruct.firstOutputChunk; + struct OutputChunk* prev_chunk; while(chunk) { prev_chunk=chunk; - chunk=(outputChunk*)chunk->next; + chunk=chunk->next; if(prev_chunk->data) { free(prev_chunk->data); @@ -1852,6 +1916,7 @@ void setAgentState(int state) void disconnect_client(void) { EPHYR_DBG("DISCONNECTING CLIENT, DOING SOME CLEAN UP"); + pthread_mutex_lock(&remoteVars.sendqueue_mutex); remoteVars.client_connected=FALSE; setAgentState(SUSPENDED); @@ -1860,44 +1925,113 @@ void disconnect_client(void) freeCursors(); clear_output_selection(); pthread_cond_signal(&remoteVars.have_sendqueue_cond); + + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } +void unpack_current_chunk_to_buffer(struct InputBuffer* selbuff) +{ + //unpacking the data from current chunk to selbuff + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + +// EPHYR_DBG("inflate %d bytes to %d", selbuff->currentChunkCompressedSize, selbuff->currentChunkSize); + + stream.avail_in = selbuff->currentChunkCompressedSize; + stream.next_in = selbuff->currentChunkCompressedData; + stream.avail_out = selbuff->currentChunkSize; + stream.next_out = selbuff->data + selbuff->bytesReady; + + inflateInit(&stream); + inflate(&stream, Z_NO_FLUSH); + inflateEnd(&stream); + + if(stream.total_out != selbuff->currentChunkSize) + { + //something is wrong with extracting the data + EPHYR_DBG("WARNING!!!! extracting the data failed output has %d bytes instead of %d", (uint32_t)stream.total_out, selbuff->currentChunkSize); + } + + +// EPHYR_DBG("%s", selbuff->data + selbuff->bytesReady); + ///freeing compressed data + free(selbuff->currentChunkCompressedData); + selbuff->currentChunkCompressedData=NULL; + + selbuff->bytesReady+=selbuff->currentChunkSize; + selbuff->currentChunkCompressedSize=0; +} + void readInputSelectionBuffer(char* buff) { //read th rest of the chunk data - inputBuffer* selbuff = &remoteVars.selstruct.inSelection[remoteVars.selstruct.readingInputBuffer]; + struct InputBuffer* selbuff; + int leftToRead, l; - 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; + selbuff = &remoteVars.selstruct.inSelection[remoteVars.selstruct.readingInputBuffer]; + + //if the data is not compressed read it directly to the buffer + if(!selbuff->currentChunkCompressedSize) + { + leftToRead=selbuff->currentChunkSize - selbuff->currentChunkBytesReady; + l=(leftToRead < EVLENGTH)?leftToRead:EVLENGTH; + + //copy data to selection + memcpy(selbuff->data+selbuff->bytesReady, buff, l); + selbuff->bytesReady+=l; + selbuff->currentChunkBytesReady+=l; + 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; + } + } + else + { + //copy to the buffer for compressed data + leftToRead=selbuff->currentChunkCompressedSize - selbuff->currentChunkBytesReady; + l=(leftToRead < EVLENGTH)?leftToRead:EVLENGTH; + + //copy data to selection + memcpy(selbuff->currentChunkCompressedData+selbuff->currentChunkBytesReady, buff, l); + selbuff->currentChunkBytesReady+=l; + if(selbuff->currentChunkBytesReady==selbuff->currentChunkCompressedSize) + { + //selection chunk received completely, next event will start with event header + EPHYR_DBG("READY Selection Chunk, read %d",selbuff->currentChunkSize); + remoteVars.selstruct.readingInputBuffer=-1; + //unpack data to selection buffer + unpack_current_chunk_to_buffer(selbuff); + } + } 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; + //if state is requested we already own this selection after notify + if(selbuff->state != REQUESTED) + own_selection(remoteVars.selstruct.readingInputBuffer); + selbuff->state=COMPLETED; + //send notification event to interrupt sleeping selection thread + client_sel_data_notify(remoteVars.selstruct.readingInputBuffer); } //unlock selection + + pthread_mutex_unlock(&remoteVars.selstruct.inMutex); } @@ -1910,9 +2044,9 @@ void readInputSelectionHeader(char* buff) uint32_t size, totalSize; uint8_t destination, mime; - inputBuffer* selbuff = NULL; + struct InputBuffer* selbuff = NULL; BOOL firstChunk=FALSE, lastChunk=FALSE; - BOOL compressed=FALSE; + uint32_t compressedSize=0; uint32_t headerSize=10; uint32_t l; @@ -1923,20 +2057,21 @@ void readInputSelectionHeader(char* buff) //if client supports ext selection, read extended header if(remoteVars.selstruct.clientSupportsExetndedSelection) { - headerSize=17; + headerSize=20; firstChunk=*((uint8_t*)buff + 10); lastChunk=*((uint8_t*)buff + 11); - compressed=*((uint8_t*)buff + 12); - totalSize=*( (uint32_t*) (buff+13)); + compressedSize=*((uint32_t*) (buff + 12)); + totalSize=*( (uint32_t*) (buff+16)); } else { - compressed=FALSE; + compressedSize=0; lastChunk=firstChunk=TRUE; totalSize=size; } -// EPHYR_DBG("HAVE NEW INCOMING SELECTION Chunk: sel %d size %d mime %d",destination, size, mime); + EPHYR_DBG("HAVE NEW INCOMING SELECTION Chunk: sel %d size %d mime %d compressed size %d, total %d",destination, size, mime, compressedSize, totalSize); + //lock selection pthread_mutex_lock(&remoteVars.selstruct.inMutex); @@ -1944,6 +2079,28 @@ void readInputSelectionHeader(char* buff) remoteVars.selstruct.readingInputBuffer=-1; selbuff = &remoteVars.selstruct.inSelection[destination]; + + //we recieved selection notify from client + if(firstChunk && lastChunk && remoteVars.selstruct.clientSupportsExetndedSelection && (totalSize == 0) &&(size == 0)) + { + EPHYR_DBG("Selection notify from client for %d", destination); + if(selbuff->size && selbuff->data) + { + free(selbuff->data); + } + selbuff->size=0; + selbuff->mimeData=mime; + selbuff->data=0; + selbuff->bytesReady=0; + selbuff->state=NOTIFIED; +// own selection + own_selection(destination); + //unlock mutex + + + pthread_mutex_unlock(&remoteVars.selstruct.inMutex); + return; + } if(firstChunk) { //if it's first chunk, initialize our selection buffer @@ -1957,30 +2114,73 @@ void readInputSelectionHeader(char* buff) selbuff->bytesReady=0; } - //read the selection data from header - l=(size < EVLENGTH-headerSize)?size:(EVLENGTH-headerSize); - memcpy(selbuff->data+selbuff->bytesReady, buff+headerSize, l); + if(selbuff->currentChunkCompressedSize && selbuff->currentChunkCompressedData) + { + free(selbuff->currentChunkCompressedData); + } + selbuff->currentChunkCompressedData=NULL; + selbuff->currentChunkCompressedSize=0; - selbuff->bytesReady+=l; + //if compressed data will be read in buffer for compressed data + if(compressedSize) + { + selbuff->currentChunkCompressedData=malloc(compressedSize); + selbuff->currentChunkCompressedSize=compressedSize; + l=(compressedSize < EVLENGTH-headerSize)?compressedSize:(EVLENGTH-headerSize); + memcpy(selbuff->currentChunkCompressedData, buff+headerSize, l); + } + else + { + //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(!compressedSize) + { + if(selbuff->currentChunkBytesReady != selbuff->currentChunkSize) + { + // we didn't recieve complete chunk yet, next event will have data + remoteVars.selstruct.readingInputBuffer=destination; + } + } + else + { + if(selbuff->currentChunkBytesReady != selbuff->currentChunkCompressedSize) + { + // we didn't recieve complete chunk yet, next event will have data + remoteVars.selstruct.readingInputBuffer=destination; + } + else + { + //we read all compressed chunk data, unpack it to sel buff + unpack_current_chunk_to_buffer(selbuff); + } + + } + if(selbuff->size == selbuff->bytesReady) { //Selection is completed // EPHYR_DBG("READY INCOMING SELECTION for %d",destination); //own the selection - own_selection(destination); + //if state is requested we already own this selection after notify + if(selbuff->state != REQUESTED) + own_selection(destination); + selbuff->state=COMPLETED; + //send notification event to interrupt sleeping selection thread + client_sel_data_notify(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); } @@ -1994,6 +2194,8 @@ clientReadNotify(int fd, int ready, void *data) pthread_mutex_lock(&remoteVars.sendqueue_mutex); con=remoteVars.client_connected; + + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); if(!con) return; @@ -2176,12 +2378,14 @@ clientReadNotify(int fd, int ready, void *data) if(remoteVars.main_img && x+width <= remoteVars.main_img_width && y+height <= remoteVars.main_img_height ) { + pthread_mutex_unlock(&remoteVars.mainimg_mutex); add_frame(width, height, x, y, 0, 0); } else { EPHYR_DBG("UPDATE: skip request"); + pthread_mutex_unlock(&remoteVars.mainimg_mutex); } break; @@ -2195,17 +2399,18 @@ clientReadNotify(int fd, int ready, void *data) { int16_t ver=*((uint16_t*)buff+2); int16_t os=*((uint16_t*)buff+3); + set_client_version(ver, os); EPHYR_DBG("Client information: vesrion %d, os %d", ver, os); - remoteVars.client_version=ver; - if(os > OS_DARWIN) - { - EPHYR_DBG("Unsupported OS, assuming OS_LINUX"); - } - else - remoteVars.client_os=os; pthread_cond_signal(&remoteVars.have_sendqueue_cond); break; } + case DEMANDSELECTION: + { + int16_t sel=*((uint16_t*)buff+2); +// EPHYR_DBG("Client requesting selection %d", sel); + client_sel_request_notify(sel); + break; + } default: { EPHYR_DBG("UNSUPPORTED EVENT: %d",event_type); @@ -2227,6 +2432,22 @@ clientReadNotify(int fd, int ready, void *data) } +void set_client_version(uint16_t ver, uint16_t os) +{ + remoteVars.client_version=ver; + if(os > OS_DARWIN) + { + EPHYR_DBG("Unsupported OS, assuming OS_LINUX"); + } + else + remoteVars.client_os=os; + //clients version >= 1 supporting extended selection (sending big amount of data aín several chunks) + remoteVars.selstruct.clientSupportsExetndedSelection=(ver > 1); + //Linux clients supporting sending selection on demand (not sending data if not needed) + remoteVars.selstruct.clientSupportsOnDemandSelection=((ver > 1) && (os == OS_LINUX)); + +} + #if XORG_VERSION_CURRENT < 11900000 void pollEvents(void) { @@ -2234,8 +2455,10 @@ void pollEvents(void) struct pollfd fds[2]; int nfds = 1; BOOL con; + pthread_mutex_lock(&remoteVars.sendqueue_mutex); con=remoteVars.client_connected; + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); if(!con) return; @@ -2254,6 +2477,7 @@ unsigned int checkSocketConnection(OsTimerPtr timer, CARD32 time, void* args) { EPHYR_DBG("CHECKING ACCEPTED CONNECTION"); TimerFree(timer); + pthread_mutex_lock(&remoteVars.sendqueue_mutex); remoteVars.checkConnectionTimer=0; if(!remoteVars.client_connected) @@ -2265,6 +2489,7 @@ unsigned int checkSocketConnection(OsTimerPtr timer, CARD32 time, void* args) { EPHYR_DBG("CLIENT CONNECTED"); } + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); return 0; } @@ -2333,6 +2558,7 @@ void terminateServer(int exitStatus) if(remoteVars.selstruct.selThreadId) { pthread_cancel(remoteVars.selstruct.selThreadId); + remove_obsolete_incr_transactions(FALSE); if(remoteVars.selstruct.xcbConnection) { xcb_disconnect(remoteVars.selstruct.xcbConnection); @@ -2718,6 +2944,7 @@ struct cache_elem* add_cache_element(uint32_t crc, int32_t dx, int32_t dy, uint3 ++i; } } + pthread_mutex_unlock(&remoteVars.mainimg_mutex); el->rval/=numOfPix; @@ -2798,6 +3025,7 @@ void initFrameRegions(struct cache_elem* frame) uint32_t bestm_crc = 0; struct cache_elem* best_match = NULL; + pthread_mutex_lock(&remoteVars.sendqueue_mutex); best_match = find_best_match(frame, &match_val); @@ -2805,6 +3033,7 @@ void initFrameRegions(struct cache_elem* frame) { best_match->busy+=1; } + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); if(best_match && best_match->width>4 && best_match->height>4 && best_match->width * best_match->height > 100 ) @@ -2916,12 +3145,15 @@ void initFrameRegions(struct cache_elem* frame) } } /* if we didn't find any common regions and have best match element, mark it as not busy */ + + pthread_mutex_lock(&remoteVars.sendqueue_mutex); if(best_match && frame->source != best_match) { // EPHYR_DBG("Have best mutch but not common region"); best_match->busy-=1; } + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } @@ -3002,10 +3234,12 @@ void add_frame(uint32_t width, uint32_t height, int32_t x, int32_t y, uint32_t c struct cache_elem* frame = 0; struct sendqueue_element* element = NULL; + pthread_mutex_lock(&remoteVars.sendqueue_mutex); if(! (remoteVars.client_connected && remoteVars.client_initialized)) { /* don't have any clients connected, return */ + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); return; } @@ -3013,6 +3247,7 @@ void add_frame(uint32_t width, uint32_t height, int32_t x, int32_t y, uint32_t c if(crc==0) { /* sending main image */ + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); } else @@ -3031,6 +3266,7 @@ void add_frame(uint32_t width, uint32_t height, int32_t x, int32_t y, uint32_t c } frame->busy+=1; + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); /* if element is new find common regions and compress the data */ @@ -3041,6 +3277,7 @@ void add_frame(uint32_t width, uint32_t height, int32_t x, int32_t y, uint32_t c } } + pthread_mutex_lock(&remoteVars.sendqueue_mutex); /* add element in the queue for sending */ element=malloc(sizeof(struct sendqueue_element)); @@ -3060,6 +3297,7 @@ void add_frame(uint32_t width, uint32_t height, int32_t x, int32_t y, uint32_t c remoteVars.first_sendqueue_element=remoteVars.last_sendqueue_element=element; } pthread_cond_signal(&remoteVars.have_sendqueue_cond); + pthread_mutex_unlock(&remoteVars.sendqueue_mutex); /* on this point will be sent wakeup single to send mutex */ @@ -3104,6 +3342,7 @@ remote_paint_rect(KdScreenInfo *screen, * OK, here we assuming that XSERVERBPP is 4. If not, we'll have troubles * but it should work faster like this */ + pthread_mutex_lock(&remoteVars.mainimg_mutex); maxdiff=2; @@ -3169,6 +3408,7 @@ remote_paint_rect(KdScreenInfo *screen, ind+=XSERVERBPP; } } + pthread_mutex_unlock(&remoteVars.mainimg_mutex); @@ -3202,11 +3442,13 @@ uint32_t calculate_crc(uint32_t width, uint32_t height, int32_t dx, int32_t dy) { uint32_t crc=adler32(0L, Z_NULL, 0); + pthread_mutex_lock(&remoteVars.mainimg_mutex); for(uint32_t y=0; y< height;++y) { crc=adler32(crc,remoteVars.main_img+ ((y+dy)*remoteVars.main_img_width + dx)*XSERVERBPP, width*XSERVERBPP); } + pthread_mutex_unlock(&remoteVars.mainimg_mutex); return crc; } @@ -3249,6 +3491,7 @@ remote_screen_init(KdScreenInfo *screen, EPHYR_DBG("host_screen=%p x=%d, y=%d, wxh=%dx%d, buffer_height=%d", screen, x, y, width, height, buffer_height); + pthread_mutex_lock(&remoteVars.mainimg_mutex); @@ -3294,6 +3537,7 @@ remote_screen_init(KdScreenInfo *screen, *bytes_per_line = width*XSERVERBPP; *bits_per_pixel = 32; + pthread_mutex_unlock(&remoteVars.mainimg_mutex); return remoteVars.main_img; diff --git a/x2gokdriveremote.h b/x2gokdriveremote.h index 4249563..7735d0b 100644 --- a/x2gokdriveremote.h +++ b/x2gokdriveremote.h @@ -95,7 +95,8 @@ //FEATURE_VERSION is not cooresponding to actual version of server //it used to tell server which features are supported by server //Changes 0 - 1: sending and recieving client and OS version -#define FEATURE_VERSION 1 +//Changes 1 - 2: supporting extended selection and sending selection on demand +#define FEATURE_VERSION 2 #define EPHYR_WANT_DEBUG 1 @@ -130,7 +131,7 @@ fprintf(stderr, __FILE__ ":%d,%s() " x "\n", __LINE__, __func__, ##a) //always 4 #define XSERVERBPP 4 -enum msg_type{FRAME,DELETED, CURSOR, DELETEDCURSOR, SELECTION, SERVERVERSION}; +enum msg_type{FRAME,DELETED, CURSOR, DELETEDCURSOR, SELECTION, SERVERVERSION, DEMANDCLIENTSELECTION}; enum AgentState{STARTING, RUNNING, RESUMING, SUSPENDING, SUSPENDED, TERMINATING, TERMINATED}; enum Compressions{JPEG,PNG}; enum SelectionType{PRIMARY,CLIPBOARD}; @@ -160,6 +161,7 @@ enum OS_VERSION{OS_LINUX, OS_WINDOWS, OS_DARWIN}; #define UPDATE 8 #define SELECTIONEVENT 9 #define CLIENTVERSION 10 +#define DEMANDSELECTION 11 #define EVLENGTH 41 @@ -267,35 +269,59 @@ struct sendqueue_element struct sendqueue_element* next; }; +//chunk of data with output selection +struct OutputChunk +{ + unsigned char* data; //data + uint32_t size; //size of chunk in B + enum SelectionMime mimeData; //UTF_STRING or PIXMAP (text or image) + uint32_t compressed_size; // if chunk is compressed the size of compressed data, otherwise 0 + 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 +}; + //input selection -typedef struct +struct InputBuffer { 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; + uint32_t currentChunkCompressedSize; //if chunk is compressed, size of compressed data + unsigned char* currentChunkCompressedData; //if chunk is compressed, compressed dat will be stored here 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; + enum {NOTIFIED, REQUESTED, COMPLETED} state; +}; +//requests which processing should be delayed till we get data from client +struct DelayedRequest +{ + xcb_selection_request_event_t *request; // request from X-client + xcb_selection_notify_event_t* event; // event which we are going to send to X-client + struct DelayedRequest* next; +}; -//chunk of data with output selection -typedef struct +//save running INCR transactions in this struct +struct IncrTransaction { - 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; + xcb_window_t requestor; + xcb_atom_t property; + xcb_atom_t target; + char* data; + uint32_t size; + uint32_t sentBytes; + xcb_timestamp_t timestamp; + struct IncrTransaction* next; +}; -typedef struct + +struct SelectionStructure { unsigned long selThreadId; //id of selection thread enum ClipboardMode selectionMode; //CLIP_NONE, CLIP_CLIENT, CLIP_SERVER, CLIP_BOTH @@ -306,17 +332,29 @@ typedef struct 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 + BOOL clientSupportsExetndedSelection; //if client supports extended selection - sending selection in several chunks for big size data + BOOL clientSupportsOnDemandSelection; //if client supports selection on demand - sending data only if client requests it 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 + struct OutputChunk* firstOutputChunk; //the first and last elements of the + struct OutputChunk* lastOutputChunk; //queue of selection chunks + xcb_atom_t best_atom[2]; //the best mime type for selection to request on demand sel request from client + BOOL requestSelection[2]; //selection thread will set it to TRUE if the selection need to be requested //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 + struct InputBuffer inSelection[2]; //PRIMARY an CLIPBOARD selection buffers + + //list of delayed requests + struct DelayedRequest* firstDelayedRequest; + struct DelayedRequest* lastDelayedRequest; + + //list of INCR transactions + struct IncrTransaction* firstIncrTransaction; + struct IncrTransaction* lastIncrTransaction; + pthread_mutex_t inMutex; //mutex for synchronization of incoming selection -}SelectionStructure; +}; struct _remoteHostVars @@ -400,11 +438,13 @@ struct _remoteHostVars BOOL client_connected; BOOL client_initialized; - SelectionStructure selstruct; + struct SelectionStructure selstruct; } RemoteHostVars; -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); +int send_selection_chunk(int sel, unsigned char* data, uint32_t length, uint32_t format, BOOL first, BOOL last, uint32_t compressed, uint32_t total); +int send_output_selection(struct OutputChunk* chunk); + +void set_client_version(uint16_t ver, uint16_t os); void readInputSelectionBuffer(char* buff); void readInputSelectionHeader(char* buff); @@ -430,6 +470,7 @@ void setAgentState(int state); void terminateServer(int exitStatus); +void unpack_current_chunk_to_buffer(struct InputBuffer* selbuff); unsigned char* image_compress(uint32_t image_width, uint32_t image_height, unsigned char* RGBA_buffer, uint32_t* compressed_size, int bpp, char* fname); @@ -477,4 +518,6 @@ void remote_paint_rect(KdScreenInfo *screen, int sx, int sy, int dx, int dy, int width, int height); +void request_selection_from_client(enum SelectionType selection); + #endif /* X2GOKDRIVE_REMOTE_H */ diff --git a/x2gokdriveselection.c b/x2gokdriveselection.c index e174aff..0a1f8d4 100644 --- a/x2gokdriveselection.c +++ b/x2gokdriveselection.c @@ -34,7 +34,7 @@ #include <stdlib.h> #include <string.h> #include <png.h> - +#include <zlib.h> #ifdef HAVE_CONFIG_H #include <dix-config.h> @@ -45,9 +45,37 @@ #endif #include "x2gokdriveselection.h" + +#define SELECTION_DELAY 30000 //timeout for selection operation +#define INCR_SIZE 256*1024 //size of part for incr selection incr selection + static struct _remoteHostVars *remoteVars = NULL; +//internal atoms +static xcb_atom_t ATOM_ATOM; static xcb_atom_t ATOM_CLIPBOARD; +static xcb_atom_t ATOM_TARGETS; +static xcb_atom_t ATOM_TIMESTAMP; +static xcb_atom_t ATOM_INCR; +static xcb_atom_t ATOM_XT_SELECTION; +static xcb_atom_t ATOM_QT_SELECTION; + + +//text atoms +static xcb_atom_t ATOM_UTF8_STRING; +static xcb_atom_t ATOM_TEXT_PLAIN_UTF; +static xcb_atom_t ATOM_STRING; +static xcb_atom_t ATOM_TEXT; +static xcb_atom_t ATOM_TEXT_PLAIN; + +//image atoms +static xcb_atom_t ATOM_IMAGE_PNG; +static xcb_atom_t ATOM_IMAGE_XPM; +static xcb_atom_t ATOM_IMAGE_JPG; +static xcb_atom_t ATOM_IMAGE_JPEG; +static xcb_atom_t ATOM_PIXMAP; +static xcb_atom_t ATOM_IMAGE_BMP; + uint32_t max_chunk(void) { @@ -57,8 +85,248 @@ uint32_t max_chunk(void) return 10*1024*1024/4; //10MB } +void init_atoms(void) +{ + //init intern atoms + + ATOM_ATOM=string_to_atom("ATOM"); + ATOM_CLIPBOARD=string_to_atom("CLIPBOARD"); + ATOM_TARGETS=string_to_atom("TARGETS"); + ATOM_TIMESTAMP=string_to_atom("TIMESTAMP"); + ATOM_INCR=string_to_atom("INCR"); + + ATOM_XT_SELECTION=string_to_atom("_XT_SELECTION_0"); + ATOM_QT_SELECTION=string_to_atom("_QT_SELECTION"); + + + ATOM_UTF8_STRING=string_to_atom("UTF8_STRING"); + ATOM_TEXT_PLAIN_UTF=string_to_atom("text/plain;charset=utf-8"); + ATOM_STRING=string_to_atom("STRING"); + ATOM_TEXT=string_to_atom("TEXT"); + ATOM_TEXT_PLAIN=string_to_atom("text/plain"); + + ATOM_IMAGE_PNG=string_to_atom("image/png"); + ATOM_IMAGE_XPM=string_to_atom("image/xpm"); + ATOM_IMAGE_JPG=string_to_atom("image/jpg"); + ATOM_IMAGE_JPEG=string_to_atom("image/jpeg"); + ATOM_PIXMAP=string_to_atom("PIXMAP"); + ATOM_IMAGE_BMP=string_to_atom("image/bmp"); +} + +struct DelayedRequest* discard_delayed_request(struct DelayedRequest* d, struct DelayedRequest* prev) +{ + //this function finalizing delayed request and destroys XCB request and event + //removing the request from list and returning the pointer of the next element for iteration + struct DelayedRequest* next=d->next; + xcb_send_event(remoteVars->selstruct.xcbConnection, FALSE, d->request->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)d->event); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(d->event); + free((xcb_generic_event_t *)(d->request)); + + //remove element from list + if(prev) + prev->next=next; + + if(d==remoteVars->selstruct.firstDelayedRequest) + remoteVars->selstruct.firstDelayedRequest=next; + + if(d==remoteVars->selstruct.lastDelayedRequest) + remoteVars->selstruct.lastDelayedRequest=prev; + + free(d); + + return next; +} + +void destroy_incr_transaction(struct IncrTransaction* tr, struct IncrTransaction* prev) +{ + //destroy incr transaction + struct IncrTransaction* next=tr->next; + + const uint32_t mask[] = { XCB_EVENT_MASK_NO_EVENT }; + //don't resive property notify events for this window anymore + xcb_change_window_attributes(remoteVars->selstruct.xcbConnection, tr->requestor, + XCB_CW_EVENT_MASK, mask); + xcb_flush(remoteVars->selstruct.xcbConnection); + + //free data + free(tr->data); + + //remove element from list + if(prev) + prev->next=next; + + if(tr==remoteVars->selstruct.firstIncrTransaction) + remoteVars->selstruct.firstIncrTransaction=next; + + if(tr==remoteVars->selstruct.lastIncrTransaction) + remoteVars->selstruct.lastIncrTransaction=prev; + + free(tr); +} + + + +BOOL check_req_sanity(xcb_selection_request_event_t* req) +{ + //check if the requested mime can be delivered + //inmutex should be locked from calling function + + enum SelectionType sel=selection_from_atom(req->selection); + 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)) + { + return TRUE; + } + else + { + EPHYR_DBG("Unsupported property requested %d", req->target); + return FALSE; + } + } + else + { + if(!is_image_atom(req->target)) + { + EPHYR_DBG("Unsupported property requested %d", req->target); + return FALSE; + } + return TRUE; + } +} + + +void remove_obsolete_incr_transactions( BOOL checkTs) +{ + //remove_obsolete_incr_transactions + //if checkTS true, check timestamp and destroy only if ts exceed delay + + struct DelayedRequest* prev=NULL; + struct DelayedRequest* d=remoteVars->selstruct.firstDelayedRequest; + while(d) + { + if(!checkTs || (currentTime.milliseconds > (d->request->time + SELECTION_DELAY))) + { + d=discard_delayed_request(d, prev); + } + else + { + prev=d; + d=d->next; + } + } +} + + +void process_delayed_requests(void) +{ + //process delayed requests + + enum SelectionType selection; + + struct DelayedRequest* prev=NULL; + struct DelayedRequest* d=remoteVars->selstruct.firstDelayedRequest; + while(d) + { + selection = selection_from_atom( d->request->selection); + if(currentTime.milliseconds > (d->request->time + SELECTION_DELAY)) + { + EPHYR_DBG("timeout selection: %d",selection); + d=discard_delayed_request(d, prev); + continue; + } + pthread_mutex_lock(&remoteVars->selstruct.inMutex); + if(!remoteVars->selstruct.inSelection[selection].owner) + { + + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + EPHYR_DBG("we are not owner of requested selection %d",selection); + //we are not anymore owners of this selection + d=discard_delayed_request(d, prev); + continue; + } + if(remoteVars->selstruct.inSelection[selection].timestamp > d->request->time ) + { + + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + EPHYR_DBG("selection request for %d is too old", selection); + //requested selection is older than the current one + d=discard_delayed_request(d, prev); + continue; + } + if(!check_req_sanity(d->request)) + { + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); +// EPHYR_DBG("can't convert selection %d to requested myme type %d",selection, d->request->property); + //our selection don't support requested mime type + d=discard_delayed_request(d, prev); + continue; + } + if(remoteVars->selstruct.inSelection[selection].state != COMPLETED) + { + + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + //we don't have the data yet + prev=d; + d=d->next; + continue; + } + //data is ready, send data to requester and discard the request + //inMutex need be locked for sending data + d->event->property=send_data(d->request); + + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + d=discard_delayed_request(d, prev); + } +} + +void process_incr_transaction_property(xcb_property_notify_event_t * pn) +{ + //process incr transactions + + struct IncrTransaction* prev=NULL; + struct IncrTransaction* tr=remoteVars->selstruct.firstIncrTransaction; + uint32_t left, sendingBytes; + while(tr) + { + if((tr->requestor == pn->window) && (tr->property == pn->atom ) && ( pn->state == XCB_PROPERTY_DELETE) ) + { + //requestor ready for the new portion of data + left=tr->size-tr->sentBytes; + if(!left) + { +// EPHYR_DBG("all INCR data sent to %d",tr->requestor); + //all data sent, sending NULL data and destroying transaction + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, tr->requestor, tr->property, + tr->target, 8, 0, NULL); + xcb_flush(remoteVars->selstruct.xcbConnection); + destroy_incr_transaction(tr, prev); + return; + } + sendingBytes=(INCR_SIZE< left)?INCR_SIZE:left; + + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, tr->requestor, tr->property, + tr->target, 8, sendingBytes, tr->data + tr->sentBytes); + xcb_flush(remoteVars->selstruct.xcbConnection); + tr->sentBytes+=sendingBytes; + tr->timestamp=currentTime.milliseconds; + return; + } + prev=tr; + tr=tr->next; + } + //notify event doesn't belong to any of started incr transactions or it's notification for new property + return; +} + + + int own_selection(enum SelectionType selection) { + //owning selection + //remoteVars.selstruct.inMutex locked in the calling function xcb_atom_t sel=XCB_ATOM_PRIMARY; if(remoteVars->selstruct.selectionMode == CLIP_NONE || remoteVars->selstruct.selectionMode == CLIP_SERVER) { @@ -72,9 +340,12 @@ int own_selection(enum SelectionType selection) 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; + remoteVars->selstruct.inSelection[selection].timestamp=currentTime.milliseconds; + +// EPHYR_DBG("own selection %d", currentTime.milliseconds); return 0; } + void selection_init(struct _remoteHostVars *obj) { remoteVars=obj; @@ -82,24 +353,23 @@ void selection_init(struct _remoteHostVars *obj) } -xcb_atom_t atom(const char* name) +xcb_atom_t string_to_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; - 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); } + EPHYR_DBG("The %s atom has ID %u", name, a); return a; } -char *atom_name(xcb_atom_t xatom) +char *atom_to_string(xcb_atom_t xatom) { //get name for atom, don't forget to free return value char* name; @@ -120,13 +390,10 @@ char *atom_name(xcb_atom_t xatom) return name; } -static xcb_atom_t target_has_atom(xcb_atom_t* list, size_t size, const char *name) +static xcb_atom_t target_has_atom(xcb_atom_t* list, size_t size, xcb_atom_t a) { //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; for (i = 0;i < size;i++) { @@ -136,31 +403,24 @@ static xcb_atom_t target_has_atom(xcb_atom_t* list, size_t size, const char *nam return 0; } -static int is_string_atom( xcb_atom_t at) +int is_string_atom( xcb_atom_t at) { //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; + if((at==ATOM_UTF8_STRING) || (at==ATOM_TEXT_PLAIN_UTF) || + (at==ATOM_STRING) || (at==ATOM_TEXT) || (at==ATOM_TEXT_PLAIN)) + return 1; return 0; } -static int is_image_atom( xcb_atom_t at) +int is_image_atom( xcb_atom_t at) { //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")) + if( at == ATOM_IMAGE_PNG || + at == ATOM_IMAGE_XPM || + at == ATOM_IMAGE_JPEG || + at == ATOM_IMAGE_JPG || + at == ATOM_PIXMAP || + at == ATOM_IMAGE_BMP) return 1; return 0; } @@ -170,59 +430,59 @@ static xcb_atom_t best_atom_from_target(xcb_atom_t* list, size_t size) //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"))) + if((a=target_has_atom(list, size, ATOM_UTF8_STRING))) { // EPHYR_DBG("selecting mime type UTF8_STRING"); return a; } - if((a=target_has_atom(list, size, "text/plain;charset=utf-8"))) + if((a=target_has_atom(list, size, ATOM_TEXT_PLAIN_UTF))) { // EPHYR_DBG("selecting mime type text/plain;charset=utf-8"); return a; } - if((a=target_has_atom(list, size, "STRING"))) + if((a=target_has_atom(list, size, ATOM_STRING))) { // EPHYR_DBG( "selecting mime type STRING"); return a; } - if((a=target_has_atom(list, size, "TEXT"))) + if((a=target_has_atom(list, size, ATOM_TEXT))) { // EPHYR_DBG( "selecting mime type TEXT"); return a; } - if((a=target_has_atom(list, size, "text/plain"))) + if((a=target_has_atom(list, size, ATOM_TEXT_PLAIN))) { // EPHYR_DBG( "selecting mime type text/plain"); return a; } //selecting loseless formats first - if((a=target_has_atom(list, size, "image/png"))) + if((a=target_has_atom(list, size, ATOM_IMAGE_PNG))) { // EPHYR_DBG( "selecting mime type image/png"); return a; } - if((a=target_has_atom(list, size, "image/xpm"))) + if((a=target_has_atom(list, size, ATOM_IMAGE_XPM))) { // EPHYR_DBG( "selecting mime type image/xpm"); return a; } - if((a=target_has_atom(list, size, "PIXMAP"))) + if((a=target_has_atom(list, size, ATOM_PIXMAP))) { // EPHYR_DBG( "selecting mime type PIXMAP"); return a; } - if((a=target_has_atom(list, size, "image/bmp"))) + if((a=target_has_atom(list, size, ATOM_IMAGE_BMP))) { // EPHYR_DBG( "selecting mime type image/bmp"); return a; } - if((a=target_has_atom(list, size, "image/jpg"))) + if((a=target_has_atom(list, size, ATOM_IMAGE_JPG))) { // EPHYR_DBG( "selecting mime type image/jpg"); return a; } - if((a=target_has_atom(list, size, "image/jpeg"))) + if((a=target_has_atom(list, size, ATOM_IMAGE_JPEG))) { // EPHYR_DBG( "selecting mime type image/jpeg"); return a; @@ -245,13 +505,13 @@ void request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t void read_selection_property(xcb_atom_t selection, xcb_atom_t property) { - 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; + struct OutputChunk* chunk; + unsigned char* compressed_data; + uint32_t compressed_size; //request property which represents value of selection (data or mime types) @@ -282,7 +542,7 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) free(sprop); */ //need to read property incrementally - if(reply->type == atom("INCR")) + if(reply->type == ATOM_INCR) { unsigned int sz=*((unsigned int*) xcb_get_property_value(reply)); // EPHYR_DBG( "have incr property size: %d", sz); @@ -297,7 +557,7 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) return; } //we have supported mime types in reply - if(reply->type == atom( "ATOM")) + if(reply->type == ATOM_ATOM) { if(reply->format!=32) { @@ -328,7 +588,29 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) xcb_flush(remoteVars->selstruct.xcbConnection); if(data_atom) - request_selection_data( selection, data_atom, data_atom, 0); + { + if(remoteVars->client_os == OS_WINDOWS && selection_from_atom(selection) == PRIMARY) + { +// EPHYR_DBG("client doesn't support PRIMARY selection"); + } + else + { + if(remoteVars->selstruct.clientSupportsOnDemandSelection) + { + //don't ask for data yet, only send notification that we have a selection to client +// EPHYR_DBG("client supports onDemand selection, notify client"); + send_notify_to_client(selection, data_atom); + //save the data atom for possible data demand + remoteVars->selstruct.best_atom[selection_from_atom(selection)]=data_atom; + } + else + { + //request selection data +// EPHYR_DBG("client not supports onDemand selection, request data"); + request_selection_data( selection, data_atom, data_atom, 0); + } + } + } else { EPHYR_DBG( "there are no supported mime types in the target"); @@ -351,9 +633,9 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) fclose(cp);*/ - chunk=(outputChunk*) malloc(sizeof(outputChunk)); + chunk=malloc(sizeof(struct OutputChunk)); - memset((void*)chunk,0,sizeof(outputChunk)); + memset((void*)chunk,0,sizeof(struct OutputChunk)); if(xcb_get_property_value_length(reply)) { @@ -362,9 +644,23 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) memcpy(chunk->data, xcb_get_property_value(reply),chunk->size); } - chunk->compressed=FALSE; + chunk->compressed_size=0; if(is_string_atom(property)) + { chunk->mimeData=UTF_STRING; + //for text chunks > 1K using zlib compression if client supports it + if(remoteVars->selstruct.clientSupportsExetndedSelection && chunk->size > 1024) + { + compressed_data=zcompress(chunk->data, chunk->size, &compressed_size); + if(compressed_data && compressed_size) + { + free(chunk->data); + chunk->data=compressed_data; + chunk->compressed_size=compressed_size; +// EPHYR_DBG("compressed chunk from %d to %d", chunk->size, chunk->compressed_size); + } + } + } else chunk->mimeData=PIXMAP; @@ -410,7 +706,8 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) } 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); +// 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 @@ -420,11 +717,13 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) } else { - remoteVars->selstruct.lastOutputChunk->next=(struct outputChunk*)chunk; + remoteVars->selstruct.lastOutputChunk->next=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) @@ -444,10 +743,7 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) } else { - stype=atom_name(reply->type); - EPHYR_DBG("Not supported mime type: %s, %d",stype, reply->type); - if(stype) - free(stype); + EPHYR_DBG("Not supported mime type:%d",reply->type); } } if(reply) @@ -459,12 +755,65 @@ void read_selection_property(xcb_atom_t selection, xcb_atom_t property) } } +void send_notify_to_client(xcb_atom_t selection, xcb_atom_t mime) +{ + //creating the selection chunk with no data, which notifyes client that we have a selection + struct OutputChunk* chunk= malloc(sizeof(struct OutputChunk)); +// EPHYR_DBG("send selection notify to client"); + + + memset((void*)chunk,0,sizeof(struct OutputChunk)); + + if(is_string_atom(mime)) + chunk->mimeData=UTF_STRING; + else + chunk->mimeData=PIXMAP; + + chunk->selection=selection_from_atom(selection); + chunk->totalSize=0; + chunk->firstChunk=chunk->lastChunk=TRUE; + + 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=chunk; + remoteVars->selstruct.lastOutputChunk=chunk; + } + pthread_cond_signal(&remoteVars->have_sendqueue_cond); + + + pthread_mutex_unlock(&remoteVars->sendqueue_mutex); +} + void process_selection_notify(xcb_generic_event_t *e) { xcb_selection_notify_event_t *sel_event; + enum SelectionType selection; + // EPHYR_DBG("selection notify"); sel_event=(xcb_selection_notify_event_t *)e; + selection=selection_from_atom(sel_event->selection); + + if(sel_event->property== XCB_NONE && sel_event->target==XCB_NONE) + { + //have data demand from client + request_selection_data( sel_event->selection, remoteVars->selstruct.best_atom[selection],remoteVars->selstruct.best_atom[selection], 0); + return; + } + + if(sel_event->property== sel_event->selection && sel_event->target==sel_event->selection) + { + //have data ready from server. We don't need to do anything here. This event interrrupted the waiting procedure and the delayed requests are already processed +// EPHYR_DBG("Have DATA READY event"); + return; + } //processing the event which is reply for convert selection call @@ -481,7 +830,7 @@ void process_selection_notify(xcb_generic_event_t *e) // 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) { - EPHYR_DBG( "NO SELECTION"); +// EPHYR_DBG( "NO SELECTION"); } else { @@ -501,7 +850,9 @@ void process_property_notify(xcb_generic_event_t *e) pn = (xcb_property_notify_event_t *)e; if (pn->window != remoteVars->selstruct.clipWinId) { -// EPHYR_DBG("not our window"); + //this property doesn't belong to our window; + //let's check if it's not the property corresponding to one of incr transactions + process_incr_transaction_property(pn); return; } // EPHYR_DBG("property %d, state %d ", pn->atom, pn->state); @@ -552,10 +903,15 @@ void process_selection_owner_notify(xcb_generic_event_t *e) selection=selection_from_atom(notify_event->selection); //we are not owners of this selction anymore + + pthread_mutex_lock(&remoteVars->selstruct.inMutex); remoteVars->selstruct.inSelection[selection].owner=FALSE; + + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + //get supported mime types - request_selection_data( notify_event->selection, atom( "TARGETS"), atom( "TARGETS"), 0); + request_selection_data( notify_event->selection, ATOM_TARGETS, ATOM_TARGETS, 0); } @@ -588,7 +944,7 @@ void *selection_thread (void* id) values[0] = screen->white_pixel; values[1] = XCB_EVENT_MASK_PROPERTY_CHANGE; - ATOM_CLIPBOARD=atom("CLIPBOARD"); + init_atoms(); //create window which will recieve selection events and provide remote selection to X-clients xcb_create_window (remoteVars->selstruct.xcbConnection, @@ -627,6 +983,8 @@ void *selection_thread (void* id) // event loop while ((e = xcb_wait_for_event(remoteVars->selstruct.xcbConnection))) { + process_delayed_requests(); + remove_obsolete_incr_transactions(TRUE); response_type = e->response_type & ~0x80; //we notified that selection is changed in primary or clipboard @@ -648,7 +1006,11 @@ void *selection_thread (void* id) } else if (response_type == XCB_SELECTION_REQUEST) { - process_selection_request(e); + if(!process_selection_request(e)) + { + //we delayed this request, not deleteing the event yet + continue; + } } else { @@ -682,10 +1044,53 @@ void install_selection_callbacks(void) return; } -void process_selection_request(xcb_generic_event_t *e) +void client_sel_request_notify(enum SelectionType sel) { + //this function will be used from main thread to send the event which will + // notify selection thread that client want us to send data for selection sel + + xcb_selection_notify_event_t* event= (xcb_selection_notify_event_t*)calloc(32, 1); + event->response_type = XCB_SELECTION_NOTIFY; + event->requestor = remoteVars->selstruct.clipWinId; + event->selection = atom_from_selection(sel); + event->target = XCB_NONE; + event->property = XCB_NONE; + event->time = XCB_TIME_CURRENT_TIME; + + xcb_send_event(remoteVars->selstruct.xcbConnection, FALSE, remoteVars->selstruct.clipWinId, XCB_EVENT_MASK_NO_EVENT, (char*)event); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(event); +} + +void client_sel_data_notify(enum SelectionType sel) +{ + //this function will be used from main thread to send the event which will + // notify selection thread that client sent us data for selection sel + + xcb_selection_notify_event_t* event= (xcb_selection_notify_event_t*)calloc(32, 1); + event->response_type = XCB_SELECTION_NOTIFY; + event->requestor = remoteVars->selstruct.clipWinId; + event->selection = atom_from_selection(sel); + event->target = atom_from_selection(sel); + event->property = atom_from_selection(sel); + event->time = XCB_TIME_CURRENT_TIME; + + xcb_send_event(remoteVars->selstruct.xcbConnection, FALSE, remoteVars->selstruct.clipWinId, XCB_EVENT_MASK_NO_EVENT, (char*)event); + xcb_flush(remoteVars->selstruct.xcbConnection); + free(event); +} + + +BOOL process_selection_request(xcb_generic_event_t *e) +{ + //processing selection request. + //return true if the processing is finishing after return + //false if data is not ready and we are delaying processing of this request + //in this case calling function SHOULD NOT destroy the request neither event should not be destroyed + //we'll free this objects after processing of the request when the data is available + 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; @@ -702,45 +1107,35 @@ void process_selection_request(xcb_generic_event_t *e) if(property == XCB_NONE) property=target; - -/* - 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"); +// 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; + return TRUE; } 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"); +// 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; + return TRUE; } - if(req->target==atom("TIMESTAMP")) + if(req->target==ATOM_TIMESTAMP) { event->property=property; // EPHYR_DBG("requested TIMESTAMP"); @@ -748,7 +1143,7 @@ void process_selection_request(xcb_generic_event_t *e) property, XCB_ATOM_INTEGER, 32, 1, &remoteVars->selstruct.inSelection[sel].timestamp); } - else if(req->target==atom("TARGETS")) + else if(req->target==ATOM_TARGETS) { event->property=property; // EPHYR_DBG("requested TARGETS"); @@ -756,12 +1151,32 @@ void process_selection_request(xcb_generic_event_t *e) } else { - event->property=send_data(req); + if(remoteVars->selstruct.inSelection[sel].state==COMPLETED) + event->property=send_data(req); + else + { +// EPHYR_DBG("the data for %d is not ready yet",sel); + delay_selection_request(req,event); + + + pthread_mutex_unlock(&remoteVars->selstruct.inMutex); + return FALSE; + } } + + 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); + return TRUE; +} + +xcb_atom_t atom_from_selection(enum SelectionType sel) +{ + if(sel==PRIMARY) + return XCB_ATOM_PRIMARY; + return ATOM_CLIPBOARD; } enum SelectionType selection_from_atom(xcb_atom_t selection) @@ -780,42 +1195,42 @@ void send_mime_types(xcb_selection_request_event_t* req) //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; - if((a=atom("TARGETS"))) - targets[mcount++]=a; - if((a=atom("TIMESTAMP"))) - targets[mcount++]=a; + targets[mcount++]=ATOM_TARGETS; + targets[mcount++]=ATOM_TIMESTAMP; if(remoteVars->selstruct.inSelection[sel].mimeData==PIXMAP) { + + //only supporting PNG here at the moment + targets[mcount++]=ATOM_IMAGE_PNG; + +/* //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; + EPHYR_DBG("SENDING PNG ATOMS"); } else { + EPHYR_DBG("SENDING JPG ATOMS"); if((a=atom("image/jpg"))) targets[mcount++]=a; if((a=atom("image/jpeg"))) targets[mcount++]=a; - } + }*/ } else { - 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; +// EPHYR_DBG("SENDING STRING ATOMS"); + targets[mcount++]=ATOM_UTF8_STRING; + targets[mcount++]=ATOM_TEXT_PLAIN_UTF; + targets[mcount++]=ATOM_STRING; + targets[mcount++]=ATOM_TEXT; + targets[mcount++]=ATOM_TEXT_PLAIN; } xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, req->property, XCB_ATOM_ATOM, @@ -828,11 +1243,7 @@ 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"); if(remoteVars->selstruct.inSelection[sel].mimeData==UTF_STRING) { @@ -844,10 +1255,7 @@ xcb_atom_t send_data(xcb_selection_request_event_t* req) } else { - starget=atom_name(req->target); - EPHYR_DBG("unsupported property requested: %s",starget); - if(starget) - free(starget); + EPHYR_DBG("unsupported property requested: %d",req->target); return XCB_NONE; } } @@ -855,10 +1263,7 @@ xcb_atom_t send_data(xcb_selection_request_event_t* req) { if(!is_image_atom(req->target)) { - starget=atom_name(req->target); - EPHYR_DBG("unsupported property requested: %s",starget); - if(starget) - free(starget); + EPHYR_DBG("unsupported property requested: %d",req->target); return XCB_NONE; } /* @@ -871,23 +1276,17 @@ xcb_atom_t send_data(xcb_selection_request_event_t* req) //TODO: implement convertion between different image formats if(is_png(remoteVars->selstruct.inSelection[sel].data, remoteVars->selstruct.inSelection[sel].size)) { - if(req->target!=png) + if(req->target!=ATOM_IMAGE_PNG) { - starget=atom_name(req->target); - EPHYR_DBG("unsupported property requested: %s",starget); - if(starget) - free(starget); + EPHYR_DBG("unsupported property requested: %d",req->target); return XCB_NONE; } } else { - if((req->target!=jpg)&&(req->target!=jpeg)) + if((req->target!=ATOM_IMAGE_JPEG)&&(req->target!=ATOM_IMAGE_JPG)) { - starget=atom_name(req->target); - EPHYR_DBG("unsupported property requested: %s",starget); - if(starget) - free(starget); + EPHYR_DBG("unsupported property requested: %d",req->target); return XCB_NONE; } } @@ -902,36 +1301,166 @@ xcb_atom_t set_data_property(xcb_selection_request_event_t* req, unsigned char* //set data to window property //change when implemented - BOOL support_incr=FALSE; + BOOL support_incr=TRUE; - 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 ) + if(req->property == ATOM_XT_SELECTION|| req->property == ATOM_QT_SELECTION ) { EPHYR_DBG("property %d doesn't support INCR",req->property); support_incr=FALSE; } //check if we are sending incr - if(size < xcb_get_maximum_request_length(remoteVars->selstruct.xcbConnection) * 4 - 24) + if(!support_incr) { -// EPHYR_DBG( "sending %d bytes, property %d, target %d", size, req->property, req->target); + 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); + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target, + 8, size, (const void *)data); + + xcb_flush(remoteVars->selstruct.xcbConnection); + return req->property; + } + //the data is to big to sent in one property and requestor doesn't support INCR + EPHYR_DBG("data is too big"); + return XCB_NONE; + } + if(size < INCR_SIZE) + { + //if size is < 256K send in one property xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target, 8, size, (const void *)data); xcb_flush(remoteVars->selstruct.xcbConnection); return req->property; } + //sending INCR atom to let requester know that we are starting data incrementally +// EPHYR_DBG("starting INCR send of size %d for win ID %d" ,size, req->requestor); + xcb_change_property(remoteVars->selstruct.xcbConnection, XCB_PROP_MODE_REPLACE, req->requestor, req->property, + ATOM_INCR, 32, 1, (const void *)&size); - EPHYR_DBG("data is too big"); - return XCB_NONE; + start_incr_transaction(req->requestor, req->property, req->target, data, size); + + + xcb_flush(remoteVars->selstruct.xcbConnection); + return req->property; } + +void start_incr_transaction(xcb_window_t requestor, xcb_atom_t property, xcb_atom_t target, unsigned char* data, uint32_t size) +{ + //creating INCR transaction + //inmutex is locked from parent thread + + const uint32_t mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; + + struct IncrTransaction* tr=malloc( sizeof(struct IncrTransaction)); + tr->requestor=requestor; + tr->property=property; + tr->target=target; + tr->sentBytes=0; + tr->timestamp=currentTime.milliseconds; + tr->data=malloc(size); + tr->size=size; + tr->next=NULL; + memcpy(tr->data, data, size); + + //add new transaction to the list + if(!remoteVars->selstruct.firstIncrTransaction) + { + remoteVars->selstruct.firstIncrTransaction=remoteVars->selstruct.lastIncrTransaction=tr; + } + else + { + remoteVars->selstruct.lastIncrTransaction->next=tr; + remoteVars->selstruct.lastIncrTransaction=tr; + } + + + //we'll recive property change events for requestor window from now + xcb_change_window_attributes(remoteVars->selstruct.xcbConnection, requestor, + XCB_CW_EVENT_MASK, mask); +} + + BOOL is_png(unsigned char* data, uint32_t size) { if( size<8) return FALSE; return !png_sig_cmp(data, 0, 8); } + +unsigned char* zcompress(unsigned char *inbuf, uint32_t size, uint32_t* compress_size) +{ + //compressing the data with zlib + //return compressed data, storing the size of compressed data in compress_size + //caller function should chek result of compression and free the output buffer + + + //out buffer at least the size of input buffer + unsigned char* out=malloc(size); + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + + stream.avail_in = size; + stream.next_in = inbuf; + stream.avail_out = size; + stream.next_out = out; + + deflateInit(&stream, Z_BEST_COMPRESSION); + deflate(&stream, Z_FINISH); + deflateEnd(&stream); + + if(!stream.total_out || stream.total_out >= size) + { + EPHYR_DBG("zlib compression failed"); + free(out); + *compress_size=0; + return NULL; + } + *compress_size=stream.total_out; + return out; +} + +void delay_selection_request( xcb_selection_request_event_t *request, xcb_selection_notify_event_t* event) +{ + //delay the request for later processing when data will be ready + //inmutex is locked in the caller function + enum SelectionType sel=selection_from_atom(request->selection); + struct DelayedRequest* dr = malloc( sizeof(struct DelayedRequest)); + dr->event=event; + dr->request=request; + dr->next=NULL; + + //add new request to the queue + if(!remoteVars->selstruct.firstDelayedRequest) + { + remoteVars->selstruct.firstDelayedRequest=remoteVars->selstruct.lastDelayedRequest=dr; + } + else + { + remoteVars->selstruct.lastDelayedRequest->next=dr; + remoteVars->selstruct.lastDelayedRequest=dr; + } + + if(remoteVars->selstruct.inSelection[sel].state==NOTIFIED) + { + //if we didn't request the data yet, let's do it now +// EPHYR_DBG("requesting data"); + + pthread_mutex_lock(&remoteVars->sendqueue_mutex); + remoteVars->selstruct.requestSelection[sel] = TRUE; + pthread_cond_signal(&remoteVars->have_sendqueue_cond); + + + pthread_mutex_unlock(&remoteVars->sendqueue_mutex); + remoteVars->selstruct.inSelection[sel].state=REQUESTED; + + } +} + diff --git a/x2gokdriveselection.h b/x2gokdriveselection.h index 7ec8d8a..e144d20 100644 --- a/x2gokdriveselection.h +++ b/x2gokdriveselection.h @@ -36,18 +36,37 @@ void selection_init(struct _remoteHostVars *obj); void install_selection_callbacks(void); int own_selection(enum SelectionType selection); -xcb_atom_t atom(const char* name); -char *atom_name(xcb_atom_t xatom); +int is_string_atom( xcb_atom_t at); +int is_image_atom( xcb_atom_t at); + +xcb_atom_t string_to_atom(const char* name); +char *atom_to_string(xcb_atom_t xatom); +void init_atoms(void); + +void send_notify_to_client(xcb_atom_t selection, xcb_atom_t mime); 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); +BOOL 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 atom_from_selection(enum SelectionType sel); 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); +void client_sel_request_notify(enum SelectionType sel); +void client_sel_data_notify(enum SelectionType sel); BOOL is_png(unsigned char* data, uint32_t size); +void delay_selection_request( xcb_selection_request_event_t *reqest, xcb_selection_notify_event_t* event); +void process_delayed_requests(void); +struct DelayedRequest* discard_delayed_request(struct DelayedRequest* d, struct DelayedRequest* prev); +BOOL check_req_sanity(xcb_selection_request_event_t* req); +void start_incr_transaction(xcb_window_t requestor, xcb_atom_t property, xcb_atom_t target, unsigned char* data, uint32_t size); +void process_incr_transaction_property(xcb_property_notify_event_t * pn); +void destroy_incr_transaction(struct IncrTransaction* tr, struct IncrTransaction* prev); +void remove_obsolete_incr_transactions( BOOL checkTs); + +unsigned char* zcompress(unsigned char *inbuf, uint32_t size, uint32_t* compress_size); #endif /* X2GOKDRIVESELECTION_H */ -- Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gokdrive.git