Source file: /~heha/vt/viewers/vtx.zip/vtx.c

#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>	/* Mauspfeil-Konstanten */
#include <X11/keysym.h>		/* Tastatur-Konstanten */
#include <signal.h>		/* wegen "Timer-Message" */
#include <X11/Xatom.h>		/* wegen XA_CUT_BUFFER0 */
#include <errno.h>

#include "gifsave.h"

/* Dieser Quelltext benötigt gcc's Verschachtelte Funktionen! */

#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
#define motorola		/* use byte swaps */
#endif

/********************************/
/* Internationale Stringtabelle */
/********************************/
static const char* const sApp[]={
 "Teletext",
 "Videotext"};
static const char* const sDisplayAbort[]={
 "VTX: Fatal: Cannot open display `%s'!\n",
 "VTX Abbruch: Display '%s' kann nicht geöffnet werden!\n"};
static const char* const sHelp[]={
"Teletext viever for X window system X11 by haftmann#software\n\
Usage: vtx [options] filename[.vt]\n\
Available options:\n\
-d displayame\tOutput display (i.e. \"-d oasis.etc:0\")\n\
-l{d|f}\tChoose language {German|(French not installed)}\n\
-p xxx/yy\toutput page/subpage as plain text to stdout without starting X\n\
-m xxx\tUse this display flags (see README)\n\
-f xx\tUse this font number (see README)\n\
-n\tSuppress all flashing characters and rolling subpages\n\
-s xx\tUse this search mode (see README)\n\
-v xxx\tVolume of XBell (-100..0..100)\n\
-h\tThis help\n",

"Videotext-Betrachter für X Window System X11 von haftmann#software\n\
Anwendung: vtx [optionen] dateiname[.vt]\n\
Mögliche Optionen:\n\
-d displayname\tAusgabe-Display (z.B. \"-d toaster.csn:0\")\n\
-l{e|d}\tSprache auswählen {englisch|deutsch} sonst automatisch\n\
-p xxx/yy\tAusgabe des Inhalts von Seite xxx Unterseite yy als Text; ohne X\n\
-m xxx\tDiese Anzeige-Flags verwenden (s. LIESMICH)\n\
-f xx\tDiese Zeichensatz-Nummer verwenden (s. LIESMICH)\n\
-n\tKeinerlei blinkende Anzeige und rollende Unterseiten\n\
-s xx\tDiesen Suchmodus benutzen (s. LIESMICH)\n\
-v xxx\tLautstärke von XBell (-100..0..100) bei falschen Tasten\n\
-h\tDiese Hilfe\n"};

static const char* const sGotoPage[]={
 "Go to page number:",
 "Gehe zu Seite Nummer:"};
static const char* const sNoPage[]={
 "Page %hX doesn't exist!",
 "Seite %hX existiert nicht!"};
static const char* const sGotoSubp[]={
 "Go to subpage number:",
 "Gehe zu Unterseite Nummer:"};
static const char* const sNoSubp[]={
 "Subpage %04hX doesn't exist!",
 "Unterseite %04hX existiert nicht!"};
static const char* const sOnlyOneSubp[]={
 "This page has only one subpage!",
 "Diese Seite hat nur eine Unterseite!"};
static const char* const sNoBack[]={
 "\"Back\" impossible!",
 "Rückschritt nicht möglich!"};
static const char* const sNoMatch[]={
 "No (more) matching pages!",
 "Keine (weiteren) Übereinstimmungen!"};
static const char* const sOpenFile[]={
 "Filename:",
 "Dateiname:"};
static const char* const sChooseFont[]={
 "Choose font:",
 "Zeichensatz:"};
static const char* const sFileError[]={
 "Error reading file %1.10s, code %hd!",
 "\"%1.10s\"-Dateiladefehler, Code %hd"};
static const char* const sWriteError[]={
 "Error writing file %1.10s, code %hd!",
 "Kann nicht speichern \"%1.6s\", Code %hd"};
static const char* const sSuchString[]={
 "Seach pattern:",
 "Suchkette:"};
static const char* const sPageInfo[]={
 "CBits=%hXh, PageFont=%s",
 "CBits=%04hXh, Zeichens.=%s"};
static const char* const sFontName[][16]={
 "auto detect", "*German", "*English", "*Scandinavian",
 "Polish", "Czech/Slovak", "*Italian", "*French",
 "*Spanish", "Icelandic", "Hungarian", "Turkish",
 " reserved", " reserved", "Cyrillic", "Arabic",

 "automatisch", "*deutsch", "*englisch", "*skandinavisch",
 "polnisch", "tschechoslowakisch", "*italienisch", "*französisch",
 "*spanisch", "isländisch", "ungarisch", "türkisch",
 " reserviert", " reserviert", "kyrillisch", "arabisch"};
/*^--------------^---Trick-Leerzeichen! Bitte stehen lassen! */

static const char* const sHelpPage[]={"\
\0\0\0\0\0\0\0\7         Help page              \
\4\x1d\7   *** navigate ***                  \
\6o F4                \7open a file...    \
\6g Home              \7page 100          \
\6j PgDn + CurDn      \7next page         \
\6k PgUp - CurUp      \7previous page     \
\6l CurRight Tabulator\7next subpage      \
\6h CurLeft           \7previous subpage  \
\6  Spacebar          \7next subpage or pg\
\6q ^C                \7quit VTX          \
\6y F1                \7this help         \
\6/ F2                \7find...           \
\6n F3                \7find next         \
\6p Left mouse button \7go to page xxx    \
\6s                   \7go to subpage xxxx\
\6b BkSp RightButton  \7go back           \
\6f                   \7go forward        \
\4\x1d\7   *** render options ***            \
\2Q\7toggle QUIZ (on remote control)      \
\2D\7toggle displaying DoubleHeight       \
\2M\7toggle mixed mode                    \
\2I\7toggle invers rendering              \
\2W\7toggle black/white or color display  \
\2B\7toggle debug display (control codes) \
\2F\7choose font... (then use cursor keys)\
","\
\0\0\0\0\0\x40\0\7         Hilfeseite             \
\4\x1d\7   *** Navigation ***                \
\6o F4                \7Datei |ffnen...   \
\6g Pos1              \7Seite 100         \
\6j BildAb  + PfeilAb \7N{chste Seite     \
\6k BildAuf - PfeilAuf\7Vorherige Seite   \
\6l PfeilRechts Tab.  \7N{chste Unterseite\
\6h PfeilLinks        \7Vorherige Unters. \
\6  Leertaste         \7N{chste Unters.+S.\
\6q F10 ^C            \7VTX beenden       \
\6y F1                \7Diese Hilfe       \
\6/ F2                \7Suchen...         \
\6n F3                \7Weitersuchen      \
\6p Linke Maustaste   \7Gehe zu Seite xxx \
\6s                   \7Gehe zu Unterseite\
\6b Rechte Maustaste  \7Gehe zur}ck       \
\6f                   \7Gehe vorw{rts     \
\4\x1d\7   *** Anzeige-Optionen ***          \
\2Q\7Quiz-Taste ein/aus                   \
\2D\7Anzeige von doppelt hohen Zeichen e/a\
\2M\7Mix-Betrieb ein/aus                  \
\2I\7Invers-Darstellung                   \
\2W\7Schwarz/Wei~- oder farbige Darstellg.\
\2B\7Debug-Anzeige (Steuercodes)          \
\2F\7Font ausw{hlen (benutze Cursortasten)\
"};


static const char* const sPagedefault[]={"\
\0\0\0\0\0\0\0\7   Welcome Page   \5h_s \00700:00:00\
 pppppppppppppppppppppppppppppppppppp  \
   \
|||||||||||||||||| \
 \
 \
######## \
         \
         \
         \
         \
\7                                       \
\7  Teletext viewer for X window system  \
\7         by haftmann_software          \
\7                                       \
               \
                 \
                     \
                     \
                       \
                         \
                           \
              //////////               \
                             \
\6 Press F1 for help or F4 to open a file\
","\
\0\0\0\0\0\x40\0\7 Begr}~ungsseite  \5h#s \00700:00:00\
 pppppppppppppppppppppppppppppppppppp  \
   \
|||||||||||||||||| \
 \
 \
######## \
         \
         \
         \
         \
\7                                       \
\7   Videotext-Betrachter f}r X-Windows  \
\7         von haftmann#software         \
\7                                       \
               \
                 \
                     \
                     \
                       \
                         \
                           \
              //////////               \
                             \
\6    F1 f}r Hilfe oder F4 zum \\ffnen    \
"};

static int country;			/* Default: englisch */
static int volume;
/******************/
/* Display öffnen */
/******************/
static Display *display;		/* Zeiger auf Display-Struktur */
static Screen *screen;
static Colormap cmap;

static void InitDisplay(char *DName){
 display=XOpenDisplay(DName);
 if (display==NULL){
  fprintf(stderr,sDisplayAbort[country],XDisplayName(DName));
  exit(1);
 }
 screen=DefaultScreenOfDisplay(display);
 cmap=DefaultColormap(display,DefaultScreen(display));
}

/**********************************************/
/* Hauptfenster erstellen, Ressourcen belegen */
/**********************************************/
#define FONTW 8
#define FONTH 10
#define GetLines(x) ( ((x) & f1)? 1: (((x) & f25)? 25 : 24) )
#define GetCols(x) ( ((x) & f41)? 41 : 40 )
static Window window;
static GC MainGC,XorGC;
static XContext Linklist;
#include "vticon.inc"
static Pixmap icon;
static Cursor idc_arrow,idc_hand,idc_wait;
static dword Farbe[9];		/* globale Pixelwerte */

tRGBTriple RGBWerte[16];	/* Farbwerte für Abspeicherung als GIF */

struct{
Pixmap hbm;
bool valid;
} Pixmaps[2]={None,false,None,false};

static short BMIdx=-1;			/* Keine Bitmap */


static void SetWindowTitle(char *FileName){
 char CatBuf[256];
 strcpy(CatBuf,sApp[country]);	/* Neuen Fenstertitel aufbauen */
 if (FileName){			/* Nur wenn einer vorhanden dann anhängen */
  strcat(CatBuf,": ");
  strcat(CatBuf,FileName);
 }
 XSetStandardProperties(display,window,CatBuf,CatBuf,icon,NULL,0,NULL);
}

static void GetColors(bool invert){
 XColor TempColor,ExactColor;
 static const char* const Farbname[9]={
 "black","red","green","yellow","blue","magenta","cyan","white","gray"};
 static const char* const InvFarbe[9]={
 "white","red","green4","yellow4","blue","magenta4","cyan4","black","gray50"};
 int i;
 const char *s;
 for (i=0; i<9; i++){
  s=invert? InvFarbe[i] : Farbname[i];
  if (!XAllocNamedColor(display,cmap,s,&TempColor,&ExactColor))
   fprintf(stderr,"VTX Warning: Error allocating color '%s'.\n",s);
  RGBWerte[i].R=ExactColor.red >>8;	/* Farben für den GIF-Packer ablegen */
  RGBWerte[i].G=ExactColor.green >>8;
  RGBWerte[i].B=ExactColor.blue >>8;
  Farbe[i]=TempColor.pixel;	/* Pixelwert für BitBlt-Operationen ablegen */
 }
}

static void CreateMainWindow(){
 XSizeHints xsh;
 XSetWindowAttributes xswa;
 XGCValues xgcv;

/* Fenster erzeugen */
/* printf("Hier vorbei: Fenster erstellen\n");*/
 xsh.flags=PSize|PMaxSize|PMinSize;
 xsh.min_width=xsh.max_width=xsh.width=41*FONTW;
 xsh.min_height=xsh.max_height=xsh.height=25*FONTH;

 xswa.cursor=idc_arrow=XCreateFontCursor(display,XC_top_left_arrow);
 xswa.border_pixel = BlackPixelOfScreen(screen);
 xswa.background_pixmap=None;
 xswa.event_mask = ButtonPressMask|ButtonReleaseMask|Button1MotionMask|
  KeyPressMask|ExposureMask;

 window=XCreateWindow(display,RootWindowOfScreen(screen),
 0,0,xsh.width,xsh.height,1,
 CopyFromParent, CopyFromParent, CopyFromParent,
 CWBorderPixel|CWCursor|CWBackPixmap|CWEventMask,&xswa);

/* Icon erzeugen und Fenster-Name geben */
 icon=XCreateBitmapFromData(display,RootWindowOfScreen(screen),
 vticon, vticon_w, vticon_h);

 XSetStandardProperties(display,window,sApp[country],sApp[country],
 icon,NULL,0,&xsh);
 XMapWindow(display,window);

/* Fenster-GC dauerhaft holen */
 xgcv.subwindow_mode=IncludeInferiors;
 xgcv.graphics_exposures=false;
 xgcv.foreground=WhitePixelOfScreen(screen);
 xgcv.background=BlackPixelOfScreen(screen);
 MainGC=XCreateGC(display,RootWindowOfScreen(screen),
 GCForeground|GCBackground|GCSubwindowMode|GCGraphicsExposures,&xgcv);

/* XorGC dauerhaft belegen */
 xgcv.function=GXxor;
 xgcv.foreground^=BlackPixelOfScreen(screen);
 XorGC=XCreateGC(display,RootWindowOfScreen(screen),
  GCFunction|GCForeground|GCBackground|GCSubwindowMode|
  GCGraphicsExposures,&xgcv);

/* Weitere Ressourcen belegen */
 idc_hand=XCreateFontCursor(display,XC_hand2);
 idc_wait=XCreateFontCursor(display,XC_watch);
 Linklist=XUniqueContext();
 Pixmaps[0].hbm=XCreatePixmap(display,window,
  41*FONTW,25*FONTH,DefaultDepthOfScreen(screen));
 Pixmaps[1].hbm=XCreatePixmap(display,RootWindowOfScreen(screen),
  41*FONTW,25*FONTH,DefaultDepthOfScreen(screen));
 GetColors(false);
/* printf("WeissPixel=%d, SchwarzPixel=%d.\n",
  WhitePixelOfScreen(screen),
  BlackPixelOfScreen(screen));*/
}

