/*
* Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include "KeyboardLayout.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <File.h>
#include <InterfaceDefs.h>
#undef TRACE
//#define TRACE_LAYOUT
#ifdef TRACE_LAYOUT
# define TRACE(x...) printf(x)
#else
# define TRACE(x...)
#endif
KeyboardLayout::KeyboardLayout()
:
fKeys(NULL),
fKeyCount(0),
fKeyCapacity(0),
fIndicators(5, true),
fIsDefault(true)
{
SetDefault();
}
KeyboardLayout::~KeyboardLayout()
{
free(fKeys);
}
const char*
KeyboardLayout::Name()
{
return fName.String();
}
int32
KeyboardLayout::CountKeys()
{
return fKeyCount;
}
Key*
KeyboardLayout::KeyAt(int32 index)
{
if (index < 0 || index >= fKeyCount)
return NULL;
return &fKeys[index];
}
int32
KeyboardLayout::CountIndicators()
{
return fIndicators.CountItems();
}
Indicator*
KeyboardLayout::IndicatorAt(int32 index)
{
return fIndicators.ItemAt(index);
}
BRect
KeyboardLayout::Bounds()
{
return fBounds;
}
BSize
KeyboardLayout::DefaultKeySize()
{
return fDefaultKeySize;
}
int32
KeyboardLayout::IndexForModifier(int32 modifier)
{
switch (modifier) {
case B_CAPS_LOCK:
return 58;
case B_NUM_LOCK:
return 33;
case B_SCROLL_LOCK:
return 14;
case B_LEFT_SHIFT_KEY:
return 74;
case B_RIGHT_SHIFT_KEY:
return 85;
case B_LEFT_CONTROL_KEY:
return 91;
case B_RIGHT_CONTROL_KEY:
return 98;
case B_LEFT_OPTION_KEY:
return 92;
case B_RIGHT_OPTION_KEY:
return 96;
case B_LEFT_COMMAND_KEY:
return 93;
case B_RIGHT_COMMAND_KEY:
return 95;
case B_MENU_KEY:
return 97;
}
return 0;
}
status_t
KeyboardLayout::Load(const char* path)
{
entry_ref ref;
get_ref_for_path(path, &ref);
return Load(ref);
}
status_t
KeyboardLayout::Load(entry_ref& ref)
{
BFile file;
status_t status = file.SetTo(&ref, B_READ_ONLY);
if (status != B_OK)
return status;
off_t size;
status = file.GetSize(&size);
if (status != B_OK)
return status;
if (size > 65536) {
// We don't accept files larger than this
return B_BAD_VALUE;
}
char* data = (char*)malloc(size + 1);
if (data == NULL)
return B_NO_MEMORY;
ssize_t bytesRead = file.Read(data, size);
if (bytesRead != size) {
free(data);
if (bytesRead < 0)
return bytesRead;
return B_IO_ERROR;
}
data[size] = '\0';
status = _InitFrom(data);
if (status == B_OK)
fIsDefault = false;
free(data);
return status;
}
void
KeyboardLayout::SetDefault()
{
// The keyboard layout description language defines the position,
// size, shape, and scancodes of the keys on the keyboard, row by
// row.
// You can define variables that are substituted in the row
// descriptions. Variables are always prefixed with a '$' symbol.
// On top level, only two different terms are accepted: value pairs,
// and row descriptions.
// Value pairs are in the form "<name> = <value>". There are only two
// predefined names at this moment "name" that specifies the name of
// a layout, and "default-size" that specifies the size of keys when
// no specific size is given. Also, variables can be specified this
// way.
// Row descriptions are embedded in the '[' and ']' brackets.
// The first term within a row is the position of the row, written as
// "<x>,<y>;" - the delimiter between terms/keys is usually the
// semi-colon. After the initial position, key descriptions are expected
// until the row is closed with a ']'.
// A key description is of the form "<shape>:<scancodes>", where <shape>
// is combined of the shape of the character, and its size, written as
// "<kind><width>,<height>[,<second-row-width>]".
// The kind can either be 'r' (the default, can also be omitted) for
// rectangular keys, or 'l' for the enter key. Additionally, you can use
// the 'd' character to mark dark keys. The size can be omitted completely,
// in which case the defined "default-size" is used. The default size is
// also used to determine the height of the first row of the enter key;
// the "<second-row-width>" specifier is only valid for enter keys, too.
// The scancodes can be written in different ways:
// 1) "+<count>": adds <count> keys, the scancodes will be used relative
// to the previous keys.
// 2) "<first>-<last>": keys are added for scancode <first> to scancode
// <last>.
// 3) "<first>+<count>": one key with the <first> scancode is added, plus
// <count> ones with relative scancode from that one.
// Finally, you can also define LED indicator. Those can be made instead
// of a key, it accepts a size, but no shape modifiers. Also, instead of
// a scancode, the name of the modifer is given, currently only the
// following are allowed: "led-num", "led-caps", and "led-scroll".
// See for examples in the default layout below.
#if 1
static const char* kDefaultLayout105 =
"name = Generic 105-key International\n"
// Size shortcuts
"default-size = 10,10\n"
"$b = 5,10\n"
"$c = 20,10\n"
"$d = 15,10\n"
"$e = l15,20,9\n"
"$f = 10,20\n"
"$g = 13,10\n"
// Key rows
"[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; "
"$g:led-num; $g:led-caps; $g:led-scroll ]\n"
"[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n"
"[ 0,30; d$d:0x26; :+12; d$e:0x47; $b:-; d:0x34-0x36; $b:-; :+3; "
"d$f:+1 ]\n"
"[ 0,40; d19,10:0x3b; :+11; :0x33; 51,10:-; :0x48-0x4a ]\n"
"[ 0,50; d$g:0x4b; :0x69; :0x4c+9; d27,10:+1; 15,10:-; d:+1; "
" 15,10:-; :+3; d$f:+1 ]\n"
"[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; "
"d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n";
_InitFrom(kDefaultLayout105);
#endif
#if 0
static const char* kDefaultLayout104 = "name = Generic 104-key\n"
// Size shortcuts
"default-size = 10,10\n"
"$b = 5,10\n"
"$c = 20,10\n"
"$d = 15,10\n"
"$enter = d20,10\n"
"$f = 10,20\n"
"$g = 13,10\n"
// Key rows
"[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; "
"$g:led-num; $g:led-caps; $g:led-scroll ]\n"
"[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n"
"[ 0,30; d$d:0x26; :+12; $d:+1; $b:-; d:+3; $b:-; :+3; "
"d$f:+1 ]\n"
"[ 0,40; d$c:0x3b; :+11; $enter:+1; 40,10:-; :+3 ]\n"
"[ 0,50; d24,10:0x4b; :+10; d26,10:+1; 15,10:-; d:+1; "
" 15,10:-; :+3; d$f:+1 ]\n"
"[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; "
"d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n";
_InitFrom(kDefaultLayout104);
#endif
#if 0
static const char* kIBMLaptop = "name = IBM Laptop International\n"
// Size shortcuts
"default-size = 18,18\n"
"$s = 17,10\n"
"$gap = 6,10\n"
"$sgap = 5,10\n"
"$backspace = 38,18\n"
"$tab = 28,18\n"
"$caps = 32,18\n"
"$enter = l28,36,22\n"
"$l-shift-ctrl = 23,18\n"
"$r-shift = 51,18\n"
"$option = 13,18\n"
"$space = 95,18\n"
// Key rows
"[ 0,0; $s:0x01; 148,10:-; $s:0x0e+2; $sgap:-; $s:0x1f+2; ]\n"
"[ 0,10; $s:0x02+3; $gap:-; $s:+4; $gap:-; $s:+4; $sgap:-; "
"$s:0x34+2; ]\n"
"[ 0,20; :0x11+12; $backspace:+1 ]\n"
"[ 0,38; $tab:0x26; :+12; $enter:0x47; ]\n"
"[ 0,56; $caps:0x3b; :+11; :0x33 ]\n"
"[ 0,74; $l-shift-ctrl:0x4b; :0x69; :0x4c+9; $r-shift:+1 ]\n"
"[ 0,92; :0x99; $l-shift-ctrl:0x5c; $option:0x66; :0x5d; $space:+1; "
":+1; :0x68; :0x60; $s:0x9a; $s:0x57; $s:0x9b ]\n"
"[ 221,102; $s:0x61+2; ]\n";
_InitFrom(kIBMLaptop);
#endif
fIsDefault = true;
}
void
KeyboardLayout::_FreeKeys()
{
free(fKeys);
fKeys = NULL;
fKeyCount = 0;
fKeyCapacity = 0;
fBounds = BRect();
fIndicators.MakeEmpty();
}
void
KeyboardLayout::_Error(const parse_state& state, const char* reason, ...)
{
va_list args;
va_start(args, reason);
fprintf(stderr, "Syntax error in line %" B_PRId32 ": ", state.line);
vfprintf(stderr, reason, args);
fprintf(stderr, "\n");
va_end(args);
}
void
KeyboardLayout::_AddAlternateKeyCode(Key* key, int32 modifier, int32 code)
{
if (key == NULL)
return;
// TODO: search for free spot
}
bool
KeyboardLayout::_AddKey(const Key& key)
{
TRACE(" add %ld (%g,%g)\n", key.code, key.frame.left, key.frame.top);
if (fKeyCount + 1 > fKeyCapacity) {
// enlarge array
int32 newCapacity = fKeyCapacity + 32;
Key* newKeys = (Key*)realloc((void*)fKeys, newCapacity * sizeof(Key));
if (newKeys == NULL)
return false;
fKeys = newKeys;
fKeyCapacity = newCapacity;
}
fKeys[fKeyCount] = key;
fKeyCount++;
fBounds = key.frame | fBounds;
return true;
}
void
KeyboardLayout::_SkipCommentsAndSpace(parse_state& state, const char*& data)
{
while (data[0] != '\0') {
while (isspace(data[0])) {
if (data[0] == '\n')
state.line++;
data++;
}
if (data[0] == '#') {
// skip comment
while (data[0] != '\0' && data[0] != '\n') {
data++;
}
} else
break;
}
}
void
KeyboardLayout::_Trim(BString& string, bool stripComments)
{
// Strip leading spaces
int32 i = 0;
while (isspace(string[i])) {
i++;
}
if (i > 0)
string.Remove(0, i);
// Remove comments
if (stripComments) {
i = string.FindFirst('#');
if (i >= 0)
string.Truncate(i);
}
// Strip trailing spaces
i = string.Length() - 1;
while (i > 0 && isspace(string[i])) {
i--;
}
string.Truncate(i + 1);
}
bool
KeyboardLayout::_GetPair(const parse_state& state, const char*& data,
BString& name, BString& value)
{
// Get name
name = "";
while (data[0] != '\0' && data[0] != '=') {
name += data[0];
data++;
}
if (data[0] != '=') {
_Error(state, "no valid pair");
return false;
}
// Skip sign
data++;
// Get value
value = "";
while (data[0] != '\0' && data[0] != '\n') {
value += data[0];
data++;
}
_Trim(name, false);
_Trim(value, true);
return true;
}
bool
KeyboardLayout::_AddKeyCodes(const parse_state& state, BPoint& rowLeftTop,
Key& key, const char* data, int32& lastCount)
{
if (data[0] == '-') {
// no key, just free space
int32 num = strtoul(data + 1, NULL, 0);
if (num < 1)
num = 1;
else if (num > 32) {
_Error(state, "empty key count too large");
return false;
}
key.frame.OffsetTo(rowLeftTop);
rowLeftTop.x = key.frame.left + key.frame.Width() * num;
return true;
}
int32 modifier = 0;
if (isalpha(data[0])) {
bool led = false;
if (!strcmp("led-caps", data)) {
modifier = B_CAPS_LOCK;
led = true;
} else if (!strcmp("led-num", data)) {
modifier = B_NUM_LOCK;
led = true;
} else if (!strcmp("led-scroll", data)) {
modifier = B_SCROLL_LOCK;
led = true;
} else {
// TODO: get modifier (ie. "num")
}
if (led) {
key.frame.OffsetTo(rowLeftTop);
rowLeftTop.x = key.frame.right;
fBounds = key.frame | fBounds;
Indicator* indicator = new(std::nothrow) Indicator;
if (indicator != NULL) {
indicator->modifier = modifier;
indicator->frame = key.frame;
fIndicators.AddItem(indicator);
}
return true;
}
}
int32 first;
int32 last;
int32 num = 1;
if (data[0] == '+') {
num = strtoul(data + 1, NULL, 0);
if (num < 1)
num = 1;
else if (num > 32) {
_Error(state, "key count too large");
return false;
}
if (fKeyCount > 0)
first = fKeys[fKeyCount - 1].code + 1;
else
first = 1;
last = first + num - 1;
} else {
char* end;
first = strtoul(data, &end, 0);
last = first;
if (end[0] == '-') {
last = strtoul(end + 1, NULL, 0);
if (first > last) {
_Error(state, "invalid key code specifier");
return false;
}
num = last - first;
} else if (end[0] == '+') {
num = strtoul(end + 1, NULL, 0) + 1;
last = first + num - 1;
} else if (end[0] != '\0') {
_Error(state, "invalid key range");
return false;
}
}
if (lastCount != 0) {
// update existing keys
if (lastCount != num) {
_Error(state, "modifier key mismatch");
return false;
}
for (int32 i = fKeyCount - num; i < fKeyCount; i++, first++) {
Key* key = KeyAt(i);
_AddAlternateKeyCode(key, modifier, first);
}
} else {
// add new keys
for (int32 i = first; i <= last; i++) {
key.code = i;
// "layout"
key.frame.OffsetTo(rowLeftTop);
rowLeftTop.x = key.frame.right;
_AddKey(key);
}
lastCount = num;
}
return true;
}
bool
KeyboardLayout::_GetSize(const parse_state& state, const char* data,
float& x, float& y, float* _secondRow)
{
if (data[0] == '\0') {
// default size
x = fDefaultKeySize.width;
y = fDefaultKeySize.height;
return true;
}
float secondRow = 0;
int num = sscanf(data, "%g,%g,%g", &x, &y, &secondRow);
if (num < 2) {
_Error(state, "invalid size");
return false;
}
if (_secondRow != NULL)
*_secondRow = secondRow;
return true;
}
bool
KeyboardLayout::_GetShape(const parse_state& state, const char* data, Key& key)
{
// the default
key.shape = kRectangleKeyShape;
key.dark = false;
while (isalpha(data[0])) {
switch (tolower(data[0])) {
case 'r':
key.shape = kRectangleKeyShape;
break;
case 'c':
key.shape = kCircleKeyShape;
break;
case 'l':
key.shape = kEnterKeyShape;
break;
case 'd':
key.dark = true;
break;
default:
_Error(state, "unknown shape specifier '%c'", data[0]);
return false;
}
data++;
}
float width, height;
if (!_GetSize(state, data, width, height, &key.second_row))
return false;
// don't accept second row with anything but kEnterKeyShape
if ((key.shape != kEnterKeyShape && key.second_row != 0)
|| (key.shape == kEnterKeyShape && key.second_row == 0)) {
_Error(state, "shape size mismatch");
return false;
}
key.frame.left = 0;
key.frame.top = 0;
key.frame.right = width;
key.frame.bottom = height;
return true;
}
/*! Returns the term delimiter expected in a certain parse mode. */
const char*
KeyboardLayout::_Delimiter(parse_mode mode)
{
switch (mode) {
default:
case kSize:
return "";
case kRowStart:
return ";";
case kKeyShape:
return ":";
case kKeyCodes:
return ";:";
}
}
bool
KeyboardLayout::_GetTerm(const char*& data, const char* delimiter,
BString& term, bool closingBracketAllowed)
{
// Get term
term = "";
while (data[0] != '\0' && strchr(delimiter, data[0]) == NULL
&& data[0] != '\n' && data[0] != '#'
&& (!closingBracketAllowed || data[0] != ']')) {
term += data[0];
data++;
}
if (data[0] == '\0' && delimiter[0])
return false;
_Trim(term, true);
return true;
}
bool
KeyboardLayout::_SubstituteVariables(BString& term, VariableMap& variables,
BString& unknown)
{
while (true) {
int32 index = term.FindFirst('$');
if (index < 0)
break;
// find variable name
VariableMap::iterator iterator = variables.begin();
VariableMap::iterator best = variables.end();
int32 bestLength = 0;
for (; iterator != variables.end(); iterator++) {
const BString& name = iterator->first;
if (!term.CompareAt(index, name, name.Length())
&& name.Length() > bestLength) {
best = iterator;
bestLength = name.Length();
}
}
if (best != variables.end()) {
// got one, replace it
term.Remove(index, bestLength);
term.Insert(best->second.String(), index);
} else {
// variable has not been found
int32 length = 1;
while (isalpha(term[length + index])) {
length++;
}
term.Truncate(length + index);
return false;
}
}
return true;
}
bool
KeyboardLayout::_ParseTerm(const parse_state& state, const char*& data,
BString& term, VariableMap& variables)
{
if (!_GetTerm(data, _Delimiter(state.mode), term,
state.mode == kKeyCodes)) {
_Error(state, state.mode == kRowStart
? "no valid row start" : "invalid term");
return false;
}
BString unknown;
if (!_SubstituteVariables(term, variables, unknown)) {
_Error(state, "Unknown variable \"%s\"", unknown.String());
return false;
}
return true;
}
/*! Initializes the keyboard layout from the data given.
The string has to be a valid keyboard layout description, otherwise
an error is returned.
*/
status_t
KeyboardLayout::_InitFrom(const char* data)
{
_FreeKeys();
VariableMap variables;
BPoint rowLeftTop;
int32 lastKeyCount = 0;
Key key;
parse_state state = {kPairs, 1};
while (data[0] != '\0') {
_SkipCommentsAndSpace(state, data);
if (data[0] == '[') {
state.mode = kRowStart;
rowLeftTop = BPoint(0, 0);
data++;
continue;
} else if (data[0] == '\0')
break;
switch (state.mode) {
case kPairs:
{
BString name;
BString value;
if (!_GetPair(state, data, name, value))
return B_BAD_VALUE;
TRACE("<%s> = <%s>\n", name.String(), value.String());
if (name == "name")
fName = value;
else if (name == "default-size") {
const char* valueString = value.String();
parse_state tempState = {kSize, state.line};
BString term;
if (!_ParseTerm(tempState, valueString, term, variables))
return B_BAD_VALUE;
TRACE(" size = %s\n", term.String());
if (!_GetSize(state, term.String(), fDefaultKeySize.width,
fDefaultKeySize.height))
return B_BAD_VALUE;
} else if (name[0] == '$')
variables[name] = value;
break;
}
case kRowStart:
case kKeyShape:
case kKeyCodes:
{
if (data[0] == ']') {
if (state.mode == kKeyShape) {
state.mode = kPairs;
data++;
continue;
}
_Error(state, "unexpected row closing bracket");
return B_BAD_VALUE;
}
BString term;
if (!_ParseTerm(state, data, term, variables))
return B_BAD_VALUE;
switch (state.mode) {
case kRowStart:
if (!_GetSize(state, term.String(), rowLeftTop.x,
rowLeftTop.y))
return B_BAD_VALUE;
TRACE("row: %s (%g:%g)\n", term.String(), rowLeftTop.x,
rowLeftTop.y);
state.mode = kKeyShape;
break;
case kKeyShape:
memset((void*)&key, 0, sizeof(Key));
if (!_GetShape(state, term.String(), key))
return B_BAD_VALUE;
TRACE(" shape: %s (%g:%g:%g)\n", term.String(),
key.frame.Width(), key.frame.Height(),
key.second_row);
lastKeyCount = 0;
state.mode = kKeyCodes;
break;
case kKeyCodes:
TRACE(" raw key: %s\n", term.String());
if (!_AddKeyCodes(state, rowLeftTop, key, term.String(),
lastKeyCount))
return B_BAD_VALUE;
if (data[0] != ':')
state.mode = kKeyShape;
break;
default:
break;
}
if (data[0] != ']' && data[0] != '\0')
data++;
break;
}
default:
return B_BAD_VALUE;
}
}
return B_OK;
}
↑ V630 The 'realloc' function is used to allocate memory for an array of objects which are classes containing constructors.