This is an automated email from the git hooks/post-receive script. x2go pushed a change to branch master in repository x2gokdrive. from 90e1cdf reinit client version on new connection and awaka sending thread when client version recieved. new aed1bdf support sending and recieving selections on demand. Support reading and writing INCR properties. 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 | 1 + x2gokdriveremote.c | 352 ++++++++++++++++++---- x2gokdriveremote.h | 93 ++++-- x2gokdriveselection.c | 787 +++++++++++++++++++++++++++++++++++++++++--------- x2gokdriveselection.h | 25 +- 5 files changed, 1047 insertions(+), 211 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 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