1 ///////////////////////////////////////////////////////////////////////////////
2 //Telnet Win32 : an ANSI telnet client.
3 //Copyright (C) 1998-2000 Paul Brannan
4 //Copyright (C) 1998 I.Ioannou
5 //Copyright (C) 1997 Brad Johnson
7 //This program is free software; you can redistribute it and/or
8 //modify it under the terms of the GNU General Public License
9 //as published by the Free Software Foundation; either version 2
10 //of the License, or (at your option) any later version.
12 //This program is distributed in the hope that it will be useful,
13 //but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 //GNU General Public License for more details.
17 //You should have received a copy of the GNU General Public License
18 //along with this program; if not, write to the Free Software
19 //Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 ///////////////////////////////////////////////////////////////////////////
26 /////////////////////////////////////////////////////////
27 // Class TMapLoader - Key/Character Mappings //
28 // - Loads from telnet.cfg //
29 // originally part of KeyTrans.cpp //
30 /////////////////////////////////////////////////////////
42 // It's probably a good idea to turn off the "identifier was truncated" warning
43 // in MSVC (Paul Brannan 5/25/98)
45 #pragma warning(disable: 4786)
49 // skip inline comments, empty lines
50 static char * getline(istream
& i
, char* buf
, int size
){
57 i
.getline(buf
,size
,'\n');
60 if ( /*(buf[len]>=0) &&*/ buf
[len
]< ' ' ) buf
[len
] = ' ';
65 // not so fast, but work ;)
67 if ( (buf
[len
] == ' ') && (buf
[len
+1] == ' ')) {
68 memmove(buf
+len
, buf
+len
+1, strlen(buf
+len
));
72 if (buf
[0] == ' ') memmove(buf
, buf
+1, size
-1);
75 if ((buf
[0]==0)||(buf
[0]==';')) continue;
77 len
= 0; // look for comment like this one
79 if ((buf
[len
] == '/') && (buf
[len
+1] == '/')) buf
[len
] = 0;
82 if (len
&& (buf
[len
-1] == ' ')) {
86 // in case for comment like this one (in line just a comment)
87 if (buf
[0]==0) continue;
95 // use string as FIFO queue for lines
96 static int getline(string
&str
, char* buf
, size_t sz
) {
98 if ( !str
.length() ) return 0;
99 const char * p
= strchr(str
.c_str(),'\n');
100 unsigned int len
; // Changed to unsigned (Paul Brannan 6/23/98)
104 len
= p
- str
.c_str();
106 len
= len
<sz
?len
:sz
-1;
108 strncpy(buf
,str
.c_str(), len
);
110 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
112 str
.erase(0, len
+ 1);
120 // parse \nnn and \Xhh
121 static int getbyte(const char*str
) {
122 unsigned char retval
= 0;
126 if ( (*str
== 'x') || (*str
== 'X') ) {
131 while (readed
!= 3 && str
[readed
]) {
132 unsigned char ch
= toupper(str
[readed
]);
134 retval
= retval
*base
+ (ch
-'0');
135 } else if (base
== 16 && ch
>= 'A' && ch
<= 'F') {
136 retval
= retval
*base
+ (ch
-'A'+10);
142 // Ioannou: If we discard the 0x00 we can't undefine a key !!!
143 // if ( retval == 0 ) {
150 // a little optimization
151 DWORD
Fix_ControlKeyState(char * Next_Token
) {
152 if (stricmp(Next_Token
, "RIGHT_ALT" ) == 0) return RIGHT_ALT_PRESSED
;
153 if (stricmp(Next_Token
, "LEFT_ALT" ) == 0) return LEFT_ALT_PRESSED
;
154 if (stricmp(Next_Token
, "RIGHT_CTRL") == 0) return RIGHT_CTRL_PRESSED
;
155 if (stricmp(Next_Token
, "LEFT_CTRL" ) == 0) return LEFT_CTRL_PRESSED
;
156 if (stricmp(Next_Token
, "SHIFT" ) == 0) return SHIFT_PRESSED
;
157 if (stricmp(Next_Token
, "NUMLOCK" ) == 0) return NUMLOCK_ON
;
158 if (stricmp(Next_Token
, "SCROLLLOCK") == 0) return SCROLLLOCK_ON
;
159 if (stricmp(Next_Token
, "CAPSLOCK" ) == 0) return CAPSLOCK_ON
;
160 if (stricmp(Next_Token
, "ENHANCED" ) == 0) return ENHANCED_KEY
;
162 // Paul Brannan 5/27/98
163 if (stricmp(Next_Token
, "APP_KEY" ) == 0) return APP_KEY
;
164 // Paul Brannan 6/28/98
165 if (stricmp(Next_Token
, "APP2_KEY" ) == 0) return APP2_KEY
;
166 // Paul Brannan 8/28/98
167 if (stricmp(Next_Token
, "APP3_KEY" ) == 0) return APP3_KEY
;
168 // Paul Brannan 12/9/98
169 if (stricmp(Next_Token
, "APP4_KEY" ) == 0) return APP4_KEY
;
176 // rewrited to suppert \xhh notation, a little optimized
177 char* Fix_Tok(char * tok
) {
181 // setmem is nonstandard; memset is standard (Paul Brannan 5/25/98)
183 // setmem(s, 256, 0);
186 for ( ; tok
[i
] != 0; ) {
189 switch ( tok
[i
+1] ) {
195 n
= getbyte(tok
+i
+1);
206 if ( tok
[i
+1] >= '@' ) {
207 s
[j
++] = tok
[i
+1] - '@';
220 // perform 'normalization' for lines like [some text], and some checks
221 // maybe it will be done faster - but no time for it
222 int normalizeSplitter(string
& buf
) {
223 if ( buf
.length() <= 2 ) return 0;
224 if ( buf
[0] == '[' && buf
[buf
.length()-1] == ']' ) {
225 while ( buf
[1] == ' ' )
226 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
232 while ( buf
[buf
.length()-2] == ' ' )
233 // Paul Brannan 6/23/98
235 buf
.erase(buf
.length()-2,1);
237 buf
.remove(buf
.length()-2,1);
245 // looking for part in string array, see Load(..) for more info
246 int TMapLoader::LookForPart(stringArray
& sa
, const char* partType
, const char* partName
) {
247 if ( !sa
.IsEmpty() ) {
253 normalizeSplitter(cmpbuf
); // if no parttype, [global] for example
254 int max
= sa
.GetItemsInContainer();
255 for ( int i
= 0; i
<max
; i
++ )
256 // I found some strange behavior if strnicmp was used here
257 if (strnicmp(cmpbuf
.c_str(),sa
[i
].c_str(),cmpbuf
.length()) == 0)
264 // load globals to 'globals'
265 // in buf must be a [global] part of input file
266 int TMapLoader::LoadGlobal(string
& buf
) {
269 while ( buf
.length() ) {
271 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
272 if ( wbuf
[0]==0 ) break;
273 char* Name
= strtok(wbuf
, TOKEN_DELIMITERS
);
274 if ( stricmp(Name
, "[global]")==0 ) continue;
276 char* Value
= strtok(NULL
, TOKEN_DELIMITERS
);
277 if ( Value
== NULL
) {
278 // cerr << "[global] -> no value for " << Name << endl;
279 printm(0, FALSE
, MSG_KEYNOVAL
, Name
);
282 int val
= atoi(Value
);
283 if ( val
> 0 && val
<= 0xff ) {
284 if ( !KeyTrans
.AddGlobalDef(val
, Name
)) return 0;
287 // cerr << "[global] -> bad value for " << Name << endl;
288 printm(0, FALSE
, MSG_KEYBADVAL
, Name
);
296 // perform parsing of strings like 'VK_CODE shifts text'
297 // returns text on success
298 char* TMapLoader::ParseKeyDef(const char* buf
, WORD
& vk_code
, DWORD
& control
) {
301 char* ptr
= strtok(wbuf
, TOKEN_DELIMITERS
);
302 if ( ptr
== NULL
) return NULL
;
304 int i
= KeyTrans
.LookOnGlobal(ptr
);
305 if ( i
== INT_MAX
) return NULL
;
307 vk_code
= KeyTrans
.GetGlobalCode(i
);
312 ptr
= strtok(NULL
, TOKEN_DELIMITERS
);
313 if ((ptr
== NULL
) || ((st
= Fix_ControlKeyState(ptr
)) == 0)) break;
317 if ( ptr
== NULL
) return NULL
;
323 // load keymap to current map
324 // be aware - buf must passed by value, its destroyed
325 int TMapLoader::LoadKeyMap(string buf
) {
332 // Paul Brannan Feb. 22, 1999
335 wbuf
[3] = ini
.get_escape_key();
336 i
= KeyTrans
.LookOnGlobal(wbuf
);
338 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_ESCAPE
);
339 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_ESCAPE
);
341 wbuf
[3] = ini
.get_scrollback_key();
342 i
= KeyTrans
.LookOnGlobal(wbuf
);
344 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_SCROLLBACK
);
345 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_SCROLLBACK
);
347 wbuf
[3] = ini
.get_dial_key();
348 i
= KeyTrans
.LookOnGlobal(wbuf
);
350 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_DIAL
);
351 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_DIAL
);
353 KeyTrans
.AddKeyDef(VK_INSERT
, SHIFT_PRESSED
, TN_PASTE
);
355 while ( buf
.length() ) {
357 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
358 if ( wbuf
[0]==0 ) break;
359 if ( strnicmp(wbuf
,"[keymap",7)==0 ) continue;
361 char * keydef
= ParseKeyDef(wbuf
,vk_code
,control
);
363 if ( keydef
!= NULL
) {
365 // Check to see if keydef is a "special" code (Paul Brannan 3/29/00)
366 if(!strnicmp(keydef
, "\\tn_escape", strlen("\\tn_escape"))) {
367 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_ESCAPE
)) return 0;
368 } else if(!strnicmp(keydef
, "\\tn_scrollback", strlen("\\tn_scrollback"))) {
369 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_SCROLLBACK
)) return 0;
370 } else if(!strnicmp(keydef
, "\\tn_dial", strlen("\\tn_dial"))) {
371 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_DIAL
)) return 0;
372 } else if(!strnicmp(keydef
, "\\tn_paste", strlen("\\tn_paste"))) {
373 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_PASTE
)) return 0;
374 } else if(!strnicmp(keydef
, "\\tn_null", strlen("\\tn_null"))) {
375 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_NULL
)) return 0;
376 } else if(!strnicmp(keydef
, "\\tn_cr", strlen("\\tn_cr"))) {
377 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_CR
)) return 0;
378 } else if(!strnicmp(keydef
, "\\tn_crlf", strlen("\\tn_crlf"))) {
379 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_CRLF
)) return 0;
381 if(!KeyTrans
.AddKeyDef(vk_code
,control
,keydef
)) return 0;
382 // else DeleteKeyDef() ???? - I'm not sure...
390 // load [charmap ...] part to xlat
391 int TMapLoader::LoadCharMap(string buf
) {
393 char charmapname
[128];
396 // xlat.init(); now it done by KeyTranslator::Load()
398 while ( buf
.length() ) {
400 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
401 if ( wbuf
[0]==0 ) break;
402 if ( strnicmp(wbuf
,"[charmap",8)==0 ) {
403 strcpy(charmapname
,wbuf
);
406 char * host
= strtok(wbuf
, " ");
407 char * console
= strtok(NULL
, " ");
412 if ( host
== NULL
|| console
== NULL
) {
413 // cerr << charmapname << " -> Bad structure" << endl;
414 printm(0, FALSE
, MSG_KEYBADSTRUCT
, charmapname
);
417 if ( strlen(host
) > 1 && host
[0] == '\\' )
418 bHost
= getbyte(host
+1);
420 bHost
= (unsigned char)host
[0];
422 if ( strlen(console
) > 1 && console
[0] == '\\' )
423 bConsole
= getbyte(console
+1);
425 bConsole
= (unsigned char)console
[0];
427 if ( bHost
<= 0 || bConsole
<= 0 ) {
428 // cerr << charmapname << " -> Bad chars? "
429 // << host << " -> " << console << endl;
430 printm(0, FALSE
, MSG_KEYBADCHARS
, charmapname
, host
, console
);
433 // xlat.table[bHost] = bConsole;
434 Charmap
.modmap(bHost
, 'B', bConsole
);
436 return (Charmap
.enabled
= 1);
441 // ignore long comment [comment] ... [end comment]
443 int getLongComment(istream
& is
, char* wbuf
, size_t sz
) {
448 getline(is
, wbuf
, sz
);
449 if ( wbuf
[0]==0 ) return 1;
450 bufLen
= strlen(wbuf
);
451 if ( wbuf
[0] == '[' && wbuf
[bufLen
-1] == ']' ) {
454 if (!normalizeSplitter(temps
)) {
455 // cerr << "Unexpected line '" << temps << "'\n";
456 printm(0, FALSE
, MSG_KEYUNEXPLINE
, temps
.c_str());
459 if ( stricmp(temps
.c_str(),"[comment]") == 0 ) {
461 if ( !getLongComment(is
, wbuf
, sz
) ) return 0;
464 if ( stricmp(temps
.c_str(),"[end comment]") == 0 ) return 1;
467 // we get a warning if we don't put a return here (Paul Brannan 5/25/98)
472 // completelly rewrited to support new conceptions
473 int TMapLoader::Load(const char * filename
, const char * szActiveEmul
) {
477 ifstream
inpfile(filename
);
478 KeyTrans
.DeleteAllDefs();
481 // it is an array for store [...] ... [end ...] parts from file
482 stringArray
SA(0,0,sizeof(string
));
487 getline(inpfile
, buf
, 255);
488 bufLen
= strlen(buf
);
489 if ( !bufLen
) continue;
491 if ( buf
[0] == '[' && buf
[bufLen
-1] == ']' ) {
492 // is a part splitter [...]
495 if (!normalizeSplitter(temps
)) {
496 printm(0, FALSE
, MSG_KEYUNEXPLINE
, temps
.c_str());
501 if ( stricmp(temps
.c_str(),"[comment]") == 0 ) {
503 printit(temps
.c_str());
505 if ( !getLongComment(inpfile
, buf
, sizeof(buf
)) ) {
506 printm(0, FALSE
, MSG_KEYUNEXPEOF
);
517 // prepare line for make it as [end ...]
519 if ( strnicmp(back
.c_str(), "[global]", 8) == 0 ) {} // do nothing
520 else if ( strnicmp(back
.c_str(), "[keymap", 7) == 0 ) {
521 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
529 else if ( strnicmp(back
.c_str(), "[charmap", 8) == 0 ) {
530 // Paul Brannan 6/23/98
538 else if ( strnicmp(back
.c_str(), "[config", 7) == 0 ) {
539 // Paul Brannan 6/23/98
548 // cerr << "Unexpected token " << back << endl;
549 printm(0, FALSE
, MSG_KEYUNEXPTOK
, back
.c_str());
553 back
.insert(1,"END "); // now it looks like [END ...]
555 printit(temps
.c_str());
561 getline(inpfile
, buf
, sizeof(buf
));
562 bufLen
= strlen(buf
);
563 if ( !bufLen
) break;
564 if ( buf
[0] == '[' && buf
[bufLen
-1] == ']' ) {
566 if ( !normalizeSplitter(t
) ) break;
568 if ( stricmp(t
.c_str(),back
.c_str()) == 0 ) {
573 // AVS 31.12.97 fix [comment] block inside another block
574 if ( stricmp(t
.c_str(),"[comment]") == 0 &&
575 getLongComment(inpfile
, buf
, sizeof(buf
)) ) continue;
583 // cerr << "Unexpected end of file or token" << endl;
584 printm(0, FALSE
, MSG_KEYUNEXP
);
591 AllOk
= SA
.Add(temps
);
594 // cerr << "Unexpected line '" << buf << "'\n";
595 printm(0, FALSE
, MSG_KEYUNEXPLINE
, buf
);
603 if ( !AllOk
) return 0;
605 // now all file are in SA, comments are stripped
607 int i
= LookForPart(SA
, "global", "");
608 if ( i
== INT_MAX
) {
609 // cerr << "No [GLOBAL] definition!" << endl;
610 printm(0, FALSE
, MSG_KEYNOGLOBAL
);
613 if ( !LoadGlobal(SA
[i
]) ) {
617 // look for need configuration
618 i
= LookForPart(SA
, "config", szActiveEmul
);
619 if ( i
== INT_MAX
) {
620 // cerr << "No [CONFIG " << szActiveEmul << "]\n";
621 printm(0, FALSE
, MSG_KEYNOCONFIG
, szActiveEmul
);
624 // cerr << "use configuration: " << szActiveEmul << endl;
625 printm(0, FALSE
, MSG_KEYUSECONFIG
, szActiveEmul
);
626 BOOL hadKeys
= FALSE
;
628 string config
= SA
[i
];
630 while ( config
.length() ) {
632 getline(config
,buf
,sizeof(buf
));
633 bufLen
= strlen(buf
);
634 if ( !bufLen
|| (buf
[0] == '[' && buf
[bufLen
-1] == ']') ) continue;
635 if ( strnicmp(buf
,"keymap",6) == 0 ) {
637 printit("\t"); printit(buf
); printit("\n");
638 char * mapdef
= strtok(buf
,":");
639 char * switchKey
= strtok(NULL
,"\n");
641 if ( !KeyTrans
.mapArray
.IsEmpty() && switchKey
== NULL
) {
642 // cerr << "no switch Key for '" << mapdef
644 printm(0, FALSE
, MSG_KEYNOSWKEY
, mapdef
);
647 if ( KeyTrans
.mapArray
.IsEmpty() ) {
648 if ( switchKey
!= NULL
) { // create default keymap
649 // cerr << "You cannot define switch key for default keymap -> ignored"
651 printm(0, FALSE
, MSG_KEYCANNOTDEF
);
654 KeyTrans
.mapArray
.Add(KeyMap(string(mapdef
)));
655 KeyTrans
.switchMap(empty
); // set it as current keymap
656 KeyTrans
.mainKeyMap
= KeyTrans
.currentKeyMap
;
659 string
keydef(switchKey
);
660 keydef
+= " !*!*!*"; // just for check
663 switchKey
= ParseKeyDef(keydef
.c_str(),vk_code
,control
);
664 if ( switchKey
!= NULL
) {
665 TKeyDef
swi(NULL
,control
,vk_code
);
666 if ( KeyTrans
.switchMap(swi
) > 0 ) {
667 // cerr << "Duplicate switching key\n";
668 printm(0, FALSE
, MSG_KEYDUPSWKEY
);
671 KeyTrans
.mapArray
.Add(KeyMap(swi
, orig
));
672 KeyTrans
.switchMap(swi
); // set it as current keymap
675 mapdef
+=7; // 'keymap '
676 // now load defined keymaps to current
677 while ((mapdef
!= NULL
)&&
678 (mapdef
= strtok(mapdef
,TOKEN_DELIMITERS
)) != NULL
) {
679 i
= LookForPart(SA
,"keymap",mapdef
);
680 if ( i
== INT_MAX
) {
681 // cerr << "Unknown KEYMAP " << mapdef << endl;
682 printm(0, FALSE
, MSG_KEYUNKNOWNMAP
, mapdef
);
684 mapdef
= strtok(NULL
,"\n"); // strtok is used in LoadKeyMap
685 // so - save pointer!
686 hadKeys
= LoadKeyMap(SA
[i
]); // load it
691 else if ( strnicmp(buf
,"charmap",7) == 0 ) {
692 printit("\t"); printit(buf
); printit("\n");
693 char * mapdef
= buf
+ 8;// 'charmap '
694 int SuccesLoaded
= 0;
695 // now load defined charmaps to current
696 while ((mapdef
!= NULL
)&&
697 (mapdef
= strtok(mapdef
,TOKEN_DELIMITERS
)) != NULL
) {
698 i
= LookForPart(SA
,"charmap",mapdef
);
699 if ( i
== INT_MAX
) {
700 // cerr << "Unknown KEYMAP " << mapdef << endl;
701 printm(0, FALSE
, MSG_KEYUNKNOWNMAP
, mapdef
);
703 mapdef
= strtok(NULL
,"\n"); // strtok is used in LoadKeyMap
704 // so - save pointer!
705 if (LoadCharMap(SA
[i
])) // load it
710 // cerr << "No charmaps loaded\n";
711 printm(0, FALSE
, MSG_KEYNOCHARMAPS
);
716 char* name = strtok(NULL," ");
717 if ( name == NULL ) {
718 cerr << "No name for CHARMAP" << endl;
720 i = LookForPart(SA,"charmap", name);
721 if ( i == INT_MAX ) {
722 cerr << "Unknown CHARMAP " << name << endl;
730 // cerr << "unexpected token in " << szActiveEmul << endl;
731 printm(0, FALSE
, MSG_KEYUNEXPTOKIN
, szActiveEmul
);
737 KeyTrans
.switchMap(empty
); // switch to default
738 KeyTrans
.mainKeyMap
= KeyTrans
.currentKeyMap
; // save it's number
739 // cerr << "There are " << (KeyTrans.mapArray.GetItemsInContainer()) << " maps\n";
740 char s
[12]; // good enough for a long int (32-bit)
741 itoa(KeyTrans
.mapArray
.GetItemsInContainer(), s
, 10);
742 printm(0, FALSE
, MSG_KEYNUMMAPS
, s
);
748 void TMapLoader::Display() {
750 int max
= KeyTrans
.mapArray
.GetItemsInContainer();
752 printm(0, FALSE
, MSG_KEYNOKEYMAPS
);
755 for ( int i
= 0; i
< max
; i
++ ) {
759 // Ioannou : we can show the current
760 if (KeyTrans
.currentKeyMap
== i
)
766 char * msg
= new char [KeyTrans
.mapArray
[i
].orig
.length()+1];
767 strcpy(msg
,KeyTrans
.mapArray
[i
].orig
.c_str());