/*****************************/
/* Videotext-Font "hochladen"*/
/*****************************/
static Pixmap vtfont1=None,vtfont2=None;	/* Die beiden Handles auf die Fonts */
#define ZEICHEN (512+128)
#include "vtfont.inc"	/* Die binären Daten */

static void FontUpload(){
 byte *Puffer,*Zeiger2;
 const byte *Zeiger1;
 int i;
 if (vtfont1!=None) {
  XFreePixmap(display,vtfont1); vtfont1=None;
 }
 if (vtfont2!=None) {
  XFreePixmap(display,vtfont2); vtfont2=None;
 }
 vtfont1=XCreateBitmapFromData(display,window,vtfont,FONTW,ZEICHEN*FONTH);
/* Doppelt hohe Zeichen generieren */
 Zeiger1=vtfont;
 Zeiger2=Puffer=(byte *) calloc(FONTH*2,ZEICHEN);
 for (i=0; i<ZEICHEN*FONTH; i++){
  *Zeiger2++ = *Zeiger1;
  *Zeiger2++ = *Zeiger1++;
 }
 vtfont2=XCreateBitmapFromData(display,window,Puffer,FONTW,ZEICHEN*FONTH*2);
 free(Puffer);
}

/* Bedeutung der C-Bits
C4  - Löschen einer Seite
C5  - Schlagzeilen
C6  - Untertitel
C7  - Unterdrücken der Kopfzeile
C8  - Texterneuerung
C9  - Unterbrochene Sequenz
C10 - Unterdrückung der Darstellung
C11 - Serielle Magazinfolge

C12 .. C14
000 - Englisch(0)
001 - Deutsch(4)
010 - Schwedisch/Finnisch(2)
011 - Italienisch(6)
100 - Belgisch/Französisch(1)
101 - Portugisisch/Spanisch(5)
110 - dynamische Zeichenzuordnung(3)
111 - reserviert(7)
Ersetzkette  40 60 7B 23 24 ** ** ** 7C 7D 7E 5B 5C 5D 5E 5F */
/****************************/
/* Videotext-Seite zeichnen */
/****************************/
#define bit 1 <<
#define fQuiz (bit 0)
#define fBlink (bit 1)
#define fDblH (bit 2)
#define fMix (bit 3)
#define fInvers (bit 4)
#define fBunt (bit 5)
#define fDebug (bit 6)
#define fRoll (bit 7)
#define fDblSize (bit 8)
#define f41 (bit 9)
#define f25 (bit 10)
#define f1 (bit 11)
#define fBBar (bit 12)
#define fNoX (bit 13)

#define fGraf (bit 8)
#define fHold (bit 9)
#define fSep (bit 10)
#define fShift (bit 11)
#define fLatin (bit 12)

typedef union{
 dword L;
 struct{
  word Page,Subp;
 } W;
} tIdx;

/*#define IdxL(x) (*(long *)&(x))*/

typedef byte VTLine[40];

typedef union{
 dword L;
 struct{
  word Page,Subp;
  word CBits;
  byte errors;
 } W;
} tHeader;


typedef struct{
 VTLine L[25];		/* 25*40=1000 Bytes Text */
 byte Page10,Page1,Subp1000,Subp100,Subp10,Subp1,Page100,C1,C2,PBLF;
/* struct tHSFTrail{	/* haftmann#software und FifiSoft Schwanz */
 byte Acqisitor;	/* Wer eingelesen hat */
 byte MaxSubp;		/* nur aus der Angabe xx/yy auf der Seite entnommen */
 dword MsDosTime;	/* Einlesezeitpunkt, zeitzonenabhängig */
 word CRC;		/* Prüfsumme über diese Seite für Fehlerkorrektur */
 word Mirror;		/* Adresse einer Spiegelseite während des Einlesens */
 long UnixTime;	/* Einlesezeitpunkt, Universal Time */
/* } E;			/* 14 Bytes freie Zusatz-Info (wie oben definiert) */
} tTrail;

typedef union{
 byte B[1024];		/* 1 Kilobyte */
 VTLine L[25];		/* 25 Zeilen mit je 40 Zeichen */
 tTrail T;		/* 25 Zeilen und der Trailer */
 tHeader H;		/* 7 Bytes kompaktierte SAA-Info */
 char TopLeft[7];	/* Zum Überschreiben des Headers, Nicht nullterminiert! */
} tPage;


static tHeader Header;
tPage Page;		/* eine passende Variable definieren */

static const char Ansitab[]={
' ','!','\"','#','$','%','&','\'','(',')','*','+',',','-','.','/',
'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
0xa7,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
'P','Q','R','S','T','U','V','W','X','Y','Z',0xc4,0xd6,0xdc,'^','_',
0xb0,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
'p','q','r','s','t','u','v','w','x','y','z',0xe4,0xf6,0xfc,0xdf,' ',
'@','-',0xbc,0xa3,'$',' ',' ',' ',0xa6,0xbe,0xad,0xab,0xbd,0xbb,'^','#',
0xc9,0xe9,0xe4,'#',0xa4,' ',' ',' ',0xf6,0xe5,0xfc,0xc4,0xd6,0xc5,0xdc,'_',
0x20,0x20,0x20,0x20,0x20,0x20,0x20,'l',0x20,0x20,'S','L',0x20,0x20,0x20,0x20,
'c',0xe9,0xe1,'#',0xf9,0x20,0x20,0x20,0xe8,0xfa,'s','t','z',0xff,0xed,'r',
0xe9,0xf9,0xe0,0xa3,'$',0xe3,0xf5,0xb7,0xf2,0xe8,0xec,0xb0,0xe7,0xbb,'^','#',
0xe0,0xe8,0xe2,0xe9,0xef,0xc3,0xd5,0xc7,0xf4,0xfb,0xe7,0xeb,0xea,0xf9,0xee,'#',
0xa1,0xbf,0xfc,0xe7,'$',0xaa,0xba,0xd1,0xf1,0xe8,0xe0,0xe1,0xe9,0xed,0xf3,0xfa,
0xc1,0xc0,0xc8,0xcd,0xcf,0xd3,0xd2,0xda,0xe6,0xc6,'d','D',0xf8,0xd8,0xfe,0xee};

static void DrawBWUtf8Char(byte col,byte line,char code){
 const char *Such;
 static char utf8lead;		/* for simplicity, expect only two-byte UTF-8 */
 if ((code&0xE0)==0xC0 && !utf8lead) {		/* UTF-8 lead */
  utf8lead=code;
  return;			/* swallow code, expect nect "character" */
 }
 if (utf8lead) {
  if ((code&0xC0)==0x80) code=utf8lead<<6|code&0x3F;
  utf8lead=0;
 }
 Such=strchr(Ansitab,code);	/* Ersatz gefunden? */
 if (Such) code=(char)(Such-Ansitab)+0x20;	/* umwandeln in VT-Font-Zeichen (?) */
 XCopyPlane(display,vtfont1,window,MainGC,
  0,(code+128)*FONTH,FONTW,FONTH,
  (short)col*FONTW,(short)line*FONTH,1);
}
static const byte LandMap[8]={2,7,3,0,1,8,6,0};


static int Translate(byte c,word FlagReg,word Font){
 const char *ChChar;
 int code;
 static const char ErsetzKette[16]={
  0x40, 0x60, 0x7B, 0x23, 0x24, 0x40, 0x40, 0x40,
  0x7C, 0x7D, 0x7E, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F};

 code=(short)c;	/* Standardfall */
 if (FlagReg & (fBlink|fQuiz)){
  return 32;		/* Leerzeichen ausgeben */
 }
 if (FlagReg & fGraf){
  if ((code>=32) && (code<64)) code=(int)c-128-32;
  if ((code>=96) && (code<128)) code=(int)c-128-96+32;
			/* Negative Codes für Grafiksymbole */
  if (FlagReg & fSep) code+=64;	/* Separierte Grafik */
  return code;
 }
 if ((Font>=2) && (Font<14)){
			/* 12 = reserve I, 13 = reserve II */
  ChChar=(byte *)memchr(ErsetzKette,c,16);	/* in Ersetzkette suchen */
  if (ChChar){		/* Gefunden ? */
   code=((Font+6) <<4) + (ChChar-ErsetzKette);
/*   printf("Zeichen-Ersetzung! Zeichen-Nummer %u, neuer Code %d\n",
    ChChar-ErsetzKette,code);*/
   return code;
			/* Code im Bereich 80..13F */
  }
  return code;
 }
 if ((Font>=14) && (Font<16) && !(FlagReg & fLatin)){
  return (Font-14)*96+0x140+(c-32);
			/* Kyrillisch, arabisch im Bereich 140..1FF */
 }
 return code;
}


