/* * Copyright 2009, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include "KeyboardLayout.h" #include #include #include #include #include #include #include #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 " = ". 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 // ",;" - 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 ":", where // is combined of the shape of the character, and its size, written as // ",[,]". // 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 "" specifier is only valid for enter keys, too. // The scancodes can be written in different ways: // 1) "+": adds keys, the scancodes will be used relative // to the previous keys. // 2) "-": keys are added for scancode to scancode // . // 3) "+": one key with the scancode is added, plus // 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; }