+static void send_key(KeySym sym) {
+ check_not_disturbed();
+ XTestFakeKeyEvent(disp, keycode(sym),1, 10);
+ XTestFakeKeyEvent(disp, keycode(sym),0, 10);
+}
+static void mouse_1_updown_here(void) {
+ check_not_disturbed();
+ XTestFakeButtonEvent(disp,1,1, 50);
+ XTestFakeButtonEvent(disp,1,0, 50);
+}
+static void mouse_1_updown(int x, int y) {
+ check_not_disturbed();
+ int screen= XScreenNumberOfScreen(attr.screen);
+ int xpos, ypos;
+ translate_coords_toroot(x,y, &xpos,&ypos);
+ XTestFakeMotionEvent(disp, screen, xpos,ypos, 0);
+ mouse_1_updown_here();
+}
+
+static int pgupdown;
+
+static void send_pgup_many(void) {
+ int i;
+ for (i=0; i<25; i++) {
+ send_key(XK_Prior);
+ pgupdown--;
+ }
+ debugf("PAGING PageUp x %d\n",i);
+ sync_after_input();
+}
+static void send_pgdown(void) {
+ send_key(XK_Next);
+ pgupdown++;
+ debugf("PAGING PageDown\n");
+ sync_after_input();
+}
+static void send_pgdown_torestore(void) {
+ debugf("PAGING PageDown x %d\n", -pgupdown);
+ while (pgupdown < 0) {
+ send_key(XK_Next);
+ pgupdown++;
+ }
+ sync_after_input();
+}
+
+static void free_snapshot(Snapshot **io) {
+ free(*io);
+ *io= 0;
+}
+
+#define SAMPLEMASK 0xfful
+
+typedef struct {
+ int lshift, rshift;
+} ShMask;
+
+static void compute_shift_mask(ShMask *sm, unsigned long ximage_mask) {
+ sm->lshift= 0;
+ sm->rshift= 0;
+
+ for (;;) {
+ if (ximage_mask <= (SAMPLEMASK>>1)) {
+ sm->lshift++; ximage_mask <<= 1;
+ } else if (ximage_mask > SAMPLEMASK) {
+ sm->rshift++; ximage_mask >>= 1;
+ } else {
+ break;
+ }
+ assert(!(sm->lshift && sm->rshift));
+ }
+ assert(sm->lshift < LONG_BIT);
+ assert(sm->rshift < LONG_BIT);
+ debugf("SHIFTMASK %p={.lshift=%d, .rshift=%d} image_mask=%lx\n",
+ sm, sm->lshift, sm->rshift, ximage_mask);
+}
+
+static void rtimestamp(double *t, const char *wh) {
+ double n= timestamp();
+ debugf("PAGING INTERVAL %f %s\n", n-*t, wh);
+ *t= n;
+}
+
+static void snapshot(Snapshot **output) {
+ XImage *im_use, *im_free=0;
+
+ ShMask shiftmasks[3];
+
+ debugf("PAGING snapshot\n");
+
+ double begin= timestamp();
+ if (shmim) {
+ rtimestamp(&begin, "XShmGetImage before");
+ xassert( XShmGetImage(disp,id,shmim, 0,0, AllPlanes) );
+ rtimestamp(&begin, "XShmGetImage");
+
+ size_t dsz= shmim->bytes_per_line * shmim->height;
+ im_use= im_free= mmalloc(sizeof(*im_use) + dsz);
+ *im_free= *shmim;
+ im_free->data= (void*)(im_free+1);
+ memcpy(im_free->data, shmim->data, dsz);
+ rtimestamp(&begin, "mmalloc/memcpy");
+ } else {
+ rtimestamp(&begin, "XGetImage before");
+ xassert( im_use= im_free=
+ XGetImage(disp,id, 0,0, wwidth,wheight, AllPlanes, ZPixmap) );
+ rtimestamp(&begin, "XGetImage");
+ }
+
+#define COMPUTE_SHIFT_MASK(ix, rgb) \
+ compute_shift_mask(&shiftmasks[ix], im_use->rgb##_mask)
+ COMPUTE_SHIFT_MASK(0, red);
+ COMPUTE_SHIFT_MASK(1, green);
+ COMPUTE_SHIFT_MASK(2, blue);
+
+ if (!*output)
+ *output= alloc_rgb_image(wwidth, wheight);
+
+ rtimestamp(&begin, "compute_shift_masks+alloc_rgb_image");
+
+ int x,y,i;
+ unsigned char *op= (*output)->data;
+ for (y=0; y<wheight; y++) {
+ if (im_use->xoffset == 0 &&
+ im_use->format == ZPixmap &&
+ im_use->byte_order == LSBFirst &&
+ im_use->depth == 24 &&
+ im_use->bits_per_pixel == 32 &&
+ im_use->red_mask == 0x0000ffU &&
+ im_use->green_mask == 0x00ff00U &&
+ im_use->blue_mask == 0xff0000U) {
+ const char *p= im_use->data + y * im_use->bytes_per_line;
+// debugf("optimised copy y=%d",y);
+ for (x=0; x<wwidth; x++) {
+ *op++ = *p++;
+ *op++ = *p++;
+ *op++ = *p++;
+ p++;
+ }
+ } else {
+ for (x=0; x<wwidth; x++) {
+ long xrgb= XGetPixel(im_use,x,y);
+ for (i=0; i<3; i++) {
+ unsigned long sample=
+ ((xrgb << shiftmasks[i].lshift) >> shiftmasks[i].rshift)
+ & SAMPLEMASK;
+ *op++= sample;
+ }
+ }
+ }
+ }
+
+ rtimestamp(&begin,"w*h*XGetPixel");
+ if (im_free)
+ XDestroyImage(im_free);
+
+ rtimestamp(&begin,"XDestroyImage");
+ check_not_disturbed();
+
+ debugf("PAGING snapshot done.\n");
+}
+
+static int identical(const Snapshot *a, const Snapshot *b) {
+ if (!(a->w == b->w &&
+ a->h == b->h))
+ return 0;
+
+ int compare_to= a->h;
+ if (max_relevant_y>=0 && compare_to > max_relevant_y)
+ compare_to= max_relevant_y;
+
+ return !memcmp(a->data, b->data, a->w * 3 * compare_to);
+}
+
+static void wait_for_stability(Snapshot **output,
+ const Snapshot *previously,
+ void (*with_keypress)(void),
+ const char *fmt, ...)
+ FMT(4,5);
+
+static void wait_for_stability(Snapshot **output,
+ const Snapshot *previously,
+ void (*with_keypress)(void),
+ const char *fmt, ...) {
+ va_list al;
+ va_start(al,fmt);
+
+ Snapshot *last=0;
+ int nidentical=0;
+ /* waits longer if we're going to return an image identical to previously
+ * if previously==0, all images are considered identical to it */
+
+ char *doing;
+ sysassert( vasprintf(&doing,fmt,al) >=0 );
+
+ debugf("PAGING wait_for_stability"
+ " last_input=%f previously=%p `%s'\n",
+ last_input, previously, doing);
+
+ double min_interval= 25000; /*us*/
+ for (;;) {
+ progress_spinner("%s",doing);
+
+ double since_last_input= timestamp() - last_input;
+ double this_interval= min_interval - since_last_input;
+
+ if (this_interval >= 0)
+ usleep(this_interval);
+
+ snapshot(output);
+
+ if (!last) {
+ debugf("PAGING wait_for_stability first...\n");
+ last=*output; *output=0;
+ } else if (!identical(*output,last)) {
+ debugf("PAGING wait_for_stability changed...\n");
+ free_snapshot(&last); last=*output; *output=0;
+ nidentical=0;
+ if (!with_keypress) {
+ min_interval *= 3.0;
+ min_interval += 0.5;
+ }
+ } else {
+ nidentical++;
+ int threshold=
+ !previously ? 3 :
+ identical(*output,previously) ? 5
+ : 1;
+ debugf("PAGING wait_for_stability nidentical=%d threshold=%d\n",
+ nidentical, threshold);
+ if (nidentical >= threshold)
+ break;
+
+ min_interval += 0.5;
+ min_interval *= 2.0;
+ }
+
+ if (with_keypress)
+ with_keypress();
+ }
+
+ free_snapshot(&last);
+ free(doing);
+ debugf("PAGING wait_for_stability done.\n");
+ va_end(al);
+}