static void DrawVT(Pixmap drw, word Flags, word Font){
 GC gc;		/* GC für Vorder- und Hintergrundfarbe */
 XGCValues xgcv;
 word FlagReg;
 byte lines,line,col;
 auto byte vfarbe=7,vfarbc=7,hfarbe=0,hfarbc=0;	/* Farbbytes und Caches */
 auto bool Mixc=false;
 bool LineContainsDblH;
 byte OldByte,CurByte;

/* Verschachtelte Hilfsfunktionen */
 void WriteChar(int code){
/* Folgender Code vermeidet unnötige XSet{Fore|Back}ground's */
  if (Flags & fBunt){			/* Nur wenn farbig! */
   if (FlagReg & fMix){			/* fMix gesetzt? */
    if (!Mixc){				/* fMix verändert? */
     Mixc=true;
     XSetBackground(display,gc,Farbe[8]);	/* Transparente Farbe */
/*     printf("Hier vorbei [8]\n");*/
    }
   }else{				/* fMix gelöscht? */
    if ((hfarbe != hfarbc) || (Mixc)){	/* Neue HFarbe oder Mix war gesetzt? */
     Mixc=false;
     XSetBackground(display,gc,Farbe[hfarbc=hfarbe]); /* Farbe setzen */
    }
   }
   if (vfarbe != vfarbc)		/* Vordergrund geändert? */
    XSetForeground(display,gc,Farbe[vfarbc=vfarbe]); /* Farbe setzen */
  }
/* Jetzt das Zeichen ausgeben */
  if (FlagReg & fDblH){
   XCopyPlane(display,vtfont2,drw,gc,
    0,(code+128)*FONTH*2,FONTW,FONTH*2,
    col*FONTW,line*FONTH,1);
  }else{
   XCopyPlane(display,vtfont1,drw,gc,
    0,(code+128)*FONTH,FONTW,FONTH,
    col*FONTW,line*FONTH,1);
   if (LineContainsDblH)
    XCopyPlane(display,vtfont1,drw,gc,
     0,(32+128)*FONTH,FONTW,FONTH,
     col*FONTW,(line+1)*FONTH,1);
  }
 } /* Prozedur WriteChar */

 void WriteCharTranslate(byte c){
  WriteChar(Translate(c,FlagReg,Font));	/* Jetzt ausgeben */
 }

 bool CanDblH(){	/* Doppelte Zeichenhöhe erlaubt? */
  return(line>0 && line<23 && Flags&fDblH);
 }

 void SetMix(){
  if (Flags&fMix || Header.W.CBits&0x60)
   FlagReg|= fMix;		/* InBox aus, also Mix ein */
 }

/* Gerätekontexte erstellen */
/* hgc=XCreateGC(display,drw,0,NULL);*/
 xgcv.subwindow_mode=IncludeInferiors;
 xgcv.graphics_exposures=false;
 xgcv.foreground=Farbe[vfarbe];
 xgcv.background=Farbe[hfarbe];
 gc=XCreateGC(display,drw,
 GCForeground|GCBackground|GCSubwindowMode|GCGraphicsExposures,&xgcv);
/* Land-Variable aufbereiten */
 if (Font==0)
  Font=LandMap[(Header.W.CBits >> 12) & 7];
			/* Wenn default(Auto-detect) dann aus den C-Bits*/

 lines=GetLines(Flags);
/* mit Schleifen loslegen */
 LineContainsDblH=false;
 for (line=0; line<lines; line++){
  if (LineContainsDblH){	/* hier: vorhergehende Linie! */
   LineContainsDblH=false;	/* nichts weiter tun, dann nächste Zeile */
  }else{
   LineContainsDblH=CanDblH() && memchr(Page.L[line],13,40);
			/* Stellen; 13 ist Code für DblH */

   FlagReg=0;
   SetMix();      	/* auf Null oder Mixbetrieb setzen */
   hfarbe=0;		/* Hintergrundfarbe */
   vfarbe=7;
   OldByte=32;		/* auch das vorhergehende Byte zu Space machen */
   for(col=0; col<40; col++){
    CurByte=Page.L[line][col];	/* aus der Seite holen */
/* Sofort wirksame Schalter auswerten *//* Ein Zeichen ausgeben */
    switch (CurByte) {
     case 28:
      hfarbe=0;
      break;
     case 29:
      hfarbe=vfarbe;
      break;
    }
    if ((CurByte<0x20) && ((Flags & fDebug)==0)){
     if ((FlagReg & fHold) && (FlagReg & fGraf) &&
	/* HoldGrafix und GrafikFlag */
      ((OldByte&0xbf)>=0x20) && ((OldByte&0xbf)<0x40)){
	/* Zeichen mit Codes 0x20..0x3f sowie 0x60..0x7f */
      WriteCharTranslate(OldByte);
     }else{
      WriteCharTranslate(32);	/* Leerzeichen */
/*       if ((FlagReg & fHold) && (FlagReg & fGraf))
	printf("Haltegrafik+Grafikflag, aber OldByte=%hX und geANDed=%hX\n",
	 OldByte,OldByte&0xbf);*/
     };				/* Auf jeden Fall OldByte behalten! */
    }else{
     WriteCharTranslate(CurByte);
     OldByte=CurByte;
    }
/* Nachher wirksame Schalter */
    if (CurByte<0x20)
     FlagReg&= ~fQuiz;		/* Secret aus */
    switch (CurByte){
     case 0:
     case 1:
     case 2:
     case 3:
     case 4:
     case 5:
     case 6:
     case 7:
      FlagReg&= ~fGraf;		/* Grafikflag aus */
      vfarbe=CurByte;
      break;
     case 8:
      if (!(Flags & fBlink))	/* wenn "blinkende Zeichen AUS" */
       FlagReg|= fBlink;	/* Blinken ein */
      break;
     case 9:
      FlagReg&= ~fBlink;	/* Blinken aus */
      break;
     case 10:
      SetMix();
      break;
     case 11:
      FlagReg&= ~fMix;		/* InBox ein */
      break;
     case 12:
      FlagReg&= ~fDblH;		/* DoubleHeigth aus */
      break;
     case 13:
      if (CanDblH())		/* wenn nicht verboten */
       FlagReg|= fDblH;		/* DoubleHeigth ein */
      break;
     case 14:
      FlagReg&= ~fShift;	/* Shift aus (später verwendet) */
      break;
     case 15:
      FlagReg|= fShift;		/* Shift ein (später verwendet) */
      break;
     case 16:
     case 17:
     case 18:
     case 19:
     case 20:
     case 21:
     case 22:
     case 23:
      FlagReg|= fGraf;		/* Grafikflag ein */
      vfarbe=(CurByte & 7);
      break;
     case 24:
      if (!(Flags & fQuiz))	/* wenn "Quiz-Zeichen AUS" */
       FlagReg|= fQuiz;		/* Secret ein */
      break;
     case 25:
      FlagReg&= ~fSep;		/* separated aus */
      break;
     case 26:
      FlagReg|= fSep;		/* separated ein */
      break;
     case 27:
      FlagReg^= fLatin;		/* Sprachumschaltung Lateinisch */
      break;
     case 30:
      FlagReg|= fHold;		/* holdgrafix ein */
      break;
     case 31:
      FlagReg&= ~fHold;		/* holdgrafix aus */
      break;
    }
   } /* 1 Zeile */
   if (Flags & f41){
    hfarbe=0;			/* schwarz */
    WriteCharTranslate(32);	/* noch ein Space dran */
   }
  } /* if-else */
 } /* for */
/* Kontexte auflösen */
 XFreeGC(display,gc);
}

static void InvokeExpose(){
 XClearArea(display,window,0,0,0,0,true);
}

static void Page2Asc(char *dest, word Flags, word Font,
 short left, short top, short right, short bottom){
 word FlagReg;
 byte lines,line,col;
 bool LineContainsDblH;
 byte CurByte;

/* Verschachtelte Hilfsfunktionen */
 void WriteChar(int code){
/* Jetzt das Zeichen ausgeben */
  if ((col>=left) && (col<=right) &&
   (line>=top) && (line<=bottom)){
   if ((code>=32) && (code<256)){
    char c=Ansitab[code-32];	/* nach ANSI gewandeltes Zeichen */
    if (c&0x80) {
     *dest++=(byte)c>>6|0xC0;		/* mache UTF-8 daraus */
     *dest++=c&0x3F|0x80;
    }else *dest++=c;
   }else{
    *dest++=' ';	/* sonst Leerzeichen (puh, UTF-8 kann doch mehr!!) */
   }
   if (col==right){
    *dest++='\n';		/* Newline dranhängen */
   }
  }
 } /* Prozedur WriteChar */

 void WriteCharTranslate(byte c){
  WriteChar(Translate(c,FlagReg,Font));	/* Jetzt ausgeben */
 }

 bool CanDblH(){	/* Doppelte Zeichenhöhe erlaubt? */
  return(line>0 && line<23 && Flags&fDblH);
 }

/* Land-Variable aufbereiten */
 if (!Font) Font=LandMap[(Header.W.CBits >> 12) & 7];
			/* Wenn default(Auto-detect) dann aus den C-Bits*/

 lines=GetLines(Flags);
/* mit Schleifen loslegen */
 LineContainsDblH=false;
 for (line=0; line<lines; line++){
  if (LineContainsDblH){	/* hier: vorhergehende Linie! */
   LineContainsDblH=false;	/* nichts weiter tun, dann nächste Zeile */
  }else{

   FlagReg=0;
   for(col=0; col<40; col++){
    CurByte=Page.L[line][col];	/* aus der Seite holen */
/* Ein Zeichen ausgeben */
    if (CurByte<0x20){
     WriteCharTranslate(32);	/* Leerzeichen */
    }else{
     WriteCharTranslate(CurByte);
    }
/* Nachher wirksame Schalter */
    if (CurByte<0x20)
     FlagReg&= ~fQuiz;		/* Secret aus */
    switch (CurByte){
     case 0:
     case 1:
     case 2:
     case 3:
     case 4:
     case 5:
     case 6:
     case 7:
      FlagReg&= ~fGraf;		/* Grafikflag aus */
      break;
     case 12:
      FlagReg&= ~fDblH;		/* DoubleHeigth aus */
      break;
     case 13:
      if (CanDblH()){		/* wenn nicht verboten */
       FlagReg|= fDblH;		/* DoubleHeigth ein */
       LineContainsDblH=true;
      }
      break;
     case 16:
     case 17:
     case 18:
     case 19:
     case 20:
     case 21:
     case 22:
     case 23:
      FlagReg|= fGraf;		/* Grafikflag ein */
      break;
     case 24:
      if (!(Flags & fQuiz))	/* wenn "Quiz-Zeichen AUS" */
       FlagReg|= fQuiz;		/* Secret ein */
      break;
     case 27:
      FlagReg^= fLatin;		/* Sprachumschaltung Lateinisch */
      break;
    }
   } /* 1 Zeile */
   if (Flags & f41){
    WriteCharTranslate(32);	/* noch ein Space dran */
   }
  } /* if-else */
 } /* for */
 *dest++='\0';			/* Abschluß-Null dranbammeln */
}

/* Such-Definitionen */
#define BACKWARD (bit 0)
#define NOCASESENS (bit 1)
#define NOHIGHCHARS (bit 2)
#define USEREGEXP (bit 3)

static void PrepareString(char *Str, word How){
/* Umkodierungstabelle für "hohe ASCIIs" */
 static const char AnsiUmcodTab[64]={
   'A','A','A','A','A','A','A','C','E','E','E','E','I','I','I','I',
   'D','N','O','O','O','O','O',0xd7,'O','U','U','U','U','Y','P','s',
   'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i',
   'd','n','o','o','o','o','o',0xf7,'o','u','u','u','u','y','p','y'};
 if (Str){			/* Wenn nicht NULL */
  for(;*Str;Str++){		/* String bis zum \0-Zeichen parsen */
   if ((How & NOHIGHCHARS) && ((byte)*Str >0xc0))
    *Str=AnsiUmcodTab[*Str-0xc0];
   if (How & NOCASESENS)
    *Str=toupper(*Str);
  }
 }
}

/****************************/
/* Datei browsen nach Index */
/****************************/

#ifdef motorola
static void byteswap(short *i){
 byte t;
 t=((byte *)i)[0];
 ((byte *)i)[0]=((byte *)i)[1];
 ((byte *)i)[1]=t;
}
static void longswap(long *i){
 byte t;
 t=((byte *)i)[0];
 ((byte *)i)[0]=((byte *)i)[3];
 ((byte *)i)[3]=t;
 t=((byte *)i)[1];
 ((byte *)i)[1]=((byte *)i)[2];
 ((byte *)i)[2]=t;
}
#endif

/* #define INDIZES 2048		/* Maximale Dateigröße in Kilobytes */

#ifdef motorola
#define MAGIC 0xd6d4e8e6	/* ('VThf'^0x80808080) */
#define MAGIC_HS 0xd6d4e8f3	/* ('VThs'^0x80808080) */
#else
#define MAGIC 0xe6e8d4d6	/* 'VThf' mit gesetzten Bit7 rückwärts */
#define MAGIC_HS 0xf3e8d4d6	/* 'VThs' mit gesetzten Bit7 rückwärts */
#endif
#define INDEXPP 249		/* Indexeinträge pro führender Index-Seite */
#define INDEXPP_HS 255		/* im h#s-Format passen mehr Einträge 'rein */


static tIdx *Index=NULL;	/* der Index über die Datei */

static word IndexCnt=0;
static FILE *VTFile=NULL;		/* Aktueller Datei-Pointer */
static char FName[256];		/* aktueller Dateiname (undefiniert) */

static void CloseFile(){
 if (VTFile){
  fclose(VTFile);
  VTFile=NULL;
 }
}

static int LoadFile(char *FileName){	/* Tatsächlicher Dateiname wird rückgeschrieben! */
 word i;
 long hdr;
 char CatBuf[256];
 tIdx *NewIndex;
 FILE *VTTemp;			/* solange bis Datei OK ist */

 void ReadNewIndex(word indexpp){
  /* Routine zum Einlesen eines New-Format-VT-Files */
  /* Die Kennung sei bereits vorher gecheckt! (schneller) */
  word i,i1;
  i=(IndexCnt+indexpp) / (indexpp+1);	/* Reservierte Seiten am Beginn */
  for (i1=0; i1<i; i1++)
   Index[i1].L=MAGIC;		/* Erste Indexeinträge sperren */
  for (; i<IndexCnt; i+=i1){
   fread(&Page,sizeof(tPage),1,VTFile);
/*   if (Page.B[0]<0x80)
    printf("Warnung: VT-Datei-Index inkonsistent oder zu kurz (evtl. Bug)!\n");*/
   i1=IndexCnt-i;
   if (i1>indexpp) i1=indexpp;
   memcpy(&Index[i],&Page.B[4],sizeof(tIdx)*i1);	/* Indizes kopieren */
  }
 }

 if (!FileName) return 9;	/* NULL übergeben? - Fehler! */
 strcpy(CatBuf,FileName);	/* die Katze verwurschteln
				   (oder kann jemand "strcpy" richtig aussprechen:-)*/
 VTTemp=fopen(CatBuf,"rb");
 if (!VTTemp){			/* Bei Fehler: Versuche es mit Suffix ".vt" */
  strcat(CatBuf,".vt");		/* die Katze aus dem Sack lassen */
  VTTemp=fopen(CatBuf,"rb");	/* Zweiter Versuch */
  if (!VTTemp){
   return errno;		/* Jetzt weiß "LoadFile()" nicht mehr weiter */
  }
 }
 fread(&hdr,4,1,VTTemp);	/* Kennung lesen */
 fseek(VTTemp,0L,SEEK_END);	/* Größe feststellen */
 i=ftell(VTTemp) / 1024;	/* Erforderliche Größe */
 if (i==0) return 11;		/* Null ist falsch (Ungültiges Format) */
 rewind(VTTemp);
 NewIndex=(tIdx *)realloc(Index,i*sizeof(tIdx));
			/* Dieser Heapbefehl trifft den Nagel auf dem Kopf */
 if (!NewIndex){		/* Kein (neuer) Speicher? */
  return 8;			/* alten Zustand beibehalten! */
 }
 Index=NewIndex;		/* Neuer Index-Zeiger */
 IndexCnt=i;			/* in globale Variable schreiben */
 CloseFile();			/* Jetzt erst alte Datei zumachen */
 VTFile=VTTemp;			/* neuen Zeiger zuweisen */
 strcpy(FName,CatBuf);		/* neuer Name - alles roger */
/* printf("'%s' geoeffnet, Laenge: %hu Seiten, Datei-Typ: ",FileName,IndexCnt); */

 switch (hdr){
  case MAGIC:			/* das Haftmann-Fiedler-Format */
/*   printf("VThf\n"); */
   ReadNewIndex(INDEXPP);
   break;
  case MAGIC_HS:		/* das etwas speichersparsamere Haftmann-F. */
/*   printf("VThs\n"); */
   ReadNewIndex(INDEXPP_HS);
   break;
  case 0x20202020:		/* Uralt-Format (immer noch von Fiedel benutzt!) */
/*   printf("uralt\n"); */
   for (i=0; i<IndexCnt; i++){
    fread(&Page,1024,1,VTFile);
    Index[i].W.Page=
     ((Page.T.Page100 & 7) <<8)|
     ((Page.T.Page10 & 0xf) <<4)|
      (Page.T.Page1 & 0xf);
    Index[i].W.Subp=
     ((Page.T.Subp1000 & 3) <<12)|
     ((Page.T.Subp100 & 0xf) <<8)|
     ((Page.T.Subp10 & 7) <<4)|
      (Page.T.Subp1 & 0xf);
   }
   break;
  default:			/* Reine Seitennummer: "File of TPage" */
/*   printf("traditionell\n"); */
   for (i=0; i<IndexCnt; i++){
    fseek(VTFile,(long)i*1024,SEEK_SET);
    fread(&Index[i],sizeof(tIdx),1,VTFile);
   }
 }
#ifdef motorola
 if (hdr!=0x20202020){		/* im Falle "Uralt" stimmt der Index schon! */
  for (i=0; i<IndexCnt; i++){
   byteswap(&Index[i].W.Page);
   byteswap(&Index[i].W.Subp);	/* Gesamten Index "behandeln" */
  }
 }
#endif
 return 0;			/* fehlerfrei */
}



