diff --git a/src/add-ons/input_server/devices/virtualkeyboard/Jamfile b/src/add-ons/input_server/devices/virtualkeyboard/Jamfile new file mode 100644 index 0000000000..661c3e3974 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/Jamfile @@ -0,0 +1,26 @@ +SubDir HAIKU_TOP src apps virtualkeyboard ; + +UsePrivateHeaders interface shared ; + +Application VirtualKeyboard : + KeyboardLayout.cpp + KeyboardLayoutView.cpp + Keymap.cpp + KeymapListItem.cpp + VirtualKeyboardApp.cpp + VirtualKeyboardWindow.cpp + + : be tracker localestub libshared.a $(TARGET_LIBSTDC++) + : VirtualKeyboard.rdef +; + +DoCatalogs Sudoku : + x-vnd.Haiku-Sudoku + : + KeyboardLayout.cpp + KeyboardLayoutView.cpp + Keymap.cpp + KeymapListItem.cpp + VirtualKeyboardApp.cpp + VirtualKeyboardWindow.cpp +; diff --git a/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayout.cpp b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayout.cpp new file mode 100644 index 0000000000..d2942011f7 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayout.cpp @@ -0,0 +1,883 @@ +/* + * 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(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 (!name.Compare(&term[index], 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 + unknown = &term[index]; + int32 length = 1; + while (isalpha(unknown[length])) { + length++; + } + unknown.Truncate(length); + 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(&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; +} + diff --git a/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayout.h b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayout.h new file mode 100644 index 0000000000..985907c133 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayout.h @@ -0,0 +1,119 @@ +/* + * Copyright 2009, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef KEYBOARD_LAYOUT_H +#define KEYBOARD_LAYOUT_H + + +#include + +#include +#include +#include +#include +#include + + +enum key_shape { + kRectangleKeyShape, + kCircleKeyShape, + kEnterKeyShape +}; + +struct Key { + int32 code; + int32 alternate_code[3]; + int32 alternate_modifier[3]; + key_shape shape; + BRect frame; + float second_row; + // this is the width of the second row of a kEnterKeyShape key + bool dark; +}; + +struct Indicator { + int32 modifier; + BRect frame; +}; + +typedef std::map VariableMap; + +class KeyboardLayout { +public: + KeyboardLayout(); + ~KeyboardLayout(); + + const char* Name(); + + int32 CountKeys(); + Key* KeyAt(int32 index); + + int32 CountIndicators(); + Indicator* IndicatorAt(int32 index); + + BRect Bounds(); + BSize DefaultKeySize(); + int32 IndexForModifier(int32 modifier); + + status_t Load(const char* path); + status_t Load(entry_ref& ref); + + void SetDefault(); + bool IsDefault() const { return fIsDefault; } + +private: + enum parse_mode { + kPairs, + kSize, + kRowStart, + kKeyShape, + kKeyCodes + }; + struct parse_state { + parse_mode mode; + int32 line; + }; + + void _FreeKeys(); + void _Error(const parse_state& state, + const char* reason, ...); + void _AddAlternateKeyCode(Key* key, int32 modifier, + int32 code); + bool _AddKey(const Key& key); + void _SkipCommentsAndSpace(parse_state& state, + const char*& data); + void _Trim(BString& string, bool stripComments); + bool _GetPair(const parse_state& state, + const char*& data, BString& name, + BString& value); + bool _AddKeyCodes(const parse_state& state, + BPoint& rowLeftTop, Key& key, const char* data, + int32& lastCount); + bool _GetSize(const parse_state& state, const char* data, + float& x, float& y, float* _secondRow = NULL); + bool _GetShape(const parse_state& state, + const char* data, Key& key); + const char* _Delimiter(parse_mode mode); + bool _GetTerm(const char*& data, const char* delimiter, + BString& term, bool closingBracketAllowed); + bool _SubstituteVariables(BString& term, + VariableMap& variables, BString& unknown); + bool _ParseTerm(const parse_state& state, + const char*& data, BString& term, + VariableMap& variables); + + status_t _InitFrom(const char* data); + + BString fName; + Key* fKeys; + int32 fKeyCount; + int32 fKeyCapacity; + BRect fBounds; + BSize fDefaultKeySize; + int32 fAlternateIndex[3]; + BObjectList fIndicators; + bool fIsDefault; +}; + +#endif // KEYBOARD_LAYOUT_H diff --git a/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayoutView.cpp b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayoutView.cpp new file mode 100644 index 0000000000..5a8fa79bc8 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayoutView.cpp @@ -0,0 +1,1170 @@ +/* + * Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ + + +#include "KeyboardLayoutView.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Keymap.h" + + +static const rgb_color kBrightColor = {230, 230, 230, 255}; +static const rgb_color kDarkColor = {200, 200, 200, 255}; +static const rgb_color kSecondDeadKeyColor = {240, 240, 150, 255}; +static const rgb_color kDeadKeyColor = {152, 203, 255, 255}; +static const rgb_color kLitIndicatorColor = {116, 212, 83, 255}; + + +KeyboardLayoutView::KeyboardLayoutView(const char* name) + : + BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), + fOffscreenBitmap(NULL), + fKeymap(NULL), + fEditable(true), + fModifiers(0), + fDeadKey(0), + fButtons(0), + fDragKey(NULL), + fDropTarget(NULL), + fOldSize(0, 0) +{ + fLayout = new KeyboardLayout; + memset(fKeyState, 0, sizeof(fKeyState)); + + SetEventMask(B_KEYBOARD_EVENTS); +} + + +KeyboardLayoutView::~KeyboardLayoutView() +{ + delete fOffscreenBitmap; +} + + +void +KeyboardLayoutView::SetKeyboardLayout(KeyboardLayout* layout) +{ + fLayout = layout; + _InitOffscreen(); + _LayoutKeyboard(); + Invalidate(); +} + + +void +KeyboardLayoutView::SetKeymap(Keymap* keymap) +{ + fKeymap = keymap; + Invalidate(); +} + + +void +KeyboardLayoutView::SetTarget(BMessenger target) +{ + fTarget = target; +} + + +void +KeyboardLayoutView::SetBaseFont(const BFont& font) +{ + fBaseFont = font; + + font_height fontHeight; + fBaseFont.GetHeight(&fontHeight); + fBaseFontHeight = fontHeight.ascent + fontHeight.descent; + fBaseFontSize = fBaseFont.Size(); + + Invalidate(); +} + + +void +KeyboardLayoutView::AttachedToWindow() +{ + SetViewColor(B_TRANSPARENT_COLOR); + + SetBaseFont(*be_plain_font); + fSpecialFont = *be_fixed_font; + fModifiers = modifiers(); +} + + +void +KeyboardLayoutView::FrameResized(float width, float height) +{ + _InitOffscreen(); + _LayoutKeyboard(); +} + + +void +KeyboardLayoutView::WindowActivated(bool active) +{ + if (active) + Invalidate(); +} + + +BSize +KeyboardLayoutView::MinSize() +{ + return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(100, 50)); +} + + +void +KeyboardLayoutView::KeyDown(const char* bytes, int32 numBytes) +{ + _KeyChanged(Window()->CurrentMessage()); +} + + +void +KeyboardLayoutView::KeyUp(const char* bytes, int32 numBytes) +{ + _KeyChanged(Window()->CurrentMessage()); +} + + +void +KeyboardLayoutView::MouseDown(BPoint point) +{ + fClickPoint = point; + fDragKey = NULL; + fDropPoint.x = -1; + + Key* key = _KeyAt(point); + if (key == NULL) + return; + + int32 buttons = 0; + if (Looper() != NULL && Looper()->CurrentMessage() != NULL) + Looper()->CurrentMessage()->FindInt32("buttons", &buttons); + + if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0 + && (fButtons & B_TERTIARY_MOUSE_BUTTON) == 0) { + // toggle the "deadness" of dead keys via middle mouse button + if (fKeymap != NULL) { + bool isEnabled = false; + uint8 deadKey + = fKeymap->DeadKey(key->code, fModifiers, &isEnabled); + if (deadKey > 0) { + fKeymap->SetDeadKeyEnabled(key->code, fModifiers, !isEnabled); + _InvalidateKey(key); + } + } + } else { + if (fKeymap != NULL && fKeymap->IsModifierKey(key->code)) { + if (_KeyState(key->code)) { + uint32 modifier = fKeymap->Modifier(key->code); + if ((modifier & modifiers()) == 0) { + _SetKeyState(key->code, false); + fModifiers &= ~modifier; + Invalidate(); + } + } else { + _SetKeyState(key->code, true); + fModifiers |= fKeymap->Modifier(key->code); + Invalidate(); + } + + // TODO: if possible, we could handle the lock keys for real + } else { + _SetKeyState(key->code, true); + _InvalidateKey(key); + } + } + + fButtons = buttons; +} + + +void +KeyboardLayoutView::MouseUp(BPoint point) +{ + Key* key = _KeyAt(fClickPoint); + + int32 buttons = 0; + if (Looper() != NULL && Looper()->CurrentMessage() != NULL) + Looper()->CurrentMessage()->FindInt32("buttons", &buttons); + + if (key != NULL) { + if ((fButtons & B_TERTIARY_MOUSE_BUTTON) != 0 + && (buttons & B_TERTIARY_MOUSE_BUTTON) == 0) { + _SetKeyState(key->code, false); + _InvalidateKey(key); + fButtons = buttons; + } else { + fButtons = buttons; + + // modifier keys are sticky when used with the mouse + if (fKeymap != NULL && fKeymap->IsModifierKey(key->code)) + return; + + _SetKeyState(key->code, false); + + if (_HandleDeadKey(key->code, fModifiers) && fDeadKey != 0) + return; + + _InvalidateKey(key); + + if (fDragKey == NULL && fKeymap != NULL) { + // Send fake key down message to target + _SendFakeKeyDown(key); + } + } + } + fDragKey = NULL; +} + + +void +KeyboardLayoutView::MouseMoved(BPoint point, uint32 transit, + const BMessage* dragMessage) +{ + if (fKeymap == NULL) + return; + + // prevent dragging for tertiary mouse button + if ((fButtons & B_TERTIARY_MOUSE_BUTTON) != 0) + return; + + if (dragMessage != NULL) { + if (fEditable) { + _InvalidateKey(fDropTarget); + fDropPoint = point; + + _EvaluateDropTarget(point); + } + + return; + } + + int32 buttons; + if (Window()->CurrentMessage() == NULL + || Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK + || buttons == 0) + return; + + if (fDragKey == NULL && (fabs(point.x - fClickPoint.x) > 4 + || fabs(point.y - fClickPoint.y) > 4)) { + // start dragging + Key* key = _KeyAt(fClickPoint); + if (key == NULL) + return; + + BRect frame = _FrameFor(key); + BPoint offset = fClickPoint - frame.LeftTop(); + frame.OffsetTo(B_ORIGIN); + + BRect rect = frame; + rect.right--; + rect.bottom--; + BBitmap* bitmap = new BBitmap(rect, B_RGBA32, true); + bitmap->Lock(); + + BView* view = new BView(rect, "drag", B_FOLLOW_NONE, 0); + bitmap->AddChild(view); + + view->SetHighColor(0, 0, 0, 0); + view->FillRect(view->Bounds()); + view->SetDrawingMode(B_OP_ALPHA); + view->SetHighColor(0, 0, 0, 128); + // set the level of transparency by value + view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); + _DrawKey(view, frame, key, frame, false); + + view->Sync(); + bitmap->Unlock(); + + BMessage drag(B_MIME_DATA); + drag.AddInt32("key", key->code); + + char* string; + int32 numBytes; + fKeymap->GetChars(key->code, fModifiers, fDeadKey, &string, + &numBytes); + if (string != NULL) { + drag.AddData("text/plain", B_MIME_DATA, string, numBytes); + delete[] string; + } + + DragMessage(&drag, bitmap, B_OP_ALPHA, offset); + fDragKey = key; + fDragModifiers = fModifiers; + + fKeyState[key->code / 8] &= ~(1 << (7 - (key->code & 7))); + _InvalidateKey(key); + } +} + + +void +KeyboardLayoutView::Draw(BRect updateRect) +{ + if (fOldSize != BSize(Bounds().Width(), Bounds().Height())) { + _InitOffscreen(); + _LayoutKeyboard(); + } + + BView* view; + if (fOffscreenBitmap != NULL) { + view = fOffscreenView; + view->LockLooper(); + } else + view = this; + + // Draw background + + if (Parent()) + view->SetLowColor(Parent()->ViewColor()); + else + view->SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + view->FillRect(updateRect, B_SOLID_LOW); + + // Draw keys + + for (int32 i = 0; i < fLayout->CountKeys(); i++) { + Key* key = fLayout->KeyAt(i); + + _DrawKey(view, updateRect, key, _FrameFor(key), + _IsKeyPressed(key->code)); + } + + // Draw LED indicators + + for (int32 i = 0; i < fLayout->CountIndicators(); i++) { + Indicator* indicator = fLayout->IndicatorAt(i); + + _DrawIndicator(view, updateRect, indicator, _FrameFor(indicator->frame), + (fModifiers & indicator->modifier) != 0); + } + + if (fOffscreenBitmap != NULL) { + view->Sync(); + view->UnlockLooper(); + + DrawBitmapAsync(fOffscreenBitmap, BPoint(0, 0)); + } +} + + +void +KeyboardLayoutView::MessageReceived(BMessage* message) +{ + if (message->WasDropped() && fEditable && fDropTarget != NULL + && fKeymap != NULL) { + int32 keyCode; + const char* data; + ssize_t size; + if (message->FindData("text/plain", B_MIME_DATA, + (const void**)&data, &size) == B_OK) { + // Automatically convert UTF-8 escaped strings (for example from + // CharacterMap) + int32 dataSize = 0; + uint8 buffer[16]; + if (size > 3 && data[0] == '\\' && data[1] == 'x') { + char tempBuffer[16]; + if (size > 15) + size = 15; + memcpy(tempBuffer, data, size); + tempBuffer[size] = '\0'; + data = tempBuffer; + + while (size > 3 && data[0] == '\\' && data[1] == 'x') { + buffer[dataSize++] = strtoul(&data[2], NULL, 16); + if ((buffer[dataSize - 1] & 0x80) == 0) + break; + + size -= 4; + data += 4; + } + data = (const char*)buffer; + } else if ((data[0] & 0xc0) != 0x80 && (data[0] & 0x80) != 0) { + // only accept the first character UTF-8 character + while (dataSize < size && (data[dataSize] & 0x80) != 0) { + dataSize++; + } + } else if ((data[0] & 0x80) == 0) { + // an ASCII character + dataSize = 1; + } else { + // no valid character + beep(); + return; + } + + int32 buttons; + if (!message->IsSourceRemote() + && message->FindInt32("buttons", &buttons) == B_OK + && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0 + && message->FindInt32("key", &keyCode) == B_OK) { + // switch keys if the dropped object came from us + Key* key = _KeyForCode(keyCode); + if (key == NULL + || (key == fDropTarget && fDragModifiers == fModifiers)) + return; + + char* string; + int32 numBytes; + fKeymap->GetChars(fDropTarget->code, fModifiers, fDeadKey, + &string, &numBytes); + if (string != NULL) { + // switch keys + fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey, + (const char*)data, dataSize); + fKeymap->SetKey(key->code, fDragModifiers, fDeadKey, + string, numBytes); + delete[] string; + } else if (fKeymap->IsModifierKey(fDropTarget->code)) { + // switch key with modifier + fKeymap->SetModifier(key->code, + fKeymap->Modifier(fDropTarget->code)); + fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey, + (const char*)data, dataSize); + } + } else { + // Send the old key to the target, so it's not lost entirely + _SendFakeKeyDown(fDropTarget); + + fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey, + (const char*)data, dataSize); + } + } else if (!message->IsSourceRemote() + && message->FindInt32("key", &keyCode) == B_OK) { + // Switch an unmapped key + + Key* key = _KeyForCode(keyCode); + if (key != NULL && key == fDropTarget) + return; + + uint32 modifier = fKeymap->Modifier(keyCode); + + char* string; + int32 numBytes; + fKeymap->GetChars(fDropTarget->code, fModifiers, fDeadKey, + &string, &numBytes); + if (string != NULL) { + // switch key with modifier + fKeymap->SetModifier(fDropTarget->code, modifier); + fKeymap->SetKey(keyCode, fDragModifiers, fDeadKey, + string, numBytes); + delete[] string; + } else { + // switch modifier keys + fKeymap->SetModifier(keyCode, + fKeymap->Modifier(fDropTarget->code)); + fKeymap->SetModifier(fDropTarget->code, modifier); + } + + _InvalidateKey(fDragKey); + } + + _InvalidateKey(fDropTarget); + fDropTarget = NULL; + fDropPoint.x = -1; + return; + } + + switch (message->what) { + case B_UNMAPPED_KEY_DOWN: + case B_UNMAPPED_KEY_UP: + _KeyChanged(message); + break; + + case B_MODIFIERS_CHANGED: + { + int32 newModifiers; + if (message->FindInt32("modifiers", &newModifiers) == B_OK + && fModifiers != newModifiers) { + fModifiers = newModifiers; + _EvaluateDropTarget(fDropPoint); + if (Window()->IsActive()) + Invalidate(); + } + break; + } + + default: + BView::MessageReceived(message); + break; + } +} + + +void +KeyboardLayoutView::_InitOffscreen() +{ + delete fOffscreenBitmap; + fOffscreenView = NULL; + + fOffscreenBitmap = new(std::nothrow) BBitmap(Bounds(), + B_BITMAP_ACCEPTS_VIEWS, B_RGB32); + if (fOffscreenBitmap != NULL && fOffscreenBitmap->IsValid()) { + fOffscreenBitmap->Lock(); + fOffscreenView = new(std::nothrow) BView(Bounds(), "offscreen view", + 0, 0); + if (fOffscreenView != NULL) { + if (Parent() != NULL) { + fOffscreenView->SetViewColor(Parent()->ViewColor()); + } else { + fOffscreenView->SetViewColor( + ui_color(B_PANEL_BACKGROUND_COLOR)); + } + + fOffscreenView->SetLowColor(fOffscreenView->ViewColor()); + fOffscreenBitmap->AddChild(fOffscreenView); + } + fOffscreenBitmap->Unlock(); + } + + if (fOffscreenView == NULL) { + // something went wrong + delete fOffscreenBitmap; + fOffscreenBitmap = NULL; + } +} + + +void +KeyboardLayoutView::_LayoutKeyboard() +{ + float factorX = Bounds().Width() / fLayout->Bounds().Width(); + float factorY = Bounds().Height() / fLayout->Bounds().Height(); + + fFactor = min_c(factorX, factorY); + fOffset = BPoint((Bounds().Width() - fLayout->Bounds().Width() + * fFactor) / 2, + (Bounds().Height() - fLayout->Bounds().Height() * fFactor) / 2); + + if (fLayout->DefaultKeySize().width < 11) + fGap = 1; + else + fGap = 2; + + fOldSize.width = Bounds().Width(); + fOldSize.height = Bounds().Height(); +} + + +void +KeyboardLayoutView::_DrawKeyButton(BView* view, BRect& rect, BRect updateRect, + rgb_color base, rgb_color background, bool pressed) +{ + be_control_look->DrawButtonFrame(view, rect, updateRect, 4.0f, base, + background, pressed ? BControlLook::B_ACTIVATED : 0); + be_control_look->DrawButtonBackground(view, rect, updateRect, 4.0f, + base, pressed ? BControlLook::B_ACTIVATED : 0); +} + + +void +KeyboardLayoutView::_DrawKey(BView* view, BRect updateRect, const Key* key, + BRect rect, bool pressed) +{ + rgb_color base = key->dark ? kDarkColor : kBrightColor; + rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR); + key_kind keyKind = kNormalKey; + int32 deadKey = 0; + bool secondDeadKey = false; + bool isDeadKeyEnabled = true; + + char text[32]; + if (fKeymap != NULL) { + _GetKeyLabel(key, text, sizeof(text), keyKind); + deadKey = fKeymap->DeadKey(key->code, fModifiers, &isDeadKeyEnabled); + secondDeadKey = fKeymap->IsDeadSecondKey(key->code, fModifiers, + fDeadKey); + } else { + // Show the key code if there is no keymap + snprintf(text, sizeof(text), "%02" B_PRIx32, key->code); + } + + _SetFontSize(view, keyKind); + + if (secondDeadKey) + base = kSecondDeadKeyColor; + else if (deadKey > 0 && isDeadKeyEnabled) + base = kDeadKeyColor; + + if (key->shape == kRectangleKeyShape) { + _DrawKeyButton(view, rect, updateRect, base, background, pressed); + + rect.InsetBy(1, 1); + + _GetAbbreviatedKeyLabelIfNeeded(view, rect, key, text, sizeof(text)); + be_control_look->DrawLabel(view, text, rect, updateRect, + base, 0, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE)); + } else if (key->shape == kEnterKeyShape) { + BRect topLeft = rect; + BRect topRight = rect; + BRect bottomLeft = rect; + BRect bottomRight = rect; + + // TODO: for some reason, this does not always equal the bottom of + // the other keys... + bottomLeft.top = floorf(rect.top + + fLayout->DefaultKeySize().height * fFactor - fGap - 1); + bottomLeft.right = floorf(rect.left + + (key->frame.Width() - key->second_row) * fFactor - fGap - 2); + + topLeft.bottom = bottomLeft.top; + topLeft.right = bottomLeft.right + 1; + // add one to make the borders meet + + topRight.bottom = topLeft.bottom; + topRight.left = topLeft.right; + + bottomRight.top = bottomLeft.top; + bottomRight.left = bottomLeft.right; + + // draw top left corner + be_control_look->DrawButtonFrame(view, topLeft, updateRect, + 4.0f, 0.0f, 4.0f, 0.0f, base, background, + pressed ? BControlLook::B_ACTIVATED : 0, + BControlLook::B_LEFT_BORDER | BControlLook::B_TOP_BORDER + | BControlLook::B_BOTTOM_BORDER); + be_control_look->DrawButtonBackground(view, topLeft, updateRect, + 4.0f, 0.0f, 4.0f, 0.0f, base, + pressed ? BControlLook::B_ACTIVATED : 0, + BControlLook::B_LEFT_BORDER | BControlLook::B_TOP_BORDER + | BControlLook::B_BOTTOM_BORDER); + + // draw top right corner + be_control_look->DrawButtonFrame(view, topRight, updateRect, + 0.0f, 4.0f, 0.0f, 0.0f, base, background, + pressed ? BControlLook::B_ACTIVATED : 0, + BControlLook::B_TOP_BORDER | BControlLook::B_RIGHT_BORDER); + be_control_look->DrawButtonBackground(view, topRight, updateRect, + 0.0f, 4.0f, 0.0f, 0.0f, base, + pressed ? BControlLook::B_ACTIVATED : 0, + BControlLook::B_TOP_BORDER | BControlLook::B_RIGHT_BORDER); + + // draw bottom right corner + be_control_look->DrawButtonFrame(view, bottomRight, updateRect, + 0.0f, 0.0f, 4.0f, 4.0f, base, background, + pressed ? BControlLook::B_ACTIVATED : 0, + BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER + | BControlLook::B_BOTTOM_BORDER); + be_control_look->DrawButtonBackground(view, bottomRight, updateRect, + 0.0f, 0.0f, 4.0f, 4.0f, base, + pressed ? BControlLook::B_ACTIVATED : 0, + BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER + | BControlLook::B_BOTTOM_BORDER); + + // clip out the bottom left corner + bottomLeft.right += 1; + bottomLeft.top -= 2; + BRegion region(rect); + region.Exclude(bottomLeft); + view->ConstrainClippingRegion(®ion); + + // Fill in the rect with the background color + SetHighColor(background); + FillRect(rect); + + // draw the button background + BRect bgRect = rect.InsetByCopy(2, 2); + be_control_look->DrawButtonBackground(view, bgRect, updateRect, + 4.0f, 4.0f, 0.0f, 4.0f, base, + pressed ? BControlLook::B_ACTIVATED : 0); + + rect.left = bottomLeft.right; + _GetAbbreviatedKeyLabelIfNeeded(view, rect, key, text, sizeof(text)); + + // draw the button label + be_control_look->DrawLabel(view, text, rect, updateRect, + base, 0, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE)); + + // reset the clipping region + view->ConstrainClippingRegion(NULL); + } +} + + +void +KeyboardLayoutView::_DrawIndicator(BView* view, BRect updateRect, + const Indicator* indicator, BRect rect, bool lit) +{ + float rectTop = rect.top; + rect.top += 2 * rect.Height() / 3; + + const char* label = NULL; + if (indicator->modifier == B_CAPS_LOCK) + label = "caps"; + else if (indicator->modifier == B_NUM_LOCK) + label = "num"; + else if (indicator->modifier == B_SCROLL_LOCK) + label = "scroll"; + if (label != NULL) { + _SetFontSize(view, kIndicator); + + font_height fontHeight; + GetFontHeight(&fontHeight); + if (ceilf(rect.top - fontHeight.ascent + fontHeight.descent - 2) + >= rectTop) { + view->SetHighColor(0, 0, 0); + view->SetLowColor(ViewColor()); + + BString text(label); + view->TruncateString(&text, B_TRUNCATE_END, rect.Width()); + view->DrawString(text.String(), + BPoint(ceilf(rect.left + (rect.Width() + - StringWidth(text.String())) / 2), + ceilf(rect.top - fontHeight.descent - 2))); + } + } + + rect.left += rect.Width() / 4; + rect.right -= rect.Width() / 3; + + rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR); + rgb_color base = lit ? kLitIndicatorColor : kDarkColor; + + be_control_look->DrawButtonFrame(view, rect, updateRect, base, + background, BControlLook::B_DISABLED); + be_control_look->DrawButtonBackground(view, rect, updateRect, + base, BControlLook::B_DISABLED); +} + + +const char* +KeyboardLayoutView::_SpecialKeyLabel(const key_map& map, uint32 code, + bool abbreviated) +{ + if (code == map.caps_key) + return abbreviated ? "CAPS" : "CAPS LOCK"; + if (code == map.scroll_key) + return "SCROLL"; + if (code == map.num_key) + return abbreviated ? "NUM" : "NUM LOCK"; + if (code == map.left_shift_key || code == map.right_shift_key) + return "SHIFT"; + if (code == map.left_command_key || code == map.right_command_key) + return abbreviated ? "CMD" : "COMMAND"; + if (code == map.left_control_key || code == map.right_control_key) + return abbreviated ? "CTRL" : "CONTROL"; + if (code == map.left_option_key || code == map.right_option_key) + return abbreviated ? "OPT" : "OPTION"; + if (code == map.menu_key) + return "MENU"; + if (code == B_PRINT_KEY) + return "PRINT"; + if (code == B_PAUSE_KEY) + return "PAUSE"; + + return NULL; +} + + +const char* +KeyboardLayoutView::_SpecialMappedKeySymbol(const char* bytes, size_t numBytes) +{ + if (numBytes != 1) + return NULL; + + if (bytes[0] == B_TAB) + return "\xe2\x86\xb9"; + if (bytes[0] == B_ENTER) + return "\xe2\x86\xb5"; + if (bytes[0] == B_BACKSPACE) + return "\xe2\x8c\xab"; + + if (bytes[0] == B_UP_ARROW) + return "\xe2\x86\x91"; + if (bytes[0] == B_LEFT_ARROW) + return "\xe2\x86\x90"; + if (bytes[0] == B_DOWN_ARROW) + return "\xe2\x86\x93"; + if (bytes[0] == B_RIGHT_ARROW) + return "\xe2\x86\x92"; + + return NULL; +} + + +const char* +KeyboardLayoutView::_SpecialMappedKeyLabel(const char* bytes, size_t numBytes, + bool abbreviated) +{ + if (numBytes != 1) + return NULL; + + if (bytes[0] == B_ESCAPE) + return "ESC"; + + if (bytes[0] == B_INSERT) + return "INS"; + if (bytes[0] == B_DELETE) + return "DEL"; + if (bytes[0] == B_HOME) + return "HOME"; + if (bytes[0] == B_END) + return "END"; + if (bytes[0] == B_PAGE_UP) + return abbreviated ? "PG \xe2\x86\x91" : "PAGE \xe2\x86\x91"; + if (bytes[0] == B_PAGE_DOWN) + return abbreviated ? "PG \xe2\x86\x93" : "PAGE \xe2\x86\x93"; + + return NULL; +} + + +bool +KeyboardLayoutView::_FunctionKeyLabel(uint32 code, char* text, size_t textSize) +{ + if (code >= B_F1_KEY && code <= B_F12_KEY) { + snprintf(text, textSize, "F%" B_PRId32, code + 1 - B_F1_KEY); + return true; + } + + return false; +} + + +void +KeyboardLayoutView::_GetAbbreviatedKeyLabelIfNeeded(BView* view, BRect rect, + const Key* key, char* text, size_t textSize) +{ + if (floorf(rect.Width()) > ceilf(view->StringWidth(text))) + return; + + // Check if we have a shorter version of this key + + const key_map& map = fKeymap->Map(); + + const char* special = _SpecialKeyLabel(map, key->code, true); + if (special != NULL) { + strlcpy(text, special, textSize); + return; + } + + char* bytes = NULL; + int32 numBytes; + fKeymap->GetChars(key->code, fModifiers, fDeadKey, &bytes, &numBytes); + if (bytes != NULL) { + special = _SpecialMappedKeyLabel(bytes, numBytes, true); + if (special != NULL) + strlcpy(text, special, textSize); + + delete[] bytes; + } +} + + +void +KeyboardLayoutView::_GetKeyLabel(const Key* key, char* text, size_t textSize, + key_kind& keyKind) +{ + const key_map& map = fKeymap->Map(); + keyKind = kNormalKey; + text[0] = '\0'; + + const char* special = _SpecialKeyLabel(map, key->code); + if (special != NULL) { + strlcpy(text, special, textSize); + keyKind = kSpecialKey; + return; + } + + if (_FunctionKeyLabel(key->code, text, textSize)) { + keyKind = kSpecialKey; + return; + } + + char* bytes = NULL; + int32 numBytes; + fKeymap->GetChars(key->code, fModifiers, fDeadKey, &bytes, &numBytes); + if (bytes != NULL) { + special = _SpecialMappedKeyLabel(bytes, numBytes); + if (special != NULL) { + strlcpy(text, special, textSize); + keyKind = kSpecialKey; + } else { + special = _SpecialMappedKeySymbol(bytes, numBytes); + if (special != NULL) { + strlcpy(text, special, textSize); + keyKind = kSymbolKey; + } else { + bool hasGlyphs; + fBaseFont.GetHasGlyphs(bytes, 1, &hasGlyphs); + if (hasGlyphs) + strlcpy(text, bytes, textSize); + } + } + + delete[] bytes; + } +} + + +bool +KeyboardLayoutView::_IsKeyPressed(uint32 code) +{ + if (fDropTarget != NULL && fDropTarget->code == (int32)code) + return true; + + return _KeyState(code); +} + + +bool +KeyboardLayoutView::_KeyState(uint32 code) const +{ + if (code >= 16 * 8) + return false; + + return (fKeyState[code / 8] & (1 << (7 - (code & 7)))) != 0; +} + + +void +KeyboardLayoutView::_SetKeyState(uint32 code, bool pressed) +{ + if (code >= 16 * 8) + return; + + if (pressed) + fKeyState[code / 8] |= (1 << (7 - (code & 7))); + else + fKeyState[code / 8] &= ~(1 << (7 - (code & 7))); +} + + +Key* +KeyboardLayoutView::_KeyForCode(uint32 code) +{ + // TODO: have a lookup array + + for (int32 i = 0; i < fLayout->CountKeys(); i++) { + Key* key = fLayout->KeyAt(i); + if (key->code == (int32)code) + return key; + } + + return NULL; +} + + +void +KeyboardLayoutView::_InvalidateKey(uint32 code) +{ + _InvalidateKey(_KeyForCode(code)); +} + + +void +KeyboardLayoutView::_InvalidateKey(const Key* key) +{ + if (key != NULL) + Invalidate(_FrameFor(key)); +} + + +/*! Updates the fDeadKey member, and invalidates the view if needed. + + \return true if the view has been invalidated. +*/ +bool +KeyboardLayoutView::_HandleDeadKey(uint32 key, int32 modifiers) +{ + if (fKeymap == NULL || fKeymap->IsModifierKey(key)) + return false; + + bool isEnabled = false; + int32 deadKey = fKeymap->DeadKey(key, modifiers, &isEnabled); + if (fDeadKey != deadKey) { + if (isEnabled) { + Invalidate(); + fDeadKey = deadKey; + return true; + } + } else if (fDeadKey != 0) { + Invalidate(); + fDeadKey = 0; + return true; + } + + return false; +} + + +void +KeyboardLayoutView::_KeyChanged(const BMessage* message) +{ + const uint8* state; + ssize_t size; + int32 key; + if (message->FindData("states", B_UINT8_TYPE, (const void**)&state, &size) + != B_OK + || message->FindInt32("key", &key) != B_OK) + return; + + // Update key state, and invalidate change keys + + bool checkSingle = true; + + if (message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN) { + if (_HandleDeadKey(key, fModifiers)) + checkSingle = false; + + if (_KeyForCode(key) == NULL) + printf("no key for code %" B_PRId32 "\n", key); + } + + for (int32 i = 0; i < 16; i++) { + if (fKeyState[i] != state[i]) { + uint8 diff = fKeyState[i] ^ state[i]; + fKeyState[i] = state[i]; + + if (!checkSingle || !Window()->IsActive()) + continue; + + for (int32 j = 7; diff != 0; j--, diff >>= 1) { + if (diff & 1) { + _InvalidateKey(i * 8 + j); + } + } + } + } +} + + +Key* +KeyboardLayoutView::_KeyAt(BPoint point) +{ + // Find key candidate + + BPoint keyPoint = point; + keyPoint -= fOffset; + keyPoint.x /= fFactor; + keyPoint.y /= fFactor; + + for (int32 i = 0; i < fLayout->CountKeys(); i++) { + Key* key = fLayout->KeyAt(i); + if (key->frame.Contains(keyPoint)) { + BRect frame = _FrameFor(key); + if (frame.Contains(point)) + return key; + + return NULL; + } + } + + return NULL; +} + + +BRect +KeyboardLayoutView::_FrameFor(BRect keyFrame) +{ + BRect rect; + rect.left = ceilf(keyFrame.left * fFactor); + rect.top = ceilf(keyFrame.top * fFactor); + rect.right = floorf((keyFrame.Width()) * fFactor + rect.left - fGap - 1); + rect.bottom = floorf((keyFrame.Height()) * fFactor + rect.top - fGap - 1); + rect.OffsetBy(fOffset); + + return rect; +} + + +BRect +KeyboardLayoutView::_FrameFor(const Key* key) +{ + return _FrameFor(key->frame); +} + + +void +KeyboardLayoutView::_SetFontSize(BView* view, key_kind keyKind) +{ + BSize size = fLayout->DefaultKeySize(); + float fontSize = fBaseFontSize; + if (fBaseFontHeight >= size.height * fFactor * 0.5) { + fontSize *= (size.height * fFactor * 0.5) / fBaseFontHeight; + if (fontSize < 8) + fontSize = 8; + } + + switch (keyKind) { + case kNormalKey: + fBaseFont.SetSize(fontSize); + view->SetFont(&fBaseFont); + break; + case kSpecialKey: + fSpecialFont.SetSize(fontSize * 0.7); + view->SetFont(&fSpecialFont); + break; + case kSymbolKey: + fSpecialFont.SetSize(fontSize * 1.6); + view->SetFont(&fSpecialFont); + break; + + case kIndicator: + { + BFont font; + font.SetSize(fontSize * 0.8); + view->SetFont(&font); + break; + } + } +} + + +void +KeyboardLayoutView::_EvaluateDropTarget(BPoint point) +{ + fDropTarget = _KeyAt(point); + if (fDropTarget != NULL) { + if (fDropTarget == fDragKey && fModifiers == fDragModifiers) + fDropTarget = NULL; + else + _InvalidateKey(fDropTarget); + } +} + + +void +KeyboardLayoutView::_SendFakeKeyDown(const Key* key) +{ + BMessage message(B_KEY_DOWN); + message.AddInt64("when", system_time()); + message.AddData("states", B_UINT8_TYPE, &fKeyState, + sizeof(fKeyState)); + message.AddInt32("key", key->code); + message.AddInt32("modifiers", fModifiers); + message.AddPointer("keymap", fKeymap); + + char* string; + int32 numBytes; + fKeymap->GetChars(key->code, fModifiers, fDeadKey, &string, + &numBytes); + if (string != NULL) { + message.AddString("bytes", string); + delete[] string; + } + + fKeymap->GetChars(key->code, 0, 0, &string, &numBytes); + if (string != NULL) { + message.AddInt32("raw_char", string[0]); + message.AddInt8("byte", string[0]); + delete[] string; + } + + fTarget.SendMessage(&message); +} diff --git a/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayoutView.h b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayoutView.h new file mode 100644 index 0000000000..1f3e5eee62 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/KeyboardLayoutView.h @@ -0,0 +1,125 @@ +/* + * Copyright 2009, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef KEYBOARD_LAYOUT_VIEW_H +#define KEYBOARD_LAYOUT_VIEW_H + + +#include +#include + +#include "KeyboardLayout.h" + + +class Keymap; + + +class KeyboardLayoutView : public BView { +public: + KeyboardLayoutView(const char* name); + ~KeyboardLayoutView(); + + void SetKeyboardLayout(KeyboardLayout* layout); + void SetKeymap(Keymap* keymap); + void SetTarget(BMessenger target); + + KeyboardLayout* GetKeyboardLayout() { return fLayout; } + + void SetBaseFont(const BFont& font); + + void SetEditable(bool editable); + +protected: + virtual void AttachedToWindow(); + virtual void FrameResized(float width, float height); + virtual BSize MinSize(); + + virtual void KeyDown(const char* bytes, int32 numBytes); + virtual void KeyUp(const char* bytes, int32 numBytes); + virtual void MouseDown(BPoint point); + virtual void MouseUp(BPoint point); + virtual void MouseMoved(BPoint point, uint32 transit, + const BMessage* dragMessage); + + virtual void Draw(BRect updateRect); + virtual void MessageReceived(BMessage* message); + virtual void WindowActivated(bool active); + +private: + enum key_kind { + kNormalKey, + kSpecialKey, + kSymbolKey, + kIndicator + }; + + void _InitOffscreen(); + void _LayoutKeyboard(); + void _DrawKeyButton(BView* view, BRect& rect, + BRect updateRect, rgb_color base, + rgb_color background, bool pressed); + void _DrawKey(BView* view, BRect updateRect, + const Key* key, BRect frame, bool pressed); + void _DrawIndicator(BView* view, BRect updateRect, + const Indicator* indicator, BRect rect, + bool lit); + const char* _SpecialKeyLabel(const key_map& map, + uint32 code, bool abbreviated = false); + const char* _SpecialMappedKeySymbol(const char* bytes, + size_t numBytes); + const char* _SpecialMappedKeyLabel(const char* bytes, + size_t numBytes, bool abbreviated = false); + bool _FunctionKeyLabel(uint32 code, char* text, + size_t textSize); + void _GetAbbreviatedKeyLabelIfNeeded(BView* view, + BRect rect, const Key* key, char* text, + size_t textSize); + void _GetKeyLabel(const Key* key, char* text, + size_t textSize, key_kind& keyKind); + bool _IsKeyPressed(uint32 code); + bool _KeyState(uint32 code) const; + void _SetKeyState(uint32 code, bool pressed); + Key* _KeyForCode(uint32 code); + void _InvalidateKey(uint32 code); + void _InvalidateKey(const Key* key); + bool _HandleDeadKey(uint32 key, int32 modifiers); + void _KeyChanged(const BMessage* message); + Key* _KeyAt(BPoint point); + BRect _FrameFor(BRect keyFrame); + BRect _FrameFor(const Key* key); + void _SetFontSize(BView* view, key_kind keyKind); + void _EvaluateDropTarget(BPoint point); + void _SendFakeKeyDown(const Key* key); + + BBitmap* fOffscreenBitmap; + BView* fOffscreenView; + + KeyboardLayout* fLayout; + Keymap* fKeymap; + BMessenger fTarget; + bool fEditable; + + uint8 fKeyState[16]; + int32 fModifiers; + int32 fDeadKey; + int32 fButtons; + + BPoint fClickPoint; + Key* fDragKey; + int32 fDragModifiers; + Key* fDropTarget; + BPoint fDropPoint; + + BSize fOldSize; + BFont fBaseFont; + BFont fSpecialFont; + float fBaseFontHeight; + float fBaseFontSize; + BPoint fOffset; + float fFactor; + float fGap; +}; + + +#endif // KEYBOARD_LAYOUT_VIEW_H diff --git a/src/add-ons/input_server/devices/virtualkeyboard/Keymap.cpp b/src/add-ons/input_server/devices/virtualkeyboard/Keymap.cpp new file mode 100644 index 0000000000..0816da3de6 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/Keymap.cpp @@ -0,0 +1,464 @@ +/* + * Copyright 2004-2011 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Sandor Vroemisse + * Jérôme Duval + * Axel Dörfler, axeld@pinc-software.de. + */ + + +#include "Keymap.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + + +static const uint32 kModifierKeys = B_SHIFT_KEY | B_CAPS_LOCK | B_CONTROL_KEY + | B_OPTION_KEY | B_COMMAND_KEY | B_MENU_KEY; + + +static void +print_key(char *chars, int32 offset, bool last = false) +{ + int size = chars[offset++]; + + switch (size) { + case 0: + // Not mapped + fputs("N/A", stdout); + break; + + case 1: + // single-byte UTF-8/ASCII character + fputc(chars[offset], stdout); + break; + + default: + { + // 2-, 3-, or 4-byte UTF-8 character + char *str = new char[size + 1]; + strncpy(str, &chars[offset], size); + str[size] = 0; + fputs(str, stdout); + delete[] str; + break; + } + } + + if (!last) + fputs("\t", stdout); +} + + +// #pragma mark - + + +Keymap::Keymap() + : + fModificationMessage(NULL) +{ +} + + +Keymap::~Keymap() +{ + delete fModificationMessage; +} + + +void +Keymap::SetTarget(BMessenger target, BMessage* modificationMessage) +{ + delete fModificationMessage; + + fTarget = target; + fModificationMessage = modificationMessage; +} + + +void +Keymap::SetName(const char* name) +{ + strlcpy(fName, name, sizeof(fName)); +} + + +void +Keymap::DumpKeymap() +{ + if (fKeys.version != 3) + return; + + // Print a chart of the normal, shift, control, option, option+shift, + // Caps, Caps+shift, Caps+option, and Caps+option+shift keys. + puts("Key #\tn\ts\tc\to\tos\tC\tCs\tCo\tCos\n"); + + for (uint8 i = 0; i < 128; i++) { + printf(" 0x%02x\t", i); + print_key(fChars, fKeys.normal_map[i]); + print_key(fChars, fKeys.shift_map[i]); + print_key(fChars, fKeys.control_map[i]); + print_key(fChars, fKeys.option_map[i]); + print_key(fChars, fKeys.option_shift_map[i]); + print_key(fChars, fKeys.caps_map[i]); + print_key(fChars, fKeys.caps_shift_map[i]); + print_key(fChars, fKeys.option_caps_map[i]); + print_key(fChars, fKeys.option_caps_shift_map[i], true); + fputs("\n", stdout); + } +} + + +//! Load a map from a file +status_t +Keymap::Load(const entry_ref& ref) +{ + BEntry entry; + status_t status = entry.SetTo(&ref, true); + if (status != B_OK) + return status; + + BFile file(&entry, B_READ_ONLY); + status = SetTo(file); + if (status != B_OK) + return status; + + // fetch name from attribute and fall back to filename + + ssize_t bytesRead = file.ReadAttr("keymap:name", B_STRING_TYPE, 0, fName, + sizeof(fName)); + if (bytesRead > 0) + fName[bytesRead] = '\0'; + else + strlcpy(fName, ref.name, sizeof(fName)); + + return B_OK; +} + + +//! We save a map to a file +status_t +Keymap::Save(const entry_ref& ref) +{ + BFile file; + status_t status = file.SetTo(&ref, + B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); + if (status != B_OK) { + printf("error %s\n", strerror(status)); + return status; + } + + for (uint32 i = 0; i < sizeof(fKeys) / 4; i++) + ((uint32*)&fKeys)[i] = B_HOST_TO_BENDIAN_INT32(((uint32*)&fKeys)[i]); + + ssize_t bytesWritten = file.Write(&fKeys, sizeof(fKeys)); + if (bytesWritten < (ssize_t)sizeof(fKeys)) + status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR; + + for (uint32 i = 0; i < sizeof(fKeys) / 4; i++) + ((uint32*)&fKeys)[i] = B_BENDIAN_TO_HOST_INT32(((uint32*)&fKeys)[i]); + + if (status == B_OK) { + fCharsSize = B_HOST_TO_BENDIAN_INT32(fCharsSize); + + bytesWritten = file.Write(&fCharsSize, sizeof(uint32)); + if (bytesWritten < (ssize_t)sizeof(uint32)) + status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR; + + fCharsSize = B_BENDIAN_TO_HOST_INT32(fCharsSize); + } + + if (status == B_OK) { + bytesWritten = file.Write(fChars, fCharsSize); + if (bytesWritten < (ssize_t)fCharsSize) + status = bytesWritten < 0 ? bytesWritten : B_IO_ERROR; + } + + if (status == B_OK) { + file.WriteAttr("keymap:name", B_STRING_TYPE, 0, fName, strlen(fName)); + // Failing would be non-fatal + } + + return status; +} + + +status_t +Keymap::SetModifier(uint32 keyCode, uint32 modifier) +{ + const uint32 kSingleModifierKeys = B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY + | B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY | B_LEFT_CONTROL_KEY + | B_RIGHT_CONTROL_KEY | B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY; + + if ((modifier & kSingleModifierKeys) != 0) + modifier &= kSingleModifierKeys; + else if ((modifier & kModifierKeys) != 0) + modifier &= kModifierKeys; + + if (modifier == B_CAPS_LOCK) + fKeys.caps_key = keyCode; + else if (modifier == B_NUM_LOCK) + fKeys.num_key = keyCode; + else if (modifier == B_SCROLL_LOCK) + fKeys.scroll_key = keyCode; + else if (modifier == B_LEFT_SHIFT_KEY) + fKeys.left_shift_key = keyCode; + else if (modifier == B_RIGHT_SHIFT_KEY) + fKeys.right_shift_key = keyCode; + else if (modifier == B_LEFT_COMMAND_KEY) + fKeys.left_command_key = keyCode; + else if (modifier == B_RIGHT_COMMAND_KEY) + fKeys.right_command_key = keyCode; + else if (modifier == B_LEFT_CONTROL_KEY) + fKeys.left_control_key = keyCode; + else if (modifier == B_RIGHT_CONTROL_KEY) + fKeys.right_control_key = keyCode; + else if (modifier == B_LEFT_OPTION_KEY) + fKeys.left_option_key = keyCode; + else if (modifier == B_RIGHT_OPTION_KEY) + fKeys.right_option_key = keyCode; + else if (modifier == B_MENU_KEY) + fKeys.menu_key = keyCode; + else + return B_BAD_VALUE; + + if (fModificationMessage != NULL) + fTarget.SendMessage(fModificationMessage); + + return B_OK; +} + + +//! Enables/disables the "deadness" of the given keycode/modifier combo. +void +Keymap::SetDeadKeyEnabled(uint32 keyCode, uint32 modifiers, bool enabled) +{ + uint32 tableMask = 0; + int32 offset = Offset(keyCode, modifiers, &tableMask); + uint8 deadKeyIndex = DeadKeyIndex(offset); + if (deadKeyIndex > 0) { + uint32* deadTables[] = { + &fKeys.acute_tables, + &fKeys.grave_tables, + &fKeys.circumflex_tables, + &fKeys.dieresis_tables, + &fKeys.tilde_tables + }; + + if (enabled) + (*deadTables[deadKeyIndex - 1]) |= tableMask; + else + (*deadTables[deadKeyIndex - 1]) &= ~tableMask; + + if (fModificationMessage != NULL) + fTarget.SendMessage(fModificationMessage); + } +} + + +/*! Returns the trigger character string that is currently set for the dead + key with the given index (which is 1..5). +*/ +void +Keymap::GetDeadKeyTrigger(dead_key_index deadKeyIndex, BString& outTrigger) +{ + outTrigger = ""; + if (deadKeyIndex < 1 || deadKeyIndex > 5) + return; + + int32 deadOffsets[] = { + fKeys.acute_dead_key[1], + fKeys.grave_dead_key[1], + fKeys.circumflex_dead_key[1], + fKeys.dieresis_dead_key[1], + fKeys.tilde_dead_key[1] + }; + + int32 offset = deadOffsets[deadKeyIndex - 1]; + if (offset < 0 || offset >= (int32)fCharsSize) + return; + + uint32 deadNumBytes = fChars[offset]; + if (!deadNumBytes) + return; + + outTrigger.SetTo(&fChars[offset + 1], deadNumBytes); +} + + +/*! Sets the trigger character string that shall be used for the dead key + with the given index (which is 1..5). +*/ +void +Keymap::SetDeadKeyTrigger(dead_key_index deadKeyIndex, const BString& trigger) +{ + if (deadKeyIndex < 1 || deadKeyIndex > 5) + return; + + int32 deadOffsets[] = { + fKeys.acute_dead_key[1], + fKeys.grave_dead_key[1], + fKeys.circumflex_dead_key[1], + fKeys.dieresis_dead_key[1], + fKeys.tilde_dead_key[1] + }; + + int32 offset = deadOffsets[deadKeyIndex - 1]; + if (offset < 0 || offset >= (int32)fCharsSize) + return; + + if (_SetChars(offset, trigger.String(), trigger.Length())) { + // reset modifier table such that new dead key is enabled wherever + // it is available + uint32* deadTables[] = { + &fKeys.acute_tables, + &fKeys.grave_tables, + &fKeys.circumflex_tables, + &fKeys.dieresis_tables, + &fKeys.tilde_tables + }; + *deadTables[deadKeyIndex - 1] + = B_NORMAL_TABLE | B_SHIFT_TABLE | B_CONTROL_TABLE | B_OPTION_TABLE + | B_OPTION_SHIFT_TABLE | B_CAPS_TABLE | B_CAPS_SHIFT_TABLE + | B_OPTION_CAPS_TABLE | B_OPTION_CAPS_SHIFT_TABLE; + + if (fModificationMessage != NULL) + fTarget.SendMessage(fModificationMessage); + } +} + + +status_t +Keymap::RestoreSystemDefault() +{ + BPath path; + status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); + if (status != B_OK) + return status; + + path.Append("Key_map"); + + BEntry entry(path.Path()); + entry.Remove(); + + return Use(); +} + + +//! We make our input server use the map in /boot/home/config/settings/Keymap +status_t +Keymap::Use() +{ + status_t result = _restore_key_map_(); + if (result == B_OK) + set_keyboard_locks(modifiers()); + return result; +} + + +void +Keymap::SetKey(uint32 keyCode, uint32 modifiers, int8 deadKey, + const char* bytes, int32 numBytes) +{ + int32 offset = Offset(keyCode, modifiers); + if (offset < 0) + return; + + if (numBytes == -1) + numBytes = strlen(bytes); + if (numBytes > 6) + return; + + if (_SetChars(offset, bytes, numBytes)) { + if (fModificationMessage != NULL) + fTarget.SendMessage(fModificationMessage); + } +} + + +Keymap& +Keymap::operator=(const Keymap& other) +{ + if (this == &other) + return *this; + + delete[] fChars; + delete fModificationMessage; + + fChars = new(std::nothrow) char[other.fCharsSize]; + if (fChars != NULL) { + memcpy(fChars, other.fChars, other.fCharsSize); + fCharsSize = other.fCharsSize; + } else + fCharsSize = 0; + + memcpy(&fKeys, &other.fKeys, sizeof(key_map)); + strlcpy(fName, other.fName, sizeof(fName)); + + fTarget = other.fTarget; + + if (other.fModificationMessage != NULL) + fModificationMessage = new BMessage(*other.fModificationMessage); + + return *this; +} + + +bool +Keymap::_SetChars(int32 offset, const char* bytes, int32 numBytes) +{ + int32 oldNumBytes = fChars[offset]; + + if (oldNumBytes == numBytes + && !memcmp(&fChars[offset + 1], bytes, numBytes)) { + // nothing to do + return false; + } + + int32 diff = numBytes - oldNumBytes; + if (diff != 0) { + fCharsSize += diff; + + if (diff > 0) { + // make space for the new data + char* chars = new(std::nothrow) char[fCharsSize]; + if (chars != NULL) { + memcpy(chars, fChars, offset + oldNumBytes + 1); + memcpy(&chars[offset + 1 + numBytes], + &fChars[offset + 1 + oldNumBytes], + fCharsSize - 2 - offset - diff); + delete[] fChars; + fChars = chars; + } else + return false; + } else if (diff < 0) { + // shrink table + memmove(&fChars[offset + numBytes], &fChars[offset + oldNumBytes], + fCharsSize - offset - 2 - diff); + } + + // update offsets + int32* data = fKeys.control_map; + int32 size = sizeof(fKeys.control_map) / 4 * 9 + + sizeof(fKeys.acute_dead_key) / 4 * 5; + for (int32 i = 0; i < size; i++) { + if (data[i] > offset) + data[i] += diff; + } + } + + memcpy(&fChars[offset + 1], bytes, numBytes); + fChars[offset] = numBytes; + + return true; +} diff --git a/src/add-ons/input_server/devices/virtualkeyboard/Keymap.h b/src/add-ons/input_server/devices/virtualkeyboard/Keymap.h new file mode 100644 index 0000000000..48e4349db2 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/Keymap.h @@ -0,0 +1,77 @@ +/* + * Copyright 2004-2011 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Jérôme Duval + * Axel Dörfler, axeld@pinc-software.de. + */ +#ifndef KEYMAP_H +#define KEYMAP_H + + +#include + +#include +#include +#include + + +enum dead_key_index { + kDeadKeyAcute = 1, + kDeadKeyGrave, + kDeadKeyCircumflex, + kDeadKeyDiaeresis, + kDeadKeyTilde +}; + + +class Keymap : public BKeymap { +public: + Keymap(); + ~Keymap(); + + void SetTarget(BMessenger target, + BMessage* modificationMessage); + + status_t Load(const entry_ref& ref); + status_t Save(const entry_ref& ref); + + void DumpKeymap(); + + status_t SetModifier(uint32 keyCode, uint32 modifier); + + void SetDeadKeyEnabled(uint32 keyCode, + uint32 modifiers, bool enabled); + void GetDeadKeyTrigger(dead_key_index deadKeyIndex, + BString& outTrigger); + void SetDeadKeyTrigger(dead_key_index deadKeyIndex, + const BString& trigger); + + status_t RestoreSystemDefault(); + status_t Use(); + + void SetKey(uint32 keyCode, uint32 modifiers, + int8 deadKey, const char* bytes, + int32 numBytes = -1); + + void SetName(const char* name); + + const key_map& Map() const { return fKeys; } + key_map& Map() { return fKeys; } + + Keymap& operator=(const Keymap& other); + +private: + bool _SetChars(int32 offset, const char* bytes, + int32 numBytes); + +private: + char fName[B_FILE_NAME_LENGTH]; + + BMessenger fTarget; + BMessage* fModificationMessage; +}; + + +#endif // KEYMAP_H diff --git a/src/add-ons/input_server/devices/virtualkeyboard/KeymapListItem.cpp b/src/add-ons/input_server/devices/virtualkeyboard/KeymapListItem.cpp new file mode 100644 index 0000000000..20e8800cfb --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/KeymapListItem.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2004-2006 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Sandor Vroemisse + * Jérôme Duval + */ + +/* + * A BStringItem modified such that it holds + * the BEntry object it corresponds with + */ + +#include "KeymapListItem.h" + + +KeymapListItem::KeymapListItem(entry_ref& keymap, const char* name) + : + BStringItem(name ? name : keymap.name), + fKeymap(keymap) +{ +} diff --git a/src/add-ons/input_server/devices/virtualkeyboard/KeymapListItem.h b/src/add-ons/input_server/devices/virtualkeyboard/KeymapListItem.h new file mode 100644 index 0000000000..88c2c6b7dd --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/KeymapListItem.h @@ -0,0 +1,31 @@ +/* + * Copyright 2004-2009 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Sandor Vroemisse + * Jérôme Duval + */ +#ifndef KEYMAP_LIST_ITEM_H +#define KEYMAP_LIST_ITEM_H + + +#include +#include + + +/*! A BStringItem modified such that it holds + the BEntry object it corresponds with +*/ +class KeymapListItem : public BStringItem { +public: + KeymapListItem(entry_ref& keymap, + const char* name = NULL); + + entry_ref& EntryRef() { return fKeymap; }; + +protected: + entry_ref fKeymap; +}; + +#endif // KEYMAP_LIST_ITEM_H diff --git a/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboard.rdef b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboard.rdef new file mode 100644 index 0000000000..26da884636 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboard.rdef @@ -0,0 +1,38 @@ +resource app_signature "application/x-vnd.VirtualKeyboard"; + +resource app_name_catalog_entry "x-vnd.VirtualKeyboard:System name:VirtualKeyboard"; + +resource app_version { + major = 1, + middle = 0, + minor = 0, + + variety = 1, + internal = 0, + + short_info = "VirtualKeybaord", + long_info = "An on-screen keyboard for Haiku" +}; + +resource vector_icon { + $"6E63696605020016023910813A2974BB43733A501C4B20D94AA7F400E9FFB902" + $"001602B82588B66C3639EE80BB7BCD4B41FC4B6A3B0066FF9002011603BC39E2" + $"364286B516B5BB0AE64B8EF94A7E0500FF56E5FFC1050004006D0E0A04525E5C" + $"CAE766C6EB5E4B0A0442544648524E525C0A04525C524E5C445E500A04464852" + $"4E5C44503E0A02B8DABA5FB913BA320A02BA25B92DB942BA0F0803B70BBCFDB8" + $"C7BB84B7BBBB82000CC151BD52C151BD52C151BD52C0B0BDBEC0B0BDBEC105BD" + $"83C0EEBD21C116BD40C0C6BD01C00CBD38C022BD28BFCCBD68BFEBBDBDBFCBBD" + $"A7C016BDDBC08ABDBEC08ABDBEC072BDD0C080BE0CC065BDF9C0ADBE2CC19CBD" + $"CEC15EBE0BC20ABD6244BCC9C1A4BCF5C0ABBC64BF88BD14BFD6BCC6BEE4BDB8" + $"BFE6BE44BFB33CC052BE81C100BE6EC0E2BE7AC100BE6E0406FA0FBD8FC04CBC" + $"96BFB3BD553FBCDC3FBDCE3FBE873FBE3BBF8DBED4BF27BE94BE7ABEDABEB4BE" + $"4EBE41BDB5BE94BDCFBE7ABDB5BE940802C63340C74DC0670802C5C6C01FC6D8" + $"C0CB0802C791BFECC5F9C0E50802C58DC0A5C712BFA00007C361C29EC361C29E" + $"C33FC2BBC39AC332C35FC30EC3D5C357C496C30FC468C33EC4C4C2E1C463C25D" + $"C4D4C29EC4D4C29EC537C245C515C26DC558C21DC50AC1AA4EC1DDC4C1C177C4" + $"4945C461C1A9C449450E0A030303020130001001178322040A0001012000100A" + $"0101022000100A0201032000100A030303020130101801178322040A00010120" + $"10180A0101022010180A0201032010180A040100000A03030302011001178322" + $"040A000101000A010102000A020103000A030A0D090A0B0C0807060504100117" + $"812204" +}; \ No newline at end of file diff --git a/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardApp.cpp b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardApp.cpp new file mode 100644 index 0000000000..59e04ce24c --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardApp.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Freeman Lou + * All rights reserved. Distributed under the terms of the MIT license. + */ + +#include + +#include "VirtualKeyboardWindow.h" + +class VirtualKeyboardApp : public BApplication{ +public: + VirtualKeyboardApp(); +private: + VirtualKeyboardWindow* fWindow; +}; + + +VirtualKeyboardApp::VirtualKeyboardApp() + : + BApplication("application/x-vnd.VirtualKeyboard") +{ + fWindow = new VirtualKeyboardWindow(); + fWindow->Show(); +} + + +int +main() +{ + VirtualKeyboardApp app; + app.Run(); + + return 0; +} diff --git a/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardWindow.cpp b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardWindow.cpp new file mode 100644 index 0000000000..c1d3aa2d9b --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardWindow.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Freeman Lou + * All rights reserved. Distributed under the terms of the MIT license. + */ +#include "VirtualKeyboardWindow.h" + +#include + +#include "KeyboardLayoutView.h" +#include "KeymapListItem.h" +#include "Keymap.h" + +VirtualKeyboardWindow::VirtualKeyboardWindow() + : + BWindow(BRect(0,0,0,0),"Virtual Keyboard", + B_NO_BORDER_WINDOW_LOOK, B_FLOATING_ALL_WINDOW_FEEL, + B_NOT_RESIZABLE | B_WILL_ACCEPT_FIRST_CLICK) +{ + BScreen screen; + BRect screenRect(screen.Frame()); + ResizeTo(screenRect.Width(), screenRect.Height()/3); + MoveTo(0,screenRect.Height()-screenRect.Height()/3); + + Keymap keymap; + fKeyboardView = new KeyboardLayoutView("Keyboard"); + fKeyboardView->SetKeymap(&keymap); + AddChild(fKeyboardView); +} + + +void +VirtualKeyboardWindow::MessageReceived(BMessage* message) +{ + +} diff --git a/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardWindow.h b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardWindow.h new file mode 100644 index 0000000000..df5326d169 --- /dev/null +++ b/src/add-ons/input_server/devices/virtualkeyboard/VirtualKeyboardWindow.h @@ -0,0 +1,21 @@ +/* + * Copyright 2014 Freeman Lou + * All rights reserved. Distributed under the terms of the MIT license. + */ +#ifndef VIRTUAL_KEYBOARD_WINDOW_H +#define VIRTUAL_KEYBOARD_WINDOW_H + +#include + +class KeyboardLayoutView; + + +class VirtualKeyboardWindow : public BWindow{ +public: + VirtualKeyboardWindow(); + virtual void MessageReceived(BMessage* message); +private: + KeyboardLayoutView* fKeyboardView; +}; + +#endif // VIRTUAL_KEYBOARD_WINDOW_H