Synergy 571 wrong characters on client for unicode (#7108)

* SYNERGY-571 Wrong characters on client for Unicode. Windows server part

* SYNERGY-571 Wrong characters on client side

* Update ChangeLog
This commit is contained in:
SerhiiGadzhilov 2021-10-19 20:09:19 +03:00 committed by GitHub
parent b932ccc46b
commit bcebb6519f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 129 deletions

View file

@ -16,6 +16,7 @@ Bug fixes:
- #7099 SYNERGY-854 Fix compilation with msvc 2014
- #7100 | #7101 No configuration available on Windows
- #7097 The title "Enterprise" disappeares after clicking on "Preferences"
- #7108 Wrong characters on client for unicode
Enhancements:
- #7068 Add Synergy restart when settings changed

View file

@ -198,9 +198,30 @@ keyboardGetState(BYTE keys[256], DWORD vkCode, bool kf_up)
static
WPARAM
makeKeyMsg(UINT virtKey, char c, bool noAltGr)
makeKeyMsg(UINT virtKey, WCHAR wc, bool noAltGr)
{
return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0);
return MAKEWPARAM((WORD)wc, MAKEWORD(virtKey & 0xff, noAltGr ? 1 : 0));
}
static
void setDeadKey(WCHAR wc[], int size, UINT flags)
{
if (g_deadVirtKey != 0) {
auto virtualKey = static_cast<UINT>(g_deadVirtKey);
auto scanCode = static_cast<UINT>((g_deadLParam & 0x10ff0000u) >> 16);
if (ToUnicode(virtualKey, scanCode, g_deadKeyState, wc, size, flags) >= 2) {
// If ToUnicode returned >=2, it means that we accidentally removed
// a double dead key instead of restoring it. Thus, we call
// ToUnicode again with the same parameters to restore the
// internal dead key state.
ToUnicode(virtualKey, scanCode, g_deadKeyState, wc, size, flags);
// We need to keep track of this because g_deadVirtKey will be
// cleared later on; this would cause the dead key release to
// incorrectly restore the dead key state.
g_deadRelease = g_deadVirtKey;
}
}
}
static
@ -300,30 +321,13 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
}
}
WORD c = 0;
// map the key event to a character. we have to put the dead
// key back first and this has the side effect of removing it.
if (g_deadVirtKey != 0) {
if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags) == 2)
{
// If ToAscii returned 2, it means that we accidentally removed
// a double dead key instead of restoring it. Thus, we call
// ToAscii again with the same parameters to restore the
// internal dead key state.
ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags);
// We need to keep track of this because g_deadVirtKey will be
// cleared later on; this would cause the dead key release to
// incorrectly restore the dead key state.
g_deadRelease = g_deadVirtKey;
}
}
WCHAR wc[] = {0, 0};
setDeadKey(wc, 2, flags);
UINT scanCode = ((lParam & 0x10ff0000u) >> 16);
int n = ToAscii((UINT)wParam, scanCode, keys, &c, flags);
int n = ToUnicode((UINT)wParam, scanCode, keys, wc, 2, flags);
// if mapping failed and ctrl and alt are pressed then try again
// with both not pressed. this handles the case where ctrl and
@ -334,17 +338,9 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
bool noAltGr = false;
if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) {
noAltGr = true;
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
wParam | 0x05000000, lParam);
if (g_deadVirtKey != 0) {
if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags) == 2)
{
ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags);
g_deadRelease = g_deadVirtKey;
}
}
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam | 0x05000000, lParam);
setDeadKey(wc, 2, flags);
BYTE keys2[256];
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
keys2[i] = keys[i];
@ -355,12 +351,12 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
keys2[VK_LMENU] = 0;
keys2[VK_RMENU] = 0;
keys2[VK_MENU] = 0;
n = ToAscii((UINT)wParam, scanCode, keys2, &c, flags);
n = ToUnicode((UINT)wParam, scanCode, keys2, wc, 2, flags);
}
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
wParam | ((c & 0xff) << 8) |
((n & 0xff) << 16) | 0x06000000,
(wc[0] & 0xffff) | ((wParam & 0xff) << 16) |
((n & 0xf) << 24) | 0x60000000,
lParam);
WPARAM charAndVirtKey = 0;
bool clearDeadKey = false;
@ -386,12 +382,12 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
case 0:
// key doesn't map to a character. this can happen if
// non-character keys are pressed after a dead key.
charAndVirtKey = makeKeyMsg((UINT)wParam, (char)0, noAltGr);
charAndVirtKey = makeKeyMsg((UINT)wParam, 0, noAltGr);
break;
case 1:
// key maps to a character composed with dead key
charAndVirtKey = makeKeyMsg((UINT)wParam, (char)LOBYTE(c), noAltGr);
charAndVirtKey = makeKeyMsg((UINT)wParam, wc[0], noAltGr);
clearDeadKey = true;
break;
@ -399,14 +395,14 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
// previous dead key not composed. send a fake key press
// and release for the dead key to our window.
WPARAM deadCharAndVirtKey =
makeKeyMsg((UINT)g_deadVirtKey, (char)LOBYTE(c), noAltGr);
makeKeyMsg((UINT)g_deadVirtKey, wc[0], noAltGr);
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
deadCharAndVirtKey, g_deadLParam & 0x7fffffffu);
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
deadCharAndVirtKey, g_deadLParam | 0x80000000u);
// use uncomposed character
charAndVirtKey = makeKeyMsg((UINT)wParam, (char)HIBYTE(c), noAltGr);
charAndVirtKey = makeKeyMsg((UINT)wParam, wc[1], noAltGr);
clearDeadKey = true;
break;
}
@ -414,8 +410,8 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam)
// put back the dead key, if any, for the application to use
if (g_deadVirtKey != 0) {
ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags);
ToUnicode((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, wc, 2, flags);
}
// clear out old dead key state