/*****************************/
/* Seite auswählen und laden */
/*****************************/

/* Suchmodi, wenn angegebene Seite nicht gefunden: */
#define EQUALPAGE 1
/* Seite und Unterseite muß stimmen */
#define NEXTPAGE 2
/* Gehe zur nächsten Seite, minimale Unterseite */
#define PREVPAGE 3
/* Gehe zur vorherigen Seite, minimale Unterseite */
#define NEXTSUB 4
/* Gehe zur nächsten Unterseite mit Wrap-Around, Seite muß stimmen */
#define PREVSUB 5
/* Gehe zur vorherigen Unterseite mit Wrap-Around, Seite muß stimmen */
#define NEXTSUBPAGE 6
/* Gehe zur nächsten Unterseite, wenn nicht dann zur nächsten Seite */
#define PREVSUBPAGE 7
/* Gehe zur vorherigen Unterseite, wenn nicht dann zur vorherigen Seite */
#define ADDNEXTPAGE 10
/* wie NEXTPAGE, jedoch nicht die angegebene Seite nehmen */
#define SUBPREVPAGE 11
/* wie PREVPAGE, jedoch nicht die angegebene Seite nehmen */
#define ADDNEXTSUB 12
/* wie NEXTSUB, jedoch nicht die angegebene Unterseite nehmen */
#define SUBPREVSUB 13
/* wie PREVSUB, jedoch nicht die angegebene Unterseite nehmen */
#define ADDNEXTSUBPAGE 14
/* wie NEXTSUBPAGE, jedoch nicht die angegebene Seite nehmen */
#define SUBPREVSUBPAGE 15
/* wie PREVSUBPAGE, jedoch nicht die angegebene Seite nehmen */


/* Videotext-Seite in "gewöhnliches Format" wandeln */
static word UsualPage(word Page){
 Page&=0x7FF;			/* Obere Bits löschen */
 if (Page<0x100) return Page+0x800; /* 000..0FF --> 800..8FF */
 return Page;			/* alles andere so lassen */
}

/* Videotext-Index in String wandeln */
static char *Idx2Str(tIdx Index){	/* Nicht reentrant! */
 static char Puffer[10];
 sprintf(Puffer,"%03hX/%02hX",
  UsualPage(Index.W.Page),Index.W.Subp & 0x3f7f);
 return Puffer;
}

/* Seite auswählen, liefert Index-Nummer oder FFFF bei Fehler */
static word SelectPage(word DP,word DS,byte SearchMode){
 word i,idx;		/* Indizes */
#define CP ((Index[i].W.Page) & 0x7ff)
#define CS ((Index[i].W.Subp) & 0x3f7f)
 dword BB,OO;		/* BB=Bester Abstand, OO=Abstand */
 auto word f01=0;	/* Entscheidet, ob GLEICHE Seite gefunden wird */

 BB=OO=0xffffffff;
 idx=0xffff;		/* ungültigen Indexeintrag erzeugen */
 DP&=0x7FF;
 DS&=0x3F7F;
/*printf("SelectPage: %hx/%hx Suchmodus %hd, ",
 UsualPage(DP),DS,SearchMode);*/
 if (SearchMode & 8){
  SearchMode&=~8;
  f01++;
 }
/* if (!Index){
  printf("SelectPage aufgerufen mit Index=NULL!\n");
 } */
 for (i=0; i<IndexCnt; i++){
  if (CP < 0x1000){	/* Nur mit gültigen Einträgen arbeiten */
/* Ziel ist es, BP bzw. BS zu minimieren und bei Erfolg wird "idx" gesetzt */
   switch (SearchMode){
    case NEXTPAGE:
     OO=(((dword)CP-DP-f01)<<16)+(CS-0);
			/* Vorwärts, DesiredSub verwerfen */
     break;

    case PREVPAGE:
     OO=(((dword)DP-CP-f01)<<16)+(CS-0);
			/* Rückwärts & Vorwärts, DesiredSub verwerfen */
     break;

    case NEXTSUB:
     if (CP==DP)	/* Gleiche Seite? */
      OO=(dword)CS-DS-f01;	/* Vorwärts */
     break;

    case PREVSUB:
     if (CP==DP)	/* Gleiche Seite? */
      OO=(dword)DS-CS-f01;	/* Rückwärts */
     break;

    case NEXTSUBPAGE:
     OO=(((dword)CP<<16)+CS)-(((dword)DP<<16)+DS)-f01;
			/* Vorwärts, Abstand als dword */
     break;

    case PREVSUBPAGE:
     OO=(((dword)DP<<16)+DS)-(((dword)CP<<16)+CS)-f01;
			/* Rückwärts, Abstand als dword */
     break;

    case EQUALPAGE:
     if ((DP==CP) && (DS==CS))
      OO=0;		/* Nur wenn gleich den Index setzen */
     break;
   } /* switch */
   if (OO<BB){		/* "Bessere" Seite? */
    BB=OO;		/* merken */
    idx=i;		/* Heißen Index merken */
   }
  } /* if */
 } /* for */
/*printf("Ergebnis %s Index %hu Abst.%X\n",(idx==0xffff)?"Nicht gefunden":
 Idx2Str(Index[idx]),idx,BB);*/
 return idx;
}

/* Seite.Zeile[24] im Speicher mit "Buttonleiste" versehen */
static void PatchPage(word Flags){

 word PageOf(word Page, byte Mode){
  Page=Index[SelectPage(Page,0,Mode)].W.Page;	/* überschreibend */
  return UsualPage(Page);
 }
 sprintf((char *)&Page.L[24],
  "\4\x1d\7(B) \7\x1d\4%03hX %03hX << >> %03hX %03hX \4\x1d\7(F) ",
  PageOf(Header.W.Page&0x700,NEXTPAGE),	/* zum Hunderter */
  PageOf(Header.W.Page, SUBPREVPAGE),	/* Seite Ab */
  PageOf(Header.W.Page, ADDNEXTPAGE),	/* Seite Auf */
  PageOf((Header.W.Page+0x100)& 0x700,NEXTPAGE));	/* zum nächsten Hunderter */
}

/* Word- und Long-Angaben korrekt ausrichten und Headerbytes extrahieren */
/* Header auf magenta Spaces setzen */
static void SetupPage(){
#ifdef motorola
 byteswap(&Page.H.W.Page);
 byteswap(&Page.H.W.Subp);
 byteswap(&Page.H.W.CBits);
 longswap(&Page.T.MsDosTime);	/* Einlesezeitpunkt, zeitzonenabhängig */
 byteswap(&Page.T.CRC);		/* Prüfsumme über diese Seite für Fehlerkorrektur */
 byteswap(&Page.T.Mirror);	/* Adresse einer Spiegelseite während des Einlesens */
 longswap(&Page.T.UnixTime);	/* Einlesezeitpunkt, Universal Time */
#endif
 memcpy(&Header,&Page.H,sizeof(tHeader));	/* Header auslagern */
 Page.B[0]=5;					/* magenta (also lila) */
 memset(&Page.B[1],' ',6);
}

/* Seite auswählen und laden */
/* sowie oben und unten "dekorieren" */
/* Ist Index=NULL dann wird die Begrüßungsseite geladen */
/* sowie das Ergebnis ebenfalls auf "Not found" 0xffff gesetzt */

static word SelectLoadPage(tIdx *Desire,byte SearchMode,word Flags){
 word i;		/* Lade-Index */
 if (!Index){
  memcpy(&Page,sPagedefault[country],1024);
  SetupPage();			/* kopieren und Header extrahieren */
  return 0xffff;		/* "Fehler" melden */
 }
/* char CatBuf[8];*/
 if (SearchMode==0)	/* Defaultwert? */
  SearchMode=NEXTSUB;	/* Default-Suchmodus */
 i=SelectPage(Desire->W.Page,Desire->W.Subp,SearchMode);
 if (i!=0xffff){	/* Suche erfolgreich */
  fseek(VTFile,(long)i*1024,SEEK_SET);
  if (fread(&Page,sizeof(Page),1,VTFile)!=1){
/*   printf("Fehler beim Lesen der VT-Datei an Position %hu Kilobytes",i); */
  }else{
   SetupPage();
   Desire->L=Header.L;		/* Ergebnis rückschreiben */
   memcpy(&Page.B[1],Idx2Str(*Desire),6);	/* links oben hinpinseln */
   if (Flags & fBBar)
    PatchPage(Flags);				/* Button-Bar anfügen */
/*   if (Desire->L!=Index[i].L){
    printf("Warnung: VT-Datei inkonsistent oder Programm-Bug!\n");
   }*/
  }
 }else{
/*  printf("Seite %s im Suchmodus %hd nicht gefunden\n",
   Idx2Str(*Desire),SearchMode); */
 }
 return i;
}

/*********************************************************/
/* History, ein Objekt? zur Verwaltung der History-Liste */
/*********************************************************/
#define HISTSIZE 100
				/* merkt sich maximal 100 Einträge */
#define History_CanFwd (HistPtr<HistLen)
#define History_CanBack (HistPtr>=2)
#define History_FwdIdx (Hist[HistPtr])
#define History_BackIdx (Hist[HistPtr-2])
static tIdx Hist[HISTSIZE];		/* Das Gedächtnis */
static word HistPtr,HistLen;		/* Zwei Arrayzeiger */

static void History_Init(){		/* Ein Objekt emulieren? */
 HistPtr=HistLen=0;
}

static void History_Append(tIdx *That){/* geht immer, notfalls wird das Array verschoben */
 if ((HistPtr>0) &&
  (Hist[HistPtr-1].L==That->L)){
/*  printf("Nichtstun\n"); */
  return;			/* Nichtstun */
 }
 if ((History_CanBack) &&	/* Zurück denkbar? */
  (Hist[HistPtr-2].L==That->L)){/* Dort liege diese Seite bereits? */
/*  printf("Zurueck\n"); */
  --HistPtr;
 }else{
  if (HistPtr>=HISTSIZE){	/* Überlauf? */
   memmove(&Hist[0],&Hist[1],sizeof(tIdx)*(--HistPtr));
				/* Jeden Index vorschieben */
/*   printf("Pufferschieben\n"); */
  }
  if ((HistPtr<HistLen) &&
   (Hist[HistPtr].L==That->L)){
   HistPtr++;			/* Vorwärtsschritt zu gleicher Seite */
/*   printf("Vorwärts\n"); */
  }else{
   Hist[HistPtr++]=*That;
   HistLen=HistPtr;		/* Länge kürzen oder verlängern */
/*   printf("Neue Seite\n");*/
  }
 }
}

word MenuFlags, MenuFont;	/* Haupt-Einstell-Organe */
tIdx CurIdx;			/* Momentaner Index */

