1 /* 2 * Copyright 2009, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "KeyboardLayout.h" 8 9 #include <ctype.h> 10 #include <stdarg.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <vector> 14 15 #include <File.h> 16 #include <InterfaceDefs.h> 17 18 19 #undef TRACE 20 21 //#define TRACE_LAYOUT 22 #ifdef TRACE_LAYOUT 23 # define TRACE(x...) printf(x) 24 #else 25 # define TRACE(x...) 26 #endif 27 28 29 KeyboardLayout::KeyboardLayout() 30 : 31 fKeys(NULL), 32 fKeyCount(0), 33 fKeyCapacity(0), 34 fIndicators(5, true), 35 fIsDefault(true) 36 { 37 SetDefault(); 38 } 39 40 41 KeyboardLayout::~KeyboardLayout() 42 { 43 free(fKeys); 44 } 45 46 47 const char* 48 KeyboardLayout::Name() 49 { 50 return fName.String(); 51 } 52 53 54 int32 55 KeyboardLayout::CountKeys() 56 { 57 return fKeyCount; 58 } 59 60 61 Key* 62 KeyboardLayout::KeyAt(int32 index) 63 { 64 if (index < 0 || index >= fKeyCount) 65 return NULL; 66 67 return &fKeys[index]; 68 } 69 70 71 int32 72 KeyboardLayout::CountIndicators() 73 { 74 return fIndicators.CountItems(); 75 } 76 77 78 Indicator* 79 KeyboardLayout::IndicatorAt(int32 index) 80 { 81 return fIndicators.ItemAt(index); 82 } 83 84 85 BRect 86 KeyboardLayout::Bounds() 87 { 88 return fBounds; 89 } 90 91 92 BSize 93 KeyboardLayout::DefaultKeySize() 94 { 95 return fDefaultKeySize; 96 } 97 98 99 int32 100 KeyboardLayout::IndexForModifier(int32 modifier) 101 { 102 return 0; 103 } 104 105 106 status_t 107 KeyboardLayout::Load(const char* path) 108 { 109 entry_ref ref; 110 get_ref_for_path(path, &ref); 111 112 return Load(ref); 113 } 114 115 116 status_t 117 KeyboardLayout::Load(entry_ref& ref) 118 { 119 BFile file; 120 status_t status = file.SetTo(&ref, B_READ_ONLY); 121 if (status != B_OK) 122 return status; 123 124 off_t size; 125 status = file.GetSize(&size); 126 if (status != B_OK) 127 return status; 128 129 if (size > 65536) { 130 // We don't accept files larger than this 131 return B_BAD_VALUE; 132 } 133 134 char* data = (char*)malloc(size + 1); 135 if (data == NULL) 136 return B_NO_MEMORY; 137 138 ssize_t bytesRead = file.Read(data, size); 139 if (bytesRead != size) { 140 free(data); 141 142 if (bytesRead < 0) 143 return bytesRead; 144 145 return B_IO_ERROR; 146 } 147 148 data[size] = '\0'; 149 150 status = _InitFrom(data); 151 if (status == B_OK) 152 fIsDefault = false; 153 154 free(data); 155 156 return status; 157 } 158 159 160 void 161 KeyboardLayout::SetDefault() 162 { 163 // The keyboard layout description language defines the position, 164 // size, shape, and scancodes of the keys on the keyboard, row by 165 // row. 166 // You can define variables that are substituted in the row 167 // descriptions. Variables are always prefixed with a '$' symbol. 168 169 // On top level, only two different terms are accepted: value pairs, 170 // and row descriptions. 171 // Value pairs are in the form "<name> = <value>". There are only two 172 // predefined names at this moment "name" that specifies the name of 173 // a layout, and "default-size" that specifies the size of keys when 174 // no specific size is given. Also, variables can be specified this 175 // way. 176 177 // Row descriptions are embedded in the '[' and ']' brackets. 178 // The first term within a row is the position of the row, written as 179 // "<x>,<y>;" - the delimiter between terms/keys is usually the 180 // semi-colon. After the initial position, key descriptions are expected 181 // until the row is closed with a ']'. 182 183 // A key description is of the form "<shape>:<scancodes>", where <shape> 184 // is combined of the shape of the character, and its size, written as 185 // "<kind><width>,<height>[,<second-row-width>]". 186 // The kind can either be 'r' (the default, can also be omitted) for 187 // rectangular keys, or 'l' for the enter key. Additionally, you can use 188 // the 'd' character to mark dark keys. The size can be omitted completely, 189 // in which case the defined "default-size" is used. The default size is 190 // also used to determine the height of the first row of the enter key; 191 // the "<second-row-width>" specifier is only valid for enter keys, too. 192 193 // The scancodes can be written in different ways: 194 // 1) "+<count>": adds <count> keys, the scancodes will be used relative 195 // to the previous keys. 196 // 2) "<first>-<last>": keys are added for scancode <first> to scancode 197 // <last>. 198 // 3) "<first>+<count>": one key with the <first> scancode is added, plus 199 // <count> ones with relative scancode from that one. 200 201 // Finally, you can also define LED indicator. Those can be made instead 202 // of a key, it accepts a size, but no shape modifiers. Also, instead of 203 // a scancode, the name of the modifer is given, currently only the 204 // following are allowed: "led-num", "led-caps", and "led-scroll". 205 206 // See for examples in the default layout below. 207 #if 1 208 static const char* kDefaultLayout105 = 209 "name = Generic 105-key International\n" 210 // Size shortcuts 211 "default-size = 10,10\n" 212 "$b = 5,10\n" 213 "$c = 20,10\n" 214 "$d = 15,10\n" 215 "$e = l15,20,9\n" 216 "$f = 10,20\n" 217 "$g = 13,10\n" 218 // Key rows 219 "[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; " 220 "$g:led-num; $g:led-caps; $g:led-scroll ]\n" 221 "[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n" 222 "[ 0,30; d$d:0x26; :+12; d$e:0x47; $b:-; d:0x34-0x36; $b:-; :+3; " 223 "d$f:+1 ]\n" 224 "[ 0,40; d19,10:0x3b; :+11; :0x33; 51,10:-; :0x48-0x4a ]\n" 225 "[ 0,50; d$g:0x4b; :0x69; :0x4c+9; d27,10:+1; 15,10:-; d:+1; " 226 " 15,10:-; :+3; d$f:+1 ]\n" 227 "[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; " 228 "d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n"; 229 230 _InitFrom(kDefaultLayout105); 231 #endif 232 #if 0 233 static const char* kDefaultLayout104 = "name = Generic 104-key\n" 234 // Size shortcuts 235 "default-size = 10,10\n" 236 "$b = 5,10\n" 237 "$c = 20,10\n" 238 "$d = 15,10\n" 239 "$enter = d20,10\n" 240 "$f = 10,20\n" 241 "$g = 13,10\n" 242 // Key rows 243 "[ 0,0; d:0x01; :-; :+4; $b:-; d:+4; $b:-; :+4; $b:-; d:+3; $b:-; " 244 "$g:led-num; $g:led-caps; $g:led-scroll ]\n" 245 "[ 0,20; :+13; d$c:+; $b:-; d:+3; $b:-; d:+4 ]\n" 246 "[ 0,30; d$d:0x26; :+12; $d:+1; $b:-; d:+3; $b:-; :+3; " 247 "d$f:+1 ]\n" 248 "[ 0,40; d$c:0x3b; :+11; $enter:+1; 40,10:-; :+3 ]\n" 249 "[ 0,50; d24,10:0x4b; :+10; d26,10:+1; 15,10:-; d:+1; " 250 " 15,10:-; :+3; d$f:+1 ]\n" 251 "[ 0,60; d$g:0x5c; d$g:0x66; d$g:0x5d; 59,10:+1; d$g:+1; d$g:0x67+1; " 252 "d$g:0x60; $b:-; d:+3; $b:-; $c:+1; :+1 ]\n"; 253 254 _InitFrom(kDefaultLayout104); 255 #endif 256 #if 0 257 static const char* kIBMLaptop = "name = IBM Laptop International\n" 258 // Size shortcuts 259 "default-size = 18,18\n" 260 "$s = 17,10\n" 261 "$gap = 6,10\n" 262 "$sgap = 5,10\n" 263 "$backspace = 38,18\n" 264 "$tab = 28,18\n" 265 "$caps = 32,18\n" 266 "$enter = l28,36,22\n" 267 "$l-shift-ctrl = 23,18\n" 268 "$r-shift = 51,18\n" 269 "$option = 13,18\n" 270 "$space = 95,18\n" 271 // Key rows 272 "[ 0,0; $s:0x01; 148,10:-; $s:0x0e+2; $sgap:-; $s:0x1f+2; ]\n" 273 "[ 0,10; $s:0x02+3; $gap:-; $s:+4; $gap:-; $s:+4; $sgap:-; " 274 "$s:0x34+2; ]\n" 275 "[ 0,20; :0x11+12; $backspace:+1 ]\n" 276 "[ 0,38; $tab:0x26; :+12; $enter:0x47; ]\n" 277 "[ 0,56; $caps:0x3b; :+11; :0x33 ]\n" 278 "[ 0,74; $l-shift-ctrl:0x4b; :0x69; :0x4c+9; $r-shift:+1 ]\n" 279 "[ 0,92; :0x99; $l-shift-ctrl:0x5c; $option:0x66; :0x5d; $space:+1; " 280 ":+1; :0x68; :0x60; $s:0x9a; $s:0x57; $s:0x9b ]\n" 281 "[ 221,102; $s:0x61+2; ]\n"; 282 283 _InitFrom(kIBMLaptop); 284 #endif 285 fIsDefault = true; 286 } 287 288 289 void 290 KeyboardLayout::_FreeKeys() 291 { 292 free(fKeys); 293 fKeys = NULL; 294 fKeyCount = 0; 295 fKeyCapacity = 0; 296 fBounds = BRect(); 297 298 fIndicators.MakeEmpty(); 299 } 300 301 302 void 303 KeyboardLayout::_Error(const parse_state& state, const char* reason, ...) 304 { 305 va_list args; 306 va_start(args, reason); 307 308 fprintf(stderr, "Syntax error in line %ld: ", state.line); 309 vfprintf(stderr, reason, args); 310 fprintf(stderr, "\n"); 311 312 va_end(args); 313 } 314 315 316 void 317 KeyboardLayout::_AddAlternateKeyCode(Key* key, int32 modifier, int32 code) 318 { 319 if (key == NULL) 320 return; 321 322 // TODO: search for free spot 323 } 324 325 326 bool 327 KeyboardLayout::_AddKey(const Key& key) 328 { 329 TRACE(" add %ld (%g,%g)\n", key.code, key.frame.left, key.frame.top); 330 if (fKeyCount + 1 > fKeyCapacity) { 331 // enlarge array 332 int32 newCapacity = fKeyCapacity + 32; 333 Key* newKeys = (Key*)realloc(fKeys, newCapacity * sizeof(Key)); 334 if (newKeys == NULL) 335 return false; 336 337 fKeys = newKeys; 338 fKeyCapacity = newCapacity; 339 } 340 341 fKeys[fKeyCount] = key; 342 fKeyCount++; 343 344 fBounds = key.frame | fBounds; 345 346 return true; 347 } 348 349 350 void 351 KeyboardLayout::_SkipCommentsAndSpace(parse_state& state, const char*& data) 352 { 353 while (data[0] != '\0') { 354 while (isspace(data[0])) { 355 if (data[0] == '\n') 356 state.line++; 357 data++; 358 } 359 360 if (data[0] == '#') { 361 // skip comment 362 while (data[0] != '\0' && data[0] != '\n') { 363 data++; 364 } 365 } else 366 break; 367 } 368 } 369 370 371 void 372 KeyboardLayout::_Trim(BString& string, bool stripComments) 373 { 374 // Strip leading spaces 375 int32 i = 0; 376 while (isspace(string[i])) { 377 i++; 378 } 379 if (i > 0) 380 string.Remove(0, i); 381 382 // Remove comments 383 if (stripComments) { 384 i = string.FindFirst('#'); 385 if (i >= 0) 386 string.Truncate(i); 387 } 388 389 // Strip trailing spaces 390 i = string.Length() - 1; 391 while (i > 0 && isspace(string[i])) { 392 i--; 393 } 394 string.Truncate(i + 1); 395 } 396 397 398 bool 399 KeyboardLayout::_GetPair(const parse_state& state, const char*& data, 400 BString& name, BString& value) 401 { 402 // Get name 403 name = ""; 404 while (data[0] != '\0' && data[0] != '=') { 405 name += data[0]; 406 data++; 407 } 408 409 if (data[0] != '=') { 410 _Error(state, "no valid pair"); 411 return false; 412 } 413 414 // Skip sign 415 data++; 416 417 // Get value 418 value = ""; 419 while (data[0] != '\0' && data[0] != '\n') { 420 value += data[0]; 421 data++; 422 } 423 424 _Trim(name, false); 425 _Trim(value, true); 426 427 return true; 428 } 429 430 431 bool 432 KeyboardLayout::_AddKeyCodes(const parse_state& state, BPoint& rowLeftTop, 433 Key& key, const char* data, int32& lastCount) 434 { 435 if (data[0] == '-') { 436 // no key, just free space 437 int32 num = strtoul(data + 1, NULL, 0); 438 if (num < 1) 439 num = 1; 440 else if (num > 32) { 441 _Error(state, "empty key count too large"); 442 return false; 443 } 444 445 key.frame.OffsetTo(rowLeftTop); 446 rowLeftTop.x = key.frame.left + key.frame.Width() * num; 447 return true; 448 } 449 450 int32 modifier = 0; 451 452 if (isalpha(data[0])) { 453 bool led = false; 454 if (!strcmp("led-caps", data)) { 455 modifier = B_CAPS_LOCK; 456 led = true; 457 } else if (!strcmp("led-num", data)) { 458 modifier = B_NUM_LOCK; 459 led = true; 460 } else if (!strcmp("led-scroll", data)) { 461 modifier = B_SCROLL_LOCK; 462 led = true; 463 } else { 464 // TODO: get modifier (ie. "num") 465 } 466 467 if (led) { 468 key.frame.OffsetTo(rowLeftTop); 469 rowLeftTop.x = key.frame.right; 470 fBounds = key.frame | fBounds; 471 472 Indicator* indicator = new(std::nothrow) Indicator; 473 if (indicator != NULL) { 474 indicator->modifier = modifier; 475 indicator->frame = key.frame; 476 477 fIndicators.AddItem(indicator); 478 } 479 return true; 480 } 481 } 482 483 int32 first; 484 int32 last; 485 int32 num = 1; 486 487 if (data[0] == '+') { 488 num = strtoul(data + 1, NULL, 0); 489 if (num < 1) 490 num = 1; 491 else if (num > 32) { 492 _Error(state, "key count too large"); 493 return false; 494 } 495 496 if (fKeyCount > 0) 497 first = fKeys[fKeyCount - 1].code + 1; 498 else 499 first = 1; 500 501 last = first + num - 1; 502 } else { 503 char* end; 504 first = strtoul(data, &end, 0); 505 last = first; 506 507 if (end[0] == '-') { 508 last = strtoul(end + 1, NULL, 0); 509 if (first > last) { 510 _Error(state, "invalid key code specifier"); 511 return false; 512 } 513 514 num = last - first; 515 } else if (end[0] == '+') { 516 num = strtoul(end + 1, NULL, 0) + 1; 517 last = first + num - 1; 518 } else if (end[0] != '\0') { 519 _Error(state, "invalid key range"); 520 return false; 521 } 522 } 523 524 if (lastCount != 0) { 525 // update existing keys 526 if (lastCount != num) { 527 _Error(state, "modifier key mismatch"); 528 return false; 529 } 530 531 for (int32 i = fKeyCount - num; i < fKeyCount; i++, first++) { 532 Key* key = KeyAt(i); 533 534 _AddAlternateKeyCode(key, modifier, first); 535 } 536 } else { 537 // add new keys 538 for (int32 i = first; i <= last; i++) { 539 key.code = i; 540 541 // "layout" 542 key.frame.OffsetTo(rowLeftTop); 543 rowLeftTop.x = key.frame.right; 544 545 _AddKey(key); 546 } 547 548 lastCount = num; 549 } 550 551 return true; 552 } 553 554 555 bool 556 KeyboardLayout::_GetSize(const parse_state& state, const char* data, 557 float& x, float& y, float* _secondRow) 558 { 559 if (data[0] == '\0') { 560 // default size 561 x = fDefaultKeySize.width; 562 y = fDefaultKeySize.height; 563 return true; 564 } 565 566 float secondRow = 0; 567 int num = sscanf(data, "%g,%g,%g", &x, &y, &secondRow); 568 if (num < 2) { 569 _Error(state, "invalid size"); 570 return false; 571 } 572 573 if (_secondRow != NULL) 574 *_secondRow = secondRow; 575 return true; 576 } 577 578 579 bool 580 KeyboardLayout::_GetShape(const parse_state& state, const char* data, Key& key) 581 { 582 // the default 583 key.shape = kRectangleKeyShape; 584 key.dark = false; 585 586 while (isalpha(data[0])) { 587 switch (tolower(data[0])) { 588 case 'r': 589 key.shape = kRectangleKeyShape; 590 break; 591 case 'c': 592 key.shape = kCircleKeyShape; 593 break; 594 case 'l': 595 key.shape = kEnterKeyShape; 596 break; 597 case 'd': 598 key.dark = true; 599 break; 600 601 default: 602 _Error(state, "unknown shape specifier '%c'", data[0]); 603 return false; 604 } 605 606 data++; 607 } 608 609 float width, height; 610 if (!_GetSize(state, data, width, height, &key.second_row)) 611 return false; 612 613 // don't accept second row with anything but kEnterKeyShape 614 if ((key.shape != kEnterKeyShape && key.second_row != 0) 615 || (key.shape == kEnterKeyShape && key.second_row == 0)) { 616 _Error(state, "shape size mismatch"); 617 return false; 618 } 619 620 key.frame.left = 0; 621 key.frame.top = 0; 622 key.frame.right = width; 623 key.frame.bottom = height; 624 625 return true; 626 } 627 628 629 /*! Returns the term delimiter expected in a certain parse mode. */ 630 const char* 631 KeyboardLayout::_Delimiter(parse_mode mode) 632 { 633 switch (mode) { 634 default: 635 case kSize: 636 return ""; 637 case kRowStart: 638 return ";"; 639 640 case kKeyShape: 641 return ":"; 642 case kKeyCodes: 643 return ";:"; 644 } 645 } 646 647 648 bool 649 KeyboardLayout::_GetTerm(const char*& data, const char* delimiter, 650 BString& term, bool closingBracketAllowed) 651 { 652 // Get term 653 term = ""; 654 while (data[0] != '\0' && strchr(delimiter, data[0]) == NULL 655 && data[0] != '\n' && data[0] != '#' 656 && (!closingBracketAllowed || data[0] != ']')) { 657 term += data[0]; 658 data++; 659 } 660 661 if (data[0] == '\0' && delimiter[0]) 662 return false; 663 664 _Trim(term, true); 665 return true; 666 } 667 668 669 bool 670 KeyboardLayout::_SubstituteVariables(BString& term, VariableMap& variables, 671 BString& unknown) 672 { 673 while (true) { 674 int32 index = term.FindFirst('$'); 675 if (index < 0) 676 break; 677 678 // find variable name 679 680 VariableMap::iterator iterator = variables.begin(); 681 VariableMap::iterator best = variables.end(); 682 int32 bestLength = 0; 683 684 for (; iterator != variables.end(); iterator++) { 685 const BString& name = iterator->first; 686 if (!name.Compare(&term[index], name.Length()) 687 && name.Length() > bestLength) { 688 best = iterator; 689 bestLength = name.Length(); 690 } 691 } 692 693 if (best != variables.end()) { 694 // got one, replace it 695 term.Remove(index, bestLength); 696 term.Insert(best->second.String(), index); 697 } else { 698 // variable has not been found 699 unknown = &term[index]; 700 int32 length = 1; 701 while (isalpha(unknown[length])) { 702 length++; 703 } 704 unknown.Truncate(length); 705 return false; 706 } 707 } 708 709 return true; 710 } 711 712 713 bool 714 KeyboardLayout::_ParseTerm(const parse_state& state, const char*& data, 715 BString& term, VariableMap& variables) 716 { 717 if (!_GetTerm(data, _Delimiter(state.mode), term, 718 state.mode == kKeyCodes)) { 719 _Error(state, state.mode == kRowStart 720 ? "no valid row start" : "invalid term"); 721 return false; 722 } 723 724 BString unknown; 725 if (!_SubstituteVariables(term, variables, unknown)) { 726 _Error(state, "Unknown variable \"%s\"", unknown.String()); 727 return false; 728 } 729 730 return true; 731 } 732 733 734 /*! Initializes the keyboard layout from the data given. 735 The string has to be a valid keyboard layout description, otherwise 736 an error is returned. 737 */ 738 status_t 739 KeyboardLayout::_InitFrom(const char* data) 740 { 741 _FreeKeys(); 742 743 VariableMap variables; 744 BPoint rowLeftTop; 745 int32 lastKeyCount = 0; 746 Key key; 747 748 parse_state state = {kPairs, 1}; 749 750 while (data[0] != '\0') { 751 _SkipCommentsAndSpace(state, data); 752 753 if (data[0] == '[') { 754 state.mode = kRowStart; 755 756 rowLeftTop = BPoint(0, 0); 757 data++; 758 continue; 759 } else if (data[0] == '\0') 760 break; 761 762 switch (state.mode) { 763 case kPairs: 764 { 765 BString name; 766 BString value; 767 if (!_GetPair(state, data, name, value)) 768 return B_BAD_VALUE; 769 770 TRACE("<%s> = <%s>\n", name.String(), value.String()); 771 if (name == "name") 772 fName = value; 773 else if (name == "default-size") { 774 const char* valueString = value.String(); 775 parse_state tempState = {kSize, state.line}; 776 BString term; 777 if (!_ParseTerm(tempState, valueString, term, variables)) 778 return B_BAD_VALUE; 779 780 TRACE(" size = %s\n", term.String()); 781 if (!_GetSize(state, term.String(), fDefaultKeySize.width, 782 fDefaultKeySize.height)) 783 return B_BAD_VALUE; 784 } else if (name[0] == '$') 785 variables[name] = value; 786 break; 787 } 788 789 case kRowStart: 790 case kKeyShape: 791 case kKeyCodes: 792 { 793 if (data[0] == ']') { 794 if (state.mode == kKeyShape) { 795 state.mode = kPairs; 796 data++; 797 continue; 798 } 799 _Error(state, "unexpected row closing bracket"); 800 return B_BAD_VALUE; 801 } 802 803 BString term; 804 if (!_ParseTerm(state, data, term, variables)) 805 return B_BAD_VALUE; 806 807 switch (state.mode) { 808 case kRowStart: 809 if (!_GetSize(state, term.String(), rowLeftTop.x, 810 rowLeftTop.y)) 811 return B_BAD_VALUE; 812 813 TRACE("row: %s (%g:%g)\n", term.String(), rowLeftTop.x, 814 rowLeftTop.y); 815 816 state.mode = kKeyShape; 817 break; 818 case kKeyShape: 819 memset(&key, 0, sizeof(Key)); 820 if (!_GetShape(state, term.String(), key)) 821 return B_BAD_VALUE; 822 823 TRACE(" shape: %s (%g:%g:%g)\n", term.String(), 824 key.frame.Width(), key.frame.Height(), 825 key.second_row); 826 827 lastKeyCount = 0; 828 state.mode = kKeyCodes; 829 break; 830 case kKeyCodes: 831 TRACE(" raw key: %s\n", term.String()); 832 833 if (!_AddKeyCodes(state, rowLeftTop, key, term.String(), 834 lastKeyCount)) 835 return B_BAD_VALUE; 836 837 if (data[0] != ':') 838 state.mode = kKeyShape; 839 break; 840 841 default: 842 break; 843 } 844 if (data[0] != ']' && data[0] != '\0') 845 data++; 846 break; 847 } 848 849 default: 850 return B_BAD_VALUE; 851 } 852 } 853 854 return B_OK; 855 } 856 857