View file

@ -690,34 +690,17 @@ MSWindowsKeyState::mapKeyFromEvent(WPARAM charAndVirtKey,
KeyModifierControl | KeyModifierAlt;
// extract character, virtual key, and if we didn't use AltGr
char c = (char)((charAndVirtKey & 0xff00u) >> 8);
UINT vkCode = (charAndVirtKey & 0xffu);
bool noAltGr = ((charAndVirtKey & 0xff0000u) != 0);
auto wc = static_cast<WCHAR>((charAndVirtKey & 0xffffu));
UINT vkCode = ((charAndVirtKey >> 16) & 0xffu);
bool noAltGr = ((charAndVirtKey & 0xff000000u) != 0);
// handle some keys via table lookup
KeyID id = getKeyID(vkCode, (KeyButton)((info >> 16) & 0x1ffu));
// check if not in table; map character to key id
if (id == kKeyNone && c != 0) {
if ((c & 0x80u) == 0) {
// ASCII
id = static_cast<KeyID>(c) & 0xffu;
}
else {
// character is not really ASCII. instead it's some
// character in the current ANSI code page. try to
// convert that to a Unicode character. if we fail
// then use the single byte character as is.
char src = c;
wchar_t unicode;
if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED,
&src, 1, &unicode, 1) > 0) {
id = static_cast<KeyID>(unicode);
}
else {
id = static_cast<KeyID>(c) & 0xffu;
}
}
if (id == kKeyNone && wc != 0) {
// UTF16
id = static_cast<KeyID>(wc) & 0xffffu;
}
// set modifier mask

View file