static int OpenVTFile(char *FileName){
 int Returncode;
 Returncode=LoadFile(FileName);	/* Öffnen, parsen */
 if (!Returncode){		/* Erfolgreich? */
  History_Init();		/* Geschichte totlegen */
  CurIdx.W.Page=0x100;
  CurIdx.W.Subp=0;		/* Definierte Startwerte */
  SetWindowTitle(FName);	/* Fenstertitel anpassen */
  SelectLoadPage(&CurIdx,NEXTSUBPAGE,MenuFlags);
 }
 return Returncode;
}

/***************************************************************/
/* "unsichtbare" Unterfenster erstellen und mit Link verbinden */
/***************************************************************/
/* Basisroutine, Rechteckangaben in Zeicheneinheiten */
static void CreateInputWindow(byte x,byte y,byte b,byte h, tIdx *Indexp){
 Window sw;
 tIdx *hilfidx;
 XSetWindowAttributes xswa;

/* printf("CreateInputWindow(%hd,%hd,%hd,%hd; %s)\n",
  x,y,b,h,Idx2Str(*Indexp));*/
 xswa.cursor=idc_hand;
 sw=XCreateWindow(display,window,
  (short)x*FONTW,(short)y*FONTH,(word)b*FONTW,(word)h*FONTH,0,
  0, InputOnly, CopyFromParent, CWCursor, &xswa);

 XSaveContext(display,sw,Linklist,(caddr_t) Indexp);

/* XFindContext(display,sw,Linklist,(caddr_t *) &hilfidx);
 if (hilfidx!=Indexp)
  printf("Nicht korrekt gespeichert!\n");*/
/* XMapWindow(display,sw);*/
}

/*************************/
/* Link-Fenster erzeugen */
/*************************/

/* Spezialbehandlungsroutine für "forward" und "back"-Knöpfe */
static void MkLinkBF(word Flags){
 if (History_CanBack){
  CreateInputWindow(3,24,3,1,&History_BackIdx);
/*  printf("Back=%s\n",Idx2Str(History_BackIdx));*/
 }
 if (History_CanFwd){
  CreateInputWindow(35,24,3,1,&History_FwdIdx);
/*  printf("Fwd=%s\n",Idx2Str(History_FwdIdx));*/
 }
}

/* eine Rechteckliste erzeugen */
/* anhand 3stelliger Zahlen und ">"..">>>", "<".."<<<" */
static void MkLinkList(word Flags){
 word FlagReg;
 byte lines,line,col;
 bool LineContainsDblH;
 byte CurByte;
 byte Ziffern,Winkel;
 char cWinkel;		/* WELCHER Winkel */
 word Seite;

/* Verschachtelte Hilfsfunktionen */
 bool CanDblH(){	/* Doppelte Zeichenhöhe erlaubt? */
  return((line) && (line!=lines-1) && (Flags & fDblH));
 }

 void TestZiffern(){
  word i;
  if ((Ziffern==3) && (Seite>=0x100) && (Seite<=0x899) &&
   ((i=SelectPage(Seite&0x7FF,0,NEXTSUB))!=0xffff) &&
   (Index[i].L!=CurIdx.L)){ /* nicht zufällig dieselbe Seite? */
   CreateInputWindow(col-Ziffern,line,
    Ziffern,(FlagReg & fDblH)? 2 : 1,
    &Index[i]);
  }
  Ziffern=0;			/* Zähler erneut scharfmachen */
  Seite=0;
 }

 void TestWinkel(){
  word i;
/*  printf("TestWinkel\n");*/
  if ((Winkel>=1) && (Winkel<=3) &&	/* Hier: 1, 2 oder 3 Winkel zulassen */
   ((i=SelectPage(Header.W.Page,Header.W.Subp,
    (cWinkel=='>')? ADDNEXTSUBPAGE : SUBPREVSUBPAGE))!=0xffff) &&
   (Index[i].L!=CurIdx.L)){ /* nicht zufällig dieselbe Seite? */
   CreateInputWindow(col-Winkel,line,
    Winkel,(FlagReg & fDblH)? 2 : 1,
    &Index[i]);
  }
  Winkel=0;			/* Zähler erneut scharfmachen */
  cWinkel='\0';
 }

/* printf("MkLinkList\n");*/
 lines=GetLines(Flags);
/* mit Schleifen loslegen */
 LineContainsDblH=false;
 for (line=0; line<lines; line++){
  if (LineContainsDblH){	/* hier: vorhergehende Linie! */
   LineContainsDblH=false;	/* nichts weiter tun, dann nächste Zeile */
  }else{
   FlagReg=0; Ziffern=Winkel=0; Seite=0; cWinkel='\0';
   for (col=0; col<40; col++){	/* Neue Zeile, neues Glück */
    CurByte=Page.L[line][col];	/* aus der Seite holen */
    if (CurByte <0x20)
     FlagReg&=~fQuiz;
    switch (CurByte){
     case 0:
     case 1:
     case 2:
     case 3:
     case 4:
     case 5:
     case 6:
     case 7:
      FlagReg&= ~fGraf;		/* Grafikflag aus */
      break;
     case 12:
      FlagReg&= ~fDblH;		/* DoubleHeigth aus */
      break;
     case 13:
      if (CanDblH())		/* wenn nicht verboten */
       FlagReg|= fDblH;		/* DoubleHeigth ein */
       LineContainsDblH=true;
      break;
     case 16:
     case 17:
     case 18:
     case 19:
     case 20:
     case 21:
     case 22:
     case 23:
      FlagReg|= fGraf;		/* Grafikflag ein */
      break;
     case 24:
      if (!(Flags & fQuiz))	/* wenn "Quiz-Zeichen AUS" */
       FlagReg|= fQuiz;		/* Secret ein */
      break;
     case 27:
      FlagReg^= fLatin;		/* Sprachumschaltung Lateinisch */
      break;
    } /* case */
    if (FlagReg & (fQuiz|fGraf))
     CurByte=0x20;
    if ((CurByte>=0x30) && (CurByte<=0x39)){	/* Ziffer? */
     Ziffern++;				/* Eine Ziffer mehr */
     Seite=(Seite << 4)+(CurByte-0x30);	/* Seitennummer zusammenschieben */
    }else TestZiffern();
    switch ((char)CurByte){
     case '>':
      if (cWinkel=='>'){
       Winkel++;
      }else{
       cWinkel='>';
       Winkel=1;
      }
      break;
     case '<':
      if (cWinkel=='<'){
       Winkel++;
      }else{
       cWinkel='<';
       Winkel=1;
      }
      break;
     default:
      TestWinkel();
    }
   } /* for col */
   TestZiffern();
   TestWinkel();
  } /* else */
 } /* for line */
}


/******************/
/* GIF-Foto-Schuß */
/******************/

static bool SchiessEinFoto(FILE *f, Pixmap hbm,
 word width, word height, char *comment){
 XImage *image;
 byte GetPixelValue(short x, short y){
  long pixval;
  byte b;
/* if (x==0) printf("%hd ",y);	/* Scan-Zeie */
  pixval=XGetPixel(image,x,y);	/* Pixel-Wert erhalten */
  for (b=0; b<9; b++){		/* Index rückgewinnen durch Suchen */
   if (Farbe[b]==pixval) break;	/* Gefunden; b=Index */
  }
  return b;			/* ausgeben */
 }
/* printf("Start GetImage \n",image); */
 image=XGetImage(display,hbm,0,0,width,height,AllPlanes,ZPixmap);
/* printf("Image besorgt, hat Adresse %p.\n",image); */
 WriteGIF89(f,width,height,4,RGBWerte,8,comment,GetPixelValue);
/* printf("Fertig.\n",image); */
 XDestroyImage(image);
}
/**************************/
/* Benutzer-Schnittstelle */
/**************************/
static XPoint P1,P2,Pn;		/* Punkt, wo Maustaste gedrückt bzw. losgelassen */
static bool moved;		/* True=Markierung auf Bildschirm vorhanden */
static byte autoredrawlock=0;	/* <>0=Kein Blinken und keine rollenden Unterseiten */
static bool AppDone=false;
static bool HelpActive=false;
static tIdx Suchstart;
static char Suchkette[40];
static word SuchArt=6;			/* vorwärts/rückwärts, mit RegExp oder ohne usw. */
static word KindOfInput;		/* Eingabezeilen-Flag */
static char ClipBuff[42*25+1];
static char FindBuff[41*24+1];

/* Suchen bzw. Weitersuchen */
static bool Suche(SuchArt){		/* liefert false wenn nichts gefunden */
 XDefineCursor(display,window,idc_wait);	/* Sanduhr (wäre das unter MS-Windows) */
 XFlush(display);
 do{
  SelectLoadPage(&CurIdx,
   (SuchArt & BACKWARD)? SUBPREVSUBPAGE : ADDNEXTSUBPAGE, MenuFlags);
  if (CurIdx.L==Suchstart.L){
   XDefineCursor(display,window,idc_arrow);	/* normaler Mauspfeil */
   return false;
  }
  Page2Asc(FindBuff,MenuFlags,MenuFont,0,0,39,23);
  PrepareString(FindBuff,SuchArt);
 }while (!strstr(FindBuff,Suchkette));	/* bis ein "Matsch" gefunden wurde */
 XDefineCursor(display,window,idc_arrow);	/* normaler Mauspfeil */
 return true;
}

/**************************/
/* grafische Eingabezeile */
/**************************/
static char Topline[50];		/* Reserve für zu lange Strings lassen */
static byte StartInput,Cursorpos;

static void DrawInputLine(){
 byte i;
 for (i=0; i<40; i++){
  DrawBWUtf8Char(i,0,Topline[i]);	/* darstellen der Zeile */
 }
}

static void BeginInput(const char *What, const char *Item){
 autoredrawlock++;		/* Fenster "statisch" machen */
 if (What) strncpy(Topline,What,38);
 StartInput=strlen(Topline);
 Topline[StartInput++]=' ';	/* Leerzeichen nach 1. String */
 Cursorpos=StartInput;
 if (Item){			/* Eingabezeilen-Vorgabe vorhanden? */
  strncpy(&Topline[Cursorpos],Item,38-Cursorpos);
  Cursorpos+=strlen(Item);
  if (Cursorpos>38) Cursorpos=38;	/* begrenzen */
 }
 memset(&Topline[Cursorpos],' ',sizeof(Topline)-Cursorpos);
 Topline[Cursorpos]='\x7f';	/* Cursor-Symbol */
 DrawInputLine();
}

static void InputChar(char What){	/* 1 Zeichen eingeben, Sonderfall ist '\b' */
 if (What=='\b'){
  if (Cursorpos==StartInput){
   XBell(display,volume);
  }else{
   DrawBWUtf8Char(Cursorpos,0,Topline[Cursorpos]=' ');
   Cursorpos--;
   DrawBWUtf8Char(Cursorpos,0,Topline[Cursorpos]='\x7f');
  }
 }else{
  if (Cursorpos==39){
   XBell(display,volume);
  }else{			/* Ansi-Zeichen abspeichern */
   DrawBWUtf8Char(Cursorpos,0,Topline[Cursorpos]=What);
   Cursorpos++;
   DrawBWUtf8Char(Cursorpos,0,Topline[Cursorpos]='\x7f');
  }
 }
}
#define GetInputChars() (Cursorpos-StartInput)

static char *EndInput(bool NeedRedraw){/* Zeichenkette zurückgeben */
 DrawBWUtf8Char(Cursorpos,0,' ');	/* Cursor verschwinden lassen */
 autoredrawlock--;
 if (NeedRedraw) InvokeExpose();/* Bildschirm restaurieren */
 Topline[Cursorpos]='\0';	/* Null-terminieren */
 return &Topline[StartInput];	/* Zeiger auf Stringanfang */
}


static void UpdateBitmaps(bool);	/* solche Prototypen sollten wieder weg */

/*********************************/
/* Maus-Ereignisse und Markieren */
/*********************************/
static void XorRect(Window window){
 auto short x=P1.x, y=P1.y;
 auto word w=P2.x-P1.x, h=P2.y-P1.y;

/* printf("XorRect(%hd,%hd,%hd,%hd)\n",P1.x,P1.y,P2.x,P2.y);*/
/* Sicherstellen, daß Breite und Höhe positiv */
 if (P1.x>P2.x){
  w=P1.x-P2.x;
  x=P2.x;
 }
 if (P1.y>P2.y){
  h=P1.y-P2.y;
  y=P2.y;
 }
/* printf("Umgerechnet(%hd,%hd,%hd,%hd)\n",x,y,x+w,y+h);*/
 XFillRectangle(display,window,XorGC,x*FONTW,y*FONTH,(w+1)*FONTW,(h+1)*FONTH);
}

static void XorPixmapRect(short bmidx){
 if (Pixmaps[bmidx].valid) XorRect(Pixmaps[bmidx].hbm);
}
static void RemoveAnySelection(){
 if (moved){
  XorRect(window);		/* Evtl. noch vorhandenes Rechteck entfernen */
  XorPixmapRect(0);
  XorPixmapRect(1);
  moved=false;			/* Cache-Pixmaps nicht vergessen! */
 }
}

