/* FreeRDP: A Remote Desktop Protocol client. XKB-based Keyboard Mapping to Microsoft Keyboard System Copyright 2009 Marc-Andre Moreau Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include #include #include #include "config.h" #include "libkbd.h" #include "keyboard.h" #include "x_layout_id_table.h" #include "layouts_xkb.h" #ifdef WITH_XKBFILE #include #include #include int init_xkb(void *dpy) { return XkbQueryExtension(dpy, NULL, NULL, NULL, NULL, NULL); } /* return substring starting after nth comma, ending at following comma */ static char * comma_substring(char *s, int n) { char *p; if (!s) return ""; while (n-- > 0) { if (!(p = strchr(s, ','))) break; s = p + 1; } if ((p = strchr(s, ','))) *p = 0; return s; } unsigned int detect_keyboard_layout_from_xkb(void *dpy) { char *layout, *variant; unsigned int keyboard_layout = 0, group = 0; XkbRF_VarDefsRec rules_names; XKeyboardState coreKbdState; XkbStateRec state; DEBUG_KBD("display: %p", dpy); if (dpy && XkbRF_GetNamesProp(dpy, NULL, &rules_names)) { DEBUG_KBD("layouts: %s", rules_names.layout); DEBUG_KBD("variants: %s", rules_names.variant); XGetKeyboardControl(dpy, &coreKbdState); if (XkbGetState(dpy, XkbUseCoreKbd, &state) == Success) group = state.group; DEBUG_KBD("group: %d", state.group); layout = comma_substring(rules_names.layout, group); variant = comma_substring(rules_names.variant, group); DEBUG_KBD("layout: %s", layout); DEBUG_KBD("variant: %s", variant); keyboard_layout = find_keyboard_layout_in_xorg_rules(layout, variant); free(rules_names.model); free(rules_names.layout); free(rules_names.variant); free(rules_names.options); } return keyboard_layout; } int init_keycodes_from_xkb(void *dpy, RdpKeycodes x_keycode_to_rdp_keycode) { int ret = 0; XkbDescPtr xkb; if (dpy && (xkb = XkbGetMap(dpy, 0, XkbUseCoreKbd))) { if (XkbGetNames(dpy, XkbKeyNamesMask, xkb) == Success) { char buf[5] = {42, 42, 42, 42, 0}; /* end-of-string at pos 5 */ int i, j; memset(x_keycode_to_rdp_keycode, 0, sizeof(x_keycode_to_rdp_keycode)); for (i = xkb->min_key_code; i <= xkb->max_key_code; i++) { memcpy(buf, xkb->names->keys[i].name, 4); /* TODO: Use more efficient search ... but it is so fast that it doesn't matter */ j = sizeof(virtualKeyboard) / sizeof(virtualKeyboard[0]) - 1; while (j >= 0) { if (virtualKeyboard[j].x_keyname && !strcmp(buf, virtualKeyboard[j].x_keyname)) break; j--; } if (j >= 0) { DEBUG_KBD("X key code %3d has keyname %-4s -> RDP keycode %d/%d", i, buf, virtualKeyboard[j].extended, virtualKeyboard[j].scancode); x_keycode_to_rdp_keycode[i].extended = virtualKeyboard[j].extended; x_keycode_to_rdp_keycode[i].keycode = virtualKeyboard[j].scancode; #ifdef WITH_DEBUG_KBD x_keycode_to_rdp_keycode[i].keyname = virtualKeyboard[j].x_keyname; #endif } else DEBUG_KBD("X key code %3d has keyname %-4s -> ??? - not found", i, buf); } ret = 1; } XkbFreeKeyboard(xkb, 0, 1); } return ret; } #else /* Default built-in keymap */ static const KeycodeToVkcode defaultKeycodeToVkcode = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0xBD, 0xBB, 0x08, 0x09, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0xDB, 0xDD, 0x0D, 0xA2, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0xBA, 0xDE, 0xC0, 0xA0, 0x00, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0xBC, 0xBE, 0xBF, 0xA1, 0x6A, 0x12, 0x20, 0x14, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x90, 0x91, 0x67, 0x68, 0x69, 0x6D, 0x64, 0x65, 0x66, 0x6B, 0x61, 0x62, 0x63, 0x60, 0x6E, 0x00, 0x00, 0x00, 0x7A, 0x7B, 0x24, 0x26, 0x21, 0x25, 0x00, 0x27, 0x23, 0x28, 0x22, 0x2D, 0x2E, 0x0D, 0xA3, 0x13, 0x2C, 0x6F, 0x12, 0x00, 0x5B, 0x5C, 0x5D, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) { char* pch; char *beg, *end; char* home; char buffer[1024] = ""; char xkbfile[256] = ""; char xkbfilepath[512] = ""; char xkbmap[256] = ""; char xkbinc[256] = ""; FILE* fp; int kbdFound = 0; int i = 0; int keycode = 0; char keycodeString[32] = ""; char vkcodeName[128] = ""; beg = kbd; /* Extract file name and keymap name */ if((end = strrchr(kbd, '(')) != NULL) { strncpy(xkbfile, &kbd[beg - kbd], end - beg); beg = end + 1; if((end = strrchr(kbd, ')')) != NULL) { strncpy(xkbmap, &kbd[beg - kbd], end - beg); xkbmap[end - beg] = '\0'; } } else { /* The keyboard name is the same as the file name */ strcpy(xkbfile, kbd); strcpy(xkbmap, kbd); } /* Get path to file relative to freerdp's directory */ snprintf(xkbfilepath, sizeof(xkbfilepath), "keymaps/%s", xkbfile); DEBUG_KBD("Loading keymap %s, first trying %s", kbd, xkbfilepath); /* * Open the file for reading only * It can happen that the same file is opened twice at the same time * in order to load multiple keyboard maps from the same file, but * it does not matter: files can be opened as many times as we want * when it is for reading only. */ if((fp = fopen(xkbfilepath, "r")) == NULL) { /* Look first in path given at compile time (install path) */ snprintf(xkbfilepath, sizeof(xkbfilepath), "%s/%s", KEYMAP_PATH, xkbfile); if((fp = fopen(xkbfilepath, "r")) == NULL) { /* If ran from the source tree, the keymaps will be in the parent directory */ snprintf(xkbfilepath, sizeof(xkbfilepath), "../keymaps/%s", xkbfile); if((fp = fopen(xkbfilepath, "r")) == NULL) { /* File wasn't found in the source tree, try ~/.freerdp/ folder */ if((home = getenv("HOME")) == NULL) return 0; /* Get path to file in ~/.freerdp/ folder */ snprintf(xkbfilepath, sizeof(xkbfilepath), "%s/.freerdp/keymaps/%s", home, xkbfile); if((fp = fopen(xkbfilepath, "r")) == NULL) { /* Try /usr/share/freerdp folder */ snprintf(xkbfilepath, sizeof(xkbfilepath), "/usr/share/freerdp/keymaps/%s", xkbfile); if((fp = fopen(xkbfilepath, "r")) == NULL) { /* Try /usr/local/share/freerdp folder */ snprintf(xkbfilepath, sizeof(xkbfilepath), "/usr/local/share/freerdp/keymaps/%s", xkbfile); if((fp = fopen(xkbfilepath, "r")) == NULL) { /* Error: Could not find keymap */ DEBUG_KBD("keymaps for %s not found", xkbfile); return 0; } } } } } } DEBUG_KBD("xkbfilepath: %s", xkbfilepath); while(fgets(buffer, sizeof(buffer), fp) != NULL) { if(buffer[0] == '#') { continue; /* Skip comments */ } if(kbdFound) { /* Closing curly bracket and semicolon */ if((pch = strstr(buffer, "};")) != NULL) { break; } else if((pch = strstr(buffer, "VK_")) != NULL) { /* The end is delimited by the first white space */ end = strcspn(pch, " \t\n\0") + pch; /* We copy the virtual key code name in a string */ beg = pch; strncpy(vkcodeName, beg, end - beg); vkcodeName[end - beg] = '\0'; /* Now we want to extract the virtual key code itself which is in between '<' and '>' */ if((beg = strchr(pch + 3, '<')) == NULL) break; else beg++; if((end = strchr(beg, '>')) == NULL) break; /* We copy the string representing the number in a string */ strncpy(keycodeString, beg, end - beg); keycodeString[end - beg] = '\0'; /* Convert the string representing the code to an integer */ keycode = atoi(keycodeString); /* Make sure it is a valid keycode */ if(keycode < 0 || keycode > 255) break; /* Load this key mapping in the keyboard mapping */ for(i = 0; i < sizeof(virtualKeyboard) / sizeof(virtualKey); i++) { if(strcmp(vkcodeName, virtualKeyboard[i].name) == 0) { map[keycode] = i; } } } else if((pch = strstr(buffer, ": extends")) != NULL) { /* * This map extends another keymap We extract its name * and we recursively load the keymap we need to include. */ if((beg = strchr(pch + sizeof(": extends"), '"')) == NULL) break; beg++; if((end = strchr(beg, '"')) == NULL) break; strncpy(xkbinc, beg, end - beg); xkbinc[end - beg] = '\0'; load_xkb_keyboard(map, xkbinc); /* Load included keymap */ } } else if((pch = strstr(buffer, "keyboard")) != NULL) { /* Keyboard map identifier */ if((beg = strchr(pch + sizeof("keyboard"), '"')) == NULL) break; beg++; if((end = strchr(beg, '"')) == NULL) break; pch = beg; buffer[end - beg] = '\0'; /* Does it match our keymap name? */ if(strncmp(xkbmap, pch, strlen(xkbmap)) == 0) kbdFound = 1; } } fclose(fp); /* Don't forget to close file */ return 1; } void load_keyboard_map(KeycodeToVkcode keycodeToVkcode, char *xkbfile) { char* kbd; char* xkbfileEnd; int keymapLoaded = 0; memset(keycodeToVkcode, 0, sizeof(keycodeToVkcode)); kbd = xkbfile; xkbfileEnd = xkbfile + strlen(xkbfile); #ifdef __APPLE__ /* Apple X11 breaks XKB detection */ keymapLoaded += load_xkb_keyboard(keycodeToVkcode, "macosx(macosx)"); #else do { /* Multiple maps are separated by '+' */ int kbdlen = strcspn(kbd + 1, "+") + 1; kbd[kbdlen] = '\0'; /* Load keyboard map */ keymapLoaded += load_xkb_keyboard(keycodeToVkcode, kbd); kbd += kbdlen + 1; } while (kbd < xkbfileEnd); #endif DEBUG_KBD("loaded %d keymaps", keymapLoaded); if(keymapLoaded <= 0) { /* No keymap was loaded, load default hard-coded keymap */ DEBUG_KBD("using default keymap"); memcpy(keycodeToVkcode, defaultKeycodeToVkcode, sizeof(keycodeToVkcode)); } } #endif