void initialize_xdamage(void) { sraRegionPtr *ptr; int i, nreg; if (! xdamage_present) { use_xdamage = 0; } if (xdamage_regions) { ptr = xdamage_regions; while (*ptr != NULL) { sraRgnDestroy(*ptr); ptr++; } free(xdamage_regions); xdamage_regions = NULL; } if (use_xdamage) { nreg = (xdamage_memory * NSCAN) + 2; xdamage_regions = (sraRegionPtr *) malloc(nreg * sizeof(sraRegionPtr)); for (i = 0; i < nreg; i++) { ptr = xdamage_regions+i; if (i == nreg - 1) { *ptr = NULL; } else { *ptr = sraRgnCreate(); sraRgnMakeEmpty(*ptr); } } /* set so will be 0 in first collect_xdamage call */ xdamage_ticker = -1; } }
void rfbNewFramebuffer(rfbScreenInfoPtr rfbScreen, char *framebuffer, int width, int height) { rfbClientIteratorPtr iterator; rfbClientPtr cl; if (width & 3) rfbErr("WARNING: New width (%d) is not a multiple of 4.\n", width); /* Update information in the rfbScreenInfo structure */ rfbScreen->width = width; rfbScreen->height = height; rfbScreen->frameBuffer = framebuffer; /* Adjust pointer position if necessary */ if (rfbScreen->cursorX >= width) rfbScreen->cursorX = width - 1; if (rfbScreen->cursorY >= height) rfbScreen->cursorY = height - 1; /* For each client: */ iterator = rfbGetClientIterator(rfbScreen); while ((cl = rfbClientIteratorNext(iterator)) != NULL) { /* Mark the screen contents as changed, and schedule sending NewFBSize message if supported by this client. */ LOCK(cl->updateMutex); sraRgnDestroy(cl->modifiedRegion); cl->modifiedRegion = sraRgnCreateRect(0, 0, width, height); sraRgnMakeEmpty(cl->copyRegion); cl->copyDX = 0; cl->copyDY = 0; if (cl->useNewFBSize) cl->newFBSizePending = TRUE; UNLOCK(cl->updateMutex); } rfbReleaseClientIterator(iterator); }
void rfbNewFramebuffer(rfbScreenInfoPtr screen, char *framebuffer, int width, int height, int bitsPerSample, int samplesPerPixel, int bytesPerPixel) { rfbPixelFormat old_format; rfbBool format_changed = FALSE; rfbClientIteratorPtr iterator; rfbClientPtr cl; /* Update information in the screenInfo structure */ old_format = screen->serverFormat; if (width & 3) rfbErr("WARNING: New width (%d) is not a multiple of 4.\n", width); screen->width = width; screen->height = height; screen->bitsPerPixel = screen->depth = 8*bytesPerPixel; screen->paddedWidthInBytes = width*bytesPerPixel; rfbInitServerFormat(screen, bitsPerSample); if (memcmp(&screen->serverFormat, &old_format, sizeof(rfbPixelFormat)) != 0) { format_changed = TRUE; } screen->frameBuffer = framebuffer; /* Adjust pointer position if necessary */ if (screen->cursorX >= width) screen->cursorX = width - 1; if (screen->cursorY >= height) screen->cursorY = height - 1; /* For each client: */ iterator = rfbGetClientIterator(screen); while ((cl = rfbClientIteratorNext(iterator)) != NULL) { /* Re-install color translation tables if necessary */ if (format_changed) screen->setTranslateFunction(cl); /* Mark the screen contents as changed, and schedule sending NewFBSize message if supported by this client. */ LOCK(cl->updateMutex); sraRgnDestroy(cl->modifiedRegion); cl->modifiedRegion = sraRgnCreateRect(0, 0, width, height); sraRgnMakeEmpty(cl->copyRegion); cl->copyDX = 0; cl->copyDY = 0; if (cl->useNewFBSize) cl->newFBSizePending = TRUE; TSIGNAL(cl->updateCond); UNLOCK(cl->updateMutex); } rfbReleaseClientIterator(iterator); }
void rfbScheduleCopyRegion(rfbScreenInfoPtr rfbScreen,sraRegionPtr copyRegion,int dx,int dy) { rfbClientIteratorPtr iterator; rfbClientPtr cl; iterator=rfbGetClientIterator(rfbScreen); while((cl=rfbClientIteratorNext(iterator))) { LOCK(cl->updateMutex); if(cl->useCopyRect) { sraRegionPtr modifiedRegionBackup; if(!sraRgnEmpty(cl->copyRegion)) { if(cl->copyDX!=dx || cl->copyDY!=dy) { /* if a copyRegion was not yet executed, treat it as a * modifiedRegion. The idea: in this case it could be * source of the new copyRect or modified anyway. */ sraRgnOr(cl->modifiedRegion,cl->copyRegion); sraRgnMakeEmpty(cl->copyRegion); } else { /* we have to set the intersection of the source of the copy * and the old copy to modified. */ modifiedRegionBackup=sraRgnCreateRgn(copyRegion); sraRgnOffset(modifiedRegionBackup,-dx,-dy); sraRgnAnd(modifiedRegionBackup,cl->copyRegion); sraRgnOr(cl->modifiedRegion,modifiedRegionBackup); sraRgnDestroy(modifiedRegionBackup); } } sraRgnOr(cl->copyRegion,copyRegion); cl->copyDX = dx; cl->copyDY = dy; /* if there were modified regions, which are now copied, * mark them as modified, because the source of these can be overlapped * either by new modified or now copied regions. */ modifiedRegionBackup=sraRgnCreateRgn(cl->modifiedRegion); sraRgnOffset(modifiedRegionBackup,dx,dy); sraRgnAnd(modifiedRegionBackup,cl->copyRegion); sraRgnOr(cl->modifiedRegion,modifiedRegionBackup); sraRgnDestroy(modifiedRegionBackup); if(!cl->enableCursorShapeUpdates) { /* * n.b. (dx, dy) is the vector pointing in the direction the * copyrect displacement will take place. copyRegion is the * destination rectangle (say), not the source rectangle. */ sraRegionPtr cursorRegion; int x = cl->cursorX - cl->screen->cursor->xhot; int y = cl->cursorY - cl->screen->cursor->yhot; int w = cl->screen->cursor->width; int h = cl->screen->cursor->height; cursorRegion = sraRgnCreateRect(x, y, x + w, y + h); sraRgnAnd(cursorRegion, cl->copyRegion); if(!sraRgnEmpty(cursorRegion)) { /* * current cursor rect overlaps with the copy region *dest*, * mark it as modified since we won't copy-rect stuff to it. */ sraRgnOr(cl->modifiedRegion, cursorRegion); } sraRgnDestroy(cursorRegion); cursorRegion = sraRgnCreateRect(x, y, x + w, y + h); /* displace it to check for overlap with copy region source: */ sraRgnOffset(cursorRegion, dx, dy); sraRgnAnd(cursorRegion, cl->copyRegion); if(!sraRgnEmpty(cursorRegion)) { /* * current cursor rect overlaps with the copy region *source*, * mark the *displaced* cursorRegion as modified since we * won't copyrect stuff to it. */ sraRgnOr(cl->modifiedRegion, cursorRegion); } sraRgnDestroy(cursorRegion); } } else { sraRgnOr(cl->modifiedRegion,copyRegion); } TSIGNAL(cl->updateCond); UNLOCK(cl->updateMutex); } rfbReleaseClientIterator(iterator); }
int VNCServer::Client::Run() { Log(">VNCServer::Client::Run [this:%p]\n",this); //Lock LOCK(cl->updateMutex); //Set new modified region cl->modifiedRegion = sraRgnCreateRect(0,0,cl->screen->width,cl->screen->height); //Until ended while (!wait.IsCanceled()) { Debug("-VNCServer::Client lopp [this:%p,state:%d,empty:%d]\n",this,cl->state,sraRgnEmpty(cl->requestedRegion)); //If connected, always require a FB Update Request (otherwise can crash.) if (cl->state == rfbClientRec::RFB_NORMAL && !sraRgnEmpty(cl->requestedRegion)) { //If reseted if (reset) { Log("-Reseting client\n"); //Reset message char msg[] = {16}; //Write it if (!Write(msg,sizeof(msg))) //WS closed break; //Clear reset flag reset = false; } /* //If the scren has been resized if (cl->newFBSizePending) { //Free region sraRgnDestroy(cl->modifiedRegion); //Set new modified region cl->modifiedRegion = sraRgnCreateRect(0,0,cl->screen->width,cl->screen->height); } */ //We need to update by default bool haveUpdate = true; /* Now, get the region we're going to update, and remove it from cl->modifiedRegion _before_ we send the update. That way, if anything that overlaps the region we're sending is updated, we'll be sure to do another update later. */ sraRegion* updateRegion = sraRgnCreateRgn(cl->modifiedRegion); //Check if we didn't already have a pending update if (!FB_UPDATE_PENDING(cl)) //Check it is inside requested region haveUpdate = !sraRgnEmpty(cl->modifiedRegion);//sraRgnAnd(updateRegion,cl->requestedRegion); //IF we are freezed if (freeze) //Don't send any change sraRgnMakeEmpty(updateRegion); //Clean modified region sraRgnMakeEmpty(cl->modifiedRegion); //Unlock region UNLOCK(cl->updateMutex); Debug("-VNCServer::Client update [this:%p,update:%d,mod:%d,req:%d]\n",this,haveUpdate,sraRgnEmpty(cl->modifiedRegion),sraRgnEmpty(cl->requestedRegion)); //If we have to update and not freezed if (haveUpdate) { Debug(">VNCServer::Client SendFramebufferUpdate [%p]\n",this); /* Now actually send the update. */ rfbIncrClientRef(cl); //LOCK(cl->sendMutex); rfbSendFramebufferUpdate(cl, updateRegion); //UNLOCK(cl->sendMutex); rfbDecrClientRef(cl); Debug("<VNCServer::Client SendFramebufferUpdate [%p]\n",this); } //Destroy region sraRgnDestroy(updateRegion); //Lock again LOCK(cl->updateMutex); } //Check if we have been cancelled if (wait.IsCanceled()) //Exit break; //Wait cond Debug("<VNCServer::Client going to sleep [this:%p]\n",this); WAIT(cl->updateCond,cl->updateMutex); Debug(">VNCServer::Client waked up from wait mutex[this:%p,isCanceled:%d]\n",this,wait.IsCanceled()); } //Unlock UNLOCK(cl->updateMutex); Log("<VNCServer::Client::Run [this:%p]\n",this); //OK return 0; }
int xdamage_hint_skip(int y) { static sraRegionPtr scanline = NULL; static sraRegionPtr tmpl_y = NULL; int fast_tmpl = 1; sraRegionPtr reg, tmpl; int ret, i, n, nreg; #ifndef NO_NCACHE static int ncache_no_skip = 0; static double last_ncache_no_skip = 0.0; static double last_ncache_no_skip_long = 0.0, ncache_fac = 0.25; #endif if (! xdamage_present || ! use_xdamage) { return 0; /* cannot skip */ } if (! xdamage_regions) { return 0; /* cannot skip */ } if (! scanline) { /* keep it around to avoid malloc etc, recreate */ scanline = sraRgnCreate(); } if (! tmpl_y) { tmpl_y = sraRgnCreateRect(0, 0, dpy_x, 1); } nreg = (xdamage_memory * NSCAN) + 1; #ifndef NO_NCACHE if (ncache > 0) { if (ncache_no_skip == 0) { double now = g_now; if (now > last_ncache_no_skip + 8.0) { ncache_no_skip = 1; } else if (now < last_bs_restore + 0.5) { ncache_no_skip = 1; } else if (now < last_su_restore + 0.5) { ncache_no_skip = 1; } else if (now < last_copyrect + 0.5) { ncache_no_skip = 1; } if (ncache_no_skip) { last_ncache_no_skip = dnow(); if (now > last_ncache_no_skip_long + 60.0) { ncache_fac = 2.0; last_ncache_no_skip_long = now; } else { ncache_fac = 0.25; } return 0; } } else { if (ncache_no_skip++ >= ncache_fac*nreg + 4) { ncache_no_skip = 0; } else { return 0; } } } #endif if (fast_tmpl) { sraRgnOffset(tmpl_y, 0, y); tmpl = tmpl_y; } else { tmpl = sraRgnCreateRect(0, y, dpy_x, y+1); } ret = 1; for (i=0; i<nreg; i++) { /* go back thru the history starting at most recent */ n = (xdamage_ticker + nreg - i) % nreg; reg = xdamage_regions[n]; if (reg == NULL) { continue; } if (sraRgnEmpty(reg)) { /* checking for emptiness is very fast */ continue; } sraRgnMakeEmpty(scanline); sraRgnOr(scanline, tmpl); if (sraRgnAnd(scanline, reg)) { ret = 0; break; } } if (fast_tmpl) { sraRgnOffset(tmpl_y, 0, -y); } else { sraRgnDestroy(tmpl); } if (0) fprintf(stderr, "xdamage_hint_skip: %d -> %d\n", y, ret); return ret; }
int collect_xdamage(int scancnt, int call) { #if HAVE_LIBXDAMAGE XDamageNotifyEvent *dev; XEvent ev; sraRegionPtr tmpregion; sraRegionPtr reg; static int rect_count = 0; int nreg, ccount = 0, dcount = 0, ecount = 0; static time_t last_rpt = 0; time_t now; int x, y, w, h, x2, y2; int i, dup, next = 0, dup_max = 0; #define DUPSZ 32 int dup_x[DUPSZ], dup_y[DUPSZ], dup_w[DUPSZ], dup_h[DUPSZ]; double tm, dt; int mark_all = 0, retries = 0, too_many = 1000, tot_ev = 0; RAWFB_RET(0) if (scancnt) {} /* unused vars warning: */ if (! xdamage_present || ! use_xdamage) { return 0; } if (! xdamage) { return 0; } if (! xdamage_base_event_type) { return 0; } if (! xdamage_regions) { return 0; } dtime0(&tm); nreg = (xdamage_memory * NSCAN) + 1; if (call == 0) { xdamage_ticker = (xdamage_ticker+1) % nreg; xdamage_direct_count = 0; reg = xdamage_regions[xdamage_ticker]; if (reg != NULL) { sraRgnMakeEmpty(reg); } } else { if (xdamage_ticker < 0) { xdamage_ticker = 0; } reg = xdamage_regions[xdamage_ticker]; } if (reg == NULL) { return 0; } X_LOCK; if (0) XFlush_wr(dpy); if (0) XEventsQueued(dpy, QueuedAfterFlush); come_back_for_more: while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) { /* * TODO max cut off time in this loop? * Could check QLength and if huge just mark the whole * screen. */ ecount++; tot_ev++; if (mark_all) { continue; } if (ecount == too_many) { int nqa = XEventsQueued(dpy, QueuedAlready); if (nqa >= too_many) { static double last_msg = 0.0; tmpregion = sraRgnCreateRect(0, 0, dpy_x, dpy_y); sraRgnOr(reg, tmpregion); sraRgnDestroy(tmpregion); if (dnow() > last_msg + xdamage_crazy_delay) { rfbLog("collect_xdamage: too many xdamage events %d+%d\n", ecount, nqa); last_msg = dnow(); } mark_all = 1; } } if (ev.type != xdamage_base_event_type + XDamageNotify) { break; } dev = (XDamageNotifyEvent *) &ev; if (dev->damage != xdamage) { continue; /* not ours! */ } x = dev->area.x; y = dev->area.y; w = dev->area.width; h = dev->area.height; /* * we try to manually remove some duplicates because * certain activities can lead to many 10's of dups * in a row. The region work can be costly and reg is * later used in xdamage_hint_skip loops, so it is good * to skip them if possible. */ dup = 0; for (i=0; i < dup_max; i++) { if (dup_x[i] == x && dup_y[i] == y && dup_w[i] == w && dup_h[i] == h) { dup = 1; break; } } if (dup) { dcount++; continue; } if (dup_max < DUPSZ) { next = dup_max; dup_max++; } else { next = (next+1) % DUPSZ; } dup_x[next] = x; dup_y[next] = y; dup_w[next] = w; dup_h[next] = h; /* translate if needed */ if (clipshift) { /* set coords relative to fb origin */ if (0 && rootshift) { /* * Note: not needed because damage is * relative to subwin, not rootwin. */ x = x - off_x; y = y - off_y; } if (clipshift) { x = x - coff_x; y = y - coff_y; } x2 = x + w; /* upper point */ x = nfix(x, dpy_x); /* place both in fb area */ x2 = nfix(x2, dpy_x+1); w = x2 - x; /* recompute w */ y2 = y + h; y = nfix(y, dpy_y); y2 = nfix(y2, dpy_y+1); h = y2 - y; if (w <= 0 || h <= 0) { continue; } } if (debug_xdamage > 2) { fprintf(stderr, "xdamage: -> event %dx%d+%d+%d area:" " %d dups: %d %s\n", w, h, x, y, w*h, dcount, (w*h > xdamage_max_area) ? "TOO_BIG" : ""); } record_desired_xdamage_rect(x, y, w, h); tmpregion = sraRgnCreateRect(x, y, x + w, y + h); sraRgnOr(reg, tmpregion); sraRgnDestroy(tmpregion); rect_count++; ccount++; } if (mark_all) { if (ecount + XEventsQueued(dpy, QueuedAlready) >= 3 * too_many && retries < 3) { retries++; XFlush_wr(dpy); usleep(20 * 1000); XFlush_wr(dpy); ecount = 0; goto come_back_for_more; } } /* clear the whole damage region for next time. XXX check */ if (call == 1) { XDamageSubtract(dpy, xdamage, None, None); } X_UNLOCK; if (tot_ev > 20 * too_many) { rfbLog("collect_xdamage: xdamage has gone crazy (screensaver or game?) ev: %d ret: %d\n", tot_ev, retries); rfbLog("collect_xdamage: disabling xdamage for %d seconds.\n", (int) xdamage_crazy_delay); destroy_xdamage_if_needed(); X_LOCK; XSync(dpy, False); while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) { ; } X_UNLOCK; xdamage_crazy_time = dnow(); } if (0 && xdamage_direct_count) { fb_push(); } dt = dtime(&tm); if ((debug_tiles > 1 && ecount) || (debug_tiles && ecount > 200) || debug_xdamage > 1) { fprintf(stderr, "collect_xdamage(%d): %.4f t: %.4f ev/dup/accept" "/direct %d/%d/%d/%d\n", call, dt, tm - x11vnc_start, ecount, dcount, ccount, xdamage_direct_count); } now = time(NULL); if (! last_rpt) { last_rpt = now; } if (now > last_rpt + 15) { double rat = -1.0; if (XD_tot) { rat = ((double) XD_skip)/XD_tot; } if (debug_tiles || debug_xdamage) { fprintf(stderr, "xdamage: == scanline skip/tot: " "%04d/%04d =%.3f rects: %d desired: %d\n", XD_skip, XD_tot, rat, rect_count, XD_des); } XD_skip = 0; XD_tot = 0; XD_des = 0; rect_count = 0; last_rpt = now; } #else if (0) scancnt++; /* compiler warnings */ if (0) call++; if (0) record_desired_xdamage_rect(0, 0, 0, 0); #endif return 0; }
int collect_non_X_xdamage(int x_in, int y_in, int w_in, int h_in, int call) { sraRegionPtr tmpregion; sraRegionPtr reg; static int rect_count = 0; int nreg, ccount = 0, dcount = 0, ecount = 0; static time_t last_rpt = 0; time_t now; double tm, dt; int x, y, w, h, x2, y2; if (call && debug_xdamage > 1) fprintf(stderr, "collect_non_X_xdamage: %d %d %d %d - %d / %d\n", x_in, y_in, w_in, h_in, call, use_xdamage); if (! use_xdamage) { return 0; } if (! xdamage_regions) { return 0; } dtime0(&tm); nreg = (xdamage_memory * NSCAN) + 1; if (call == 0) { xdamage_ticker = (xdamage_ticker+1) % nreg; xdamage_direct_count = 0; reg = xdamage_regions[xdamage_ticker]; if (reg != NULL) { sraRgnMakeEmpty(reg); } } else { if (xdamage_ticker < 0) { xdamage_ticker = 0; } reg = xdamage_regions[xdamage_ticker]; } if (reg == NULL) { return 0; } if (x_in < 0) { return 0; } x = x_in; y = y_in; w = w_in; h = h_in; /* translate if needed */ if (clipshift) { /* set coords relative to fb origin */ if (0 && rootshift) { /* * Note: not needed because damage is * relative to subwin, not rootwin. */ x = x - off_x; y = y - off_y; } if (clipshift) { x = x - coff_x; y = y - coff_y; } x2 = x + w; /* upper point */ x = nfix(x, dpy_x); /* place both in fb area */ x2 = nfix(x2, dpy_x+1); w = x2 - x; /* recompute w */ y2 = y + h; y = nfix(y, dpy_y); y2 = nfix(y2, dpy_y+1); h = y2 - y; if (w <= 0 || h <= 0) { return 0; } } if (debug_xdamage > 2) { fprintf(stderr, "xdamage: -> event %dx%d+%d+%d area:" " %d dups: %d %s reg: %p\n", w, h, x, y, w*h, dcount, (w*h > xdamage_max_area) ? "TOO_BIG" : "", (void *)reg); } record_desired_xdamage_rect(x, y, w, h); tmpregion = sraRgnCreateRect(x, y, x + w, y + h); sraRgnOr(reg, tmpregion); sraRgnDestroy(tmpregion); rect_count++; ccount++; if (0 && xdamage_direct_count) { fb_push(); } dt = dtime(&tm); if ((debug_tiles > 1 && ecount) || (debug_tiles && ecount > 200) || debug_xdamage > 1) { fprintf(stderr, "collect_non_X_xdamage(%d): %.4f t: %.4f ev/dup/accept" "/direct %d/%d/%d/%d\n", call, dt, tm - x11vnc_start, ecount, dcount, ccount, xdamage_direct_count); } now = time(NULL); if (! last_rpt) { last_rpt = now; } if (now > last_rpt + 15) { double rat = -1.0; if (XD_tot) { rat = ((double) XD_skip)/XD_tot; } if (debug_tiles || debug_xdamage) { fprintf(stderr, "xdamage: == scanline skip/tot: " "%04d/%04d =%.3f rects: %d desired: %d\n", XD_skip, XD_tot, rat, rect_count, XD_des); } XD_skip = 0; XD_tot = 0; XD_des = 0; rect_count = 0; last_rpt = now; } return 0; }
void rfbScheduleCopyRegion(rfbScreenInfoPtr rfbScreen,sraRegionPtr copyRegion,int dx,int dy) { rfbClientIteratorPtr iterator; rfbClientPtr cl; iterator=rfbGetClientIterator(rfbScreen); while((cl=rfbClientIteratorNext(iterator))) { LOCK(cl->updateMutex); if(cl->useCopyRect) { sraRegionPtr modifiedRegionBackup; if(!sraRgnEmpty(cl->copyRegion)) { if(cl->copyDX!=dx || cl->copyDY!=dy) { /* if a copyRegion was not yet executed, treat it as a * modifiedRegion. The idea: in this case it could be * source of the new copyRect or modified anyway. */ sraRgnOr(cl->modifiedRegion,cl->copyRegion); sraRgnMakeEmpty(cl->copyRegion); } else { /* we have to set the intersection of the source of the copy * and the old copy to modified. */ modifiedRegionBackup=sraRgnCreateRgn(copyRegion); sraRgnOffset(modifiedRegionBackup,-dx,-dy); sraRgnAnd(modifiedRegionBackup,cl->copyRegion); sraRgnOr(cl->modifiedRegion,modifiedRegionBackup); sraRgnDestroy(modifiedRegionBackup); } } sraRgnOr(cl->copyRegion,copyRegion); cl->copyDX = dx; cl->copyDY = dy; /* if there were modified regions, which are now copied, * mark them as modified, because the source of these can be overlapped * either by new modified or now copied regions. */ modifiedRegionBackup=sraRgnCreateRgn(cl->modifiedRegion); sraRgnOffset(modifiedRegionBackup,dx,dy); sraRgnAnd(modifiedRegionBackup,cl->copyRegion); sraRgnOr(cl->modifiedRegion,modifiedRegionBackup); sraRgnDestroy(modifiedRegionBackup); #if 0 /* TODO: is this needed? Or does it mess up deferring? */ /* while(!sraRgnEmpty(cl->copyRegion)) */ { { sraRegionPtr updateRegion = sraRgnCreateRgn(cl->modifiedRegion); sraRgnOr(updateRegion,cl->copyRegion); UNLOCK(cl->updateMutex); rfbSendFramebufferUpdate(cl,updateRegion); sraRgnDestroy(updateRegion); continue; } } #endif } else { sraRgnOr(cl->modifiedRegion,copyRegion); } UNLOCK(cl->updateMutex); } rfbReleaseClientIterator(iterator); }