static void WMLButtonDown(XEvent *event){
/* printf("WMLButtonDown\n");*/
 RemoveAnySelection();
 P1.x=P2.x=event->xbutton.x/FONTW;
 P1.y=P2.y=event->xbutton.y/FONTH;
 autoredrawlock++;	/* Blinken u.ä. sperren */
}

static void WMMouseMove(XEvent *event){
/* printf("WMMouseMove\n"); */
 Pn.x=event->xmotion.x/FONTW;		/* Neue Koordinaten */
 Pn.y=event->xmotion.y/FONTH;
 if (Pn.x<0) Pn.x=0;		/* gegen Fenstergrenzen begrenzen */
 if (Pn.x>=41) Pn.x=41-1;
 if (Pn.y<0) Pn.y=0;
 if (Pn.y>=25) Pn.y=25-1;
 if ((P2.x!=Pn.x) ||		/* Änderung der Position von Belang? */
  (P2.y!=Pn.y)){		/* (Flackern minimieren) */
  if (moved) XorRect(window);
  P2=Pn;			/* Neuer 2. Punkt */
  moved=true;			/* Berührt - geführt: fortan Rechteck */
  XorRect(window);
 }
}

static void WMLButtonUp(XEvent *event){
 auto short left=P1.x,top=P1.y,right=P2.x,bottom=P2.y;
 tIdx *IdxPtr;			/* zur Hilfe ein Zeiger auf einen Index */
 XEvent hevent;

/* printf("WMLButtonUp, moved=%s\n",
  moved?"true":"false"); */
 autoredrawlock--;	/* Blinken u.ä. wieder freigeben */
 if (moved){		/* Rechtecke nun auch in Cachepixmaps nachführen */
  XorPixmapRect(0);
  XorPixmapRect(1);	/* Cache-Pixmaps nachführen, wenn gültig */
/* "Schnelles Clipboard" (wie sonst in X üblich */
  if (P1.x>P2.x){	/* erst wieder die Koordinaten sortieren */
   left=P2.x;
   right=P1.x;
  }
  if (P1.y>P2.y){
   top=P2.y;
   bottom=P1.y;
  }
  Page2Asc(ClipBuff,MenuFlags,MenuFont,left,top,right,bottom);
/*  printf("left=%hd, top=%hd, right=%hd, bottom=%hd\n",
   left,top,right,bottom);*/
  ClipBuff[strlen(ClipBuff)-1]='\0';	/* Letztes '\n' abschneiden */
/*  printf("%s<END>\n",ClipBuff);*/
  XSetSelectionOwner(display,XA_PRIMARY,window,event->xbutton.time);
  if (XGetSelectionOwner(display,XA_PRIMARY)==window){
/*   printf("Selektion erfolgreich gekapert!\n"); */
   XStoreBytes(display,ClipBuff,strlen(ClipBuff));
  }else{
   XBell(display,volume);
   RemoveAnySelection();
  }
/*
  XChangeProperty(display, RootWindowOfScreen(screen),
   XA_CUT_BUFFER0,XA_STRING,8,PropModeReplace,ClipBuff,strlen(ClipBuff));
  XChangeProperty(display, RootWindowOfScreen(screen),
  hevent.type=PropertyNotify;
  hevent.xproperty.time=event->xbutton.time;
  hevent.xproperty.display=display;
  hevent.xproperty.window=RootWindowOfScreen(screen);
  hevent.xproperty.atom=XA_CUT_BUFFER0;
  hevent.xproperty.state=PropertyNewValue;
  if (XSendEvent(display,RootWindowOfScreen(screen),true,0xffffff,&hevent))
   printf("Hier vorbei(XSendEvent)\n");
  XFlush(display);*/
 }else{			/* zur neuen Seite hüpfen? */
  if (XFindContext(display,event->xbutton.subwindow,Linklist,
   (caddr_t *)&IdxPtr)==0){
/*   printf("subwindow im Kontext gefunden\n");*/
   if (SelectLoadPage(IdxPtr,EQUALPAGE,MenuFlags)!=0xffff){
    CurIdx=*IdxPtr;		/* aktualisieren */
    UpdateBitmaps(true);
   }else{
/*    printf("Programm-Bug: Seite %s nicht (mehr) da!\n",Idx2Str(*IdxPtr));*/
   }
  }else{
/*   printf("subwindow im Kontext NICHT gefunden\n");*/
  }
 }
}

static void WMRButtonDown(XEvent *event){
/*
 if (moved){
/* ergänzen: Rechteck ins Clipboard bringen *
  XorRect(window);		/* Evtl. noch vorhandenes Rechteck entfernen * /
  XorPixmapRect(0);		/* Cache-Pixmaps Rechteck entfernen * /
  XorPixmapRect(1);
  moved=false;
 }else*/ if (History_CanBack){
  CurIdx=History_BackIdx;
  SelectLoadPage(&CurIdx,EQUALPAGE,MenuFlags);
  UpdateBitmaps(true);
 }
}

static void WMRButtonUp(XEvent *event){
}

/*******************************/
/* Tastatur-Ereignisse (ASCII) */
/*******************************/

/* Unterprogramm zur Dateinamenseingabe (nur Backspace zum Editieren) */
static bool HandleNameInput(char ch){
 switch (ch){
  case '\x1b':
   EndInput(true);		/* Eingabe ergebnislos beenden */
   KindOfInput=0;
   break;
  case '\n':
  case '\r':
   KindOfInput=0;		/* EndInput muß vom Programm gerufen werden! */
   return true;		/* (ist zugegebenermaßen etwas unsauber) */
  default:
   InputChar(ch);
 }
 return false;
}

