/* * Copyright (C) 2002-2007 The DOSBox Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "dosbox.h" #include "bios.h" #include "setup.h" #include "support.h" #include "../ints/int10.h" #include "regs.h" #include "callback.h" #include "mapper.h" #include "drives.h" #include "dos_inc.h" #include "dos_codepages.h" #include "dos_keyboard_layout_data.h" static FILE* OpenDosboxFile(const char* name) { Bit8u drive; char fullname[DOS_PATHLENGTH]; localDrive* ldp=0; // try to build dos name if (DOS_MakeName(name,fullname,&drive)) { try { // try to open file on mounted drive first ldp=dynamic_cast(Drives[drive]); if (ldp) { FILE *tmpfile=ldp->GetSystemFilePtr(fullname, "rb"); if (tmpfile != NULL) return tmpfile; } } catch(...) {} } FILE *tmpfile=fopen(name, "rb"); return tmpfile; } class keyboard_layout { public: keyboard_layout() { this->reset(); language_codes=NULL; use_foreign_layout=false; sprintf(current_keyboard_file_name, "none"); }; ~keyboard_layout(); // read in a codepage from a .cpi-file Bitu read_codepage_file(const char* codepage_file_name, Bit32s codepage_id); Bit16u extract_codepage(const char* keyboard_file_name); // read in a keyboard layout from a .kl-file Bitu read_keyboard_file(const char* keyboard_file_name, Bit32s req_cp); // call layout_key to apply the current language layout bool layout_key(Bitu key, Bit8u flags1, Bit8u flags2, Bit8u flags3); Bitu switch_keyboard_layout(const char* new_layout, keyboard_layout* &created_layout); void switch_foreign_layout(); private: static const Bit8u layout_pages=12; Bit16u current_layout[(MAX_SCAN_CODE+1)*layout_pages]; struct { Bit16u required_flags,forbidden_flags; Bit16u required_userflags,forbidden_userflags; } current_layout_planes[layout_pages-4]; Bit8u additional_planes,used_lock_modifiers; // diacritics table Bit8u diacritics[2048]; Bit16u diacritics_entries; Bit16u diacritics_character; Bit16u user_keys; char current_keyboard_file_name[256]; bool use_foreign_layout; // language code storage used when switching layouts char** language_codes; Bitu language_code_count; void reset(); void read_keyboard_file(Bit32s specific_layout); Bitu read_keyboard_file(const char* keyboard_file_name, Bit32s specific_layout, Bit32s requested_codepage); bool map_key(Bitu key, Bit16u layouted_key, bool is_command, bool is_keypair); }; keyboard_layout::~keyboard_layout() { if (language_codes) { for (Bitu i=0; iread_keyboard_file(keyboard_file_name, -1, req_cp); } // switch to a different layout void keyboard_layout::read_keyboard_file(Bit32s specific_layout) { if (strcmp(current_keyboard_file_name,"none")) this->read_keyboard_file(current_keyboard_file_name, specific_layout, dos.loaded_codepage); } static Bit32u read_kcl_file(const char* kcl_file_name, const char* layout_id) { FILE* tempfile = OpenDosboxFile(kcl_file_name); if (tempfile==0) return 0; static Bit8u rbuf[8192]; // check ID-bytes of file Bit32u dr=(Bit32u)fread(rbuf, sizeof(Bit8u), 7, tempfile); if ((dr<7) || (rbuf[0]!=0x4b) || (rbuf[1]!=0x43) || (rbuf[2]!=0x46)) { fclose(tempfile); return 0; } fseek(tempfile, 7+rbuf[6], SEEK_SET); for (;;) { Bit32u cur_pos=(Bit32u)(ftell(tempfile)); dr=(Bit32u)fread(rbuf, sizeof(Bit8u), 5, tempfile); if (dr<5) break; Bit16u len=host_readw(&rbuf[0]); Bit8u data_len=rbuf[2]; char lng_codes[256]; // get all language codes for this layout for (Bitu i=0; ikcl_data_size) break; Bit32u cur_pos=dpos; Bit16u len=host_readw(&kcl_data[dpos]); Bit8u data_len=kcl_data[dpos+2]; dpos+=5; char lng_codes[256]; // get all language codes for this layout for (Bitu i=0; ikcl_data_size) break; char lc=(char)kcl_data[dpos]; dpos++; i++; if (lc==',') break; lng_codes[lcpos++]=lc; } lng_codes[lcpos]=0; if (strcasecmp(lng_codes, layout_id)==0) { // language ID found, return position return cur_pos; } dpos+=2; } dpos=cur_pos+3+len; } return 0; } Bitu keyboard_layout::read_keyboard_file(const char* keyboard_file_name, Bit32s specific_layout, Bit32s requested_codepage) { this->reset(); if (specific_layout==-1) strcpy(current_keyboard_file_name, keyboard_file_name); if (!strcmp(keyboard_file_name,"none")) return KEYB_NOERROR; static Bit8u read_buf[65535]; Bit32u read_buf_size, read_buf_pos, bytes_read; Bit32u start_pos=5; char nbuf[512]; sprintf(nbuf, "%s.kl", keyboard_file_name); FILE* tempfile = OpenDosboxFile(nbuf); if (tempfile==NULL) { // try keyboard layout libraries next if (start_pos=read_kcl_file("keyboard.sys",keyboard_file_name)) { tempfile = OpenDosboxFile("keyboard.sys"); fseek(tempfile, start_pos+2, SEEK_SET); read_buf_size=(Bit32u)fread(read_buf, sizeof(Bit8u), 65535, tempfile); fclose(tempfile); } else if (start_pos=read_kcl_file("keybrd2.sys",keyboard_file_name)) { tempfile = OpenDosboxFile("keybrd2.sys"); fseek(tempfile, start_pos+2, SEEK_SET); read_buf_size=(Bit32u)fread(read_buf, sizeof(Bit8u), 65535, tempfile); fclose(tempfile); } else if (start_pos=read_kcl_file("keybrd3.sys",keyboard_file_name)) { tempfile = OpenDosboxFile("keybrd3.sys"); fseek(tempfile, start_pos+2, SEEK_SET); read_buf_size=(Bit32u)fread(read_buf, sizeof(Bit8u), 65535, tempfile); fclose(tempfile); } else if (start_pos=read_kcl_data(layout_keyboardsys,33196,keyboard_file_name)) { read_buf_size=0; for (Bitu ct=start_pos+2; ct<33196; ct++) read_buf[read_buf_size++]=layout_keyboardsys[ct]; } else if (start_pos=read_kcl_data(layout_keybrd2sys,25431,keyboard_file_name)) { read_buf_size=0; for (Bitu ct=start_pos+2; ct<25431; ct++) read_buf[read_buf_size++]=layout_keybrd2sys[ct]; } else if (start_pos=read_kcl_data(layout_keybrd3sys,27122,keyboard_file_name)) { read_buf_size=0; for (Bitu ct=start_pos+2; ct<27122; ct++) read_buf[read_buf_size++]=layout_keybrd3sys[ct]; } else { LOG(LOG_BIOS,LOG_ERROR)("Keyboard layout file %s not found",keyboard_file_name); return KEYB_FILENOTFOUND; } start_pos=0; } else { // check ID-bytes of file Bit32u dr=(Bit32u)fread(read_buf, sizeof(Bit8u), 4, tempfile); if ((dr<4) || (read_buf[0]!=0x4b) || (read_buf[1]!=0x4c) || (read_buf[2]!=0x46)) { LOG(LOG_BIOS,LOG_ERROR)("Invalid keyboard layout file %s",keyboard_file_name); return KEYB_INVALIDFILE; } fseek(tempfile, 0, SEEK_SET); read_buf_size=(Bit32u)fread(read_buf, sizeof(Bit8u), 65535, tempfile); fclose(tempfile); } Bit8u data_len,submappings; data_len=read_buf[start_pos++]; language_codes=new char*[data_len]; language_code_count=0; // get all language codes for this layout for (Bitu i=0; i(layout_pages-4)) additional_planes=(layout_pages-4); // seek to plane descriptor read_buf_pos=start_pos+0x14+submappings*8; for (Bit16u cplane=0; cplane0)) { // add all available mappings for (Bit16u addmap=0; addmapadditional_planes+2) break; Bitu charptr=read_buf_pos+addmap*((read_buf[read_buf_pos-2]&0x80)?2:1); Bit16u kchar=read_buf[charptr]; if (kchar!=0) { // key remapped if (read_buf[read_buf_pos-2]&0x80) kchar|=read_buf[charptr+1]<<8; // scancode/char pair // overwrite mapping current_layout[scan*layout_pages+addmap]=kchar; // clear command bit current_layout[scan*layout_pages+layout_pages-2]&=~(1<use_foreign_layout=true; return KEYB_NOERROR; } LOG(LOG_BIOS,LOG_ERROR)("No matching keyboard layout found in %s",keyboard_file_name); // reset layout data (might have been changed by general layout) this->reset(); return KEYB_LAYOUTNOTFOUND; } bool keyboard_layout::layout_key(Bitu key, Bit8u flags1, Bit8u flags2, Bit8u flags3) { if (key>MAX_SCAN_CODE) return false; if (!this->use_foreign_layout) return false; bool is_special_pair=(current_layout[key*layout_pages+layout_pages-1] & 0x80)==0x80; if ((((flags1&used_lock_modifiers)&0x7c)==0) && ((flags3&2)==0)) { // check if shift/caps is active: // (left_shift OR right_shift) XOR (key_affected_by_caps AND caps_locked) if ((((flags1&2)>>1) | (flags1&1)) ^ (((current_layout[key*layout_pages+layout_pages-1] & 0x40) & (flags1 & 0x40))>>6)) { // shift plane if (current_layout[key*layout_pages+1]!=0) { // check if command-bit is set for shift plane bool is_command=(current_layout[key*layout_pages+layout_pages-2]&2)!=0; if (this->map_key(key, current_layout[key*layout_pages+1], is_command, is_special_pair)) return true; } } else { // normal plane if (current_layout[key*layout_pages]!=0) { // check if command-bit is set for normal plane bool is_command=(current_layout[key*layout_pages+layout_pages-2]&1)!=0; if (this->map_key(key, current_layout[key*layout_pages], is_command, is_special_pair)) return true; } } } // calculate current flags Bit16u current_flags=(flags1&0x7f) | (((flags2&3) | (flags3&0xc))<<8); if (flags1&3) current_flags|=0x4000; // either shift key active if (flags3&2) current_flags|=0x1000; // e0 prefixed // check all planes if flags fit for (Bit16u cplane=0; cplane>(cplane+2))&1)!=0; if (this->map_key(key, current_layout[key*layout_pages+2+cplane], is_command, is_special_pair)) return true; } else break; // abort plane checking } } if (diacritics_character>0) { // ignore state-changing keys switch(key) { case 0x1d: /* Ctrl Pressed */ case 0x2a: /* Left Shift Pressed */ case 0x36: /* Right Shift Pressed */ case 0x38: /* Alt Pressed */ case 0x3a: /* Caps Lock */ case 0x45: /* Num Lock */ case 0x46: /* Scroll Lock */ break; default: if (diacritics_character-200>=diacritics_entries) { diacritics_character=0; return true; } Bit16u diacritics_start=0; // search start of subtable for (Bit16u i=0; i=200) && (key_command<235)) { // diacritics command diacritics_character=key_command; if (diacritics_character-200>=diacritics_entries) diacritics_character=0; return true; } else if ((key_command>=120) && (key_command<129)) { // switch layout command this->read_keyboard_file(key_command-119); return true; } else if ((key_command>=180) && (key_command<188)) { // switch user key off user_keys&=~(1<<(key_command-180)); return true; } else if ((key_command>=188) && (key_command<196)) { // switch user key on user_keys|=(1<<(key_command-188)); return true; } else if (key_command==160) return true; // nop command } else { // non-command if (diacritics_character>0) { if (diacritics_character-200>=diacritics_entries) diacritics_character = 0; else { Bit16u diacritics_start=0; // search start of subtable for (Bit16u i=0; i(&nbuf[strsz-1])); if (plc=='I') { // try CPX-extension as well nbuf[strsz-1]='X'; tempfile=OpenDosboxFile(nbuf); } else if (plc=='X') { // try CPI-extension as well nbuf[strsz-1]='I'; tempfile=OpenDosboxFile(nbuf); } } } static Bit8u cpi_buf[65536]; Bit32u cpi_buf_size=0,size_of_cpxdata=0;; bool upxfound=false; Bit16u found_at_pos=5; if (tempfile==NULL) { // check if build-in codepage is available switch (codepage_id) { case 437: case 850: case 852: case 853: case 857: case 858: for (Bitu bct=0; bct<6322; bct++) cpi_buf[bct]=font_ega_cpx[bct]; cpi_buf_size=6322; break; case 771: case 772: case 808: case 855: case 866: case 872: for (Bitu bct=0; bct<5455; bct++) cpi_buf[bct]=font_ega3_cpx[bct]; cpi_buf_size=5455; break; case 737: case 851: case 869: for (Bitu bct=0; bct<5720; bct++) cpi_buf[bct]=font_ega5_cpx[bct]; cpi_buf_size=5720; break; default: return KEYB_INVALIDCPFILE; break; } upxfound=true; found_at_pos=0x29; size_of_cpxdata=cpi_buf_size; } else { Bit32u dr=(Bit32u)fread(cpi_buf, sizeof(Bit8u), 5, tempfile); // check if file is valid if (dr<5) { LOG(LOG_BIOS,LOG_ERROR)("Codepage file %s invalid",cp_filename); return KEYB_INVALIDCPFILE; } // check if non-compressed cpi file if ((cpi_buf[0]!=0xff) || (cpi_buf[1]!=0x46) || (cpi_buf[2]!=0x4f) || (cpi_buf[3]!=0x4e) || (cpi_buf[4]!=0x54)) { // check if dr-dos custom cpi file if ((cpi_buf[0]==0x7f) && (cpi_buf[1]!=0x44) && (cpi_buf[2]!=0x52) && (cpi_buf[3]!=0x46) && (cpi_buf[4]!=0x5f)) { LOG(LOG_BIOS,LOG_ERROR)("Codepage file %s has unsupported DR-DOS format",cp_filename); return KEYB_INVALIDCPFILE; } // check if compressed cpi file Bit8u next_byte=0; for (Bitu i=0; i<100; i++) { fread(&next_byte, sizeof(Bit8u), 1, tempfile); found_at_pos++; while (next_byte==0x55) { fread(&next_byte, sizeof(Bit8u), 1, tempfile); found_at_pos++; if (next_byte==0x50) { fread(&next_byte, sizeof(Bit8u), 1, tempfile); found_at_pos++; if (next_byte==0x58) { fread(&next_byte, sizeof(Bit8u), 1, tempfile); found_at_pos++; if (next_byte==0x21) { // read version ID fread(&next_byte, sizeof(Bit8u), 1, tempfile); found_at_pos++; upxfound=true; break; } } } } if (upxfound) break; } if (!upxfound) { LOG(LOG_BIOS,LOG_ERROR)("Codepage file %s invalid: %x",cp_filename,cpi_buf[0]); return KEYB_INVALIDCPFILE; } else { if (next_byte<10) E_Exit("UPX-compressed cpi file, but upx-version too old"); // read in compressed CPX-file fseek(tempfile, 0, SEEK_SET); size_of_cpxdata=(Bitu)fread(cpi_buf, sizeof(Bit8u), 65536, tempfile); } } else { // standard uncompressed cpi-file fseek(tempfile, 0, SEEK_SET); cpi_buf_size=(Bit32u)fread(cpi_buf, sizeof(Bit8u), 65536, tempfile); } } if (upxfound) { if (size_of_cpxdata>0xfe00) E_Exit("Size of cpx-compressed data too big"); found_at_pos+=19; // prepare for direct decompression cpi_buf[found_at_pos]=0xcb; Bit16u seg=0; Bit16u size=0x1500; if (!DOS_AllocateMemory(&seg,&size)) E_Exit("Not enough free low memory to unpack data"); MEM_BlockWrite((seg<<4)+0x100,cpi_buf,size_of_cpxdata); // setup segments Bit16u save_ds=SegValue(ds); Bit16u save_es=SegValue(es); Bit16u save_ss=SegValue(ss); Bit32u save_esp=reg_esp; SegSet16(ds,seg); SegSet16(es,seg); SegSet16(ss,seg+0x1000); reg_esp=0xfffe; // let UPX unpack the file CALLBACK_RunRealFar(seg,0x100); SegSet16(ds,save_ds); SegSet16(es,save_es); SegSet16(ss,save_ss); reg_esp=save_esp; // get unpacked content MEM_BlockRead((seg<<4)+0x100,cpi_buf,65536); cpi_buf_size=65536; DOS_FreeMemory(seg); } start_pos=host_readd(&cpi_buf[0x13]); number_of_codepages=host_readw(&cpi_buf[start_pos]); start_pos+=4; // search if codepage is provided by file for (Bit16u test_codepage=0; test_codepagetype==M_TEXT) && (machine==MCH_VGA)) { INT10_LoadFont(Real2Phys(int10.rom.font_16),true,256,0,0,16); } INT10_SetupRomMemoryChecksum(); return KEYB_NOERROR; } start_pos=host_readd(&cpi_buf[start_pos]); start_pos+=2; } LOG(LOG_BIOS,LOG_ERROR)("Codepage %i not found",codepage_id); return KEYB_INVALIDCPFILE; } Bitu keyboard_layout::switch_keyboard_layout(const char* new_layout, keyboard_layout*& created_layout) { if (strncasecmp(new_layout,"US",2)) { // switch to a foreign layout char tbuf[256]; strcpy(tbuf, new_layout); size_t newlen=strlen(tbuf); bool language_code_found=false; // check if language code is present in loaded foreign layout for (Bitu i=0; iuse_foreign_layout) { // switch to foreign layout this->use_foreign_layout=true; diacritics_character=0; LOG(LOG_BIOS,LOG_NORMAL)("Switched to layout %s",tbuf); } } else { keyboard_layout * temp_layout=new keyboard_layout(); Bitu req_codepage=temp_layout->extract_codepage(new_layout); Bitu kerrcode=temp_layout->read_keyboard_file(new_layout, req_codepage); if (kerrcode) { delete temp_layout; return kerrcode; } // ...else keyboard layout loaded successfully, change codepage accordingly kerrcode=temp_layout->read_codepage_file("auto", req_codepage); if (kerrcode) { delete temp_layout; return kerrcode; } // Everything went fine, switch to new layout created_layout=temp_layout; } } else if (this->use_foreign_layout) { // switch to the US layout this->use_foreign_layout=false; diacritics_character=0; LOG(LOG_BIOS,LOG_NORMAL)("Switched to US layout"); } return KEYB_NOERROR; } void keyboard_layout::switch_foreign_layout() { this->use_foreign_layout=!this->use_foreign_layout; diacritics_character=0; if (this->use_foreign_layout) LOG(LOG_BIOS,LOG_NORMAL)("Switched to foreign layout"); else LOG(LOG_BIOS,LOG_NORMAL)("Switched to US layout"); } static keyboard_layout* loaded_layout=NULL; // CTRL-ALT-F2 switches between foreign and US-layout using this function static void switch_keyboard_layout(bool pressed) { if (!pressed) return; if (loaded_layout) loaded_layout->switch_foreign_layout(); } // called by int9-handler bool DOS_LayoutKey(Bitu key, Bit8u flags1, Bit8u flags2, Bit8u flags3) { if (loaded_layout) return loaded_layout->layout_key(key, flags1, flags2, flags3); else return false; } Bitu DOS_LoadKeyboardLayout(const char * layoutname, Bit32s codepage, const char * codepagefile) { keyboard_layout * temp_layout=new keyboard_layout(); // try to read the layout for the specified codepage Bitu kerrcode=temp_layout->read_keyboard_file(layoutname, codepage); if (kerrcode) { delete temp_layout; return kerrcode; } // ...else keyboard layout loaded successfully, change codepage accordingly kerrcode=temp_layout->read_codepage_file(codepagefile, codepage); if (kerrcode) { delete temp_layout; return kerrcode; } // Everything went fine, switch to new layout loaded_layout=temp_layout; return KEYB_NOERROR; } Bitu DOS_SwitchKeyboardLayout(const char* new_layout) { if (loaded_layout) { keyboard_layout* changed_layout=NULL; Bitu ret_code=loaded_layout->switch_keyboard_layout(new_layout, changed_layout); if (changed_layout) { // Remove old layout, activate new layout delete loaded_layout; loaded_layout=changed_layout; } return ret_code; } else return 0xff; } class DOS_KeyboardLayout: public Module_base { public: DOS_KeyboardLayout(Section* configuration):Module_base(configuration){ Section_prop * section=static_cast(configuration); dos.loaded_codepage=437; // US codepage already initialized loaded_layout=new keyboard_layout(); const char * layoutname=section->Get_string("keyboardlayout"); // try to find a good codepage for the requested layout Bitu req_codepage=loaded_layout->extract_codepage(layoutname); loaded_layout->read_codepage_file("auto", req_codepage); if (loaded_layout->read_keyboard_file(layoutname, dos.loaded_codepage)) LOG_MSG("Error loading keyboard layout %s",layoutname); } ~DOS_KeyboardLayout(){ if ((dos.loaded_codepage!=437) && (CurMode->type==M_TEXT)) { INT10_ReloadRomFonts(); dos.loaded_codepage=437; // US codepage } if (loaded_layout) { delete loaded_layout; loaded_layout=NULL; } } }; static DOS_KeyboardLayout* test; void DOS_KeyboardLayout_ShutDown(Section* /*sec*/) { delete test; } void DOS_KeyboardLayout_Init(Section* sec) { test = new DOS_KeyboardLayout(sec); sec->AddDestroyFunction(&DOS_KeyboardLayout_ShutDown,true); // MAPPER_AddHandler(switch_keyboard_layout,MK_f2,MMOD1|MMOD2,"sw_layout","Switch Layout"); }