1 /*
2 netrik -- The ANTRIK Internet Viewer
3 Copyright (C) Olaf D. Buddenhagen AKA antrik, et al (see AUTHORS)
4 Published under the GNU GPL; see LICENSE for details.
5 */
6 /*
7 * main.c -- you guess it ;-) : Thats's the main program.
8 *
9 * (C) 2001, 2002, 2003 antrik
10 * 2001, 2002 Patrice Neff
11 *
12 * handle startup (command line etc.); then load (and layout) first file (given
13 * as argument), and either dump it and quit immediatly (with --dump), or
14 * display it interactively and load new files (requested by user) in a loop
15 */
16 #include <curses.h>
17 #include <errno.h>
18 #include <locale.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25
26 #include "config.h"
27
28 #include "cfg.h"
29 #include "cmdline.h"
30 #include "colors.h" /* for load_color_map() */
31 #include "debug.h"
32 #include "forms.h"
33 #include "form-file.h" /* for edit_textarea() */
34 #include "interrupt.h"
35 #include "links.h"
36 #include "layout.h"
37 #include "page.h"
38 #include "pager.h"
39 #include "readline.h"
40 #include "render.h"
41 #include "screen.h"
42 #include "search.h"
43 #include "url.h"
44
main(int argc,char * argv[])45 int main(int argc, char *argv[])
46 {
47 unsigned cfg_len; /* config file size */
48 int argc_all; /* total number of arguments in config file and command line */
49 char **argv_all; /* array (vector) of pointers to all config file and command line arguments */
50 char *cfg_args; /* array holding arguments from config file */
51
52 int opt_index; /* position of first non-option parameter in (merged) command line */
53
54 int cur_page; /* number of current page in page list */
55 int page_width;
56 enum Pager_ret pager_ret; /* return value of pager (indicates if ended by 'q', by ':', or by following link) */
57 enum Syntax_error syntax_err; /* parse_syntax() found syntax errors in the HTML page */
58
59 setlocale(LC_ALL, ""); /* enable (default) locale settings */
60
61 /* read config file */
62 {
63 char *home; /* user's home directory */
64
65 home=getenv("HOME");
66 if(home!=NULL) {
67 struct stat stats; /* config file stats */
68 char cfg_name[strlen(home)+sizeof("/.netrikrc")];
69 snprintf(cfg_name, sizeof(cfg_name), "%s/.netrikrc", home);
70
71 if(stat(cfg_name, &stats)==0) { /* file exists, and stat succeeded */
72 cfg_len=(int)stats.st_size;
73 } else { /* can't stat */
74 if(errno==ENOENT) { /* OK, just doesn't exist */
75 cfg_len=0;
76 } else {
77 fprintf(stderr, "error while checking for config file (%s):", cfg_name); perror("");
78 exit(1);
79 }
80 } /* can't stat */
81
82 if(cfg_len) { /* have a (nonempty) config file -> merge options from file with command line options */
83 FILE *cfg_file;
84
85 char *dest_ptr; /* current position in cfg_args */
86 char *arg_start; /* where currently scanned argument began */
87
88 cfg_file=fopen(cfg_name, "r");
89 if(cfg_file==NULL) {
90 fprintf(stderr, "error: can't open config file (%s):", cfg_name); perror("");
91 exit(1);
92 }
93
94 cfg_args=malloc(sizeof(char[cfg_len]));
95 if(cfg_args==NULL) {
96 fprintf(stderr, "memory allocation error while reading config file\n");
97 exit(1);
98 }
99
100 if(fread(cfg_args, sizeof(char), cfg_len, cfg_file)!=cfg_len) { /* read whole file at once */
101 fprintf(stderr, "error reading config file:"); perror("");
102 exit(1);
103 }
104
105 if(cfg_args[cfg_len-1]!='\n') {
106 fprintf(stderr, "syntax error in config file: last line not terminated with newline character\n");
107 exit(1);
108 }
109
110 /* store config file arguments in argument list */
111 for(argc_all=0, argv_all=NULL, dest_ptr=arg_start=cfg_args; dest_ptr<cfg_args+cfg_len; ++dest_ptr) { /* scan whole config file data */
112 if(*dest_ptr=='\n') { /* line end */
113 *dest_ptr='\0'; /* -> end argument here */
114
115 /* store pointer to argument */
116 argv_all=realloc(argv_all, sizeof(char *[++argc_all+argc])); /* also reserve the space for the command line arguments already */
117 if(argv_all==NULL) {
118 fprintf(stderr, "memory allocation error while reading config file\n");
119 exit(1);
120 }
121 argv_all[argc_all]=arg_start; /* store (beginning with 1, not 0, as argument 0 is normally own name!) */
122
123 arg_start=dest_ptr+1; /* next argument starts after the '\n' */
124 } /* line end */
125 } /* for all chars in config file */
126
127 /* append command line args (starting with 1st, not 0th!) after config file args */
128 memcpy(&argv_all[argc_all+1], argv+1, sizeof(char *[argc-1]));
129 argc_all+=argc;
130 } else { /* no config file -> just use command line arguments */
131 argv_all=argv;
132 argc_all=argc;
133 }
134 } else { /* no $HOME */
135 fprintf(stderr, "error: can't find your home directory (while looking for config file)\n");
136 exit(1);
137 }
138
139 #ifdef DEBUG
140 if(cfg.debug) {
141 int arg;
142
143 debug_printf("arguments:");
144 for(arg=1; arg<argc_all; ++arg)
145 debug_printf(" %s", argv_all[arg]);
146 debug_printf("\n");
147 }
148 #endif
149 }
150
151 /* Patrice --> */
152 /* process arguments */
153 opt_index=config_cmdln(argc_all, argv_all);
154 if(opt_index==argc_all) { /* no non-option arguments */
155 fprintf(stderr,"usage: %1$s html-file\n or: %1$s - (read from stdin)\n", argv[0]);
156 exit(3);
157 }
158 /* <-- Patrice */
159
160 if(cfg.force_colors < 0) /* not explicitely set -> default depends on color scheme */
161 cfg.force_colors=!cfg.bright_background; /* force colors if dark color scheme to be used */
162
163 load_color_map();
164
165 if(cfg.term_width)
166 page_width=init_curses();
167 else {
168 init_curses();
169 page_width=80;
170 }
171
172 init_int();
173
174 cur_page=load_page(NULL, argv_all[argc_all-1], NULL, NULL, page_width, &syntax_err); /* load URL given in last command line argument */
175
176 #ifdef DEBUG
177 if(cfg_len) { /* have read config file -> need to clean up */
178 DMSG(("\nfreeing memory used by argument list...\n"));
179 free(cfg_args);
180 free(argv_all);
181 }
182 #endif
183
184 if(!cfg.dump) {
185 start_curses();
186 endwin(); /* return to scroll mode for now, as we might want print something still before actually starting pager */
187
188 if(syntax_err
189 #ifdef DEBUG
190 || cfg.debug
191 #endif
192 ) {
193 syntax_err=SE_NO;
194 fprintf(stderr, "\nHit some key to start viewer.\n");
195 cbreak(); getchar(); endwin(); /* wait for *single* keypress (endwin() resets normal scroll mode terminal settings) */
196 }
197
198 do { /* until quit */
199 int pager_wait=0; /* wait for keypress before returning to pager */
200
201 pager_ret=display(page_list.page[cur_page]); /* start pager with current page (returns when 'q' pressed, when command line mode entered with ':', or when link followed) */
202
203 switch(pager_ret) {
204 /* pager ended by ':' */
205 case RET_COMMAND: {
206 char *command; /* user input */
207
208 command=read_line(":");
209 if(command!=NULL && *command) { /* nonempty (any command entered) */
210 add_history(command);
211
212 if(strncmp(command, "e ", 2)==0 || strncmp(command, "E ", 2)==0) { /* load file */
213 const char *url=command+2; /* name begins after "e " */
214
215 char *blank_pos; /* first blank in url */
216
217 /* remove trailing blank(s) */
218 blank_pos=strchr(url, ' '); /* find first blank */
219 if(blank_pos!=NULL) /* anything found */
220 *blank_pos=0; /* end string at first blank */
221
222 DMSG(("\nfreeing memory used by old page...\n"));
223 free_layout(page_list.page[cur_page]->layout); /* get rid of old document */
224
225 if(command[0]=='e' && page_list.page[cur_page]->url->proto.type!=PT_INTERNAL) /* load relative URL */
226 cur_page=load_page(page_list.page[cur_page]->url, url, NULL, NULL, page_width, &syntax_err); /* load new page (use current page URL as base) */
227 else /* load absolute URL */
228 cur_page=load_page(NULL, url, NULL, NULL, page_width, &syntax_err); /* load new page using absoulute URL (no base) */
229 } else { /* command not "e ..." */
230 printf("unknown command\n");
231 pager_wait=1;
232 } /* command not "e " */
233
234 #ifdef DEBUG
235 if(cfg.debug)
236 pager_wait=1;
237 #endif
238 } /* command nonempty */
239 free(command);
240 break;
241 } /* RET_COMMAND */
242
243 /* pager ended by requesting text search */
244 case RET_SEARCH: {
245 struct Page *page=page_list.page[cur_page];
246
247 char *string;
248
249 struct Item *start_item;
250 int start_pos;
251
252 string=read_line("/");
253 if(string==NULL) { /* aborted (^D) */
254 search.type=SEARCH_NO;
255 break;
256 }
257
258 if(*string) { /* nonempty -> store as new search string */
259 add_history(string);
260 if(search.string!=NULL) {
261 DMSG(("freeing old search string...\n"));
262 free(search.string);
263 }
264 search.string=string;
265 }
266
267 if(search.string==NULL) { /* nothing entered, and there was no previous search string either */
268 set_color_raw(COLOR_RED|8);
269 printf("No search string.\n");
270 reset_color_raw();
271 pager_wait=1;
272
273 search.type=SEARCH_NO;
274 break;
275 }
276
277 /* find start position for search (text item containing cursor and position inside the text) */
278 DMSG(("looking for search start position (cursor position: %d, %d)\n ", page->cursor_x, page->cursor_y));
279 {
280 int cur_item;
281
282 start_item=NULL;
283
284 for(cur_item=0; cur_item < page->layout->page_map[page->cursor_y].count; ++cur_item) { /* all items in cursor line */
285 const struct Item *item=page->layout->page_map[page->cursor_y].item[cur_item];
286
287 DMSG(("."));
288 if(item->type==ITEM_TEXT && item->x_start <= page->cursor_x && item->x_end > page->cursor_x) { /* text block, and cursor is inside */
289 int line_offset; /* position of cursor relative to start of text in this line */
290
291 line_offset=page->cursor_x - line_pos(item, page->cursor_y);
292 if(line_offset<0) { /* cursor before start of text in this line */
293 DMSG(("cursor before text line -> moving\n "));
294 line_offset=-1; /* -> assume it stands just before first char in line, so search will begin at line start */
295 }
296
297 if(line_offset < line_end(item, page->cursor_y) - line_start(item, page->cursor_y)) { /* text in this line doesn't end before cursor position -> begin search here */
298 DMSG(("cursor in text line\n"));
299 start_item=(struct Item *)item;
300 start_pos=line_start(item, page->cursor_y) + line_offset + 1; /* begin search after cursor position */
301 break;
302 } else if(item->y_end > page->cursor_y) { /* cursor after end of line, but not last line in text block -> begin search with next line */
303 DMSG(("cursor after current text line, but in text block\n"));
304 start_item=(struct Item *)item;
305 start_pos=line_end(item, page->cursor_y); /* begin search at beginning of next line */
306 break;
307 }
308 } /* cursor in text block */
309 } /* all items in line */
310 if(start_item==NULL) { /* nothing found (cursor not inside text block) -> find first text block starting after cursor (or first on page if nothing after cursor) */
311 struct Item *item;
312
313 DMSG(("cursor not in text block\nsearching for first text block after cursor\n "));
314 for(item=page->layout->item_tree->parent; item!=NULL; item=item->list_next) { /* all items (in page order) */
315 DMSG(("."));
316 if(item->type==ITEM_TEXT) {
317 if(item->y_start > page->cursor_y) { /* starts after cursor pos -> begin search at this one */
318 DMSG(("found a text block after cursor"));
319 start_item=item;
320 start_pos=0;
321 break; /* don't search further */
322 }
323 } /* text item */
324 } /* for all items */
325 DMSG(("\n"));
326 } /* cursor not in text block */
327 } /* find start position */
328
329 /* perform search */
330 {
331 struct Item *item;
332 char *found_pos=NULL;
333
334 #ifdef DEBUG
335 if(start_item!=NULL) {
336 DMSG(("start item:\n\n%s\n\n", start_item->data.string->text));
337 DMSG(("start pos: %d\n", start_pos));
338 } else
339 DMSG(("at page end\n"));
340 #endif
341
342 DMSG(("starting search "));
343 for(item=start_item; item!=NULL; (item=item->list_next), start_pos=0) { /* all items beginning with "start_item" (don't use "start_pos" except in first!) */
344 DMSG(("."));
345 if(item->type==ITEM_TEXT) {
346 DMSG(("\""));
347 found_pos=strstr(item->data.string->text + start_pos, search.string);
348 if(found_pos!=NULL)
349 break;
350 }
351 }
352
353 if(found_pos==NULL) { /* nothing found yet -> wrap search */
354 struct Item *end_item;
355
356 DMSG(("\n"));
357 set_color_raw(COLOR_BLUE|8);
358 printf("Search wrapped.\n");
359 reset_color_raw();
360 fflush(stdout);
361 pager_wait=1;
362
363 if(start_item!=NULL) /* have a start item -> search up to start item (inclusive!) */
364 end_item=start_item->list_next;
365 else /* no start item -> search to page end (last item) */
366 end_item=NULL;
367
368 for(item=page->layout->item_tree->parent; item!=end_item; item=item->list_next) { /* all items until back at start */
369 DMSG(("."));
370 if(item->type==ITEM_TEXT) {
371 DMSG(("\""));
372 found_pos=strstr(item->data.string->text, search.string);
373 if(found_pos!=NULL)
374 break;
375 }
376 }
377 } /* wrap search */
378
379 if(found_pos!=NULL) { /* something found -> set cursor to match position */
380 const int string_offset=found_pos - item->data.string->text; /* position of match relative to string start */
381
382 int line;
383
384 DMSG(("found\n"));
385 DMSG(("item:\n\n%s\n\n", item->data.string->text));
386 DMSG(("position: %d\n", string_offset));
387
388 DMSG(("determining new cursor position "));
389 /* find line containing match */
390 for(line=item->y_start; line<item->y_end; ++line) { /* all lines in text block */
391 DMSG(("."));
392 if(line_end(item, line) > string_offset) /* first line ending after match pos -> found */
393 break;
394 }
395 #ifdef DEBUG
396 if(line==item->y_end) {
397 fprintf(stderr, "internal error: failed to find line containing match position\n");
398 exit(100);
399 }
400 #endif
401
402 page->cursor_y=line;
403 page->cursor_x=line_pos(item, line) + string_offset-line_start(item, line);
404 DMSG(("\nposition: %d, %d\n", page->cursor_x, page->cursor_y));
405 } else { /* nothing found */
406 set_color_raw(COLOR_RED|8);
407 printf("No match.\n");
408 reset_color_raw();
409 pager_wait=1;
410
411 search.type=SEARCH_NO;
412 }
413 } /* perform search */
414
415 #ifdef DEBUG
416 if(cfg.debug)
417 pager_wait=1;
418 #endif
419 break;
420 } /* RET_SEARCH */
421
422 /* pager ended by following link */
423 case RET_LINK: {
424 const struct Page *old_page=page_list.page[cur_page];
425 struct Link *link=get_link(old_page->layout, old_page->active_link); /* link data in item tree */
426
427 switch(link->form) { /* dispatch on link/form type */
428 case FORM_NO: { /* normal link (not form control) */
429 char url[strlen(link->value.data)+1];
430 strcpy(url, link->value.data); /* save link URL before killing old page */
431
432 if(url[0]!='#') { /* not local anchor -> load new document */
433 DMSG(("\nfreeing memory used by old page...\n"));
434 free_layout(old_page->layout); /* get rid of old document */
435 cur_page=load_page(old_page->url, url, NULL, NULL, page_width, &syntax_err); /* load link (use current page URL as base) */
436 } else /* local anchor -> keep document, just jump to anchor */
437 cur_page=load_page(old_page->url, url, NULL, old_page, page_width, &syntax_err); /* load link (use current page URL as base) */
438 #ifdef DEBUG
439 if(cfg.debug)
440 pager_wait=1;
441 #endif
442 break;
443 } /* FORM_NO */
444
445 case FORM_TEXT:
446 case FORM_PASS:
447 case FORM_FILE: {
448 char *new_value;
449
450 new_value=read_line(link->form==FORM_FILE ? "file:" : "value:");
451 if(new_value!=NULL) { /* not aborted -> store new value */
452 free(link->value.data);
453 link->value.data=new_value; link->value.size=strlen(new_value);
454 update_form(old_page->layout, old_page->active_link);
455 }
456
457 if(link->form==FORM_FILE && *new_value) { /* file to upload -> check */
458 if(access(new_value, R_OK)==-1) {
459 set_color_raw(COLOR_RED|8);
460 printf("\nWarning: Can't access file \"%s\".\n", new_value);
461 reset_color_raw();
462 pager_wait=1;
463 }
464 }
465
466 break;
467 } /* FORM_TEXT/FORM_PASS/FORM_FILE */
468
469 case FORM_HIDDEN:
470 break; /* shouldn't occur... */
471
472 case FORM_CHECKBOX:
473 case FORM_MULTIOPTION:
474 link->enabled=1-link->enabled;
475 update_form(old_page->layout, old_page->active_link);
476 break;
477
478 case FORM_RADIO:
479 case FORM_OPTION: {
480 const struct Item *form_item=get_form_item(old_page->layout, old_page->active_link);
481
482 struct Form_handle handle=form_start(form_item, 0); /* handle to iterate through all form controls (unfiltered) */
483 struct Link *sibling; /* other controls in this form */
484
485 /* disable any other radio buttons/options with same name */
486 if(link->name!=NULL) /* belongs to some group at all... */
487 while((sibling=form_next(&handle)) != NULL) { /* for all form controls */
488 if(sibling->name!=NULL && !strcmp(sibling->name, link->name)) { /* in same group -> disable */
489 if(sibling->enabled) {
490 sibling->enabled=0;
491 set_form(handle.cur_item->data.string, sibling); /* beware -- crude hack! */
492 }
493 }
494 }
495
496 link->enabled=1;
497 update_form(old_page->layout, old_page->active_link);
498
499 break;
500 }
501
502 case FORM_TEXTAREA: {
503 char *err=edit_textarea(&link->value, link->name);
504 if(err==NULL) /* no error */
505 update_form(old_page->layout, old_page->active_link);
506 else {
507 set_color_raw(COLOR_RED|8);
508 printf("\nEditing textarea failed: %s\n", err);
509 reset_color_raw();
510 pager_wait=1;
511 }
512 break;
513 }
514
515 /* Patrice, antrik --> */
516 case FORM_SUBMIT: {
517 const struct Item *form_item=get_form_item(old_page->layout, old_page->active_link);
518
519 if(form_item==NULL) { /* orphaned button (no parent form) */
520 set_color_raw(COLOR_RED|8);
521 printf("\nError: Submit button outside form\n");
522 reset_color_raw();
523 pager_wait=1;
524 break;
525 }
526
527 if(form_item->data.form->url!=NULL) { /* "action" given */
528 link->enabled=1; /* make the used submit button successful */
529
530 cur_page=load_page(old_page->url, form_item->data.form->url, form_item, NULL, page_width, &syntax_err); /* load form "action" URL (use current page URL as base); submit form data with HTTP request */
531 DMSG(("\nfreeing memory used by old page...\n"));
532 free_layout(old_page->layout);
533 #ifdef DEBUG
534 if(cfg.debug)
535 pager_wait=1;
536 #endif
537 } else { /* no "action" */
538 set_color_raw(COLOR_RED|8);
539 printf("\nError: Form gives no URL to submit to\n");
540 reset_color_raw();
541 pager_wait=1;
542 }
543 break;
544 } /* FORM_SUBMIT */
545 /* <-- Patrice, antrik */
546
547 } /* switch link type */
548
549 break;
550 } /* RET_LINK */
551
552 /* "show link URL" requested */
553 case RET_LINK_URL: {
554 const struct Page *page=page_list.page[cur_page];
555 const struct Link *link=get_link(page->layout, page->active_link); /* link data in item tree */
556
557 char *url;
558
559 if(link->form) {
560 const struct Item *form_item=get_form_item(page->layout, page->active_link);
561 url=form_item->data.form->url;
562 } else
563 url=link->value.data;
564
565 printf("\n%s URL:\n", link->form?"submit":"link");
566 set_color_raw(COLOR_WHITE|8);
567 printf("%s\n", url);
568 reset_color_raw();
569 pager_wait=1;
570 break;
571 } /* RET_LINK_URL */
572
573 /* show absolute link target URL */
574 case RET_ABSOLUTE_URL: {
575 const struct Page *page=page_list.page[cur_page];
576 const struct Link *link=get_link(page->layout, page->active_link); /* link data in item tree */
577
578 char *url;
579 struct Url *target_url; /* effective URL of link target */
580
581 if(link->form) {
582 const struct Item *form_item=get_form_item(page->layout, page->active_link);
583 url=form_item->data.form->url;
584 } else
585 url=link->value.data;
586
587 target_url=merge_urls(page->url, url, NULL); /* (temporarily) construct target URL merged from current URL and link URL */
588
589 if(target_url->proto.type!=PT_INTERNAL) { /* could extract absolute URL */
590 printf("\nabsolute link target URL:\n");
591 set_color_raw(COLOR_WHITE|8);
592 printf("%s\n", target_url->full_url);
593 reset_color_raw();
594 } else {
595 set_color_raw(COLOR_RED|8);
596 printf("\ncan't get target URL\n");
597 reset_color_raw();
598 }
599
600 free_url(target_url); /* don't keep */
601
602 pager_wait=1;
603 break;
604 } /* RET_ABSOLUTE_URL */
605
606 /* "show current page URL" requested */
607 case RET_URL: {
608 const struct Page *page=page_list.page[cur_page];
609 const char *url_str=page->url->full_url;
610
611 if(page->url->proto.type!=PT_INTERNAL) { /* has URL */
612 printf("\ncurrent page URL:\n");
613 set_color_raw(COLOR_WHITE|8);
614 printf("%s\n", url_str);
615 reset_color_raw();
616 } else /* internal */
617 printf("\npage is from stdin and has no URL\n");
618 pager_wait=1;
619 break;
620 } /* RET_LINK_URL */
621
622 /* pager ended by going back or forward in URL history */
623 case RET_HISTORY: {
624 struct Page *ref=page_list.page[cur_page]; /* old page descriptor, if new page is from same URL (can reuse layout data) */
625 int first_page, last_page; /* range of page list entries to check for local */
626 int page; /* currently checked page list entry */
627
628 if(page_list.pos!=cur_page) { /* not reloading same page -> check if can reuse layout data */
629 DMSG(("\nchecking whether we stay in same document...\n"));
630 if(page_list.pos>cur_page) {
631 first_page=cur_page;
632 last_page=page_list.pos;
633 } else {
634 first_page=page_list.pos;
635 last_page=cur_page;
636 }
637 for(page=first_page+1; page<=last_page; ++page) { /* test all pages between old an new one for being created by jumping to local anchors */
638 if(!page_list.page[page]->url->local) { /* not local -> can't reuse layout data */
639 ref=NULL;
640 break;
641 }
642 }
643 } else /* reloading same page -> never reuse */
644 ref=NULL;
645
646 if(ref==NULL) { /* layout data won't be reused */
647 DMSG(("freeing memory used by old page...\n"));
648 free_layout(page_list.page[cur_page]->layout);
649 }
650
651 DMSG(("loading new page:\n"));
652 cur_page=load_page(page_list.page[page_list.pos]->url, NULL, NULL, ref, page_width, &syntax_err); /* reload page from history (using already split url) */
653
654 #ifdef DEBUG
655 if(cfg.debug)
656 pager_wait=1;
657 #endif
658 break;
659 } /* RET_HISTORY */
660
661 /* pager ended by SIGWINCH */
662 case RET_WINCH:
663 resize(page_list.page[cur_page]->layout, COLS);
664 break;
665
666 case RET_QUIT:
667 break;
668 case RET_NO: /* suppress warning */
669 break;
670 } /* switch pager_ret */
671
672 if(syntax_err) {
673 syntax_err=SE_NO;
674 pager_wait=1;
675 }
676
677 if(pager_wait) {
678 printf("\nHit some key to return to pager.\n"); fflush(stdout);
679 cbreak(); getchar(); endwin(); /* wait for *single* keypress (endwin() resets normal scroll mode terminal settings) */
680 }
681
682 } while(pager_ret!=RET_QUIT); /* until pager ended by 'q' */
683 } else { /* cfg.dump */
684 DMSG(("layouted page:\n"));
685 dump(page_list.page[cur_page]->layout);
686 }
687
688 #ifdef DEBUG
689 DMSG(("\nfreeing memory used by page...\n"));
690 free_layout(page_list.page[cur_page]->layout);
691
692 DMSG(("freeing memory used by page list...\n"));
693 free_page_list();
694
695 DMSG(("freeing memory use by search string...\n"));
696 free(search.string);
697 #endif
698
699 return(0);
700 }
701