void WMChar(char ch){
#define iPage 1
#define iSubp 2
#define iFind 3
#define iExit 4
#define iMsg  5
#define iOpen 6
#define iFont 7
#define iText 8
#define iGif 9
 tIdx HilfIdx;
 char FileName[40], *sp;	/* Stringzeiger für Hilfszwecke */
 word CurFont;
 int Returncode;
 FILE *f;			/* Ausgabe-Datei */

 if (HelpActive){
  if (SelectLoadPage(&CurIdx,EQUALPAGE,MenuFlags)==0xffff){
   UpdateBitmaps(false);
  }else{
   UpdateBitmaps(true);
  }
  HelpActive=false;
 }


/* Zahl 1..8 eingegeben und kein Input aktiv und Index vorhanden? */
 if ((ch>='1') && (ch<='8') && !KindOfInput && Index){
  BeginInput(sGotoPage[country],NULL);
  KindOfInput=iPage;		/* flugs Eingabezeile eröffnen */
 }

 switch (KindOfInput){
/*case iExit:
   break;*/
/****************** Speichern-Eingabezeile (Text) *****************/
  case iText:
   if (HandleNameInput(ch)){	/* wenn mit ENTER beendet */
    strcpy(FileName,EndInput(true));	/* beschaffen */
    sp=(char *)malloc(41*24+1);		/* Kurzzeit-Speicher beschaffen */
    if (sp){
     Page2Asc(sp,MenuFlags,MenuFont,0,0,39,23);	/* Langen String draus machen */

     if (strlen(FileName)){
      f=fopen(FileName,"a");	/* anhängen an evtl. vorhandene Datei */
     }else{
      f=stdout;			/* Standardausgabe */
     }
     if (f){			/* Gültiger Zeiger? */
      fprintf(f,"%s",sp);	/* String schreiben */
      if (f!=stdout)
       fclose(f);		/* zumachen */
     }else{
      sprintf(Topline,sWriteError[country],FileName,(word)errno);
      BeginInput(NULL,NULL);
      KindOfInput=iMsg;
     }
     free(sp);			/* Angeforderten Speicher freigeben */
    }
   }
   break;

/****************** Speichern-Eingabezeile (Bild) *****************/
  case iGif:
   if (HandleNameInput(ch)){	/* wenn mit ENTER beendet */
    strcpy(FileName,EndInput(true));	/* beschaffen */
    if (strlen(FileName)){
     f=fopen(FileName,"wb");
     if (f){
      sp=(char *)malloc(41*24+1);	/* Kurzzeit-Speicher beschaffen */
      if (sp){
       XDefineCursor(display,window,idc_wait);
/* "Sanduhr" anzeigen (Hände bleiben bei den Subwindows) */
/* sonst müßte hier noch ein UnmapSubwindows() hin */
       XFlush(display);
       Page2Asc(sp,MenuFlags,MenuFont,0,0,39,23);	/* Pascal-Strings draus machen */
       SchiessEinFoto(f,Pixmaps[0].hbm,41*FONTW,24*FONTH,sp);
       XDefineCursor(display,window,idc_arrow);
       free(sp);
      }
      fclose(f);		/* Scließen */
     }else{
      sprintf(Topline,sWriteError[country],FileName,(word)errno);
      BeginInput(NULL,NULL);
      KindOfInput=iMsg;
     }
    }
   }
   break;

/****************** Such-Eingabezeile *****************/
  case iFind:
   if (HandleNameInput(ch)){	/* wenn mit ENTER beendet */
    strcpy(Suchkette,EndInput(false));	/* "herbekommen" */
    if (strlen(Suchkette)){
     PrepareString(Suchkette,SuchArt);	/* zu Großbuchstaben usw. */
     if (SuchArt & USEREGEXP){
 /* hier Regexp-Übersetzung einbauen (später) */
     }
     if (Suche(SuchArt)){
      UpdateBitmaps(true);
     }else{
      BeginInput(sNoMatch[country],NULL);
      KindOfInput=iMsg;
     }
    }
   }
   break;

/****************** Öffnen-Eingabezeile *****************/
  case iOpen:
   if (HandleNameInput(ch)){	/* wenn mit ENTER beendet */
    strcpy(FileName,EndInput(false));
    if ((strlen(FileName)==0) || (Returncode=OpenVTFile(FileName))){
     sprintf(Topline,sFileError[country],FileName,(word)Returncode);
     BeginInput(NULL,NULL);
     KindOfInput=iMsg;
    }else{			/* erfolgreich geladen */
     UpdateBitmaps(true);
    }
   }
   break;

/****************** Fontwahl-Eingabezeile *****************/
  case iFont:
   switch (ch){
    case '\x1b':
    case 'q':
    case '\3':
     EndInput(true);		/* Eingabe ergebnislos beenden */
     KindOfInput=0;
     break;
    case ' ':
    case 'j':
    case 'l':
    case '\t':
    case '+':
     CurFont=(CurFont+2)%16;	/* 2 vorwärts */
    case 'h':
    case 'k':
    case '\b':
    case '-':
     CurFont=(CurFont+15)%16;	/* und 1 zurück */
     EndInput(false);		/* kein Redraw nötig! */
     BeginInput(sChooseFont[country],sFontName[country][CurFont]);
     break;
    case '\n':
    case '\r':
     MenuFont=CurFont;
     EndInput(false);
     KindOfInput=0;
     UpdateBitmaps(Index!=NULL);/* wenn Index=NULL dann nicht "laden" */
     break;
    default:
     XBell(display,volume);
   }
   break;

/****************** Seiten-Eingabezeile *****************/
  case iPage:		/* Es müssen exakt 3 Ziffern eingegeben werden */
   if ((isxdigit(ch)) || (ch=='\b')){
    InputChar(ch);
    if (GetInputChars()==3){
     sscanf(EndInput(false),"%hx",&HilfIdx.W.Page);
     KindOfInput=0;
     HilfIdx.W.Subp=0;
     if (SelectLoadPage(&HilfIdx,NEXTSUB,MenuFlags)!=0xffff){
      CurIdx=HilfIdx;
      UpdateBitmaps(true);
     }else{
      sprintf(Topline,sNoPage[country],UsualPage(HilfIdx.W.Page));
      BeginInput(NULL,NULL);
      KindOfInput=iMsg;
     }
    }
   }else{
    EndInput(true);
    KindOfInput=0;
   }
   break;

/****************** Unterseiten-Eingabezeile *****************/
  case iSubp:		/* es können max. 4 Ziffern eingegeben werden */
   if ((isxdigit(ch)) || (ch=='\b')){
    InputChar(ch);
   }
   if ((isxdigit(ch) && (GetInputChars()==4)) ||
    (((ch=='\n') || (ch=='\r')) && (GetInputChars()>0))){
    sscanf(EndInput(false),"%hx",&HilfIdx.W.Subp);
    KindOfInput=0;
    HilfIdx.W.Page=CurIdx.W.Page;
    if (SelectLoadPage(&HilfIdx,EQUALPAGE,MenuFlags)!=0xffff){
     CurIdx=HilfIdx;
     UpdateBitmaps(true);
    }else{
     sprintf(Topline,sNoSubp[country],HilfIdx.W.Subp);
     BeginInput(NULL,NULL);
     KindOfInput=iMsg;
    }
   }else if (!((isxdigit(ch)) || (ch=='\b'))){
    EndInput(true);
    KindOfInput=0;
   }
   break;

/****************** Hinweis-Zeile *****************/
  case iMsg:
/*   printf("Hier iMsg!\n");*/
   EndInput(true);		/* Meldungszeile verschwinden lassen */
   KindOfInput=0;
   break;

/****************** normale Tastenauswertung *****************/
  default:
   switch (ch){
    case '\x1b':
     break;			/* Nicht piepsen */
    case ' ':	/* Leertaste nextsubpage */
     if (Index){
      SelectLoadPage(&CurIdx,ADDNEXTSUBPAGE,MenuFlags);
      UpdateBitmaps(true);
     }
     break;
    case '/':	/* Suchen */
     if (Index){
      Suchstart=CurIdx;		/* alte Suchkette vorgeben */
      BeginInput(sSuchString[country],Suchkette);
      KindOfInput=iFind;
     }
     break;
    case 'g':	/* Homepage */
     if (Index){
      CurIdx.W.Page=0x100; CurIdx.W.Subp=0;
      SelectLoadPage(&CurIdx,NEXTSUBPAGE,MenuFlags);
      UpdateBitmaps(true);
      break;
     }
    case 'h':	/* prevsub */
     if (Index){
      if (SelectLoadPage(&CurIdx,SUBPREVSUB,MenuFlags)!=0xffff){
       UpdateBitmaps(true);
      }else{
       BeginInput(sOnlyOneSubp[country],NULL);
       KindOfInput=iMsg;
      }
     }
     break;
    case 'i':	/* Infos über aktuelle Seite */
     CurFont=LandMap[(Header.W.CBits >> 12) & 7];
     if (CurFont==0) CurFont=13;	/* "reserviert" statt "automatisch" */
     sprintf(Topline,sPageInfo[country],
      Header.W.CBits,&sFontName[country][CurFont][1]);
     BeginInput(NULL,NULL);
     KindOfInput=iMsg;
     break;
    case 'j':
    case 'd':	/* nextpage */
    case '+':
     if (Index){
      SelectLoadPage(&CurIdx,ADDNEXTPAGE,MenuFlags);
      UpdateBitmaps(true);
      break;
     }
    case 'k':
    case 'u':	/* prevpage */
    case '-':
     if (Index){
      SelectLoadPage(&CurIdx,SUBPREVPAGE,MenuFlags);
      UpdateBitmaps(true);
      break;
     }
    case 'l':	/* nextsub */
    case '\t':	/* nextsub */
     if (Index){
      if (SelectLoadPage(&CurIdx,ADDNEXTSUB,MenuFlags)!=0xffff){
       UpdateBitmaps(true);
      }else{
       BeginInput(sOnlyOneSubp[country],NULL);
       KindOfInput=iMsg;
      }
     }
     break;
    case 'n':	/* Weitersuchen */
     if (strlen(Suchkette)){
      if (Suche(SuchArt)){
       UpdateBitmaps(true);
      }else{
       BeginInput(sNoMatch[country],NULL);
       KindOfInput=iMsg;
      }
     }
     break;
    case 'N':	/* Weitersuchen */
     if (strlen(Suchkette)){
      if (Suche(SuchArt^BACKWARD)){
       UpdateBitmaps(true);
      }else{
       BeginInput(sNoMatch[country],NULL);
       KindOfInput=iMsg;
      }
     }
     break;
    case 'o':
     strcpy(FileName,FName);		/* den alten Pfad schon mal bereiten */
     sp=(char *)strrchr(FileName,'/');	/* Pfad-Trenner enthalten? */
     if (sp){			/* Ja? */
      sp++;			/* Zeiger dahinter positionieren */
     }else{			/* Nein? */
      sp=FileName;		/* Zeiger auf Stringanfang */
     }
     *sp='\0';			/* Abschneiden: Dahinter oder am Anfang */
     BeginInput(sOpenFile[country],FileName);	/* aktuellen Pfad setzen */
     KindOfInput=iOpen;
     break;
    case 'p':	/* Eingabe Seitennummer */
     if (Index){
      BeginInput(sGotoPage[country],NULL);
      KindOfInput=iPage;		/* flugs Eingabezeile eröffnen */
     }
     break;
    case 'q':
    case '\3':			/* ^C bricht auch ab */
     AppDone=true;		/* Abbruch erzwingen */
     break;
    case 's':	/* subpageinput */
     if (Index){
      BeginInput(sGotoSubp[country],NULL);
      KindOfInput=iSubp;
     }
     break;
    case 't':			/* Textdatei schreiben */
     strcpy(FileName,FName);	/* den alten Dateinamen schon mal bereiten */
     sp=(char *)strrchr(FileName,'.');	/* Extension enthalten? */
     if (sp){			/* Ja? */
      *sp='\0';			/* Da abschneiden */
     }
     strcat(FileName,Idx2Str(CurIdx));	/* aktuellen Index dranhängen */
     *(char *)strrchr(FileName,'/')='S';	/* den (hier) lästigen Slash killen */
     strcat(FileName,".txt");	/* Ein bißchen DOS muß sein... */
     BeginInput(sOpenFile[country],FileName);	/* aktuellen Pfad setzen */
     KindOfInput=iText;
     break;
    case 'y':	/* Hilfe */
     memcpy(&Page,sHelpPage[country],1024);
     SetupPage();
     UpdateBitmaps(false);
     HelpActive=true;		/* nächsten Tastendruck wegfangen */
     break;
    case 'z':			/* Bilddatei schreiben */
     strcpy(FileName,FName);	/* den alten Dateinamen schon mal bereiten */
     sp=(char *)strrchr(FileName,'.');	/* Extension enthalten? (Naja, etwas gewagt) */
     if (sp){			/* Ja? */
      *sp='\0';			/* Da abschneiden */
     }
     strcat(FileName,Idx2Str(CurIdx));	/* aktuellen Index dranhängen */
     *(char *)strrchr(FileName,'/')='s';/* den (hier) lästigen Slash killen */
     strcat(FileName,".gif");	/* Ein bißchen DOS muß sein... */
     BeginInput(sOpenFile[country],FileName);	/* aktuellen Pfad setzen */
     KindOfInput=iGif;
     break;
    case 'b':
    case '\b':
     if (History_CanBack){
      CurIdx=History_BackIdx;
      SelectLoadPage(&CurIdx,EQUALPAGE,MenuFlags);
      UpdateBitmaps(true);
     }else{
      BeginInput(sNoBack[country],NULL);
      KindOfInput=iMsg;		/* flugs Eingabezeile eröffnen */
     }
     break;
    case 'D':
     MenuFlags^=fDblH;
     if (memchr(&Page,13,24*40))	/* DblH-Steuercode enthalten? */
      UpdateBitmaps(true);		/* Dann sofort auffrischen */
     break;
    case 'B':
     MenuFlags^=fDebug;
     UpdateBitmaps(true);
     break;
    case 'E':
     BeginInput("Henrik.Haftmann@E-Technik.TU-Chemnitz.de",NULL);
     KindOfInput=iMsg;
     break;
    case 'F':
     CurFont=MenuFont;
     BeginInput(sChooseFont[country],sFontName[country][CurFont]);
     KindOfInput=iFont;
     break;
    case 'I':
     MenuFlags^=fInvers;
     GetColors(MenuFlags&fInvers);	/* neue Farben besorgen */
     UpdateBitmaps(true);
     break;
    case 'L':
     MenuFlags^=fBlink;		/* Blinken toggeln */
     break;
    case 'M':
     MenuFlags^=fMix;
     if ((Header.W.CBits & 0x60)==0)
      UpdateBitmaps(true);
     break;
    case 'Q':
     MenuFlags^=fQuiz;		/* Quiz-Flag toggeln */
     if (memchr(&Page,24,24*40))
      UpdateBitmaps(true);	/* Bei Vorhandensein von QUIZ aktualisieren */
     break;
    case 'R':
     MenuFlags^=fRoll;
     break;
    case 'V':
     BeginInput(" h#s VTX teletext for X version 0.91",NULL);
     KindOfInput=iMsg;
     break;
    case 'W':
     MenuFlags^=fBunt;
     UpdateBitmaps(true);
     break;
    default:
     XBell(display,volume);	/* Ein "bellendes" Programm für "liegengebliebene" Ereignisse */
   } /* switch(ch) */
 } /* switch */
}

/**************************************/
/* Tastatur-Ereignisse (Sondertasten) */
/**************************************/
static void WMKey(KeySym key){		/* Behandlungsroutine für Sondertasten */
 switch (key){
  case XK_Cancel:
  case XK_Break:
  case XK_F10:
   WMChar('q');
   break;
  case XK_Home:
   WMChar('g');
   break;
  case XK_End:
   WMChar('p');
   break;
  case XK_Prior:
   WMChar('-');
   break;
  case XK_Next:
   WMChar('+');
   break;
  case XK_F1:
  case XK_Help:
   WMChar('y');
   break;
  case XK_F2:
  case XK_Find:
   WMChar('/');
   break;
  case XK_F3:
   WMChar('n');
   break;
  case XK_F4:
  case XK_Execute:
   WMChar('o');
   break;
  case XK_Left:
   WMChar('h');
   break;
  case XK_Right:
   WMChar('l');
   break;
  case XK_Up:
   WMChar('k');
   break;
  case XK_Down:
   WMChar('j');
   break;
  case XK_Shift_L:
  case XK_Shift_R:
  case XK_Control_L:
  case XK_Control_R:
  case XK_Caps_Lock:
  case XK_Shift_Lock:
  case XK_Meta_L:
  case XK_Meta_R:
  case XK_Alt_L:
  case XK_Alt_R:
  case XK_Super_L:
  case XK_Super_R:
  case XK_Hyper_L:
  case XK_Hyper_R:
   break;			/* Nicht läuten */
  default:
   XBell(display,volume);	/* Glocken läuten */
 }
}

static void DestroyOld(){
/* if (XDeleteContext(display,None,Linklist))	/* ich will ALLE Fenster löschen */
/*  printf("Kontext loeschen versagt\n");	/* aber das geht halt nicht! */
 XDestroySubwindows(display,window);	/* Alle "Eingabefenster" killen */
}
/* void SetTimer(word);		/* Forward */
static word TimerCount;		/* zählt die Sekunden */

/* Parameter gibt an, ob's vielleicht bloß 'ne Hilfeseite ist */
static void SetupNew(bool TruePage){
 Pixmaps[0].valid=Pixmaps[1].valid=false;
 BMIdx=0;
 moved=false;			/* Rechteck verschwindet */
 KindOfInput=0;			/* Eingabezeile verschwindet */
 TimerCount=0;			/* "StopBlink" */
 if (TruePage){
  History_Append(&CurIdx);
  MkLinkList(MenuFlags);	/* Neue Linkliste erzeugen (dauert nicht lange??) */
  MkLinkBF(MenuFlags);		/* Neue Linkliste erzeugen (dauert nicht lange??) */
 }
}
/******************/
/* Expose-Message */
/******************/
static void UpdateBitmaps(bool TruePage){
 DestroyOld();
 SetupNew(TruePage);
 InvokeExpose();
}

static void UpdateBitmap(short CurIdx){
 XDefineCursor(display,window,idc_wait);	/* Sanduhr (wäre das unter MS-Windows) */
 DrawVT(Pixmaps[CurIdx].hbm,
  (CurIdx)? MenuFlags & ~fBlink : MenuFlags | fBlink, MenuFont);
 XDefineCursor(display,window,idc_arrow);
 Pixmaps[CurIdx].valid=true;
}

static void WMPaint(){
 if (BMIdx>=0){			/* Gültiger Wert (gegen verfrühte Exposes) */
  if (!Pixmaps[BMIdx].valid){	/* Pixmap noch nicht erstellt? */
   UpdateBitmap(BMIdx);		/* Na dann aber los! */
   if (moved) XorPixmapRect(BMIdx);
  }
  XCopyArea(display,Pixmaps[BMIdx].hbm,window,MainGC,0,0,41*FONTW,25*FONTH,0,0);
  if (KindOfInput)		/* Eingabezeile? */
   DrawInputLine();		/* Drüberpinseln (Einzelzeichen) */
  XMapSubwindows(display,window);	/* Jetzt erst Eingabefenster erzeugen */
 }else{
/*  printf("BMIdx negativ!\n");*/
 }
}

/******************/
/* Timer-Messages */
/******************/
static Atom WM_TIMER;
static bool SafeToSendTimerEvent=false,
     RequestToSendTimerEvent=false;

static void DefineWMTimer(){
 WM_TIMER=XInternAtom(display,"WM_TIMER",false);
}

static void XSendTimerMsg(int dummy){
 XEvent event;
 event.type=ClientMessage;
 event.xclient.display=display;
 event.xclient.window=window;
 event.xclient.message_type=WM_TIMER;
 event.xclient.format=32;
 event.xclient.data.l[0]=(long)dummy;
 if (SafeToSendTimerEvent){	/* Hauptprogramm tätigt gerade XNextEvent? */
  XSendEvent(display,window,True,0,&event);	/* XNextEvent() erlösen */
  XFlush(display);
 }else{
  RequestToSendTimerEvent=true;	/* Nur vormerken */
 }
/* printf("SendTimerEvent\n"); */
}

static void SetTimer(word seconds){
 signal(SIGALRM,XSendTimerMsg);
 alarm(seconds);
}


