+ unsigned bd, depth;
+ int rxpos, rypos;
+
+ xassert( XGetGeometry(disp,attr.root, &dummy, &rxpos,&rypos,
+ &rwidth, &rheight,
+ &bd,&depth) );
+
+ translate_coords_toroot(0,0, &onroot.tl.x,&onroot.tl.y);
+ translate_coords_toroot(wwidth-1,wheight-1, &onroot.br.x,&onroot.br.y);
+ if (!(onroot.tl.x >= 0 &&
+ onroot.tl.y >= 0 &&
+ onroot.br.x < rwidth &&
+ onroot.br.y < rheight))
+ fatal("YPP client window is not entirely on the screen.");
+}
+
+static void check_not_disturbed(void) {
+ XEvent ev;
+ int r;
+
+ for (;;) {
+ r= XCheckMaskEvent(disp, ~0, &ev);
+ if (r==False) return;
+
+ switch (ev.type) {
+ case VisibilityNotify:
+ if (ev.xvisibility.state != VisibilityUnobscured)
+ fatal("YPP client window has become obscured.");
+ break;
+ case ConfigureNotify:
+ check_client_window_all_on_screen();
+ break;
+ case FocusOut:
+ fatal("Focus left YPP client window.");
+ break;
+ case FocusIn:
+ warning("focus entered YPP client window ?!");
+ break;
+ default:
+ fatal("Received unexpected X11 event (type code %d)!", ev.type);
+ }
+ }
+}
+
+static void send_key(KeySym sym) {
+ check_not_disturbed();
+ XTestFakeKeyEvent(disp, keycode(sym),1, 10);
+ XTestFakeKeyEvent(disp, keycode(sym),0, 10);
+}
+
+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) {
+ if (*io) XDestroyImage(*io);
+ *io= 0;
+}
+
+static void snapshot(Snapshot **output) {
+ free_snapshot(output);
+
+ debugf("PAGING snapshot\n");
+
+ timestamp();
+ *output= XGetImage(disp,id, 0,0, wwidth,wheight, AllPlanes, ZPixmap);
+ timestamp();
+
+ check_not_disturbed();
+
+ debugf("PAGING snapshot done.\n");
+}
+
+static int identical(const Snapshot *a, const Snapshot *b) {
+ if (!(a->width == b->width &&
+ a->height == b->height &&
+ a->bytes_per_line == b->bytes_per_line &&
+ a->format == b->format))
+ return 0;
+
+ int compare_to= a->height;
+ if (max_relevant_y && compare_to > max_relevant_y)
+ compare_to= max_relevant_y;
+
+ return !memcmp(a->data, b->data, a->bytes_per_line * 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;
+ /* waits longer if we're going to return an image identical to previously
+ * if previously==0, all images are considered identical to it */
+
+ debugf("PAGING wait_for_stability"
+ " last_input=%f previously=%p\n",
+ last_input, previously);
+
+ char *doing;
+ sysassert( vasprintf(&doing,fmt,al) >=0);
+
+ progress("%s",doing);
+
+ for (;;) {
+ double at_snapshot= timestamp();
+ double need_sleep= min_update_allowance - (at_snapshot - last_input);
+ if (need_sleep > 0) { delay(need_sleep); continue; }
+
+ snapshot(output);
+
+ if (!with_keypress &&
+ !(previously && identical(*output,previously))) {
+ debugf("PAGING wait_for_stability simple\n");
+ break;
+ }
+
+ if (last && identical(*output,last)) {
+ debugf("PAGING wait_for_stability stabilised\n");
+ break;
+ }
+
+ progress_spinner("%s",doing);
+
+ debugf("PAGING wait_for_stability retry\n");
+
+ free_snapshot(&last); last=*output; *output=0;
+
+ if (with_keypress)
+ with_keypress();
+
+ delay(0.5);
+ }
+
+ free_snapshot(&last);
+ free(doing);
+ debugf("PAGING wait_for_stability done.\n");
+ va_end(al);
+}
+
+static void raise_and_get_details(void) {
+ int evbase,errbase,majver,minver;
+ int wxpos, wypos;
+ unsigned bd,depth;
+
+ progress("raising and checking YPP client window...");
+
+ debugf("PAGING raise_and_get_details\n");
+
+ int xtest= XTestQueryExtension(disp, &evbase,&errbase,&majver,&minver);
+ if (!xtest) fatal("X server does not support the XTEST extension.");
+
+ xassert( XRaiseWindow(disp, id) );
+ /* in case VisibilityNotify triggers right away before we have had a
+ * change to raise; to avoid falsely detecting lowering in that case */