|
|
1.1 root 1: /* $Id: cterm.c,v 1.89 2006/09/02 08:25:24 deuce Exp $ */
2:
3: /****************************************************************************
4: * @format.tab-size 4 (Plain Text/Source Code File Header) *
5: * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
6: * *
7: * Copyright 2006 Rob Swindell - http://www.synchro.net/copyright.html *
8: * *
9: * This library is free software; you can redistribute it and/or *
10: * modify it under the terms of the GNU Lesser General Public License *
11: * as published by the Free Software Foundation; either version 2 *
12: * of the License, or (at your option) any later version. *
13: * See the GNU Lesser General Public License for more details: lgpl.txt or *
14: * http://www.fsf.org/copyleft/lesser.html *
15: * *
16: * Anonymous FTP access to the most recent released source is available at *
17: * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
18: * *
19: * Anonymous CVS access to the development source and modification history *
20: * is available at cvs.synchro.net:/cvsroot/sbbs, example: *
21: * cvs -d :pserver:[email protected]:/cvsroot/sbbs login *
22: * (just hit return, no password is necessary) *
23: * cvs -d :pserver:[email protected]:/cvsroot/sbbs checkout src *
24: * *
25: * For Synchronet coding style and modification guidelines, see *
26: * http://www.synchro.net/source.html *
27: * *
28: * You are encouraged to submit any modifications (preferably in Unix diff *
29: * format) via e-mail to [email protected] *
30: * *
31: * Note: If this box doesn't appear square, then you need to fix your tabs. *
32: ****************************************************************************/
33:
34: #include <ctype.h>
35: #include <stdlib.h>
36: #include <string.h>
37: #if defined(_WIN32)
38: #include <malloc.h> /* alloca() on Win32 */
39: #endif
40:
41: #include <genwrap.h>
42: #include <xpbeep.h>
43: #include <link_list.h>
44: #ifdef __unix__
45: #include <xpsem.h>
46: #endif
47: #include <threadwrap.h>
48:
49: #if (defined CIOLIB_IMPORTS)
50: #undef CIOLIB_IMPORTS
51: #endif
52: #if (defined CIOLIB_EXPORTS)
53: #undef CIOLIB_EXPORTS
54: #endif
55:
56: #include "ciolib.h"
57: #include "keys.h"
58:
59: #include "cterm.h"
60: #include "allfonts.h"
61:
62: #define BUFSIZE 2048
63:
64: struct cterminal cterm;
65:
66: /* const int tabs[11]={1,8,16,24,32,40,48,56,64,72,80}; */
67: const int cterm_tabs[11]={9,17,25,33,41,49,57,65,73,80,80};
68:
69: const char *octave="C#D#EF#G#A#B";
70:
71: /* Characters allowed in music strings... if one that is NOT in here is
72: * found, CTerm leaves music capture mode and tosses the buffer away */
73: const char *musicchars="aAbBcCdDeEfFgGlLmMnNoOpPsStT0123456789.-+#<> ";
74: const uint note_frequency[]={ /* Hz*1000 */
75: /* Octave 0 (Note 1) */
76: 65406
77: ,69296
78: ,73416
79: ,77782
80: ,82407
81: ,87307
82: ,92499
83: ,97999
84: ,103820
85: ,110000
86: ,116540
87: ,123470
88: /* Octave 1 */
89: ,130810
90: ,138590
91: ,146830
92: ,155560
93: ,164810
94: ,174610
95: ,185000
96: ,196000
97: ,207650
98: ,220000
99: ,233080
100: ,246940
101: /* Octave 2 */
102: ,261620
103: ,277180
104: ,293660
105: ,311130
106: ,329630
107: ,349230
108: ,369990
109: ,392000
110: ,415300
111: ,440000
112: ,466160
113: ,493880
114: /* Octave 3 */
115: ,523250
116: ,554370
117: ,587330
118: ,622250
119: ,659260
120: ,698460
121: ,739990
122: ,783990
123: ,830610
124: ,880000
125: ,932330
126: ,987770
127: /* Octave 4 */
128: ,1046500
129: ,1108700
130: ,1174700
131: ,1244500
132: ,1318500
133: ,1396900
134: ,1480000
135: ,1568000
136: ,1661200
137: ,1760000
138: ,1864600
139: ,1975500
140: /* Octave 5 */
141: ,2093000
142: ,2217500
143: ,2349300
144: ,2489000
145: ,2637000
146: ,2793800
147: ,2959900
148: ,3136000
149: ,3322400
150: ,3520000
151: ,3729300
152: ,3951100
153: /* Octave 6 */
154: ,4186000
155: ,4435000
156: ,4698600
157: ,4978000
158: ,5274000
159: ,5587000
160: ,5920000
161: ,6272000
162: ,6644000
163: ,7040000
164: ,7458600
165: ,7902200
166: };
167:
168: struct note_params {
169: int notenum;
170: int notelen;
171: int dotted;
172: int tempo;
173: int noteshape;
174: int foreground;
175: };
176:
177: static int playnote_thread_running=FALSE;
178: static link_list_t notes;
179: sem_t playnote_thread_terminated;
180: sem_t note_completed_sem;
181:
182: void playnote_thread(void *args)
183: {
184: /* Tempo is quarter notes per minute */
185: int duration;
186: int pauselen;
187: struct note_params *note;
188: int device_open=FALSE;
189:
190: playnote_thread_running=TRUE;
191: while(1) {
192: if(device_open) {
193: if(!listSemTryWaitBlock(¬es,5000)) {
194: xptone_close();
195: device_open=FALSE;
196: listSemWait(¬es);
197: }
198: }
199: else
200: listSemWait(¬es);
201: xptone_open();
202: device_open=TRUE;
203: note=listShiftNode(¬es);
204: if(note==NULL)
205: break;
206: if(note->dotted)
207: duration=360000/note->tempo;
208: else
209: duration=240000/note->tempo;
210: duration/=note->notelen;
211: switch(note->noteshape) {
212: case CTERM_MUSIC_STACATTO:
213: pauselen=duration/4;
214: break;
215: case CTERM_MUSIC_LEGATO:
216: pauselen=0;
217: break;
218: case CTERM_MUSIC_NORMAL:
219: default:
220: pauselen=duration/8;
221: break;
222: }
223: duration-=pauselen;
224: if(note->notenum < 72 && note->notenum >= 0)
225: xptone(((double)note_frequency[note->notenum])/1000,duration,WAVE_SHAPE_SINE_SAW_HARM);
226: else
227: SLEEP(duration);
228: SLEEP(pauselen);
229: if(note->foreground)
230: sem_post(¬e_completed_sem);
231: free(note);
232: }
233: playnote_thread_running=FALSE;
234: sem_post(&playnote_thread_terminated);
235: }
236:
237: void play_music(void)
238: {
239: int i;
240: char *p;
241: char *out;
242: int offset;
243: char note;
244: int notelen;
245: char numbuf[10];
246: int dotted;
247: int notenum;
248: struct note_params *np;
249: int fore_count;
250:
251: p=cterm.musicbuf;
252: fore_count=0;
253: if(cterm.music==1) {
254: switch(toupper(*p)) {
255: case 'F':
256: cterm.musicfore=TRUE;
257: p++;
258: break;
259: case 'B':
260: cterm.musicfore=FALSE;
261: p++;
262: break;
263: case 'N':
264: if(!isdigit(*(p+1))) {
265: cterm.noteshape=CTERM_MUSIC_NORMAL;
266: p++;
267: }
268: break;
269: case 'L':
270: cterm.noteshape=CTERM_MUSIC_LEGATO;
271: p++;
272: break;
273: case 'S':
274: cterm.noteshape=CTERM_MUSIC_STACATTO;
275: p++;
276: break;
277: }
278: }
279: for(;*p;p++) {
280: notenum=0;
281: offset=0;
282: switch(toupper(*p)) {
283: case 'M':
284: p++;
285: switch(toupper(*p)) {
286: case 'F':
287: cterm.musicfore=TRUE;
288: break;
289: case 'B':
290: cterm.musicfore=FALSE;
291: break;
292: case 'N':
293: cterm.noteshape=CTERM_MUSIC_NORMAL;
294: break;
295: case 'L':
296: cterm.noteshape=CTERM_MUSIC_LEGATO;
297: break;
298: case 'S':
299: cterm.noteshape=CTERM_MUSIC_STACATTO;
300: break;
301: default:
302: p--;
303: }
304: break;
305: case 'T': /* Tempo */
306: out=numbuf;
307: while(isdigit(*(p+1)))
308: *(out++)=*(++p);
309: *out=0;
310: cterm.tempo=atoi(numbuf);
311: if(cterm.tempo>255)
312: cterm.tempo=255;
313: if(cterm.tempo<32)
314: cterm.tempo=32;
315: break;
316: case 'O': /* Octave */
317: out=numbuf;
318: while(isdigit(*(p+1)))
319: *(out++)=*(++p);
320: *out=0;
321: cterm.octave=atoi(numbuf);
322: if(cterm.octave>6)
323: cterm.octave=6;
324: break;
325: case 'L':
326: out=numbuf;
327: while(isdigit(*(p+1)))
328: *(out++)=*(++p);
329: *out=0;
330: cterm.notelen=atoi(numbuf);
331: if(cterm.notelen<1)
332: cterm.notelen=1;
333: if(cterm.notelen>64)
334: cterm.notelen=64;
335: break;
336: case 'N': /* Note by number */
337: if(isdigit(*(p+1))) {
338: out=numbuf;
339: while(isdigit(*(p+1))) {
340: *(out++)=*(p+1);
341: p++;
342: }
343: *out=0;
344: notenum=atoi(numbuf);
345: }
346: if(notenum==0) {
347: notenum=-1;
348: offset=1;
349: }
350: /* Fall-through */
351: case 'A': /* Notes in current octave by letter */
352: case 'B':
353: case 'C':
354: case 'D':
355: case 'E':
356: case 'F':
357: case 'G':
358: case 'P':
359: note=toupper(*p);
360: notelen=cterm.notelen;
361: dotted=0;
362: i=1;
363: while(i) {
364: i=0;
365: if(*(p+1)=='+' || *(p+1)=='#') { /* SHARP */
366: offset+=1;
367: p++;
368: i=1;
369: }
370: if(*(p+1)=='-') { /* FLAT */
371: offset-=1;
372: p++;
373: i=1;
374: }
375: if(*(p+1)=='.') { /* Dotted note (1.5*notelen) */
376: dotted=1;
377: p++;
378: i=1;
379: }
380: if(isdigit(*(p+1))) {
381: out=numbuf;
382: while(isdigit(*(p+1))) {
383: *(out++)=*(p+1);
384: p++;
385: }
386: *out=0;
387: notelen=atoi(numbuf);
388: i=1;
389: }
390: }
391: if(note=='P') {
392: notenum=-1;
393: offset=0;
394: }
395: if(notenum==0) {
396: out=strchr(octave,note);
397: if(out==NULL) {
398: notenum=-1;
399: offset=1;
400: }
401: else {
402: notenum=cterm.octave*12+1;
403: notenum+=(out-octave);
404: }
405: }
406: notenum+=offset;
407: np=(struct note_params *)malloc(sizeof(struct note_params));
408: if(np!=NULL) {
409: np->notenum=notenum;
410: np->notelen=notelen;
411: np->dotted=dotted;
412: np->tempo=cterm.tempo;
413: np->noteshape=cterm.noteshape;
414: np->foreground=cterm.musicfore;
415: listPushNode(¬es, np);
416: if(cterm.musicfore)
417: fore_count++;
418: }
419: break;
420: case '<': /* Down one octave */
421: cterm.octave--;
422: if(cterm.octave<0)
423: cterm.octave=0;
424: break;
425: case '>': /* Up one octave */
426: cterm.octave++;
427: if(cterm.octave>6)
428: cterm.octave=6;
429: break;
430: }
431: }
432: cterm.music=0;
433: cterm.musicbuf[0]=0;
434: while(fore_count) {
435: sem_wait(¬e_completed_sem);
436: fore_count--;
437: }
438: }
439:
440: void scrolldown(void)
441: {
442: char *buf;
443: int i,j;
444:
445: buf=(char *)alloca(cterm.width*(cterm.height-1)*2);
446: gettext(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y+cterm.height-2,buf);
447: puttext(cterm.x,cterm.y+1,cterm.x+cterm.width-1,cterm.y+cterm.height-1,buf);
448: j=0;
449: for(i=0;i<cterm.width;i++) {
450: buf[j++]=' ';
451: buf[j++]=cterm.attr;
452: }
453: puttext(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y,buf);
454: }
455:
456: void scrollup(void)
457: {
458: char *buf;
459: int i,j;
460:
461: cterm.backpos++;
462: if(cterm.scrollback!=NULL) {
463: if(cterm.backpos>cterm.backlines) {
464: memmove(cterm.scrollback,cterm.scrollback+cterm.width*2,cterm.width*2*(cterm.backlines-1));
465: cterm.backpos--;
466: }
467: gettext(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y,cterm.scrollback+(cterm.backpos-1)*cterm.width*2);
468: }
469: buf=(char *)alloca(cterm.width*(cterm.height-1)*2);
470: gettext(cterm.x,cterm.y+1,cterm.x+cterm.width-1,cterm.y+cterm.height-1,buf);
471: puttext(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y+cterm.height-2,buf);
472: j=0;
473: for(i=0;i<cterm.width;i++) {
474: buf[j++]=' ';
475: buf[j++]=cterm.attr;
476: }
477: puttext(cterm.x,cterm.y+cterm.height-1,cterm.x+cterm.width-1,cterm.y+cterm.height-1,buf);
478: }
479:
480: void dellines(int lines)
481: {
482: char *buf;
483: int i,j,k;
484: int linestomove;
485:
486: if(lines<1)
487: return;
488: linestomove=cterm.height-wherey();
489: buf=(char *)alloca(cterm.width*(linestomove>lines?linestomove:lines)*2);
490: gettext(cterm.x,cterm.y+wherey()-1+lines,cterm.x+cterm.width-1,cterm.y+cterm.height-1,buf);
491: puttext(cterm.x,cterm.y+wherey()-1,cterm.x+cterm.width-1,cterm.y+cterm.height-1-lines,buf);
492: j=0;
493: k=cterm.width*lines;
494: for(i=0;i<k;i++) {
495: buf[j++]=' ';
496: buf[j++]=cterm.attr;
497: }
498: puttext(cterm.x,cterm.y+cterm.height-lines,cterm.x+cterm.width-1,cterm.y+cterm.height-1,buf);
499: }
500:
501: void clear2bol(void)
502: {
503: char *buf;
504: int i,j,k;
505:
506: k=wherex();
507: buf=(char *)alloca(k*2);
508: j=0;
509: for(i=0;i<k;i++) {
510: buf[j++]=' ';
511: buf[j++]=cterm.attr;
512: }
513: puttext(cterm.x,cterm.y+wherey()-1,cterm.x+wherex()-1,cterm.y+wherey()-1,buf);
514: }
515:
516: void clear2eol(void)
517: {
518: clreol();
519: }
520:
521: void clearscreen(char attr)
522: {
523: if(cterm.scrollback!=NULL) {
524: cterm.backpos+=cterm.height;
525: if(cterm.backpos>cterm.backlines) {
526: memmove(cterm.scrollback,cterm.scrollback+cterm.width*2*(cterm.backpos-cterm.backlines),cterm.width*2*(cterm.backlines-(cterm.backpos-cterm.backlines)));
527: cterm.backpos=cterm.backlines;
528: }
529: gettext(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y+cterm.height-1,cterm.scrollback+(cterm.backpos-cterm.height)*cterm.width*2);
530: }
531: clrscr();
532: }
533:
534: void do_ansi(char *retbuf, size_t retsize, int *speed)
535: {
536: char *p;
537: char *p2;
538: char tmp[1024];
539: int i,j,k,l;
540: int row,col;
541:
542: switch(cterm.escbuf[0]) {
543: case '[':
544: /* ANSI stuff */
545: p=cterm.escbuf+strlen(cterm.escbuf)-1;
546: if(cterm.escbuf[1]>=60 && cterm.escbuf[1] <= 63) { /* Private extenstions */
547: switch(*p) {
548: case 'M':
549: if(cterm.escbuf[1] == '=') { /* ANSI Music setup */
550: i=atoi(cterm.escbuf+2);
551: switch(i) {
552: case 1: /* BANSI (ESC[N) music only) */
553: cterm.music_enable=CTERM_MUSIC_BANSI;
554: break;
555: case 2: /* ESC[M ANSI music */
556: cterm.music_enable=CTERM_MUSIC_ENABLED;
557: break;
558: default: /* Disable ANSI Music */
559: cterm.music_enable=CTERM_MUSIC_SYNCTERM;
560: break;
561: }
562: }
563: break;
564: case '{':
565: if(cterm.escbuf[1] == '=') { /* Font loading */
566: i=255;
567: j=0;
568: if(strlen(cterm.escbuf)>2) {
569: if((p=strtok(cterm.escbuf+2,";"))!=NULL) {
570: i=atoi(p);
571: if(!i && cterm.escbuf[2] != '0')
572: i=255;
573: if((p=strtok(NULL,";"))!=NULL) {
574: j=atoi(p);
575: }
576: }
577: }
578: if(i>255)
579: break;
580: cterm.font_start_time=time(NULL);
581: cterm.font_read=0;
582: cterm.font_slot=i;
583: switch(j) {
584: case 0:
585: cterm.font_size=4096;
586: break;
587: case 1:
588: cterm.font_size=3586;
589: break;
590: case 2:
591: cterm.font_size=2048;
592: break;
593: default:
594: cterm.font_size=0;
595: break;
596: }
597: }
598: break;
599: }
600: break;
601: }
602: switch(*p) {
603: case '@': /* Insert Char */
604: i=wherex();
605: j=wherey();
606: k=atoi(cterm.escbuf+1);
607: if(k<1)
608: k=1;
609: if(k>cterm.width - j)
610: k=cterm.width - j;
611: gettext(cterm.x+i-1,cterm.y+j-1,cterm.x+cterm.width-1-k,cterm.y+j-1,tmp);
612: for(l=0; l< k; l++)
613: putch(' ');
614: puttext(cterm.x+i-1+k,cterm.y+j-1,cterm.x+cterm.width-1,cterm.y+j-1,tmp);
615: gotoxy(i,j);
616: break;
617: case 'A': /* Cursor Up */
618: i=atoi(cterm.escbuf+1);
619: if(i==0)
620: i=1;
621: i=wherey()-i;
622: if(i<1)
623: i=1;
624: gotoxy(wherex(),i);
625: break;
626: case 'B': /* Cursor Down */
627: i=atoi(cterm.escbuf+1);
628: if(i==0)
629: i=1;
630: i=wherey()+i;
631: if(i>cterm.height)
632: i=cterm.height;
633: gotoxy(wherex(),i);
634: break;
635: case 'C': /* Cursor Right */
636: i=atoi(cterm.escbuf+1);
637: if(i==0)
638: i=1;
639: i=wherex()+i;
640: if(i>cterm.width)
641: i=cterm.width;
642: gotoxy(i,wherey());
643: break;
644: case 'D': /* Cursor Left and Font Select */
645: if(*(p-1)==' ') { /* Font Select */
646: i=0;
647: j=0;
648: if(strlen(cterm.escbuf)>2) {
649: if((p=strtok(cterm.escbuf+1,";"))!=NULL) {
650: i=atoi(p);
651: if((p=strtok(NULL,";"))!=NULL) {
652: j=atoi(p);
653: }
654: }
655: if(i==0) { /* Only the primary font is currently supported */
656: setfont(j,FALSE);
657: }
658: }
659: }
660: else {
661: i=atoi(cterm.escbuf+1);
662: if(i==0)
663: i=1;
664: i=wherex()-i;
665: if(i<1)
666: i=1;
667: gotoxy(i,wherey());
668: }
669: break;
670: case 'E': /* Cursor next line */
671: i=atoi(cterm.escbuf+1);
672: if(i==0)
673: i=1;
674: i=wherey()+i;
675: if(i>cterm.height)
676: i=cterm.height;
677: gotoxy(1,i);
678: break;
679: case 'F': /* Cursor preceding line */
680: i=atoi(cterm.escbuf+1);
681: if(i==0)
682: i=1;
683: i=wherey()-i;
684: if(i<1)
685: i=1;
686: gotoxy(1,i);
687: break;
688: case 'f':
689: case 'H':
690: row=1;
691: col=1;
692: *p=0;
693: if(strlen(cterm.escbuf)>1) {
694: if((p=strtok(cterm.escbuf+1,";"))!=NULL) {
695: row=atoi(p);
696: if((p=strtok(NULL,";"))!=NULL) {
697: col=atoi(p);
698: }
699: }
700: }
701: if(row<1)
702: row=1;
703: if(col<1)
704: col=1;
705: if(row>cterm.height)
706: row=cterm.height;
707: if(col>cterm.width)
708: col=cterm.width;
709: gotoxy(col,row);
710: break;
711: case 'J':
712: i=atoi(cterm.escbuf+1);
713: switch(i) {
714: case 0:
715: clear2eol();
716: p2=(char *)alloca(cterm.width*2);
717: j=0;
718: for(i=0;i<cterm.width;i++) {
719: p2[j++]=' ';
720: p2[j++]=cterm.attr;
721: }
722: for(i=wherey()+1;i<=cterm.height;i++) {
723: puttext(cterm.x,cterm.y+i-1,cterm.x+cterm.width-1,cterm.y+i-1,p2);
724: }
725: break;
726: case 1:
727: clear2bol();
728: p2=(char *)alloca(cterm.width*2);
729: j=0;
730: for(i=0;i<cterm.width;i++) {
731: p2[j++]=' ';
732: p2[j++]=cterm.attr;
733: }
734: for(i=wherey()-1;i>=1;i--) {
735: puttext(cterm.x,cterm.y+i-1,cterm.x+cterm.width-1,cterm.y+i-1,p2);
736: }
737: break;
738: case 2:
739: clearscreen((char)cterm.attr);
740: gotoxy(1,1);
741: break;
742: }
743: break;
744: case 'K':
745: i=atoi(cterm.escbuf+1);
746: switch(i) {
747: case 0:
748: clear2eol();
749: break;
750: case 1:
751: clear2bol();
752: break;
753: case 2:
754: p2=(char *)alloca(cterm.width*2);
755: j=0;
756: for(i=0;i<cterm.width;i++) {
757: p2[j++]=' ';
758: p2[j++]=cterm.attr;
759: }
760: puttext(cterm.x,cterm.y+wherey()-1,cterm.x+cterm.width-1,cterm.y+wherey()-1,p2);
761: break;
762: }
763: break;
764: case 'L': /* Insert line */
765: i=atoi(cterm.escbuf+1);
766: if(i==0)
767: i=1;
768: if(i>cterm.height-wherey())
769: i=cterm.height-wherey();
770: if(i) {
771: p2=(char *)alloca((cterm.height-wherey()-i+1)*cterm.width*2);
772: gettext(cterm.x,cterm.y+wherey()-1,cterm.x+cterm.width-1,cterm.y+cterm.height-1-i,p2);
773: puttext(cterm.x,cterm.y+wherey()-1+i,cterm.x+cterm.width-1,cterm.y+cterm.height-1,p2);
774: }
775: else {
776: p2=(char *)alloca(cterm.width*2);
777: }
778: j=0;
779: for(k=0;k<cterm.width;k++) {
780: p2[j++]=' ';
781: p2[j++]=cterm.attr;
782: }
783: for(j=0;j<i;j++) {
784: puttext(cterm.x,cterm.y+wherey()-1+j,cterm.x+cterm.width-1,cterm.y+wherey()-1+j,p2);
785: }
786: break;
787: case 'M': /* ANSI music and also supposed to be delete line! */
788: if(cterm.music_enable==CTERM_MUSIC_ENABLED) {
789: cterm.music=1;
790: }
791: else {
792: i=atoi(cterm.escbuf+1);
793: if(i<1)
794: i=1;
795: dellines(i);
796: }
797: break;
798: case 'N':
799: /* BananANSI style... does NOT start with MF or MB */
800: /* This still conflicts (ANSI erase field) */
801: if(cterm.music_enable >= CTERM_MUSIC_BANSI)
802: cterm.music=2;
803: break;
804: case 'P': /* Delete char */
805: i=atoi(cterm.escbuf+1);
806: if(i==0)
807: i=1;
808: if(i>cterm.width-wherex()+1)
809: i=cterm.width-wherex()+1;
810: p2=(char *)alloca((cterm.width-wherex()+1)*2);
811: gettext(cterm.x+wherex(),cterm.y+wherey()-1,cterm.x+cterm.width-1,cterm.y+wherey()-1,p2);
812: j=(cterm.width-wherex())*2;
813: for(k=0;k<0;k++) {
814: p2[j++]=' ';
815: p2[j++]=cterm.attr;
816: }
817: puttext(cterm.x+wherex()-1,cterm.y+wherey()-1,cterm.x+cterm.width-1,cterm.y+wherey()-1,p2);
818: break;
819: case 'S':
820: i=atoi(cterm.escbuf+1);
821: if(i==0 && cterm.escbuf[1] != '0')
822: i=1;
823: for(j=0; j<i; j++)
824: scrollup();
825: break;
826: case 'T':
827: i=atoi(cterm.escbuf+1);
828: if(i==0 && cterm.escbuf[1] != '0')
829: i=1;
830: for(j=0; j<i; j++)
831: scrolldown();
832: break;
833: #if 0
834: case 'U':
835: clearscreen(7);
836: gotoxy(1,1);
837: break;
838: #endif
839: case 'X':
840: i=atoi(cterm.escbuf+1);
841: if(i<1)
842: i=1;
843: if(i>cterm.width-wherex())
844: i=cterm.width-wherex();
845: p2=alloca(i*2);
846: j=0;
847: for(k=0;k<i;k++) {
848: p2[j++]=' ';
849: p2[j++]=cterm.attr;
850: }
851: puttext(cterm.x+wherex()-1,cterm.y+wherey()-1,cterm.x+wherex()-1+i-1,cterm.y+wherey()-1,p2);
852: break;
853: case 'Z':
854: i=atoi(cterm.escbuf+1);
855: if(i==0 && cterm.escbuf[0] != '0')
856: i=1;
857: for(j=10;j>=0;j--) {
858: if(cterm_tabs[j]<wherex()) {
859: k=j-i+1;
860: if(k<0)
861: k=0;
862: gotoxy(cterm_tabs[k],wherey());
863: break;
864: }
865: }
866: break;
867: case 'b': /* ToDo? Banana ANSI */
868: break;
869: case 'c': /* Device Attributes */
870: i=atoi(cterm.escbuf+1);
871: if(!i) {
872: if(retbuf!=NULL) {
873: if(strlen(retbuf)+strlen(cterm.DA) < retsize)
874: strcat(retbuf,cterm.DA);
875: }
876: }
877: break;
878: case 'g': /* ToDo? VT100 Tabs */
879: break;
880: case 'h': /* ToDo? Scrolling regeion, word-wrap, doorway mode */
881: break;
882: case 'i': /* ToDo? Printing */
883: break;
884: case 'l': /* ToDo? Scrolling regeion, word-wrap, doorway mode */
885: break;
886: case 'm':
887: *(p--)=0;
888: p2=cterm.escbuf+1;
889: if(p2>p) {
890: cterm.attr=7;
891: break;
892: }
893: while((p=strtok(p2,";"))!=NULL) {
894: p2=NULL;
895: switch(atoi(p)) {
896: case 0:
897: cterm.attr=7;
898: break;
899: case 1:
900: cterm.attr|=8;
901: break;
902: case 2:
903: cterm.attr&=247;
904: break;
905: case 4: /* Underscore */
906: break;
907: case 5:
908: case 6:
909: cterm.attr|=128;
910: break;
911: case 7:
912: case 27:
913: i=cterm.attr&7;
914: j=cterm.attr&112;
915: cterm.attr &= 136;
916: cterm.attr |= j>>4;
917: cterm.attr |= i<<4;
918: break;
919: case 8:
920: j=cterm.attr&112;
921: cterm.attr&=112;
922: cterm.attr |= j>>4;
923: break;
924: case 22:
925: cterm.attr &= 0xf7;
926: break;
927: case 25:
928: cterm.attr &= 0x7f;
929: break;
930: case 30:
931: cterm.attr&=248;
932: break;
933: case 31:
934: cterm.attr&=248;
935: cterm.attr|=4;
936: break;
937: case 32:
938: cterm.attr&=248;
939: cterm.attr|=2;
940: break;
941: case 33:
942: cterm.attr&=248;
943: cterm.attr|=6;
944: break;
945: case 34:
946: cterm.attr&=248;
947: cterm.attr|=1;
948: break;
949: case 35:
950: cterm.attr&=248;
951: cterm.attr|=5;
952: break;
953: case 36:
954: cterm.attr&=248;
955: cterm.attr|=3;
956: break;
957: case 37:
958: case 39:
959: cterm.attr&=248;
960: cterm.attr|=7;
961: break;
962: case 40:
963: cterm.attr&=143;
964: break;
965: case 41:
966: cterm.attr&=143;
967: cterm.attr|=4<<4;
968: break;
969: case 42:
970: cterm.attr&=143;
971: cterm.attr|=2<<4;
972: break;
973: case 43:
974: cterm.attr&=143;
975: cterm.attr|=6<<4;
976: break;
977: case 44:
978: cterm.attr&=143;
979: cterm.attr|=1<<4;
980: break;
981: case 45:
982: cterm.attr&=143;
983: cterm.attr|=5<<4;
984: break;
985: case 46:
986: cterm.attr&=143;
987: cterm.attr|=3<<4;
988: break;
989: case 47:
990: case 49:
991: cterm.attr&=143;
992: cterm.attr|=7<<4;
993: break;
994: }
995: }
996: textattr(cterm.attr);
997: break;
998: case 'n':
999: i=atoi(cterm.escbuf+1);
1000: switch(i) {
1001: case 5:
1002: if(retbuf!=NULL) {
1003: strcpy(tmp,"\x1b[0n");
1004: if(strlen(retbuf)+strlen(tmp) < retsize)
1005: strcat(retbuf,tmp);
1006: }
1007: break;
1008: case 6:
1009: if(retbuf!=NULL) {
1010: sprintf(tmp,"%c[%d;%dR",27,wherey(),wherex());
1011: if(strlen(retbuf)+strlen(tmp) < retsize)
1012: strcat(retbuf,tmp);
1013: }
1014: break;
1015: case 255:
1016: if(retbuf!=NULL) {
1017: sprintf(tmp,"%c[%d;%dR",27,cterm.height,cterm.width);
1018: if(strlen(retbuf)+strlen(tmp) < retsize)
1019: strcat(retbuf,tmp);
1020: }
1021: break;
1022: }
1023: break;
1024: case 'p': /* ToDo? ANSI keyboard reassignment */
1025: break;
1026: case 'q': /* ToDo? VT100 keyboard lights */
1027: break;
1028: case 'r': /* ToDo? Scrolling reigon */
1029: /* Set communication speed (if has a * before it) */
1030: if(*(p-1) == '*' && speed != NULL) {
1031: /*
1032: * Ps1 Comm Line Ps2 Communication Speed
1033: * none, 0, 1 Host Transmit none, 0 Use default speed.
1034: * 2 Host Receive 1 300
1035: * 3 Printer 2 600
1036: * 4 Modem Hi 3 1200
1037: * 5 Modem Lo 4 2400
1038: * 5 4800
1039: * 6 9600
1040: * 7 19200
1041: * 8 38400
1042: * 9 57600
1043: * 10 76800
1044: * 11 115200
1045: */
1046: int newspeed=0;
1047:
1048: *(--p)=0;
1049: if(cterm.escbuf[1]) {
1050: p=strtok(cterm.escbuf+1,";");
1051: if(p!=NULL) {
1052: if(p!=cterm.escbuf+1 || atoi(p)<2) {
1053: if(p==cterm.escbuf+1)
1054: p=strtok(NULL,";");
1055: if(p!=NULL) {
1056: switch(atoi(p)) {
1057: case 0:
1058: newspeed=0;
1059: break;
1060: case 1:
1061: newspeed=300;
1062: break;
1063: case 2:
1064: newspeed=600;
1065: break;
1066: case 3:
1067: newspeed=1200;
1068: break;
1069: case 4:
1070: newspeed=2400;
1071: break;
1072: case 5:
1073: newspeed=4800;
1074: break;
1075: case 6:
1076: newspeed=9600;
1077: break;
1078: case 7:
1079: newspeed=19200;
1080: break;
1081: case 8:
1082: newspeed=38400;
1083: break;
1084: case 9:
1085: newspeed=57600;
1086: break;
1087: case 10:
1088: newspeed=76800;
1089: break;
1090: case 11:
1091: newspeed=115200;
1092: break;
1093: default:
1094: newspeed=-1;
1095: break;
1096: }
1097: }
1098: }
1099: else
1100: newspeed = -1;
1101: }
1102: }
1103: if(newspeed >= 0)
1104: *speed = newspeed;
1105: }
1106: break;
1107: case 's':
1108: cterm.save_xpos=wherex();
1109: cterm.save_ypos=wherey();
1110: break;
1111: case 'u':
1112: if(cterm.save_ypos>0 && cterm.save_ypos<=cterm.height
1113: && cterm.save_xpos>0 && cterm.save_xpos<=cterm.width) {
1114: gotoxy(cterm.save_xpos,cterm.save_ypos);
1115: }
1116: break;
1117: case 'y': /* ToDo? VT100 Tests */
1118: break;
1119: case 'z': /* ToDo? Reset */
1120: break;
1121: case '|': /* SyncTERM ANSI Music */
1122: break;
1123: }
1124: break;
1125: #if 0
1126: case 'D':
1127: scrollup();
1128: break;
1129: case 'M':
1130: scrolldown();
1131: break;
1132: #endif
1133: case 'c':
1134: /* ToDo: Reset Terminal */
1135: break;
1136: }
1137: cterm.escbuf[0]=0;
1138: cterm.sequence=0;
1139: }
1140:
1141: void cterm_init(int height, int width, int xpos, int ypos, int backlines, unsigned char *scrollback)
1142: {
1143: char *revision="$Revision: 1.89 $";
1144: char *in;
1145: char *out;
1146:
1147: memset(&cterm, 0, sizeof(cterm));
1148: cterm.x=xpos;
1149: cterm.y=ypos;
1150: cterm.height=height;
1151: cterm.width=width;
1152: cterm.attr=7;
1153: cterm.save_xpos=0;
1154: cterm.save_ypos=0;
1155: cterm.escbuf[0]=0;
1156: cterm.sequence=0;
1157: cterm.music_enable=CTERM_MUSIC_BANSI;
1158: cterm.music=0;
1159: cterm.tempo=120;
1160: cterm.octave=4;
1161: cterm.notelen=4;
1162: cterm.noteshape=CTERM_MUSIC_NORMAL;
1163: cterm.musicfore=TRUE;
1164: cterm.backpos=0;
1165: cterm.backlines=backlines;
1166: cterm.scrollback=scrollback;
1167: cterm.log=CTERM_LOG_NONE;
1168: cterm.logfile=NULL;
1169: if(cterm.scrollback!=NULL)
1170: memset(cterm.scrollback,0,cterm.width*2*cterm.backlines);
1171: textattr(cterm.attr);
1172: _setcursortype(_NORMALCURSOR);
1173: window(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y+cterm.height-1);
1174: clrscr();
1175: gotoxy(1,1);
1176: strcpy(cterm.DA,"\x1b[=67;84;101;114;109;");
1177: out=strchr(cterm.DA, 0);
1178: if(out != NULL) {
1179: for(in=revision; *in; in++) {
1180: if(isdigit(*in))
1181: *(out++)=*in;
1182: if(*in=='.')
1183: *(out++)=';';
1184: }
1185: *out=0;
1186: }
1187: strcat(cterm.DA,"c");
1188: /* Did someone call _init() without calling _end()? */
1189: if(playnote_thread_running) {
1190: if(sem_trywait(&playnote_thread_terminated)==-1) {
1191: listSemPost(¬es);
1192: sem_wait(&playnote_thread_terminated);
1193: }
1194: sem_destroy(&playnote_thread_terminated);
1195: sem_destroy(¬e_completed_sem);
1196: listFree(¬es);
1197: }
1198: /* Fire up note playing thread */
1199: if(!playnote_thread_running) {
1200: listInit(¬es, LINK_LIST_SEMAPHORE|LINK_LIST_MUTEX);
1201: sem_init(¬e_completed_sem,0,0);
1202: sem_init(&playnote_thread_terminated,0,0);
1203: _beginthread(playnote_thread, 0, NULL);
1204: }
1205: }
1206:
1207: void ctputs(char *buf)
1208: {
1209: char *outp;
1210: char *p;
1211: int oldscroll;
1212: int cx;
1213: int cy;
1214: int i;
1215:
1216: outp=buf;
1217: oldscroll=_wscroll;
1218: _wscroll=0;
1219: cx=wherex();
1220: cy=wherey();
1221: if(cterm.log==CTERM_LOG_ASCII && cterm.logfile != NULL)
1222: fputs(buf, cterm.logfile);
1223: for(p=buf;*p;p++) {
1224: switch(*p) {
1225: case '\r':
1226: cx=1;
1227: break;
1228: case '\n':
1229: *p=0;
1230: cputs(outp);
1231: outp=p+1;
1232: if(cy==cterm.height)
1233: scrollup();
1234: else
1235: cy++;
1236: gotoxy(cx,cy);
1237: break;
1238: case '\b':
1239: *p=0;
1240: cputs(outp);
1241: outp=p+1;
1242: if(cx>0)
1243: cx--;
1244: gotoxy(cx,cy);
1245: break;
1246: case 7: /* Bell */
1247: break;
1248: case '\t':
1249: for(i=0;i<10;i++) {
1250: if(cterm_tabs[i]>cx) {
1251: while(cx<cterm_tabs[i]) {
1252: cx++;
1253: }
1254: break;
1255: }
1256: }
1257: if(i==10) {
1258: cx=1;
1259: if(cy==cterm.height) {
1260: *p=0;
1261: cputs(outp);
1262: outp=p+1;
1263: scrollup();
1264: gotoxy(cx,cy);
1265: }
1266: else
1267: cy++;
1268: }
1269: break;
1270: default:
1271: if(cy==cterm.height
1272: && cx==cterm.width) {
1273: char ch;
1274: ch=*p;
1275: *p=0;
1276: cputs(outp);
1277: *p=ch;
1278: outp=p;
1279: scrollup();
1280: cx=1;
1281: gotoxy(cx,cy);
1282: }
1283: else {
1284: if(cx==cterm.width) {
1285: cx=1;
1286: cy++;
1287: }
1288: else {
1289: cx++;
1290: }
1291: }
1292: break;
1293: }
1294: }
1295: cputs(outp);
1296: _wscroll=oldscroll;
1297: }
1298:
1299: char *cterm_write(unsigned char *buf, int buflen, char *retbuf, size_t retsize, int *speed)
1300: {
1301: unsigned char ch[2];
1302: unsigned char prn[BUFSIZE];
1303: int j,k;
1304: struct text_info ti;
1305: int olddmc;
1306: int oldptnm;
1307:
1308: oldptnm=puttext_can_move;
1309: puttext_can_move=1;
1310: olddmc=hold_update;
1311: hold_update=1;
1312: if(retbuf!=NULL)
1313: retbuf[0]=0;
1314: gettextinfo(&ti);
1315: window(cterm.x,cterm.y,cterm.x+cterm.width-1,cterm.y+cterm.height-1);
1316: gotoxy(cterm.xpos,cterm.ypos);
1317: textattr(cterm.attr);
1318: ch[1]=0;
1319: switch(buflen) {
1320: case 0:
1321: break;
1322: default:
1323: if(cterm.log==CTERM_LOG_RAW && cterm.logfile != NULL)
1324: fwrite(buf, buflen, 1, cterm.logfile);
1325: prn[0]=0;
1326: for(j=0;j<buflen;j++) {
1327: ch[0]=buf[j];
1328: if(cterm.font_size) {
1329: cterm.fontbuf[cterm.font_read++]=ch[0];
1330: if(cterm.font_read == cterm.font_size) {
1331: char *buf;
1332:
1333: if((buf=(char *)malloc(cterm.font_size))!=NULL) {
1334: memcpy(buf,cterm.fontbuf,cterm.font_size);
1335: if(cterm.font_slot >= CONIO_FIRST_FREE_FONT) {
1336: switch(cterm.font_size) {
1337: case 4096:
1338: FREE_AND_NULL(conio_fontdata[cterm.font_slot].eight_by_sixteen);
1339: conio_fontdata[cterm.font_slot].eight_by_sixteen=buf;
1340: FREE_AND_NULL(conio_fontdata[cterm.font_slot].desc);
1341: conio_fontdata[cterm.font_slot].desc=strdup("Remote Defined Font");
1342: break;
1343: case 3586:
1344: FREE_AND_NULL(conio_fontdata[cterm.font_slot].eight_by_fourteen);
1345: conio_fontdata[cterm.font_slot].eight_by_fourteen=buf;
1346: FREE_AND_NULL(conio_fontdata[cterm.font_slot].desc);
1347: conio_fontdata[cterm.font_slot].desc=strdup("Remote Defined Font");
1348: break;
1349: case 2048:
1350: FREE_AND_NULL(conio_fontdata[cterm.font_slot].eight_by_eight);
1351: conio_fontdata[cterm.font_slot].eight_by_eight=buf;
1352: FREE_AND_NULL(conio_fontdata[cterm.font_slot].desc);
1353: conio_fontdata[cterm.font_slot].desc=strdup("Remote Defined Font");
1354: break;
1355: }
1356: }
1357: else
1358: FREE_AND_NULL(buf);
1359: }
1360: cterm.font_size=0;
1361: }
1362: }
1363: else if(cterm.sequence) {
1364: k=strlen(cterm.escbuf);
1365: strcat(cterm.escbuf,ch);
1366: if(k) {
1367: if(cterm.escbuf[0] != '[') { /* Not a CSI code. */
1368: /* ANSI control characters */
1369: if(ch[0] >= 32 && ch[0] <= 47) {
1370: /* Legal intermediate character */
1371: }
1372: else if(ch[0] >= 48 && ch[0] <= 126) {
1373: /* Terminating character */
1374: do_ansi(retbuf, retsize, speed);
1375: }
1376: else {
1377: /* Broken sequence detected */
1378: strcat(prn,"\033");
1379: strcat(prn,cterm.escbuf);
1380: cterm.escbuf[0]=0;
1381: cterm.sequence=0;
1382: }
1383: }
1384: else {
1385: /* We know that it was a CSI at this point */
1386: /* Here's where we get funky! */
1387: /* the last character defines the set of legal next characters */
1388: if(ch[0] >= 48 && ch[0] <= 63) {
1389: /* Parameter character. Only legal after '[' and other param chars */
1390: if(cterm.escbuf[k]!='['
1391: && (cterm.escbuf[k] < 48 || cterm.escbuf[k] > 63)) {
1392: /* Broken sequence detected */
1393: strcat(prn,"\033");
1394: strcat(prn,cterm.escbuf);
1395: cterm.escbuf[0]=0;
1396: cterm.sequence=0;
1397: }
1398: }
1399: else if(ch[0] >= 32 && ch[0] <= 47) {
1400: /* Intermediate character. Legal after '[', param, or intermetiate chars */
1401: if(cterm.escbuf[k]!='['
1402: && (cterm.escbuf[k] < 48 || cterm.escbuf[k] > 63)
1403: && (cterm.escbuf[k] < 32 || cterm.escbuf[k] > 47)) {
1404: /* Broken sequence detected */
1405: strcat(prn,"\033");
1406: strcat(prn,cterm.escbuf);
1407: cterm.escbuf[0]=0;
1408: cterm.sequence=0;
1409: }
1410: }
1411: else if(ch[0] >= 64 && ch[0] <= 126) {
1412: /* Terminating character. Always legal at this point. */
1413: do_ansi(retbuf, retsize, speed);
1414: }
1415: else {
1416: /* Broken sequence detected */
1417: strcat(prn,"\033");
1418: strcat(prn,cterm.escbuf);
1419: cterm.escbuf[0]=0;
1420: cterm.sequence=0;
1421: }
1422: }
1423: }
1424: else {
1425: /* First char after the ESC */
1426: if(ch[0] >= 32 && ch[0] <= 47) {
1427: /* Legal intermediate character */
1428: /* No CSI then */
1429: }
1430: else if(ch[0]=='[') {
1431: /* CSI received */
1432: }
1433: else if(ch[0] >= 48 && ch[0] <= 126) {
1434: /* Terminating character */
1435: do_ansi(retbuf, retsize, speed);
1436: }
1437: else {
1438: /* Broken sequence detected */
1439: strcat(prn,"\033");
1440: strcat(prn,cterm.escbuf);
1441: cterm.escbuf[0]=0;
1442: cterm.sequence=0;
1443: }
1444: }
1445: if(ch[0]=='\033') { /* Broken sequence followed by a legal one! */
1446: if(prn[0]) /* Don't display the ESC */
1447: prn[strlen(prn)-1]=0;
1448: ctputs(prn);
1449: prn[0]=0;
1450: cterm.sequence=1;
1451: }
1452: }
1453: else if (cterm.music) {
1454: if(ch[0]==14)
1455: play_music();
1456: else {
1457: if(strchr(musicchars,ch[0])!=NULL)
1458: strcat(cterm.musicbuf,ch);
1459: else {
1460: /* Kill non-music strings */
1461: cterm.music=0;
1462: cterm.musicbuf[0]=0;
1463: }
1464: }
1465: }
1466: else {
1467: switch(buf[j]) {
1468: case 0:
1469: break;
1470: case 7: /* Beep */
1471: ctputs(prn);
1472: prn[0]=0;
1473: if(cterm.log==CTERM_LOG_ASCII && cterm.logfile != NULL)
1474: fputs("\t", cterm.logfile);
1475: #ifdef __unix__
1476: putch(7);
1477: #else
1478: MessageBeep(MB_OK);
1479: #endif
1480: break;
1481: case 12: /* ^L - Clear screen */
1482: ctputs(prn);
1483: prn[0]=0;
1484: if(cterm.log==CTERM_LOG_ASCII && cterm.logfile != NULL)
1485: fputs("\t", cterm.logfile);
1486: clearscreen((char)cterm.attr);
1487: gotoxy(1,1);
1488: break;
1489: case 27: /* ESC */
1490: ctputs(prn);
1491: prn[0]=0;
1492: cterm.sequence=1;
1493: break;
1494: case '\t':
1495: ctputs(prn);
1496: prn[0]=0;
1497: if(cterm.log==CTERM_LOG_ASCII && cterm.logfile != NULL)
1498: fputs("\t", cterm.logfile);
1499: for(k=0;k<11;k++) {
1500: if(cterm_tabs[k]>wherex()) {
1501: gotoxy(cterm_tabs[k],wherey());
1502: break;
1503: }
1504: }
1505: break;
1506: default:
1507: strcat(prn,ch);
1508: }
1509: }
1510: }
1511: ctputs(prn);
1512: prn[0]=0;
1513: break;
1514: }
1515: cterm.xpos=wherex();
1516: cterm.ypos=wherey();
1517: #if 0
1518: window(ti.winleft,ti.wintop,ti.winright,ti.winbottom);
1519: gotoxy(ti.curx,ti.cury);
1520: textattr(ti.attribute);
1521: #endif
1522:
1523: hold_update=olddmc;
1524: puttext_can_move=oldptnm;
1525: gotoxy(wherex(),wherey());
1526: return(retbuf);
1527: }
1528:
1529: int cterm_openlog(char *logfile, int logtype)
1530: {
1531: cterm.logfile=fopen(logfile, "a");
1532: if(cterm.logfile==NULL)
1533: return(0);
1534: cterm.log=logtype;
1535: return(1);
1536: }
1537:
1538: void cterm_closelog()
1539: {
1540: if(cterm.logfile != NULL)
1541: fclose(cterm.logfile);
1542: cterm.logfile=NULL;
1543: cterm.log=CTERM_LOG_NONE;
1544: }
1545:
1546: void cterm_end(void)
1547: {
1548: int i;
1549:
1550: cterm_closelog();
1551: for(i=CONIO_FIRST_FREE_FONT; i < 256; i++) {
1552: FREE_AND_NULL(conio_fontdata[i].eight_by_sixteen);
1553: FREE_AND_NULL(conio_fontdata[i].eight_by_fourteen);
1554: FREE_AND_NULL(conio_fontdata[i].eight_by_eight);
1555: FREE_AND_NULL(conio_fontdata[i].desc);
1556: }
1557: if(playnote_thread_running) {
1558: if(sem_trywait(&playnote_thread_terminated)==-1) {
1559: listSemPost(¬es);
1560: sem_wait(&playnote_thread_terminated);
1561: }
1562: sem_destroy(&playnote_thread_terminated);
1563: sem_destroy(¬e_completed_sem);
1564: listFree(¬es);
1565: }
1566: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.