FreeRDP/libfreerdp-kbd/layouts_xkb.c
2011-08-15 14:33:04 -04:00

398 lines
11 KiB
C

/**
* FreeRDP: A Remote Desktop Protocol Client
* XKB-based Keyboard Mapping to Microsoft Keyboard System
*
* Copyright 2009 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libkbd.h"
#include <freerdp/kbd/vkcodes.h>
#include "x_layout_id_table.h"
#include "layouts_xkb.h"
#ifndef KEYMAP_PATH
#define KEYMAP_PATH "/usr/local/freerdp/keymaps"
#endif
#ifdef WITH_XKBFILE
#include <X11/XKBlib.h>
#include <X11/extensions/XKBfile.h>
#include <X11/extensions/XKBrules.h>
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