chiark / gitweb /
69f097da0fc0ad1b1414e313c6657ea506b7df37
[ypp-sc-tools.web-live.git] / pctb / pages.c
1 /*
2   */
3
4 #include "ocr.h"
5
6 #include <X11/Xlib.h>
7 #include <X11/extensions/XTest.h>
8 #include <X11/keysym.h>
9
10 CanonImage *page_images[MAX_PAGES];
11 int npages;
12
13 static Window id;
14 static Display *disp;
15 static struct timeval tv_startup;
16
17 static KeyCode keycode(KeySym sym) {
18   return XKeysymToKeycode(disp,sym);
19 }
20
21 static void check_exitstatus(int st) {
22   eassert(WIFEXITED(st));
23   eassert(!WEXITSTATUS(st));
24 }
25
26 static void check_pclose(FILE *f, char *cmd) {
27   int r;
28   eassert(!ferror(f));
29   r= fgetc(f);  eassert(r==EOF);  eassert(feof(f));
30   r= pclose(f);  eassert(r>=0);  check_exitstatus(r);
31   free(cmd);
32 }
33
34 #if 0
35 static CanonImage *screenshot_now(void) {
36   char *cmd;
37   CanonImage *ci;
38   int r;
39   
40   r= asprintf(&cmd, "xwd -silent -id 0x%lx | xwdtopnm", (unsigned long)id);
41   eassert(r>=0);
42   FILE *f= popen(cmd,"r");  eassert(f);
43   ci= file_read_image(f);
44   check_pclose(f, cmd);
45   return ci;
46 }
47 #endif
48
49 static void screenshot_startup(void) {
50   int r;
51   disp= XOpenDisplay(0);  eassert(disp);
52   r= gettimeofday(&tv_startup,0);  eassert(!r);
53 }
54
55 #if 0
56 static CanonImage *single_page(void) {
57   int r;
58   r= XRaiseWindow(disp, id);  eassert(r);
59   r= XSync(disp, False);  eassert(r);
60   return screenshot_now();
61 }
62 #endif
63
64 /*---------- pager ----------*/
65
66 typedef struct {
67   size_t len;
68   unsigned char d[];
69 } Snapshot;
70
71 static size_t snapshot_alloc= 1024;
72 static double last_input;
73 static const double min_update_allowance= 0.5;
74
75 static double timestamp(void) {
76   struct timeval tv;
77   int r;
78   
79   r= gettimeofday(&tv,0);  eassert(!r);
80   double t= (tv.tv_sec - tv_startup.tv_sec) +
81             (tv.tv_usec - tv_startup.tv_usec) * 1e-6;
82   fprintf(stderr,"%f\n",t);
83   return t;
84 }
85 static void delay(double need_sleep) {
86   int r;
87   fprintf(stderr,"PAGING     delay %f\n",need_sleep);
88   r= usleep(need_sleep * 1e6);  eassert(!r);
89 }
90
91 static void sync_after_input(void) {
92   int r;
93   r= XSync(disp, False);  eassert(r);
94   last_input= timestamp();
95 }
96
97 static void send_key(KeySym sym) {
98   XTestFakeKeyEvent(disp, keycode(sym),1, 10);
99   XTestFakeKeyEvent(disp, keycode(sym),0, 10);
100 }
101
102 static void send_pgup_many(void) {
103   int i;
104   for (i=0; i<25; i++)
105     send_key(XK_Prior);
106   fprintf(stderr,"PAGING   PageUp x %d\n",i);
107   sync_after_input();
108 }
109 static void send_pgdown(void) {
110   send_key(XK_Next);
111   fprintf(stderr,"PAGING   PageDown\n");
112   sync_after_input();
113 }
114
115 static void snapshot(Snapshot **output) {
116   char *cmd;
117   int r;
118   
119   free(*output);  *output=0;
120
121   fprintf(stderr,"PAGING   snapshot\n");
122
123   r= asprintf(&cmd, "xwd -silent -id 0x%lx", (unsigned long)id); eassert(r>=0);
124   FILE *f= popen(cmd,"r"); eassert(f);
125
126   int need_alloc=1;
127   size_t used=0;
128   for (;;) {
129     size_t allow= snapshot_alloc - used;
130     if (!allow) {
131       snapshot_alloc <<= 1;
132       need_alloc= 1;
133       fprintf(stderr,"PAGING   snapshot   grow %ld\n", (long)snapshot_alloc);
134       continue;
135     }
136     if (need_alloc) {
137       *output= realloc(*output, sizeof(**output) + snapshot_alloc);
138       eassert(*output);
139       need_alloc= 0;
140     }
141     size_t got= fread((*output)->d, 1, allow, f);
142     if (got==0) break;
143     used += got;
144   }
145   check_pclose(f,cmd);
146
147   (*output)->len= used;
148   snapshot_alloc= used+1;
149   *output= realloc(*output, sizeof(**output) + snapshot_alloc);
150   eassert(*output);
151   
152   fprintf(stderr,"PAGING   snapshot len=%ld\n", (long)used);
153 }
154
155 static int identical(const Snapshot *a, const Snapshot *b) {
156   return !(memcmp(a,    b,    sizeof(*a)) ||
157            memcmp(a->d, b->d, a->len));
158 }
159
160 static void wait_for_stability(Snapshot **output,
161                                const Snapshot *previously,
162                                void (*with_keypress)(void)) {
163   Snapshot *last=0;
164   /* waits longer if we're going to return an image identical to previously
165    * if previously==0, all images are considered identical to it */
166
167   fprintf(stderr,"PAGING  wait_for_stability"
168           "  last_input=%f previously=%p\n",
169           last_input, previously);
170
171   for (;;) {
172     double at_snapshot= timestamp();
173     double need_sleep= min_update_allowance - (at_snapshot - last_input);
174     if (need_sleep > 0) { delay(need_sleep); continue; }
175
176     snapshot(output);
177
178     if (!with_keypress &&
179         !(previously && identical(*output,previously))) {
180       fprintf(stderr,"PAGING  wait_for_stability  simple\n");
181       break;
182     }
183
184     if (last && identical(*output,last)) {
185       fprintf(stderr,"PAGING  wait_for_stability  stabilised\n");
186       break;
187     }
188     
189     fprintf(stderr,"PAGING  wait_for_stability  retry\n");
190
191     free(last); last=*output; *output=0;
192
193     if (with_keypress)
194       with_keypress();
195
196     delay(0.5);
197   }
198
199   free(last);
200   fprintf(stderr,"PAGING  wait_for_stability done.\n");
201 }
202
203 static void raise_and_set_focus(void) {
204   int r;
205   XWindowAttributes attr;
206   int xpos,ypos, evbase,errbase,majver,minver;
207   unsigned width,height,bd,depth;
208   Window dummy;
209   
210   fprintf(stderr,"PAGING raise_and_set_focus\n");
211
212   r= XTestQueryExtension(disp, &evbase,&errbase,&majver,&minver);
213   eassert(r==True);
214
215   r= XRaiseWindow(disp, id);  eassert(r);
216
217   r= XGetWindowAttributes(disp, id, &attr);  eassert(r);
218   r= XGetGeometry(disp,id, &attr.root,&xpos,&ypos,&width,&height, &bd,&depth);
219   eassert(r);
220
221   r= XTranslateCoordinates(disp, id,attr.root, 160,160, &xpos,&ypos,
222                            &dummy);
223   eassert(r);
224
225   int screen= XScreenNumberOfScreen(attr.screen);
226   XTestFakeMotionEvent(disp,screen, xpos, ypos, 0);
227
228   XTestFakeButtonEvent(disp,1,1, 50);
229   XTestFakeButtonEvent(disp,1,0, 50);
230
231   sync_after_input();
232   fprintf(stderr,"PAGING raise_and_set_focus done.\n");
233 }
234
235 static void store_page(int pageno, Snapshot *sn) {
236   pid_t converter, paster;
237   eassert(pageno < MAX_PAGES);
238   int paste[2], results[2];
239   FILE *err;
240   int r;
241
242   eassert(!fflush(stdout));
243   eassert(!fflush(stderr));
244   
245   r= pipe(paste);  eassert(!r);
246   r= pipe(results);  eassert(!r);
247   err= tmpfile();  eassert(err);
248
249   converter= fork();  eassert(converter!=-1);
250   if (!converter) {
251     r= dup2(paste[0],0);  eassert(r==0);
252     r= dup2(results[1],1);  eassert(r==1);
253     r= dup2(2,4);            eassert(r==4); /* fileno(errn) > 4, see above */
254     r= dup2(fileno(err),2);  eassert(r==2);
255     close(paste[0]);
256     close(paste[1]);
257     close(results[0]);
258     close(results[1]);
259     execlp("xwdtopnm", "xwdtopnm",(char*)0);
260     dup2(4,2);
261     eassert(!"xwdtopnm exec failure");
262   }
263
264   paster= fork();  eassert(paster!=-1);
265   if (!paster) {
266     FILE *f= fdopen(paste[1],"w");  eassert(f);
267     close(paste[0]);
268     close(results[0]);
269     close(results[1]);
270     size_t did= fwrite(sn->d, 1, sn->len, f);
271     eassert(did==sn->len);
272     eassert(!fclose(f));
273     exit(0);
274   }
275
276   close(paste[0]);
277   close(paste[1]);
278   close(results[1]);
279
280   FILE *f= fdopen(results[0],"r");
281   int c1= fgetc(f);
282   if (c1!=EOF) {
283     ungetc(c1,f);
284     page_images[pageno]= file_read_image(f);
285     r= fgetc(f);  eassert(r==EOF);  eassert(!ferror(f));  eassert(feof(f));
286     fclose(f);
287   }
288
289   pid_t got_conv,got_paste;
290   int st_conv, st_paste;
291
292   got_conv= waitpid(converter,&st_conv,0);  eassert(got_conv==converter);
293   got_paste= waitpid(paster,&st_paste,0);  eassert(got_paste==paster);
294
295   if (!st_conv &&
296       (!st_paste || (WIFSIGNALED(st_paste) && WTERMSIG(st_paste)==SIGPIPE))
297       && c1!=EOF) {
298     fclose(err);
299     return;
300   }
301   rewind(err); int c; while ((c=getc(err))!=EOF) fputc(c,stderr);
302   fprintf(stderr, "convert: subprocess statuses: %d %d\n", st_conv, st_paste);
303   _exit(127);
304 }
305
306 static void read_pages(void) {
307   Snapshot *current=0, *last=0;
308
309   raise_and_set_focus();
310
311   /* page to the top - keep pressing page up until the image stops changing */
312   wait_for_stability(&current,0, send_pgup_many);
313
314   /* now to actually page down */
315   for (;;) {
316     fprintf(stderr,"PAGING page %d\n",npages);
317     store_page(npages, current);
318     free(last); last=current; current=0;
319     fprintf(stderr,"PAGING page %d converted\n",npages);
320
321     wait_for_stability(&current,last, 0);
322     if (npages &&  /* first pagedown doesn't do much */
323         identical(current,last)) {
324       free(current);
325       break;
326     }
327
328     send_pgdown();
329     npages++;
330   }
331   fprintf(stderr,"PAGING all done.\n");
332 }    
333
334 int main(int argc, char **argv) {
335   screenshot_startup();
336
337   id= strtoul(*++argv,0,0);
338
339   read_pages();
340   return 0;
341 }
342