@ -1192,7 +1192,7 @@ MSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
static const KeyModifierMask s_ctrlAlt =
KeyModifierControl | KeyModifierAlt;
LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, (wParam & 0x10000u) ? 1 : 0, lParam));
LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, lParam=0x%08x", (wParam & 0xffffu), (wParam >> 16) & 0xffu, (wParam & 0x1000000u) ? 1 : 0, lParam));
// get event info
KeyButton button = (KeyButton)((lParam & 0x01ff0000) >> 16);
@ -1210,7 +1210,7 @@ MSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
// that maps mouse buttons to keys is known to do this.
// alternatively, we could just throw these events out.
if (button == 0) {
button = m_keyState->virtualKeyToButton(wParam & 0xffu);
button = m_keyState->virtualKeyToButton((wParam >> 16) & 0xffu);
if (button == 0) {
return true;
}
@ -1276,7 +1276,7 @@ MSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
if (!ignore()) {
// check for ctrl+alt+del. we do not want to pass that to the
// client. the user can use ctrl+alt+pause to emulate it.
UINT virtKey = (wParam & 0xffu);
UINT virtKey = ((wParam >> 16) & 0xffu);
if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) {
LOG((CLOG_DEBUG "discard ctrl+alt+del"));
return true;
@ -1290,9 +1290,9 @@ MSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam)
// pressed or released. when mapping the key we require that
// we not use AltGr (the 0x10000 flag in wParam) and we not
// use the keypad delete key (the 0x01000000 flag in lParam).
wParam = VK_DELETE | 0x00010000u;
wParam = (VK_DELETE << 16) | 0x01000000u;
lParam &= 0xfe000000;
lParam |= m_keyState->virtualKeyToButton(wParam & 0xffu) << 16;
lParam |= m_keyState->virtualKeyToButton(VK_DELETE) << 16;
lParam |= 0x01000001;
}
@ -1320,7 +1320,7 @@ MSWindowsScreen::onHotKey(WPARAM wParam, LPARAM lParam)
{
// get the key info
KeyModifierMask state = getActiveModifiers();
UINT virtKey = (wParam & 0xffu);
UINT virtKey = ((wParam >> 16) & 0xffu);
UINT modifiers = 0;
if ((state & KeyModifierShift) != 0) {
modifiers |= MOD_SHIFT;
@ -2015,7 +2015,7 @@ MSWindowsScreen::isModifierRepeat(KeyModifierMask oldState, KeyModifierMask stat
bool result = false;
if (oldState == state && state != 0) {
UINT virtKey = (wParam & 0xffu);
UINT virtKey = ((wParam >> 16) & 0xffu);
if ((state & KeyModifierShift) != 0
&& (virtKey == VK_LSHIFT || virtKey == VK_RSHIFT)) {
result = true;

View file

@ -339,6 +339,22 @@ void KeyMap::setLanguageData(std::vector<String> layouts)
m_keyboardLayouts = std::move(layouts);
}
SInt32 KeyMap::getLanguageGroupID(SInt32 group, const String& lang) const
{
SInt32 id = group;
auto it = std::find(m_keyboardLayouts.begin(), m_keyboardLayouts.end(), lang);
if (it != m_keyboardLayouts.end()) {
id = static_cast<int>(std::distance(m_keyboardLayouts.begin(), it));
LOG((CLOG_DEBUG1 "Language %s has group id %d", lang.c_str(), id));
}
else {
LOG((CLOG_DEBUG1 "Could not found requested language"));
}
return id;
}
SInt32
KeyMap::getNumGroups() const
{
@ -601,6 +617,25 @@ KeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group,
return keyItem;
}
const KeyMap::KeyItemList*
KeyMap::getKeyItemList(const KeyMap::KeyGroupTable& keyGroupTable, SInt32 group, KeyModifierMask desiredMask) const
{
const KeyItemList* itemList = nullptr;
// find best key in any group, starting with the active group
for (SInt32 groupOffset = 0; groupOffset < getNumGroups(); ++groupOffset) {
auto effectiveGroup = getEffectiveGroup(group, groupOffset);
auto keyIndex = findBestKey(keyGroupTable[effectiveGroup], desiredMask);
if (keyIndex != -1) {
LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup));
itemList = &keyGroupTable[effectiveGroup][keyIndex];
break;
}
}
return itemList;
}
const KeyMap::KeyItem*
KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group,
ModifierToKeys& activeModifiers,
@ -616,35 +651,16 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group,
return NULL;
}
const KeyGroupTable& keyGroupTable = i->second;
// find best key in any group, starting with the active group
SInt32 keyIndex = -1;
SInt32 numGroups = getNumGroups();
SInt32 groupOffset;
LOG((CLOG_DEBUG1 "find best: %04x %04x", currentState, desiredMask));
for (groupOffset = 0; groupOffset < numGroups; ++groupOffset) {
SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
keyIndex = findBestKey(keyGroupTable[effectiveGroup],
currentState, desiredMask);
if (keyIndex != -1) {
LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup));
break;
}
}
if (keyIndex == -1) {
// get keys to press for key
auto itemList = getKeyItemList(i->second, getLanguageGroupID(group, lang), desiredMask);
if (!itemList || itemList->empty()) {
// no mapping for this keysym
LOG((CLOG_DEBUG1 "no mapping for key %04x", id));
return NULL;
}
// get keys to press for key
SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset);
const KeyItemList& itemList = keyGroupTable[effectiveGroup][keyIndex];
if (itemList.empty()) {
return NULL;
}
const KeyItem& keyItem = itemList.back();
const KeyItem& keyItem = itemList->back();
// make working copy of modifiers
ModifierToKeys newModifiers = activeModifiers;
@ -652,8 +668,8 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group,
SInt32 newGroup = group;
// add each key
for (size_t j = 0; j < itemList.size(); ++j) {
if (!keysForKeyItem(itemList[j], newGroup, newModifiers,
for (size_t j = 0; j < itemList->size(); ++j) {
if (!keysForKeyItem(itemList->at(j), newGroup, newModifiers,
newState, desiredMask,
0, isAutoRepeat, keys, lang)) {
LOG((CLOG_DEBUG1 "can't map key"));
@ -680,14 +696,7 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group,
void
KeyMap::addGroupToKeystroke(Keystrokes& keys, SInt32& group, const String& lang) const
{
auto it = std::find(m_keyboardLayouts.begin(), m_keyboardLayouts.end(), lang);
if (it != m_keyboardLayouts.end()) {
LOG((CLOG_DEBUG2 "keystroke group detected as %s, group is %d", lang.c_str(), group));
group = static_cast<int>(std::distance(m_keyboardLayouts.begin(), it));
}
else {
LOG((CLOG_DEBUG2 "could not found requested language, guessing that correct is %d", group));
}
group = getLanguageGroupID(group, lang);
keys.push_back(Keystroke(group, true, false));
}
@ -703,9 +712,7 @@ KeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group,
}
SInt32
KeyMap::findBestKey(const KeyEntryList& entryList,
KeyModifierMask /*currentState*/,
KeyModifierMask desiredState) const
KeyMap::findBestKey(const KeyEntryList& entryList, KeyModifierMask desiredState) const
{
// check for an item that can accommodate the desiredState exactly
for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) {

View file

@ -408,9 +408,7 @@ private:
// returns the index into \p entryList of the KeyItemList requiring
// the fewest modifier changes between \p currentState and
// \p desiredState.
SInt32 findBestKey(const KeyEntryList& entryList,
KeyModifierMask currentState,
KeyModifierMask desiredState) const;
SInt32 findBestKey(const KeyEntryList& entryList, KeyModifierMask desiredState) const;
// gets the \c KeyItem used to synthesize the modifier who's bit is
// given by \p modifierBit in group \p group and does not synthesize
@ -470,16 +468,19 @@ private:
// Initialize key name/id maps
static void initKeyNameMaps();
// Ways to synthesize a KeyID over multiple keyboard groups
typedef std::vector<KeyEntryList> KeyGroupTable;
void addGroupToKeystroke(Keystrokes& keys, SInt32& group, const String& lang) const;
SInt32 getLanguageGroupID(SInt32 group, const String& lang) const;
const KeyItemList* getKeyItemList(const KeyGroupTable& keyGroupTable, SInt32 group, KeyModifierMask desiredMask) const;
// not implemented
KeyMap(const KeyMap&);
KeyMap& operator=(const KeyMap&);
private:
// Ways to synthesize a KeyID over multiple keyboard groups
typedef std::vector<KeyEntryList> KeyGroupTable;
// Table of KeyID to ways to synthesize that KeyID
typedef std::map<KeyID, KeyGroupTable> KeyIDMap;

View file

@ -39,12 +39,11 @@ TEST(KeyMapTests, findBestKey_requiredDown_matchExactFirstItem)
KeyMap::KeyItem item;
item.m_required = KeyModifierShift;
item.m_sensitive = KeyModifierShift;
KeyModifierMask currentState = KeyModifierShift;
KeyModifierMask desiredState = KeyModifierShift;
itemList.push_back(item);
entryList.push_back(itemList);
EXPECT_EQ(0, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(0, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, findBestKey_requiredAndExtraSensitiveDown_matchExactFirstItem)
@ -55,12 +54,11 @@ TEST(KeyMapTests, findBestKey_requiredAndExtraSensitiveDown_matchExactFirstItem)
KeyMap::KeyItem item;
item.m_required = KeyModifierShift;
item.m_sensitive = KeyModifierShift | KeyModifierAlt;
KeyModifierMask currentState = KeyModifierShift;
KeyModifierMask desiredState = KeyModifierShift;
itemList.push_back(item);
entryList.push_back(itemList);
EXPECT_EQ(0, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(0, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, findBestKey_requiredAndExtraSensitiveDown_matchExactSecondItem)
@ -75,14 +73,13 @@ TEST(KeyMapTests, findBestKey_requiredAndExtraSensitiveDown_matchExactSecondItem
KeyMap::KeyItem item2;
item2.m_required = KeyModifierShift;
item2.m_sensitive = KeyModifierShift | KeyModifierAlt;
KeyModifierMask currentState = KeyModifierShift;
KeyModifierMask desiredState = KeyModifierShift;
itemList1.push_back(item1);
itemList2.push_back(item2);
entryList.push_back(itemList1);
entryList.push_back(itemList2);
EXPECT_EQ(1, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(1, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, findBestKey_extraSensitiveDown_matchExactSecondItem)
@ -97,14 +94,13 @@ TEST(KeyMapTests, findBestKey_extraSensitiveDown_matchExactSecondItem)
KeyMap::KeyItem item2;
item2.m_required = 0;
item2.m_sensitive = KeyModifierShift;
KeyModifierMask currentState = KeyModifierAlt;
KeyModifierMask desiredState = KeyModifierAlt;
itemList1.push_back(item1);
itemList2.push_back(item2);
entryList.push_back(itemList1);
entryList.push_back(itemList2);
EXPECT_EQ(1, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(1, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, findBestKey_noRequiredDown_matchOneRequiredChangeItem)
@ -119,14 +115,13 @@ TEST(KeyMapTests, findBestKey_noRequiredDown_matchOneRequiredChangeItem)
KeyMap::KeyItem item2;
item2.m_required = KeyModifierShift;
item2.m_sensitive = KeyModifierShift | KeyModifierAlt;
KeyModifierMask currentState = 0;
KeyModifierMask desiredState = 0;
itemList1.push_back(item1);
itemList2.push_back(item2);
entryList.push_back(itemList1);
entryList.push_back(itemList2);
EXPECT_EQ(1, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(1, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, findBestKey_onlyOneRequiredDown_matchTwoRequiredChangesItem)
@ -141,14 +136,13 @@ TEST(KeyMapTests, findBestKey_onlyOneRequiredDown_matchTwoRequiredChangesItem)
KeyMap::KeyItem item2;
item2.m_required = KeyModifierShift| KeyModifierAlt;
item2.m_sensitive = KeyModifierShift | KeyModifierAlt | KeyModifierControl;
KeyModifierMask currentState = 0;
KeyModifierMask desiredState = 0;
itemList1.push_back(item1);
itemList2.push_back(item2);
entryList.push_back(itemList1);
entryList.push_back(itemList2);
EXPECT_EQ(1, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(1, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, findBestKey_noRequiredDown_cannotMatch)
@ -159,12 +153,11 @@ TEST(KeyMapTests, findBestKey_noRequiredDown_cannotMatch)
KeyMap::KeyItem item;
item.m_required = 0xffffffff;
item.m_sensitive = 0xffffffff;
KeyModifierMask currentState = 0;
KeyModifierMask desiredState = 0;
itemList.push_back(item);
entryList.push_back(itemList);
EXPECT_EQ(-1, keyMap.findBestKey(entryList, currentState, desiredState));
EXPECT_EQ(-1, keyMap.findBestKey(entryList, desiredState));
}
TEST(KeyMapTests, isCommand_shiftMask_returnFalse)
@ -235,6 +228,5 @@ TEST(KeyMapTests, mapkey_handles_setmodifier_with_no_mapped)
result = keyMap.mapKey(strokes, kKeySetModifiers, 1, activeModifiers, currentState, desiredMask, false, "en");
EXPECT_TRUE(result == nullptr);
}
}