/* Reaktionsprogramm auf Timer-Message */
/* (Möglicherweise ginge alles auch ohne die Erfindung einer
 extra Timermessage, d.h. WMTimer würde direkt vom Alarm-Handler aufgerufen.
 Aber so ist's mehr zu MS-Windows kompatibel, und außerdem lernte ich
 etwas über den Nutzen der Atome) */
static void WMTimer(XEvent *event){
 short BMMerk;		/* zum Entscheiden, ob Expose generiert werden soll */
 if (!autoredrawlock){		/* Timer nicht gesperrt? */
  autoredrawlock++;		/* Reentranz-Hahn zudrehen (sicherheitshalber) */
  TimerCount++;			/* Neuen "Sekundenwert" erzeugen */
  BMMerk=BMIdx;
  BMIdx=0; 			/* Grundsätzlich Pixmap #0 nehmen */
  if ((MenuFlags & fBlink) && (TimerCount % 3==2) && /* Rest=2 */
   memchr(&Page,9,24*40)){	/* Blink-Zeichen enthalten? */
   BMIdx=1;			/* Nur bei Rest=2 Pixmap #1 nehmen */
  }
  if ((MenuFlags & fRoll) && (TimerCount % 3==0)){
				/* Seitenwechsel fällig? */
   if ((Header.W.CBits & (bit 11)) || (TimerCount % 24==0)){
				/* Schneller oder langsamer Wechsel? */
    if (SelectLoadPage(&CurIdx,ADDNEXTSUB,MenuFlags)!=0xFFFF){
     UpdateBitmaps(false);	/* Hier: Nicht in History eintragen */
     MkLinkList(MenuFlags);	/* Neue Linkliste erzeugen (dauert nicht lange??) */
     MkLinkBF(MenuFlags);	/* Neue Linkliste erzeugen (dauert nicht lange??) */
     BMMerk=BMIdx;		/* Gleichsetzen, da InvokeExpose */
    }				/* von UpdateBitmaps() ausgeführt wurde */
   }
  }
  if (BMMerk!=BMIdx)		/* Verschieden? */
   InvokeExpose();		/* Expose generieren */
  autoredrawlock--;
 }
 SetTimer(1);			/* Hier erst neu programmieren */
}


/*****************/
/* Hauptprogramm */
/*****************/
int main(int argc, char **argv){
 XEvent event;

 char CatBuf[16];
 XWMHints xwmh;
 char *DName=NULL;	/* Display-Name */
 char *iFName=NULL;	/* Kommandozeilen-Dateiname */
 char switchchar1, switchchar2;
 int i;
 KeySym key;
 XComposeStatus cs;

/* ':' und '=' im Kommando übergehen; wenn '\0' nächsten String nehmen */
 char *AdjustPP(char *sp){
  if (*sp=='\0'){
   i++;
   if (i==argc){
    fprintf(stderr,"Missing argument!\n");
    exit(1);
   }else{
   return argv[i];
   }
  }else if ((*sp==':') || (*sp=='=')){
   return ++sp;
  }else{
   return sp;
  }
 }

/* Dateiname der Variablen iFName zuweisen; aber NUR EINMAL */
 void AssignFName(char *Name){
  if (iFName){			/* Wenn bereits ein Name angegeben wurde */
   fprintf(stderr,"VTX accepts only one filename; ignore %s\n",Name);
  }else{
   iFName=Name;
  }
 }

 MenuFlags=fBunt|f41|f25|fDblH|fBlink|fBBar;
 MenuFont=0;
 CurIdx.W.Page=0x100;
 CurIdx.W.Subp=0;
/* printf("sizeof(tPage)=%hd, sizeof(tHeader)=%hd, \
sizeof(byte)=%hd, sizeof(word)=%hd, \
sizeof(long)=%hd, sizeof(tIdx)=%hd, sizeof(Ansitab)=%hx\n",
   sizeof(tPage),sizeof(tHeader),
/*   sizeof(tSAATrail),sizeof(tHSFTrail),
   sizeof(byte),sizeof(word),
   sizeof(long),sizeof(tIdx),sizeof(Ansitab));
/*  scanf("c",NULL);*/

 const char*lang=getenv("LANG");
 if (lang && !strncmp(lang,"de",2)) country = 1;

 /* Parameter parsen */
 for (i=1; i<argc; i++){
  if (argv[i][0]=='-'){
   switch (switchchar1=argv[i][1]){
    case 'D':
    case 'd':
     DName=AdjustPP(&argv[i][2]);
     break;
    case 'H':
    case 'h':
    case '?':
     puts(sHelp[country]);
     exit(0);
     break;
    case 'L':
    case 'l':
     switch (switchchar2=(AdjustPP(&argv[i][2])[0])){
      case 'D':
      case 'd':
       country=1;
       break;
      case 'E':
      case 'e':
       country=0;
       break;
      default:
       fprintf(stderr,"VTX: Unknown country %c!\n",switchchar2);
       exit(1);
     }
     break;
    case 'P':
    case 'p':
     sscanf(AdjustPP(&argv[i][2]),"%hx/%hx",&CurIdx.W.Page,&CurIdx.W.Subp);
     MenuFlags|=fNoX;
     break;
    case 'F':
    case 'f':
     sscanf(AdjustPP(&argv[i][2]),"%hd",&MenuFont);
     break;
    case 'M':
    case 'm':
     sscanf(AdjustPP(&argv[i][2]),"%hd",&MenuFlags);
     break;
    case 'N':
    case 'n':
     autoredrawlock++;		/* So das Blinken unterbinden */
     break;
    case 'S':
    case 's':
     sscanf(AdjustPP(&argv[i][2]),"%hd",&SuchArt);
     break;
    case 'V':
    case 'v':
     sscanf(AdjustPP(&argv[i][2]),"%d",&volume);
     break;
    case '-':
     AssignFName(&argv[i][1]);
     break;
    default:
     fprintf(stderr,"VTX: Unknown option -%c - for help type \"vtx -h\"!\n",
      switchchar1);
     exit(1);
   }
  }else{
   AssignFName(argv[i]);
  }
 } /* for */

 if (MenuFlags & fNoX){
  if (!iFName){
   fprintf(stderr,"You MUST specify a file here!\n");
   return -2;
  }
  if (i=LoadFile(iFName)){
   fprintf(stderr,"Error reading file %s, code %d\n",iFName,i);
   return i;
  }else{
   SelectLoadPage(&CurIdx,NEXTSUBPAGE,MenuFlags & ~fBBar);
   Page2Asc(FindBuff,MenuFlags,MenuFont,0,0,39,23);
   printf("%s",FindBuff);
   return 0;
  }
 }
 InitDisplay(DName);
 if (DefaultDepthOfScreen(screen)<3){
  MenuFlags&=~fBunt;		/* Bei armseligen Displays auf Schwarzweiß schalten */
 }
/* printf("Display geoeffnet\n"); */
/* XSynchronize(display,true);*/
 CreateMainWindow();
/* printf("Hauptfenster erstellt\n"); */
/* GetColors();*/
/* printf("Farben geholt\n");*/
 FontUpload();
/* printf("Font geladen\n");*/
 if (iFName){
  i=OpenVTFile(iFName);		/* Datei mit Trara öffnen, Returncode aufheben */
  if (i){
   SelectLoadPage(&CurIdx,NEXTSUBPAGE,MenuFlags);
				/* Startseite laden (lassen) */
   SetupNew(false);
   sprintf(Topline,sFileError[country],iFName,i);
   BeginInput(NULL,NULL);
   KindOfInput=iMsg;
  }else{
   SetupNew(true);
  }
 }else{
  SelectLoadPage(&CurIdx,NEXTSUBPAGE,MenuFlags);
				/* Startseite laden (lassen) */
  SetupNew(false);
 }
 DefineWMTimer();		/* Timer-Atom vereinbaren */
 if (!autoredrawlock)
  SetTimer(1);			/* Timer starten wenn erlaubt */
/* Hauptereignisschleife */
 while (!AppDone){
  XFlush(display);		/* Warteschlange leeren */
  if (RequestToSendTimerEvent){	/* Anhängiges Timer-Event vorlassen */
   event.type=ClientMessage;
   event.xclient.message_type=WM_TIMER;
   RequestToSendTimerEvent=false;
  }else{
   SafeToSendTimerEvent=true;	/* Reentranz zulassen */
   XNextEvent(display,&event);
   SafeToSendTimerEvent=false;	/* Reentranz sperren */
  }
/*  printf("Hier vorbei (XNextEvent)!\n");*/

  switch (event.type){
   case MappingNotify:
/*    printf("Mapping-Message\n");*/
    XRefreshKeyboardMapping((XMappingEvent *)&event);	/* sollte halt so sein */
    break;

   case Expose:
/*    printf("1 Expose\n"); */
/*    if (event.xany.window != window)
     printf("Da stimmt was nicht!"); */
    if (event.xexpose.count==0){
/*     printf("Start Expose\n"); */
     WMPaint();
/*     printf("Ende Expose\n"); */
    }
    break;

   case ButtonPress:
    switch (event.xbutton.button){
     case 1:				/* Linke Maustaste */
      WMLButtonDown(&event);
      break;
     default:				/* Alle anderen */
      WMRButtonDown(&event);
    }
    break;

   case MotionNotify:
    WMMouseMove(&event);
    break;

   case ButtonRelease:
    switch (event.xbutton.button){
     case 1:				/* Linke Maustaste */
      WMLButtonUp(&event);
      break;
     default:				/* Alle anderen */
      WMRButtonUp(&event);
    }
    break;

   case KeyPress:
    CatBuf[XLookupString((XKeyEvent *)&event,CatBuf,sizeof(CatBuf),&key,&cs)]='\0';

/*    printf("Tastatur-Ereignis! String=\"%s\" Code %hX, ",CatBuf,CatBuf[0]);
    printf("KeySym=0x%04X\n",key);*/
    if (strlen(CatBuf)==1){
     WMChar(CatBuf[0]);
    }else{
     WMKey(key);
    }
    break;

   case ClientMessage:
/*    printf("Timer-Event?\n"); */
    if (event.xclient.message_type==WM_TIMER){
/*     printf("Das Timer-Event! Hurra!\n"); */
     WMTimer(&event);		/* Windows-like aufrufen */
    }
    break;

   case NoExpose:
/*    printf("Huch! Ein NoExpose Event.\n");*/
    break;

   case SelectionRequest:
/*    printf("SelectionRequest!\n"); */
/*    if (event.xselectionrequest.target==XA_STRING)
     printf("Hervorragend: als String!\n");
    if (event.xselectionrequest.owner==window)
     printf("Hervorragend: Owner=Window!\n");
    if (event.xselectionrequest.selection==XA_PRIMARY)
     printf("Hervorragend: selection=XA_PRIMARY!\n");
    if (event.xselectionrequest.property==XA_CUT_BUFFER0){
     printf("Hervorragend: property=CUT_BUFFER0!\n");
    }else{
     printf("Nicht Hervorragend: property=%d!\n",
      event.xselectionrequest.property);
    }
    if (event.xselectionrequest.requestor==RootWindowOfScreen(screen)){
     printf("Hervorragend: requestor=CRootWindow!\n");
    }else{
     printf("Nicht Hervorragend: requestor=%d!%d\n",
      event.xselectionrequest.requestor,window);
    } */
/*    XChangeProperty(display,
     event.xselection.requestor,
     event.xselectionrequest.property,
     event.xselectionrequest.target,
     8,PropModeReplace,ClipBuff,strlen(ClipBuff));*/
  XChangeProperty(display, RootWindowOfScreen(screen),
   XA_CUT_BUFFER0,XA_STRING,8,PropModeReplace,ClipBuff,strlen(ClipBuff));
  XChangeProperty(display, event.xselection.requestor,
   XA_CUT_BUFFER0,XA_STRING,8,PropModeReplace,ClipBuff,strlen(ClipBuff));

    event.type=SelectionNotify;
    event.xselection.requestor=event.xselectionrequest.requestor;
    event.xselection.selection=XA_PRIMARY;
    event.xselection.target   =XA_STRING;
    event.xselection.property =XA_CUT_BUFFER0;
    event.xselection.time     =event.xselectionrequest.time;
    XSendEvent(display,event.xselection.requestor,
     true,0xffffffL,&event);
/*    event.xselection.requestor=RootWindowOfScreen(screen);
    XSendEvent(display,RootWindowOfScreen(screen),
     true,0xffffffL,&event);
    event.xselection.requestor=window;
    XSendEvent(display,RootWindowOfScreen(screen),
     true,0xffffffL,&event);
    if (XSendEvent(display,event.xselection.requestor,
     true,0xffffffL,&event))
     printf("Erfolgreich gesendet!\n");*/

    break;
   case SelectionClear:
    if (event.xselectionclear.selection==XA_PRIMARY){
     RemoveAnySelection();
    }
    break;
   default:;
/*    printf("Unbekanntes Event %d\n",event.type); */
  } /* switch */
 } /* while */
 CloseFile();
 return 0;
} /* main */
Detected encoding: UTF-80