|
27 | 27 | #include <guacamole/client.h> |
28 | 28 | #include <guacamole/mem.h> |
29 | 29 | #include <guacamole/rwlock.h> |
30 | | - |
31 | 30 | #include <stdlib.h> |
32 | 31 |
|
33 | 32 | /** |
@@ -562,7 +561,229 @@ static void guac_rdp_keyboard_send_missing_key(guac_rdp_keyboard* keyboard, |
562 | 561 |
|
563 | 562 | guac_client* client = keyboard->client; |
564 | 563 | guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; |
| 564 | + |
| 565 | + /* If system modifiers (Ctrl/Alt) are held, prefer sending US scancode so |
| 566 | + * that shortcuts like Ctrl+C/V/A work even in Unicode/failsafe layout. */ |
| 567 | + int ctrl_or_alt_pressed = |
| 568 | + guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LCTRL) |
| 569 | + || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RCTRL) |
| 570 | + || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LALT) |
| 571 | + || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RALT); |
| 572 | + |
| 573 | + /* Normalize Cyrillic letters to Latin equivalents when Ctrl/Alt pressed |
| 574 | + * so that shortcuts like Ctrl+C, Ctrl+V, etc. work even in Russian layout. |
| 575 | + */ |
| 576 | + if (ctrl_or_alt_pressed) { |
| 577 | + |
| 578 | + |
| 579 | +/* Treat Ctrl or Alt (but NOT AltGr) as shortcut modifiers */ |
| 580 | +int ctrl_pressed = |
| 581 | + guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LCTRL) |
| 582 | + || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RCTRL); |
| 583 | + |
| 584 | +int alt_pressed = |
| 585 | + guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LALT) |
| 586 | + || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RALT); |
| 587 | + |
| 588 | +/* AltGr appears as Ctrl+Alt together; don’t hijack that. */ |
| 589 | +int altgr = ctrl_pressed && alt_pressed; |
| 590 | + |
| 591 | +/* We only force scancodes for real shortcuts: Ctrl-only OR Alt-only. */ |
| 592 | +int shortcut_mod = !altgr && (ctrl_pressed || alt_pressed); |
| 593 | + |
| 594 | +if (shortcut_mod) { |
| 595 | + /* Map both Latin and Russian keysyms for C/V to US scancodes. |
| 596 | + * US Set 1 scancodes: C=0x2E, V=0x2F. |
| 597 | + * This makes Ctrl+C / Ctrl+V work in ru-RU and en-US equally. |
| 598 | + */ |
| 599 | + int sc = 0; |
| 600 | + |
| 601 | + switch (keysym) { |
| 602 | + /* --- Ctrl+C --- */ |
| 603 | + case 'c': case 'C': |
| 604 | + case 0x441: /* Cyrillic small 'с' */ |
| 605 | + case 0x421: /* Cyrillic capital 'С' */ |
| 606 | + sc = 0x2E; /* US 'C' key */ |
| 607 | + break; |
| 608 | + |
| 609 | + /* --- Ctrl+V --- */ |
| 610 | + case 'v': case 'V': |
| 611 | + case 0x43C: /* Cyrillic small 'м' */ |
| 612 | + case 0x41C: /* Cyrillic capital 'М' */ |
| 613 | + sc = 0x2F; /* US 'V' key */ |
| 614 | + break; |
| 615 | + |
| 616 | + default: |
| 617 | + sc = 0; |
| 618 | + } |
565 | 619 |
|
| 620 | + if (sc) { |
| 621 | + guac_client_log(client, GUAC_LOG_DEBUG, |
| 622 | + "Shortcut: modifiers active, sending US scancode 0x%X for keysym 0x%X", |
| 623 | + sc, keysym); |
| 624 | + /* press + release with no extended flags */ |
| 625 | + guac_rdp_send_key_event(rdp_client, sc, 0, 1); |
| 626 | + guac_rdp_send_key_event(rdp_client, sc, 0, 0); |
| 627 | + return; |
| 628 | + } |
| 629 | +} |
| 630 | + switch (keysym) { |
| 631 | + /* Lowercase Cyrillic */ |
| 632 | + case 0x430: keysym = 'f'; break; // а |
| 633 | + case 0x431: keysym = ','; break; // б |
| 634 | + case 0x432: keysym = 'd'; break; // в |
| 635 | + case 0x433: keysym = 'u'; break; // г |
| 636 | + case 0x434: keysym = 'l'; break; // д |
| 637 | + case 0x435: keysym = 't'; break; // е |
| 638 | + case 0x436: keysym = ';'; break; // ж |
| 639 | + case 0x437: keysym = 'p'; break; // з |
| 640 | + case 0x438: keysym = 'b'; break; // и |
| 641 | + case 0x439: keysym = 'q'; break; // й |
| 642 | + case 0x43A: keysym = 'r'; break; // к |
| 643 | + case 0x43B: keysym = 'k'; break; // л |
| 644 | + case 0x43C: keysym = 'v'; break; // м |
| 645 | + case 0x43D: keysym = 'y'; break; // н |
| 646 | + case 0x43E: keysym = 'j'; break; // о |
| 647 | + case 0x43F: keysym = 'g'; break; // п |
| 648 | + case 0x440: keysym = 'h'; break; // р |
| 649 | + case 0x441: keysym = 'c'; break; // с |
| 650 | + case 0x442: keysym = 'n'; break; // т |
| 651 | + case 0x443: keysym = 'e'; break; // у |
| 652 | + case 0x444: keysym = 'a'; break; // ф |
| 653 | + case 0x445: keysym = '['; break; // х |
| 654 | + case 0x446: keysym = 'w'; break; // ц |
| 655 | + case 0x447: keysym = 'x'; break; // ч |
| 656 | + case 0x448: keysym = 'i'; break; // ш |
| 657 | + case 0x449: keysym = 'o'; break; // щ |
| 658 | + case 0x44A: keysym = ']'; break; // ъ |
| 659 | + case 0x44B: keysym = 's'; break; // ы |
| 660 | + case 0x44C: keysym = 'm'; break; // ь |
| 661 | + case 0x44D: keysym = '\''; break; // э |
| 662 | + case 0x44E: keysym = '.'; break; // ю |
| 663 | + case 0x44F: keysym = 'z'; break; // я |
| 664 | + |
| 665 | + /* Uppercase Cyrillic */ |
| 666 | + case 0x410: keysym = 'F'; break; // А |
| 667 | + case 0x411: keysym = '<'; break; // Б |
| 668 | + case 0x412: keysym = 'D'; break; // В |
| 669 | + case 0x413: keysym = 'U'; break; // Г |
| 670 | + case 0x414: keysym = 'L'; break; // Д |
| 671 | + case 0x415: keysym = 'T'; break; // Е |
| 672 | + case 0x416: keysym = ':'; break; // Ж |
| 673 | + case 0x417: keysym = 'P'; break; // З |
| 674 | + case 0x418: keysym = 'B'; break; // И |
| 675 | + case 0x419: keysym = 'Q'; break; // Й |
| 676 | + case 0x41A: keysym = 'R'; break; // К |
| 677 | + case 0x41B: keysym = 'K'; break; // Л |
| 678 | + case 0x41C: keysym = 'V'; break; // М |
| 679 | + case 0x41D: keysym = 'Y'; break; // Н |
| 680 | + case 0x41E: keysym = 'J'; break; // О |
| 681 | + case 0x41F: keysym = 'G'; break; // П |
| 682 | + case 0x420: keysym = 'H'; break; // Р |
| 683 | + case 0x421: keysym = 'C'; break; // С |
| 684 | + case 0x422: keysym = 'N'; break; // Т |
| 685 | + case 0x423: keysym = 'E'; break; // У |
| 686 | + case 0x424: keysym = 'A'; break; // Ф |
| 687 | + case 0x425: keysym = '{'; break; // Х |
| 688 | + case 0x426: keysym = 'W'; break; // Ц |
| 689 | + case 0x427: keysym = 'X'; break; // Ч |
| 690 | + case 0x428: keysym = 'I'; break; // Ш |
| 691 | + case 0x429: keysym = 'O'; break; // Щ |
| 692 | + case 0x42A: keysym = '}'; break; // Ъ |
| 693 | + case 0x42B: keysym = 'S'; break; // Ы |
| 694 | + case 0x42C: keysym = 'M'; break; // Ь |
| 695 | + case 0x42D: keysym = '"'; break; // Э |
| 696 | + case 0x42E: keysym = '>'; break; // Ю |
| 697 | + case 0x42F: keysym = 'Z'; break; // Я |
| 698 | + } |
| 699 | + } |
| 700 | + |
| 701 | + if (ctrl_or_alt_pressed) { |
| 702 | + /* Map ASCII letters/digits to PC/AT set 1 scancodes (US layout). |
| 703 | + * This is enough for common shortcuts (A, C, V, X, Z, Y, ...). */ |
| 704 | + int sc = 0; |
| 705 | + /* Normalize X11-style keysyms (0x1000000 + Unicode) to plain Unicode */ |
| 706 | + if ((keysym & 0xFF000000) == 0x01000000) |
| 707 | + keysym &= 0x00FFFFFF; |
| 708 | + |
| 709 | + switch (keysym) { |
| 710 | + /* digits 0-9 */ |
| 711 | + case '1': sc = 0x02; break; case '2': sc = 0x03; break; |
| 712 | + case '3': sc = 0x04; break; case '4': sc = 0x05; break; |
| 713 | + case '5': sc = 0x06; break; case '6': sc = 0x07; break; |
| 714 | + case '7': sc = 0x08; break; case '8': sc = 0x09; break; |
| 715 | + case '9': sc = 0x0A; break; case '0': sc = 0x0B; break; |
| 716 | + |
| 717 | + /* Latin letters A-Z (both cases) */ |
| 718 | + case 'a': case 'A': sc = 0x1E; break; |
| 719 | + case 'b': case 'B': sc = 0x30; break; |
| 720 | + case 'c': case 'C': sc = 0x2E; break; |
| 721 | + case 'd': case 'D': sc = 0x20; break; |
| 722 | + case 'e': case 'E': sc = 0x12; break; |
| 723 | + case 'f': case 'F': sc = 0x21; break; |
| 724 | + case 'g': case 'G': sc = 0x22; break; |
| 725 | + case 'h': case 'H': sc = 0x23; break; |
| 726 | + case 'i': case 'I': sc = 0x17; break; |
| 727 | + case 'j': case 'J': sc = 0x24; break; |
| 728 | + case 'k': case 'K': sc = 0x25; break; |
| 729 | + case 'l': case 'L': sc = 0x26; break; |
| 730 | + case 'm': case 'M': sc = 0x32; break; |
| 731 | + case 'n': case 'N': sc = 0x31; break; |
| 732 | + case 'o': case 'O': sc = 0x18; break; |
| 733 | + case 'p': case 'P': sc = 0x19; break; |
| 734 | + case 'q': case 'Q': sc = 0x10; break; |
| 735 | + case 'r': case 'R': sc = 0x13; break; |
| 736 | + case 's': case 'S': sc = 0x1F; break; |
| 737 | + case 't': case 'T': sc = 0x14; break; |
| 738 | + case 'u': case 'U': sc = 0x16; break; |
| 739 | + case 'v': case 'V': sc = 0x2F; break; |
| 740 | + case 'w': case 'W': sc = 0x11; break; |
| 741 | + case 'x': case 'X': sc = 0x2D; break; |
| 742 | + case 'y': case 'Y': sc = 0x15; break; |
| 743 | + case 'z': case 'Z': sc = 0x2C; break; |
| 744 | + |
| 745 | + /* Russian Cyrillic letters - most common shortcuts */ |
| 746 | + /* Lowercase Cyrillic */ |
| 747 | + case 0x0444: case 0x0424: sc = 0x1E; break; /* ф/Ф -> A */ |
| 748 | + case 0x0438: case 0x0418: sc = 0x30; break; /* и/И -> B */ |
| 749 | + case 0x0441: case 0x0421: sc = 0x2E; break; /* с/С -> C */ |
| 750 | + case 0x0432: case 0x0412: sc = 0x20; break; /* в/В -> D */ |
| 751 | + case 0x0443: case 0x0423: sc = 0x12; break; /* у/У -> E */ |
| 752 | + case 0x0430: case 0x0410: sc = 0x21; break; /* а/А -> F */ |
| 753 | + case 0x043f: case 0x041f: sc = 0x22; break; /* п/П -> G */ |
| 754 | + case 0x0440: case 0x0420: sc = 0x23; break; /* р/Р -> H */ |
| 755 | + case 0x0448: case 0x0428: sc = 0x17; break; /* ш/Ш -> I */ |
| 756 | + case 0x043e: case 0x041e: sc = 0x24; break; /* о/О -> J */ |
| 757 | + case 0x043b: case 0x041b: sc = 0x25; break; /* л/Л -> K */ |
| 758 | + case 0x0434: case 0x0414: sc = 0x26; break; /* д/Д -> L */ |
| 759 | + case 0x044c: case 0x042c: sc = 0x32; break; /* ь/Ь -> M */ |
| 760 | + case 0x0442: case 0x0422: sc = 0x31; break; /* т/Т -> N */ |
| 761 | + case 0x0449: case 0x0429: sc = 0x18; break; /* щ/Щ -> O */ |
| 762 | + case 0x0437: case 0x0417: sc = 0x19; break; /* з/З -> P */ |
| 763 | + case 0x0439: case 0x0419: sc = 0x10; break; /* й/Й -> Q */ |
| 764 | + case 0x043a: case 0x041a: sc = 0x13; break; /* к/К -> R */ |
| 765 | + case 0x044b: case 0x042b: sc = 0x1F; break; /* ы/Ы -> S */ |
| 766 | + case 0x0435: case 0x0415: sc = 0x14; break; /* е/Е -> T */ |
| 767 | + case 0x0433: case 0x0413: sc = 0x16; break; /* г/Г -> U */ |
| 768 | + case 0x043c: case 0x041c: sc = 0x2F; break; /* м/М -> V */ |
| 769 | + case 0x0446: case 0x0426: sc = 0x11; break; /* ц/Ц -> W */ |
| 770 | + case 0x0447: case 0x0427: sc = 0x2D; break; /* ч/Ч -> X */ |
| 771 | + case 0x043d: case 0x041d: sc = 0x15; break; /* н/Н -> Y */ |
| 772 | + case 0x044f: case 0x042f: sc = 0x2C; break; /* я/Я -> Z */ |
| 773 | + |
| 774 | + |
| 775 | + default: sc = 0; break; |
| 776 | + } |
| 777 | + if (sc) { |
| 778 | + guac_client_log(client, GUAC_LOG_DEBUG, |
| 779 | + "Sending keysym 0x%x as US scancode 0x%X due to modifiers", |
| 780 | + keysym, sc); |
| 781 | + /* send press+release; 'flags' = 0 for regular keys */ |
| 782 | + guac_rdp_send_key_event(rdp_client, sc, 0, 1); |
| 783 | + guac_rdp_send_key_event(rdp_client, sc, 0, 0); |
| 784 | + return; |
| 785 | + } |
| 786 | + } |
566 | 787 | /* Attempt to type using dead keys */ |
567 | 788 | if (!guac_rdp_decompose_keysym(keyboard, keysym)) |
568 | 789 | return; |
@@ -738,3 +959,4 @@ BOOL guac_rdp_keyboard_set_indicators(rdpContext* context, UINT16 flags) { |
738 | 959 | return TRUE; |
739 | 960 |
|
740 | 961 | } |
| 962 | + |
0 commit comments