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 /////////////////////////////////////////////////////////
34 // It's probably a good idea to turn off the "identifier was truncated" warning
35 // in MSVC (Paul Brannan 5/25/98)
37 #pragma warning(disable: 4786)
41 // skip inline comments, empty lines
42 static char * getline(istream
& i
, char* buf
, int size
){
49 i
.getline(buf
,size
,'\n');
52 if ( /*(buf[len]>=0) &&*/ buf
[len
]< ' ' ) buf
[len
] = ' ';
57 // not so fast, but work ;)
59 if ( (buf
[len
] == ' ') && (buf
[len
+1] == ' ')) {
60 memmove(buf
+len
, buf
+len
+1, strlen(buf
+len
));
64 if (buf
[0] == ' ') memmove(buf
, buf
+1, size
-1);
67 if ((buf
[0]==0)||(buf
[0]==';')) continue;
69 len
= 0; // look for comment like this one
71 if ((buf
[len
] == '/') && (buf
[len
+1] == '/')) buf
[len
] = 0;
74 if (len
&& (buf
[len
-1] == ' ')) {
78 // in case for comment like this one (in line just a comment)
79 if (buf
[0]==0) continue;
87 // use string as FIFO queue for lines
88 static int getline(string
&str
, char* buf
, size_t sz
) {
90 if ( !str
.length() ) return 0;
91 const char * p
= strchr(str
.c_str(),'\n');
92 unsigned int len
; // Changed to unsigned (Paul Brannan 6/23/98)
96 len
= p
- str
.c_str();
98 len
= len
<sz
?len
:sz
-1;
100 strncpy(buf
,str
.c_str(), len
);
102 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
104 str
.erase(0, len
+ 1);
112 // parse \nnn and \Xhh
113 static int getbyte(const char*str
) {
114 unsigned char retval
= 0;
118 if ( (*str
== 'x') || (*str
== 'X') ) {
123 while (readed
!= 3 && str
[readed
]) {
124 unsigned char ch
= toupper(str
[readed
]);
126 retval
= retval
*base
+ (ch
-'0');
127 } else if (base
== 16 && ch
>= 'A' && ch
<= 'F') {
128 retval
= retval
*base
+ (ch
-'A'+10);
134 // Ioannou: If we discard the 0x00 we can't undefine a key !!!
135 // if ( retval == 0 ) {
142 // a little optimization
143 DWORD
Fix_ControlKeyState(char * Next_Token
) {
144 if (stricmp(Next_Token
, "RIGHT_ALT" ) == 0) return RIGHT_ALT_PRESSED
;
145 if (stricmp(Next_Token
, "LEFT_ALT" ) == 0) return LEFT_ALT_PRESSED
;
146 if (stricmp(Next_Token
, "RIGHT_CTRL") == 0) return RIGHT_CTRL_PRESSED
;
147 if (stricmp(Next_Token
, "LEFT_CTRL" ) == 0) return LEFT_CTRL_PRESSED
;
148 if (stricmp(Next_Token
, "SHIFT" ) == 0) return SHIFT_PRESSED
;
149 if (stricmp(Next_Token
, "NUMLOCK" ) == 0) return NUMLOCK_ON
;
150 if (stricmp(Next_Token
, "SCROLLLOCK") == 0) return SCROLLLOCK_ON
;
151 if (stricmp(Next_Token
, "CAPSLOCK" ) == 0) return CAPSLOCK_ON
;
152 if (stricmp(Next_Token
, "ENHANCED" ) == 0) return ENHANCED_KEY
;
154 // Paul Brannan 5/27/98
155 if (stricmp(Next_Token
, "APP_KEY" ) == 0) return APP_KEY
;
156 // Paul Brannan 6/28/98
157 if (stricmp(Next_Token
, "APP2_KEY" ) == 0) return APP2_KEY
;
158 // Paul Brannan 8/28/98
159 if (stricmp(Next_Token
, "APP3_KEY" ) == 0) return APP3_KEY
;
160 // Paul Brannan 12/9/98
161 if (stricmp(Next_Token
, "APP4_KEY" ) == 0) return APP4_KEY
;
168 // rewrited to suppert \xhh notation, a little optimized
169 char* Fix_Tok(char * tok
) {
173 // setmem is nonstandard; memset is standard (Paul Brannan 5/25/98)
175 // setmem(s, 256, 0);
178 for ( ; tok
[i
] != 0; ) {
181 switch ( tok
[i
+1] ) {
187 n
= getbyte(tok
+i
+1);
198 if ( tok
[i
+1] >= '@' ) {
199 s
[j
++] = tok
[i
+1] - '@';
212 // perform 'normalization' for lines like [some text], and some checks
213 // maybe it will be done faster - but no time for it
214 int normalizeSplitter(string
& buf
) {
215 if ( buf
.length() <= 2 ) return 0;
216 if ( buf
[0] == '[' && buf
[buf
.length()-1] == ']' ) {
217 while ( buf
[1] == ' ' )
218 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
224 while ( buf
[buf
.length()-2] == ' ' )
225 // Paul Brannan 6/23/98
227 buf
.erase(buf
.length()-2,1);
229 buf
.remove(buf
.length()-2,1);
237 // looking for part in string array, see Load(..) for more info
238 int TMapLoader::LookForPart(stringArray
& sa
, const char* partType
, const char* partName
) {
239 if ( !sa
.IsEmpty() ) {
245 normalizeSplitter(cmpbuf
); // if no parttype, [global] for example
246 int max
= sa
.GetItemsInContainer();
247 for ( int i
= 0; i
<max
; i
++ )
248 // I found some strange behavior if strnicmp was used here
249 if (strnicmp(cmpbuf
.c_str(),sa
[i
].c_str(),cmpbuf
.length()) == 0)
256 // load globals to 'globals'
257 // in buf must be a [global] part of input file
258 int TMapLoader::LoadGlobal(string
& buf
) {
261 while ( buf
.length() ) {
263 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
264 if ( wbuf
[0]==0 ) break;
265 char* Name
= strtok(wbuf
, TOKEN_DELIMITERS
);
266 if ( stricmp(Name
, "[global]")==0 ) continue;
268 char* Value
= strtok(NULL
, TOKEN_DELIMITERS
);
269 if ( Value
== NULL
) {
270 // cerr << "[global] -> no value for " << Name << endl;
271 printm(0, FALSE
, MSG_KEYNOVAL
, Name
);
274 int val
= atoi(Value
);
275 if ( val
> 0 && val
<= 0xff ) {
276 if ( !KeyTrans
.AddGlobalDef(val
, Name
)) return 0;
279 // cerr << "[global] -> bad value for " << Name << endl;
280 printm(0, FALSE
, MSG_KEYBADVAL
, Name
);
288 // perform parsing of strings like 'VK_CODE shifts text'
289 // returns text on success
290 char* TMapLoader::ParseKeyDef(const char* buf
, WORD
& vk_code
, DWORD
& control
) {
293 char* ptr
= strtok(wbuf
, TOKEN_DELIMITERS
);
294 if ( ptr
== NULL
) return NULL
;
296 int i
= KeyTrans
.LookOnGlobal(ptr
);
297 if ( i
== INT_MAX
) return NULL
;
299 vk_code
= KeyTrans
.GetGlobalCode(i
);
304 ptr
= strtok(NULL
, TOKEN_DELIMITERS
);
305 if ((ptr
== NULL
) || ((st
= Fix_ControlKeyState(ptr
)) == 0)) break;
309 if ( ptr
== NULL
) return NULL
;
315 // load keymap to current map
316 // be aware - buf must passed by value, its destroyed
317 int TMapLoader::LoadKeyMap(string buf
) {
324 // Paul Brannan Feb. 22, 1999
327 wbuf
[3] = ini
.get_escape_key();
328 i
= KeyTrans
.LookOnGlobal(wbuf
);
330 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_ESCAPE
);
331 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_ESCAPE
);
333 wbuf
[3] = ini
.get_scrollback_key();
334 i
= KeyTrans
.LookOnGlobal(wbuf
);
336 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_SCROLLBACK
);
337 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_SCROLLBACK
);
339 wbuf
[3] = ini
.get_dial_key();
340 i
= KeyTrans
.LookOnGlobal(wbuf
);
342 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_DIAL
);
343 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_DIAL
);
345 KeyTrans
.AddKeyDef(VK_INSERT
, SHIFT_PRESSED
, TN_PASTE
);
347 while ( buf
.length() ) {
349 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
350 if ( wbuf
[0]==0 ) break;
351 if ( strnicmp(wbuf
,"[keymap",7)==0 ) continue;
353 char * keydef
= ParseKeyDef(wbuf
,vk_code
,control
);
355 if ( keydef
!= NULL
) {
357 // Check to see if keydef is a "special" code (Paul Brannan 3/29/00)
358 if(!strnicmp(keydef
, "\\tn_escape", strlen("\\tn_escape"))) {
359 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_ESCAPE
)) return 0;
360 } else if(!strnicmp(keydef
, "\\tn_scrollback", strlen("\\tn_scrollback"))) {
361 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_SCROLLBACK
)) return 0;
362 } else if(!strnicmp(keydef
, "\\tn_dial", strlen("\\tn_dial"))) {
363 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_DIAL
)) return 0;
364 } else if(!strnicmp(keydef
, "\\tn_paste", strlen("\\tn_paste"))) {
365 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_PASTE
)) return 0;
366 } else if(!strnicmp(keydef
, "\\tn_null", strlen("\\tn_null"))) {
367 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_NULL
)) return 0;
368 } else if(!strnicmp(keydef
, "\\tn_cr", strlen("\\tn_cr"))) {
369 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_CR
)) return 0;
370 } else if(!strnicmp(keydef
, "\\tn_crlf", strlen("\\tn_crlf"))) {
371 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_CRLF
)) return 0;
373 if(!KeyTrans
.AddKeyDef(vk_code
,control
,keydef
)) return 0;
374 // else DeleteKeyDef() ???? - I'm not sure...
382 // load [charmap ...] part to xlat
383 int TMapLoader::LoadCharMap(string buf
) {
385 char charmapname
[128];
388 // xlat.init(); now it done by KeyTranslator::Load()
390 while ( buf
.length() ) {
392 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
393 if ( wbuf
[0]==0 ) break;
394 if ( strnicmp(wbuf
,"[charmap",8)==0 ) {
395 strcpy(charmapname
,wbuf
);
398 char * host
= strtok(wbuf
, " ");
399 char * console
= strtok(NULL
, " ");
404 if ( host
== NULL
|| console
== NULL
) {
405 // cerr << charmapname << " -> Bad structure" << endl;
406 printm(0, FALSE
, MSG_KEYBADSTRUCT
, charmapname
);
409 if ( strlen(host
) > 1 && host
[0] == '\\' )
410 bHost
= getbyte(host
+1);
412 bHost
= (unsigned char)host
[0];
414 if ( strlen(console
) > 1 && console
[0] == '\\' )
415 bConsole
= getbyte(console
+1);
417 bConsole
= (unsigned char)console
[0];
419 if ( bHost
<= 0 || bConsole
<= 0 ) {
420 // cerr << charmapname << " -> Bad chars? "
421 // << host << " -> " << console << endl;
422 printm(0, FALSE
, MSG_KEYBADCHARS
, charmapname
, host
, console
);
425 // xlat.table[bHost] = bConsole;
426 Charmap
.modmap(bHost
, 'B', bConsole
);
428 return (Charmap
.enabled
= 1);
433 // ignore long comment [comment] ... [end comment]
435 int getLongComment(istream
& is
, char* wbuf
, size_t sz
) {
440 getline(is
, wbuf
, sz
);
441 if ( wbuf
[0]==0 ) return 1;
442 bufLen
= strlen(wbuf
);
443 if ( wbuf
[0] == '[' && wbuf
[bufLen
-1] == ']' ) {
446 if (!normalizeSplitter(temps
)) {
447 // cerr << "Unexpected line '" << temps << "'\n";
448 printm(0, FALSE
, MSG_KEYUNEXPLINE
, temps
.c_str());
451 if ( stricmp(temps
.c_str(),"[comment]") == 0 ) {
453 if ( !getLongComment(is
, wbuf
, sz
) ) return 0;
456 if ( stricmp(temps
.c_str(),"[end comment]") == 0 ) return 1;
459 // we get a warning if we don't put a return here (Paul Brannan 5/25/98)
464 // completelly rewrited to support new conceptions
465 int TMapLoader::Load(const char * filename
, const char * szActiveEmul
) {
469 ifstream
inpfile(filename
);
470 KeyTrans
.DeleteAllDefs();
473 // it is an array for store [...] ... [end ...] parts from file
474 stringArray
SA(0,0,sizeof(string
));
479 getline(inpfile
, buf
, 255);
480 bufLen
= strlen(buf
);
481 if ( !bufLen
) continue;
483 if ( buf
[0] == '[' && buf
[bufLen
-1] == ']' ) {
484 // is a part splitter [...]
487 if (!normalizeSplitter(temps
)) {
488 printm(0, FALSE
, MSG_KEYUNEXPLINE
, temps
.c_str());
493 if ( stricmp(temps
.c_str(),"[comment]") == 0 ) {
495 printit(temps
.c_str());
497 if ( !getLongComment(inpfile
, buf
, sizeof(buf
)) ) {
498 printm(0, FALSE
, MSG_KEYUNEXPEOF
);
509 // prepare line for make it as [end ...]
511 if ( strnicmp(back
.c_str(), "[global]", 8) == 0 ) {} // do nothing
512 else if ( strnicmp(back
.c_str(), "[keymap", 7) == 0 ) {
513 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
521 else if ( strnicmp(back
.c_str(), "[charmap", 8) == 0 ) {
522 // Paul Brannan 6/23/98
530 else if ( strnicmp(back
.c_str(), "[config", 7) == 0 ) {
531 // Paul Brannan 6/23/98
540 // cerr << "Unexpected token " << back << endl;
541 printm(0, FALSE
, MSG_KEYUNEXPTOK
, back
.c_str());
545 back
.insert(1,"END "); // now it looks like [END ...]
547 printit(temps
.c_str());
553 getline(inpfile
, buf
, sizeof(buf
));
554 bufLen
= strlen(buf
);
555 if ( !bufLen
) break;
556 if ( buf
[0] == '[' && buf
[bufLen
-1] == ']' ) {
558 if ( !normalizeSplitter(t
) ) break;
560 if ( stricmp(t
.c_str(),back
.c_str()) == 0 ) {
565 // AVS 31.12.97 fix [comment] block inside another block
566 if ( stricmp(t
.c_str(),"[comment]") == 0 &&
567 getLongComment(inpfile
, buf
, sizeof(buf
)) ) continue;
575 // cerr << "Unexpected end of file or token" << endl;
576 printm(0, FALSE
, MSG_KEYUNEXP
);
583 AllOk
= SA
.Add(temps
);
586 // cerr << "Unexpected line '" << buf << "'\n";
587 printm(0, FALSE
, MSG_KEYUNEXPLINE
, buf
);
595 if ( !AllOk
) return 0;
597 // now all file are in SA, comments are stripped
599 int i
= LookForPart(SA
, "global", "");
600 if ( i
== INT_MAX
) {
601 // cerr << "No [GLOBAL] definition!" << endl;
602 printm(0, FALSE
, MSG_KEYNOGLOBAL
);
605 if ( !LoadGlobal(SA
[i
]) ) {
609 // look for need configuration
610 i
= LookForPart(SA
, "config", szActiveEmul
);
611 if ( i
== INT_MAX
) {
612 // cerr << "No [CONFIG " << szActiveEmul << "]\n";
613 printm(0, FALSE
, MSG_KEYNOCONFIG
, szActiveEmul
);
616 // cerr << "use configuration: " << szActiveEmul << endl;
617 printm(0, FALSE
, MSG_KEYUSECONFIG
, szActiveEmul
);
618 BOOL hadKeys
= FALSE
;
620 string config
= SA
[i
];
622 while ( config
.length() ) {
624 getline(config
,buf
,sizeof(buf
));
625 bufLen
= strlen(buf
);
626 if ( !bufLen
|| (buf
[0] == '[' && buf
[bufLen
-1] == ']') ) continue;
627 if ( strnicmp(buf
,"keymap",6) == 0 ) {
629 printit("\t"); printit(buf
); printit("\n");
630 char * mapdef
= strtok(buf
,":");
631 char * switchKey
= strtok(NULL
,"\n");
633 if ( !KeyTrans
.mapArray
.IsEmpty() && switchKey
== NULL
) {
634 // cerr << "no switch Key for '" << mapdef
636 printm(0, FALSE
, MSG_KEYNOSWKEY
, mapdef
);
639 if ( KeyTrans
.mapArray
.IsEmpty() ) {
640 if ( switchKey
!= NULL
) { // create default keymap
641 // cerr << "You cannot define switch key for default keymap -> ignored"
643 printm(0, FALSE
, MSG_KEYCANNOTDEF
);
646 KeyTrans
.mapArray
.Add(KeyMap(string(mapdef
)));
647 KeyTrans
.switchMap(empty
); // set it as current keymap
648 KeyTrans
.mainKeyMap
= KeyTrans
.currentKeyMap
;
651 string
keydef(switchKey
);
652 keydef
+= " !*!*!*"; // just for check
655 switchKey
= ParseKeyDef(keydef
.c_str(),vk_code
,control
);
656 if ( switchKey
!= NULL
) {
657 TKeyDef
swi(NULL
,control
,vk_code
);
658 if ( KeyTrans
.switchMap(swi
) > 0 ) {
659 // cerr << "Duplicate switching key\n";
660 printm(0, FALSE
, MSG_KEYDUPSWKEY
);
663 KeyTrans
.mapArray
.Add(KeyMap(swi
, orig
));
664 KeyTrans
.switchMap(swi
); // set it as current keymap
667 mapdef
+=7; // 'keymap '
668 // now load defined keymaps to current
669 while ((mapdef
!= NULL
)&&
670 (mapdef
= strtok(mapdef
,TOKEN_DELIMITERS
)) != NULL
) {
671 i
= LookForPart(SA
,"keymap",mapdef
);
672 if ( i
== INT_MAX
) {
673 // cerr << "Unknown KEYMAP " << mapdef << endl;
674 printm(0, FALSE
, MSG_KEYUNKNOWNMAP
, mapdef
);
676 mapdef
= strtok(NULL
,"\n"); // strtok is used in LoadKeyMap
677 // so - save pointer!
678 hadKeys
= LoadKeyMap(SA
[i
]); // load it
683 else if ( strnicmp(buf
,"charmap",7) == 0 ) {
684 printit("\t"); printit(buf
); printit("\n");
685 char * mapdef
= buf
+ 8;// 'charmap '
686 int SuccesLoaded
= 0;
687 // now load defined charmaps to current
688 while ((mapdef
!= NULL
)&&
689 (mapdef
= strtok(mapdef
,TOKEN_DELIMITERS
)) != NULL
) {
690 i
= LookForPart(SA
,"charmap",mapdef
);
691 if ( i
== INT_MAX
) {
692 // cerr << "Unknown KEYMAP " << mapdef << endl;
693 printm(0, FALSE
, MSG_KEYUNKNOWNMAP
, mapdef
);
695 mapdef
= strtok(NULL
,"\n"); // strtok is used in LoadKeyMap
696 // so - save pointer!
697 if (LoadCharMap(SA
[i
])) // load it
702 // cerr << "No charmaps loaded\n";
703 printm(0, FALSE
, MSG_KEYNOCHARMAPS
);
708 char* name = strtok(NULL," ");
709 if ( name == NULL ) {
710 cerr << "No name for CHARMAP" << endl;
712 i = LookForPart(SA,"charmap", name);
713 if ( i == INT_MAX ) {
714 cerr << "Unknown CHARMAP " << name << endl;
722 // cerr << "unexpected token in " << szActiveEmul << endl;
723 printm(0, FALSE
, MSG_KEYUNEXPTOKIN
, szActiveEmul
);
729 KeyTrans
.switchMap(empty
); // switch to default
730 KeyTrans
.mainKeyMap
= KeyTrans
.currentKeyMap
; // save it's number
731 // cerr << "There are " << (KeyTrans.mapArray.GetItemsInContainer()) << " maps\n";
732 char s
[12]; // good enough for a long int (32-bit)
733 itoa(KeyTrans
.mapArray
.GetItemsInContainer(), s
, 10);
734 printm(0, FALSE
, MSG_KEYNUMMAPS
, s
);
740 void TMapLoader::Display() {
742 int max
= KeyTrans
.mapArray
.GetItemsInContainer();
744 printm(0, FALSE
, MSG_KEYNOKEYMAPS
);
747 for ( int i
= 0; i
< max
; i
++ ) {
751 // Ioannou : we can show the current
752 if (KeyTrans
.currentKeyMap
== i
)
758 char * msg
= new char [KeyTrans
.mapArray
[i
].orig
.length()+1];
759 strcpy(msg
,KeyTrans
.mapArray
[i
].orig
.c_str());