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 /////////////////////////////////////////////////////////
43 // It's probably a good idea to turn off the "identifier was truncated" warning
44 // in MSVC (Paul Brannan 5/25/98)
46 #pragma warning(disable: 4786)
50 // skip inline comments, empty lines
51 static char * getline(istream
& i
, char* buf
, int size
){
58 i
.getline(buf
,size
,'\n');
61 if ( /*(buf[len]>=0) &&*/ buf
[len
]< ' ' ) buf
[len
] = ' ';
66 // not so fast, but work ;)
68 if ( (buf
[len
] == ' ') && (buf
[len
+1] == ' ')) {
69 memmove(buf
+len
, buf
+len
+1, strlen(buf
+len
));
73 if (buf
[0] == ' ') memmove(buf
, buf
+1, size
-1);
76 if ((buf
[0]==0)||(buf
[0]==';')) continue;
78 len
= 0; // look for comment like this one
80 if ((buf
[len
] == '/') && (buf
[len
+1] == '/')) buf
[len
] = 0;
83 if (len
&& (buf
[len
-1] == ' ')) {
87 // in case for comment like this one (in line just a comment)
88 if (buf
[0]==0) continue;
96 // use string as FIFO queue for lines
97 static int getline(string
&str
, char* buf
, size_t sz
) {
99 if ( !str
.length() ) return 0;
100 const char * p
= strchr(str
.c_str(),'\n');
101 unsigned int len
; // Changed to unsigned (Paul Brannan 6/23/98)
105 len
= p
- str
.c_str();
107 len
= len
<sz
?len
:sz
-1;
109 strncpy(buf
,str
.c_str(), len
);
111 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
113 str
.erase(0, len
+ 1);
121 // parse \nnn and \Xhh
122 static int getbyte(const char*str
) {
123 unsigned char retval
= 0;
127 if ( (*str
== 'x') || (*str
== 'X') ) {
132 while (readed
!= 3 && str
[readed
]) {
133 unsigned char ch
= toupper(str
[readed
]);
135 retval
= retval
*base
+ (ch
-'0');
136 } else if (base
== 16 && ch
>= 'A' && ch
<= 'F') {
137 retval
= retval
*base
+ (ch
-'A'+10);
143 // Ioannou: If we discard the 0x00 we can't undefine a key !!!
144 // if ( retval == 0 ) {
151 // a little optimization
152 DWORD
Fix_ControlKeyState(char * Next_Token
) {
153 if (stricmp(Next_Token
, "RIGHT_ALT" ) == 0) return RIGHT_ALT_PRESSED
;
154 if (stricmp(Next_Token
, "LEFT_ALT" ) == 0) return LEFT_ALT_PRESSED
;
155 if (stricmp(Next_Token
, "RIGHT_CTRL") == 0) return RIGHT_CTRL_PRESSED
;
156 if (stricmp(Next_Token
, "LEFT_CTRL" ) == 0) return LEFT_CTRL_PRESSED
;
157 if (stricmp(Next_Token
, "SHIFT" ) == 0) return SHIFT_PRESSED
;
158 if (stricmp(Next_Token
, "NUMLOCK" ) == 0) return NUMLOCK_ON
;
159 if (stricmp(Next_Token
, "SCROLLLOCK") == 0) return SCROLLLOCK_ON
;
160 if (stricmp(Next_Token
, "CAPSLOCK" ) == 0) return CAPSLOCK_ON
;
161 if (stricmp(Next_Token
, "ENHANCED" ) == 0) return ENHANCED_KEY
;
163 // Paul Brannan 5/27/98
164 if (stricmp(Next_Token
, "APP_KEY" ) == 0) return APP_KEY
;
165 // Paul Brannan 6/28/98
166 if (stricmp(Next_Token
, "APP2_KEY" ) == 0) return APP2_KEY
;
167 // Paul Brannan 8/28/98
168 if (stricmp(Next_Token
, "APP3_KEY" ) == 0) return APP3_KEY
;
169 // Paul Brannan 12/9/98
170 if (stricmp(Next_Token
, "APP4_KEY" ) == 0) return APP4_KEY
;
177 // rewrited to suppert \xhh notation, a little optimized
178 char* Fix_Tok(char * tok
) {
182 // setmem is nonstandard; memset is standard (Paul Brannan 5/25/98)
184 // setmem(s, 256, 0);
187 for ( ; tok
[i
] != 0; ) {
190 switch ( tok
[i
+1] ) {
196 n
= getbyte(tok
+i
+1);
207 if ( tok
[i
+1] >= '@' ) {
208 s
[j
++] = tok
[i
+1] - '@';
221 // perform 'normalization' for lines like [some text], and some checks
222 // maybe it will be done faster - but no time for it
223 int normalizeSplitter(string
& buf
) {
224 if ( buf
.length() <= 2 ) return 0;
225 if ( buf
[0] == '[' && buf
[buf
.length()-1] == ']' ) {
226 while ( buf
[1] == ' ' )
227 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
233 while ( buf
[buf
.length()-2] == ' ' )
234 // Paul Brannan 6/23/98
236 buf
.erase(buf
.length()-2,1);
238 buf
.remove(buf
.length()-2,1);
246 // looking for part in string array, see Load(..) for more info
247 int TMapLoader::LookForPart(stringArray
& sa
, const char* partType
, const char* partName
) {
248 if ( !sa
.IsEmpty() ) {
254 normalizeSplitter(cmpbuf
); // if no parttype, [global] for example
255 int max
= sa
.GetItemsInContainer();
256 for ( int i
= 0; i
<max
; i
++ )
257 // I found some strange behavior if strnicmp was used here
258 if (strnicmp(cmpbuf
.c_str(),sa
[i
].c_str(),cmpbuf
.length()) == 0)
265 // load globals to 'globals'
266 // in buf must be a [global] part of input file
267 int TMapLoader::LoadGlobal(string
& buf
) {
270 while ( buf
.length() ) {
272 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
273 if ( wbuf
[0]==0 ) break;
274 char* Name
= strtok(wbuf
, TOKEN_DELIMITERS
);
275 if ( stricmp(Name
, "[global]")==0 ) continue;
277 char* Value
= strtok(NULL
, TOKEN_DELIMITERS
);
278 if ( Value
== NULL
) {
279 // cerr << "[global] -> no value for " << Name << endl;
280 printm(0, FALSE
, MSG_KEYNOVAL
, Name
);
283 int val
= atoi(Value
);
284 if ( val
> 0 && val
<= 0xff ) {
285 if ( !KeyTrans
.AddGlobalDef(val
, Name
)) return 0;
288 // cerr << "[global] -> bad value for " << Name << endl;
289 printm(0, FALSE
, MSG_KEYBADVAL
, Name
);
297 // perform parsing of strings like 'VK_CODE shifts text'
298 // returns text on success
299 char* TMapLoader::ParseKeyDef(const char* buf
, WORD
& vk_code
, DWORD
& control
) {
302 char* ptr
= strtok(wbuf
, TOKEN_DELIMITERS
);
303 if ( ptr
== NULL
) return NULL
;
305 int i
= KeyTrans
.LookOnGlobal(ptr
);
306 if ( i
== INT_MAX
) return NULL
;
308 vk_code
= KeyTrans
.GetGlobalCode(i
);
313 ptr
= strtok(NULL
, TOKEN_DELIMITERS
);
314 if ((ptr
== NULL
) || ((st
= Fix_ControlKeyState(ptr
)) == 0)) break;
318 if ( ptr
== NULL
) return NULL
;
324 // load keymap to current map
325 // be aware - buf must passed by value, its destroyed
326 int TMapLoader::LoadKeyMap(string buf
) {
333 // Paul Brannan Feb. 22, 1999
336 wbuf
[3] = ini
.get_escape_key();
337 i
= KeyTrans
.LookOnGlobal(wbuf
);
339 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_ESCAPE
);
340 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_ESCAPE
);
342 wbuf
[3] = ini
.get_scrollback_key();
343 i
= KeyTrans
.LookOnGlobal(wbuf
);
345 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_SCROLLBACK
);
346 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_SCROLLBACK
);
348 wbuf
[3] = ini
.get_dial_key();
349 i
= KeyTrans
.LookOnGlobal(wbuf
);
351 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), RIGHT_ALT_PRESSED
, TN_DIAL
);
352 KeyTrans
.AddKeyDef(KeyTrans
.GetGlobalCode(i
), LEFT_ALT_PRESSED
, TN_DIAL
);
354 KeyTrans
.AddKeyDef(VK_INSERT
, SHIFT_PRESSED
, TN_PASTE
);
356 while ( buf
.length() ) {
358 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
359 if ( wbuf
[0]==0 ) break;
360 if ( strnicmp(wbuf
,"[keymap",7)==0 ) continue;
362 char * keydef
= ParseKeyDef(wbuf
,vk_code
,control
);
364 if ( keydef
!= NULL
) {
366 // Check to see if keydef is a "special" code (Paul Brannan 3/29/00)
367 if(!strnicmp(keydef
, "\\tn_escape", strlen("\\tn_escape"))) {
368 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_ESCAPE
)) return 0;
369 } else if(!strnicmp(keydef
, "\\tn_scrollback", strlen("\\tn_scrollback"))) {
370 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_SCROLLBACK
)) return 0;
371 } else if(!strnicmp(keydef
, "\\tn_dial", strlen("\\tn_dial"))) {
372 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_DIAL
)) return 0;
373 } else if(!strnicmp(keydef
, "\\tn_paste", strlen("\\tn_paste"))) {
374 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_PASTE
)) return 0;
375 } else if(!strnicmp(keydef
, "\\tn_null", strlen("\\tn_null"))) {
376 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_NULL
)) return 0;
377 } else if(!strnicmp(keydef
, "\\tn_cr", strlen("\\tn_cr"))) {
378 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_CR
)) return 0;
379 } else if(!strnicmp(keydef
, "\\tn_crlf", strlen("\\tn_crlf"))) {
380 if(!KeyTrans
.AddKeyDef(vk_code
, control
, TN_CRLF
)) return 0;
382 if(!KeyTrans
.AddKeyDef(vk_code
,control
,keydef
)) return 0;
383 // else DeleteKeyDef() ???? - I'm not sure...
391 // load [charmap ...] part to xlat
392 int TMapLoader::LoadCharMap(string buf
) {
394 char charmapname
[128];
397 // xlat.init(); now it done by KeyTranslator::Load()
399 while ( buf
.length() ) {
401 if (!getline(buf
,wbuf
,sizeof(wbuf
))) break;
402 if ( wbuf
[0]==0 ) break;
403 if ( strnicmp(wbuf
,"[charmap",8)==0 ) {
404 strcpy(charmapname
,wbuf
);
407 char * host
= strtok(wbuf
, " ");
408 char * console
= strtok(NULL
, " ");
413 if ( host
== NULL
|| console
== NULL
) {
414 // cerr << charmapname << " -> Bad structure" << endl;
415 printm(0, FALSE
, MSG_KEYBADSTRUCT
, charmapname
);
418 if ( strlen(host
) > 1 && host
[0] == '\\' )
419 bHost
= getbyte(host
+1);
421 bHost
= (unsigned char)host
[0];
423 if ( strlen(console
) > 1 && console
[0] == '\\' )
424 bConsole
= getbyte(console
+1);
426 bConsole
= (unsigned char)console
[0];
428 if ( bHost
<= 0 || bConsole
<= 0 ) {
429 // cerr << charmapname << " -> Bad chars? "
430 // << host << " -> " << console << endl;
431 printm(0, FALSE
, MSG_KEYBADCHARS
, charmapname
, host
, console
);
434 // xlat.table[bHost] = bConsole;
435 Charmap
.modmap(bHost
, 'B', bConsole
);
437 return (Charmap
.enabled
= 1);
442 // ignore long comment [comment] ... [end comment]
444 int getLongComment(istream
& is
, char* wbuf
, size_t sz
) {
449 getline(is
, wbuf
, sz
);
450 if ( wbuf
[0]==0 ) return 1;
451 bufLen
= strlen(wbuf
);
452 if ( wbuf
[0] == '[' && wbuf
[bufLen
-1] == ']' ) {
455 if (!normalizeSplitter(temps
)) {
456 // cerr << "Unexpected line '" << temps << "'\n";
457 printm(0, FALSE
, MSG_KEYUNEXPLINE
, temps
.c_str());
460 if ( stricmp(temps
.c_str(),"[comment]") == 0 ) {
462 if ( !getLongComment(is
, wbuf
, sz
) ) return 0;
465 if ( stricmp(temps
.c_str(),"[end comment]") == 0 ) return 1;
468 // we get a warning if we don't put a return here (Paul Brannan 5/25/98)
473 // completelly rewrited to support new conceptions
474 int TMapLoader::Load(const char * filename
, const char * szActiveEmul
) {
478 ifstream
inpfile(filename
);
479 KeyTrans
.DeleteAllDefs();
482 // it is an array for store [...] ... [end ...] parts from file
483 stringArray
SA(0,0,sizeof(string
));
488 getline(inpfile
, buf
, 255);
489 bufLen
= strlen(buf
);
490 if ( !bufLen
) continue;
492 if ( buf
[0] == '[' && buf
[bufLen
-1] == ']' ) {
493 // is a part splitter [...]
496 if (!normalizeSplitter(temps
)) {
497 printm(0, FALSE
, MSG_KEYUNEXPLINE
, temps
.c_str());
502 if ( stricmp(temps
.c_str(),"[comment]") == 0 ) {
504 printit(temps
.c_str());
506 if ( !getLongComment(inpfile
, buf
, sizeof(buf
)) ) {
507 printm(0, FALSE
, MSG_KEYUNEXPEOF
);
518 // prepare line for make it as [end ...]
520 if ( strnicmp(back
.c_str(), "[global]", 8) == 0 ) {} // do nothing
521 else if ( strnicmp(back
.c_str(), "[keymap", 7) == 0 ) {
522 // DJGPP also uses erase rather than remove (Paul Brannan 6/23/98)
530 else if ( strnicmp(back
.c_str(), "[charmap", 8) == 0 ) {
531 // Paul Brannan 6/23/98
539 else if ( strnicmp(back
.c_str(), "[config", 7) == 0 ) {
540 // Paul Brannan 6/23/98
549 // cerr << "Unexpected token " << back << endl;
550 printm(0, FALSE
, MSG_KEYUNEXPTOK
, back
.c_str());
554 back
.insert(1,"END "); // now it looks like [END ...]
556 printit(temps
.c_str());
562 getline(inpfile
, buf
, sizeof(buf
));
563 bufLen
= strlen(buf
);
564 if ( !bufLen
) break;
565 if ( buf
[0] == '[' && buf
[bufLen
-1] == ']' ) {
567 if ( !normalizeSplitter(t
) ) break;
569 if ( stricmp(t
.c_str(),back
.c_str()) == 0 ) {
574 // AVS 31.12.97 fix [comment] block inside another block
575 if ( stricmp(t
.c_str(),"[comment]") == 0 &&
576 getLongComment(inpfile
, buf
, sizeof(buf
)) ) continue;
584 // cerr << "Unexpected end of file or token" << endl;
585 printm(0, FALSE
, MSG_KEYUNEXP
);
592 AllOk
= SA
.Add(temps
);;
595 // cerr << "Unexpected line '" << buf << "'\n";
596 printm(0, FALSE
, MSG_KEYUNEXPLINE
, buf
);
604 if ( !AllOk
) return 0;
606 // now all file are in SA, comments are stripped
608 int i
= LookForPart(SA
, "global", "");
609 if ( i
== INT_MAX
) {
610 // cerr << "No [GLOBAL] definition!" << endl;
611 printm(0, FALSE
, MSG_KEYNOGLOBAL
);
614 if ( !LoadGlobal(SA
[i
]) ) {
618 // look for need configuration
619 i
= LookForPart(SA
, "config", szActiveEmul
);
620 if ( i
== INT_MAX
) {
621 // cerr << "No [CONFIG " << szActiveEmul << "]\n";
622 printm(0, FALSE
, MSG_KEYNOCONFIG
, szActiveEmul
);
625 // cerr << "use configuration: " << szActiveEmul << endl;
626 printm(0, FALSE
, MSG_KEYUSECONFIG
, szActiveEmul
);
627 BOOL hadKeys
= FALSE
;
629 string config
= SA
[i
];
631 while ( config
.length() ) {
633 getline(config
,buf
,sizeof(buf
));
634 bufLen
= strlen(buf
);
635 if ( !bufLen
|| (buf
[0] == '[' && buf
[bufLen
-1] == ']') ) continue;
636 if ( strnicmp(buf
,"keymap",6) == 0 ) {
638 printit("\t"); printit(buf
); printit("\n");
639 char * mapdef
= strtok(buf
,":");
640 char * switchKey
= strtok(NULL
,"\n");
642 if ( !KeyTrans
.mapArray
.IsEmpty() && switchKey
== NULL
) {
643 // cerr << "no switch Key for '" << mapdef
645 printm(0, FALSE
, MSG_KEYNOSWKEY
, mapdef
);
648 if ( KeyTrans
.mapArray
.IsEmpty() ) {
649 if ( switchKey
!= NULL
) { // create default keymap
650 // cerr << "You cannot define switch key for default keymap -> ignored"
652 printm(0, FALSE
, MSG_KEYCANNOTDEF
);
655 KeyTrans
.mapArray
.Add(KeyMap(string(mapdef
)));
656 KeyTrans
.switchMap(empty
); // set it as current keymap
657 KeyTrans
.mainKeyMap
= KeyTrans
.currentKeyMap
;
660 string
keydef(switchKey
);
661 keydef
+= " !*!*!*"; // just for check
664 switchKey
= ParseKeyDef(keydef
.c_str(),vk_code
,control
);
665 if ( switchKey
!= NULL
) {
666 TKeyDef
swi(NULL
,control
,vk_code
);
667 if ( KeyTrans
.switchMap(swi
) > 0 ) {
668 // cerr << "Duplicate switching key\n";
669 printm(0, FALSE
, MSG_KEYDUPSWKEY
);
672 KeyTrans
.mapArray
.Add(KeyMap(swi
, orig
));
673 KeyTrans
.switchMap(swi
); // set it as current keymap
676 mapdef
+=7; // 'keymap '
677 // now load defined keymaps to current
678 while ((mapdef
!= NULL
)&&
679 (mapdef
= strtok(mapdef
,TOKEN_DELIMITERS
)) != NULL
) {
680 i
= LookForPart(SA
,"keymap",mapdef
);
681 if ( i
== INT_MAX
) {
682 // cerr << "Unknown KEYMAP " << mapdef << endl;
683 printm(0, FALSE
, MSG_KEYUNKNOWNMAP
, mapdef
);
685 mapdef
= strtok(NULL
,"\n"); // strtok is used in LoadKeyMap
686 // so - save pointer!
687 hadKeys
= LoadKeyMap(SA
[i
]); // load it
692 else if ( strnicmp(buf
,"charmap",7) == 0 ) {
693 printit("\t"); printit(buf
); printit("\n");
694 char * mapdef
= buf
+ 8;// 'charmap '
695 int SuccesLoaded
= 0;
696 // now load defined charmaps to current
697 while ((mapdef
!= NULL
)&&
698 (mapdef
= strtok(mapdef
,TOKEN_DELIMITERS
)) != NULL
) {
699 i
= LookForPart(SA
,"charmap",mapdef
);
700 if ( i
== INT_MAX
) {
701 // cerr << "Unknown KEYMAP " << mapdef << endl;
702 printm(0, FALSE
, MSG_KEYUNKNOWNMAP
, mapdef
);
704 mapdef
= strtok(NULL
,"\n"); // strtok is used in LoadKeyMap
705 // so - save pointer!
706 if (LoadCharMap(SA
[i
])) // load it
711 // cerr << "No charmaps loaded\n";
712 printm(0, FALSE
, MSG_KEYNOCHARMAPS
);
717 char* name = strtok(NULL," ");
718 if ( name == NULL ) {
719 cerr << "No name for CHARMAP" << endl;
721 i = LookForPart(SA,"charmap", name);
722 if ( i == INT_MAX ) {
723 cerr << "Unknown CHARMAP " << name << endl;
731 // cerr << "unexpected token in " << szActiveEmul << endl;
732 printm(0, FALSE
, MSG_KEYUNEXPTOKIN
, szActiveEmul
);
738 KeyTrans
.switchMap(empty
); // switch to default
739 KeyTrans
.mainKeyMap
= KeyTrans
.currentKeyMap
; // save it's number
740 // cerr << "There are " << (KeyTrans.mapArray.GetItemsInContainer()) << " maps\n";
741 char s
[12]; // good enough for a long int (32-bit)
742 itoa(KeyTrans
.mapArray
.GetItemsInContainer(), s
, 10);
743 printm(0, FALSE
, MSG_KEYNUMMAPS
, s
);
749 void TMapLoader::Display() {
751 int max
= KeyTrans
.mapArray
.GetItemsInContainer();
753 printm(0, FALSE
, MSG_KEYNOKEYMAPS
);
756 for ( int i
= 0; i
< max
; i
++ ) {
760 // Ioannou : we can show the current
761 if (KeyTrans
.currentKeyMap
== i
)
767 char * msg
= new char [KeyTrans
.mapArray
[i
].orig
.length()+1];
768 strcpy(msg
,KeyTrans
.mapArray
[i
].orig
.c_str());