diff --git a/.gitmodules b/.gitmodules index ba1a5ef6e..1b6d52f29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "ext/WinToast"] path = ext/WinToast url = https://github.com/mohabouje/WinToast +[submodule "ext/pugixml"] + path = ext/pugixml + url = https://github.com/zeux/pugixml diff --git a/ChangeLog b/ChangeLog index dc9307b4f..777021da9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ v1.14.2-snapshot =========== Bug fixes: - #7070 Fix radio button shifted focus on macOS +- #7038 Fix client and server different keyboard layout errors - #7078 Fix clipboard re-enables automatically - #7077 Fix Ubuntu, CentOS and Debian build after SYNERGY-1161 - #7080 Add trace if the system can't open file with trusted fingerprints diff --git a/ext/pugixml b/ext/pugixml new file mode 160000 index 000000000..9e382f980 --- /dev/null +++ b/ext/pugixml @@ -0,0 +1 @@ +Subproject commit 9e382f98076e57581fcc61323728443374889646 diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 43efb6c92..499240218 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -77,7 +77,8 @@ const char* AppConfig::m_SynergySettingsName[] = { "serverHostname", "tlsCertPath", "tlsKeyLength", - "preventSleep" + "preventSleep", + "languageSync" }; static const char* logLevelNames[] = @@ -241,6 +242,7 @@ void AppConfig::loadSettings() m_ClientGroupChecked = loadSetting(kGroupClientCheck, false).toBool(); m_ServerHostname = loadSetting(kServerHostname).toString(); m_PreventSleep = loadSetting(kPreventSleep, false).toBool(); + m_LanguageSync = loadSetting(kLanguageSync, false).toBool(); //only change the serial key if the settings being loaded contains a key bool updateSerial = ConfigWriter::make() @@ -302,6 +304,7 @@ void AppConfig::saveSettings() setSetting(kGroupClientCheck, m_ClientGroupChecked); setSetting(kServerHostname, m_ServerHostname); setSetting(kPreventSleep, m_PreventSleep); + setSetting(kLanguageSync, m_LanguageSync); } m_unsavedChanges = false; diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index 5ff9d2e34..c936ec0ff 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -204,7 +204,8 @@ protected: kServerHostname, kTLSCertPath, kTLSKeyLength, - kPreventSleep + kPreventSleep, + kLanguageSync }; void setScreenName(const QString& s); diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 3fafbfe6a..992df46a4 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -807,6 +807,10 @@ bool MainWindow::clientArgs(QStringList& args, QString& app) args << "--log" << appConfig().logFilenameCmd(); } + if (appConfig().getLanguageSync()) { + args << "--sync-language"; + } + #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG) // check auto config first, if it is disabled or no server detected, diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index 6fd2579e8..347d4f88f 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -52,8 +52,7 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); enableControls(appConfig().isWritable()); - //temporary disable language sync logic - m_pCheckBoxLanguageSync->setVisible(false); + m_pCheckBoxLanguageSync->setVisible(m_pMainWindow->synergyType() == MainWindow::synergyClient); const auto& serveConfig = m_pMainWindow->serverConfig(); m_pLineEditScreenName->setValidator(new validators::ScreenNameValidator(m_pLineEditScreenName, m_pLabelNameError, (&serveConfig.screens()))); @@ -69,6 +68,7 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : connect(m_pSpinBoxPort, SIGNAL(valueChanged(int)), this, SLOT(onChange())); connect(m_pLineEditScreenName, SIGNAL(textEdited(QString)), this, SLOT(onChange())); connect(m_pComboElevate, SIGNAL(currentIndexChanged(int)), this, SLOT(onChange())); + connect(m_pCheckBoxLanguageSync, SIGNAL(clicked()), this, SLOT(onChange())); adjustSize(); } @@ -91,6 +91,7 @@ void SettingsDialog::accept() appConfig().setTLSCertPath(m_pLineEditCertificatePath->text()); appConfig().setTLSKeyLength(m_pComboBoxKeyLength->currentText()); appConfig().setCryptoEnabled(m_pCheckBoxEnableCrypto->isChecked()); + appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked()); appConfig().saveSettings(); QDialog::accept(); @@ -148,6 +149,7 @@ void SettingsDialog::loadFromConfig() { m_pCheckBoxMinimizeToTray->setChecked(appConfig().getMinimizeToTray()); m_pLineEditCertificatePath->setText(appConfig().getTLSCertPath()); m_pCheckBoxEnableCrypto->setChecked(m_appConfig.getCryptoEnabled()); + m_pCheckBoxLanguageSync->setChecked(m_appConfig.getLanguageSync()); //If the tls file exists test its key length if (QFile(appConfig().getTLSCertPath()).exists()) { @@ -338,7 +340,8 @@ bool SettingsDialog::isModified() || appConfig().getTLSCertPath() != m_pLineEditCertificatePath->text() || appConfig().getTLSKeyLength() != m_pComboBoxKeyLength->currentText() || appConfig().getCryptoEnabled() != m_pCheckBoxEnableCrypto->isChecked() - || appConfig().isSystemScoped() != m_isSystemAtStart) + || appConfig().isSystemScoped() != m_isSystemAtStart + || appConfig().getLanguageSync() != m_pCheckBoxLanguageSync->isChecked()) ); } @@ -359,6 +362,7 @@ void SettingsDialog::enableControls(bool enable) { m_pPushButtonBrowseCert->setEnabled(enable); m_pCheckBoxEnableCrypto->setEnabled(enable); m_labelAdminRightsMessage->setVisible(!enable); + m_pCheckBoxLanguageSync->setEnabled(enable); if (enable) { m_pLabelLogPath->setEnabled(m_pCheckBoxLogToFile->isChecked()); diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 2aed5ea4c..d38896134 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -19,6 +19,7 @@ #include "client/Client.h" #include "client/ServerProxy.h" +#include "synergy/AppUtil.h" #include "synergy/Screen.h" #include "synergy/FileChunk.h" #include "synergy/DropHelper.h" @@ -46,6 +47,7 @@ #include #include #include +#include // // Client @@ -304,16 +306,16 @@ Client::setClipboardDirty(ClipboardID, bool) } void -Client::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +Client::keyDown(KeyID id, KeyModifierMask mask, KeyButton button, const String &lang) { - m_screen->keyDown(id, mask, button); + m_screen->keyDown(id, mask, button, lang); } void Client::keyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) + SInt32 count, KeyButton button, const String& lang) { - m_screen->keyRepeat(id, mask, count, button); + m_screen->keyRepeat(id, mask, count, button, lang); } void @@ -721,7 +723,8 @@ void Client::handleHello(const Event&, void*) { SInt16 major, minor; - if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) { + String keyboardLayoutList(""); + if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor, &keyboardLayoutList)) { sendConnectionFailedEvent("Protocol error from server, check encryption settings"); cleanupTimer(); cleanupConnection(); @@ -746,10 +749,37 @@ Client::handleHello(const Event&, void*) } // say hello back + LOG((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion)); + String allKeyboardLayoutsStr; + for (const auto& layout : AppUtil::instance().getKeyboardLayoutList()) { + allKeyboardLayoutsStr += layout; + } + LOG((CLOG_DEBUG1 "say hello version %d.%d", helloBackMajor, helloBackMinor)); ProtocolUtil::writef(m_stream, kMsgHelloBack, helloBackMajor, - helloBackMinor, & m_name); + helloBackMinor, & m_name, &allKeyboardLayoutsStr); + + if(m_args.m_enableLangSync) { + std::vector missed; + std::vector supported; + AppUtil::getKeyboardLayoutsDiff(keyboardLayoutList, + AppUtil::instance().getKeyboardLayoutList(), + missed, supported); + + if(!supported.empty()) { + LOG((CLOG_DEBUG "Supported server languages: %s", AppUtil::joinStrVector(supported, ", ").c_str())); + } + + if(!missed.empty()) { + auto result = AppUtil::joinStrVector(missed, ", "); + AppUtil::instance().showNotification("Language synchronization error", + "These languages are required for client proper work: " + result); + } + } + else { + LOG((CLOG_DEBUG "Language sync logic is disabled.")); + } // now connected but waiting to complete handshake setupScreen(); diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index a8bf83772..c8171b273 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -162,9 +162,9 @@ public: virtual void setClipboard(ClipboardID, const IClipboard*); virtual void grabClipboard(ClipboardID); virtual void setClipboardDirty(ClipboardID, bool); - virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton, const String&); virtual void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton); + SInt32 count, KeyButton, const String& lang); virtual void keyUp(KeyID, KeyModifierMask, KeyButton); virtual void mouseDown(ButtonID); virtual void mouseUp(ButtonID); diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp index 2cf76e17b..37fb9bd0d 100644 --- a/src/lib/client/ServerProxy.cpp +++ b/src/lib/client/ServerProxy.cpp @@ -34,6 +34,7 @@ #include "base/XBase.h" #include +#include // // ServerProxy @@ -528,11 +529,13 @@ ServerProxy::enter() LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask)); // discard old compressed mouse motion, if any - m_compressMouse = false; - m_compressMouseRelative = false; - m_dxMouse = 0; - m_dyMouse = 0; - m_seqNum = seqNum; + m_compressMouse = false; + m_compressMouseRelative = false; + m_dxMouse = 0; + m_dyMouse = 0; + m_seqNum = seqNum; + m_serverLanguage = ""; + m_isUserNotifiedAboutLanguageSyncError = false; // forward m_client->enter(x, y, seqNum, static_cast(mask), false); @@ -603,8 +606,25 @@ ServerProxy::keyDown() // parse UInt16 id, mask, button; - ProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button); - LOG((CLOG_DEBUG1 "recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + String lang; + ProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button, &lang); + LOG((CLOG_DEBUG1 "recv key down id=0x%08x, mask=0x%04x, button=0x%04x, lang=\"%s\"", id, mask, button, lang.c_str())); + + if(m_serverLanguage != lang) { + m_isUserNotifiedAboutLanguageSyncError = false; + m_serverLanguage = lang; + } + + auto localList = AppUtil::instance().getKeyboardLayoutList(); + if(std::find(localList.begin(), localList.end(), lang) == localList.end()) { + if(!m_isUserNotifiedAboutLanguageSyncError) { + AppUtil::instance().showNotification("Language error", "Current server language is not installed on client!"); + m_isUserNotifiedAboutLanguageSyncError = true; + } + } + else { + m_isUserNotifiedAboutLanguageSyncError = false; + } // translate KeyID id2 = translateKey(static_cast(id)); @@ -615,7 +635,7 @@ ServerProxy::keyDown() LOG((CLOG_DEBUG1 "key down translated to id=0x%08x, mask=0x%04x", id2, mask2)); // forward - m_client->keyDown(id2, mask2, button); + m_client->keyDown(id2, mask2, button, lang); } void @@ -626,9 +646,10 @@ ServerProxy::keyRepeat() // parse UInt16 id, mask, count, button; + String lang; ProtocolUtil::readf(m_stream, kMsgDKeyRepeat + 4, - &id, &mask, &count, &button); - LOG((CLOG_DEBUG1 "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button)); + &id, &mask, &count, &button, &lang); + LOG((CLOG_DEBUG1 "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x, lang=\"%s\"", id, mask, count, button, lang.c_str())); // translate KeyID id2 = translateKey(static_cast(id)); @@ -639,7 +660,7 @@ ServerProxy::keyRepeat() LOG((CLOG_DEBUG1 "key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2)); // forward - m_client->keyRepeat(id2, mask2, count, button); + m_client->keyRepeat(id2, mask2, count, button, lang); } void diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h index e9bdae8bb..deb83d5a1 100644 --- a/src/lib/client/ServerProxy.h +++ b/src/lib/client/ServerProxy.h @@ -136,4 +136,6 @@ private: MessageParser m_parser; IEventQueue* m_events; + String m_serverLanguage = ""; + bool m_isUserNotifiedAboutLanguageSyncError = false; }; diff --git a/src/lib/platform/MSWindowsDesks.cpp b/src/lib/platform/MSWindowsDesks.cpp index 5bb4894c7..73ad36ed1 100644 --- a/src/lib/platform/MSWindowsDesks.cpp +++ b/src/lib/platform/MSWindowsDesks.cpp @@ -31,6 +31,7 @@ #include "base/TMethodEventJob.h" #include "base/TMethodJob.h" #include "base/IEventQueue.h" +#include "synergy/win32/AppUtilWindows.h" #include @@ -145,7 +146,7 @@ MSWindowsDesks::MSWindowsDesks( m_cursor = createBlankCursor(); m_deskClass = createDeskWindowClass(m_isPrimary); - m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); + m_keyLayout = AppUtilWindows::instance().getCurrentKeyboardLayout(); resetOptions(); } @@ -583,6 +584,9 @@ MSWindowsDesks::deskLeave(Desk* desk, HKL keyLayout) SetWindowPos(desk->m_window, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_SHOWWINDOW); + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); + // if not using low-level hooks we have to also activate the // window to ensure we don't lose keyboard focus. // FIXME -- see if this can be avoided. if so then always @@ -608,14 +612,12 @@ MSWindowsDesks::deskLeave(Desk* desk, HKL keyLayout) GetWindowThreadProcessId(desk->m_window, NULL); DWORD thatThread = GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); SetForegroundWindow(desk->m_window); AttachThreadInput(thatThread, thisThread, FALSE); } } - - // switch to requested keyboard layout - ActivateKeyboardLayout(keyLayout, 0); } else { // move hider window under the cursor center, raise, and show it diff --git a/src/lib/platform/MSWindowsKeyState.cpp b/src/lib/platform/MSWindowsKeyState.cpp index d693cbd24..bb8b4e352 100644 --- a/src/lib/platform/MSWindowsKeyState.cpp +++ b/src/lib/platform/MSWindowsKeyState.cpp @@ -577,8 +577,9 @@ static const Win32Modifiers s_modifiers[] = }; MSWindowsKeyState::MSWindowsKeyState( - MSWindowsDesks* desks, void* eventTarget, IEventQueue* events) : - KeyState(events), + MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, + std::vector layouts, bool isLangSyncEnabled) : + KeyState(events, std::move(layouts), isLangSyncEnabled), m_eventTarget(eventTarget), m_desks(desks), m_keyLayout(GetKeyboardLayout(0)), @@ -593,8 +594,9 @@ MSWindowsKeyState::MSWindowsKeyState( } MSWindowsKeyState::MSWindowsKeyState( - MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, synergy::KeyMap& keyMap) : - KeyState(events, keyMap), + MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, synergy::KeyMap& keyMap, + std::vector layouts, bool isLangSyncEnabled) : + KeyState(events, keyMap, std::move(layouts), isLangSyncEnabled), m_eventTarget(eventTarget), m_desks(desks), m_keyLayout(GetKeyboardLayout(0)), @@ -794,16 +796,16 @@ MSWindowsKeyState::sendKeyEvent(void* target, void MSWindowsKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button) + KeyButton button, const String& lang) { - KeyState::fakeKeyDown(id, mask, button); + KeyState::fakeKeyDown(id, mask, button, lang); } bool MSWindowsKeyState::fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) + SInt32 count, KeyButton button, const String& lang) { - return KeyState::fakeKeyRepeat(id, mask, count, button); + return KeyState::fakeKeyRepeat(id, mask, count, button, lang); } bool @@ -1331,8 +1333,10 @@ MSWindowsKeyState::setWindowGroup(SInt32 group) // XXX -- determine if m_groups[group] can be used with the system // character set. - PostMessage(targetWindow, WM_INPUTLANGCHANGEREQUEST, - sysCharSet ? 1 : 0, (LPARAM)m_groups[group]); + if(!PostMessage(targetWindow, WM_INPUTLANGCHANGEREQUEST, + sysCharSet ? 1 : 0, (LPARAM)m_groups[group])) { + LOG((CLOG_WARN "Failed to post change language message!")); + } // XXX -- use a short delay to let the target window process the message // before it sees the keyboard events. i'm not sure why this is diff --git a/src/lib/platform/MSWindowsKeyState.h b/src/lib/platform/MSWindowsKeyState.h index fcbd5d98b..ceef7fc4e 100644 --- a/src/lib/platform/MSWindowsKeyState.h +++ b/src/lib/platform/MSWindowsKeyState.h @@ -36,8 +36,10 @@ This class maps KeyIDs to keystrokes. */ class MSWindowsKeyState : public KeyState { public: - MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events); - MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, synergy::KeyMap& keyMap); + MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, + std::vector layouts, bool isLangSyncEnabled); + MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, synergy::KeyMap& keyMap, + std::vector layouts, bool isLangSyncEnabled); virtual ~MSWindowsKeyState(); //! @name manipulators @@ -136,9 +138,9 @@ public: // IKeyState overrides virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button); + KeyButton button, const String& lang); virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button); + SInt32 count, KeyButton button, const String& lang); virtual bool fakeCtrlAltDel(); virtual KeyModifierMask pollActiveModifiers() const; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index e616b7925..510e01906 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -145,7 +145,9 @@ MSWindowsScreen::MSWindowsScreen( new TMethodJob( this, &MSWindowsScreen::updateKeysCB), stopOnDeskSwitch); - m_keyState = new MSWindowsKeyState(m_desks, getEventTarget(), m_events); + m_keyState = new MSWindowsKeyState(m_desks, getEventTarget(), m_events, + AppUtil::instance().getKeyboardLayoutList(), + ClientApp::instance().args().m_enableLangSync); updateScreenShape(); m_class = createWindowClass(); @@ -331,9 +333,7 @@ MSWindowsScreen::leave() } // get keyboard layout of foreground window. we'll use this // keyboard layout for translating keys sent to clients. - HWND window = GetForegroundWindow(); - DWORD thread = GetWindowThreadProcessId(window, NULL); - m_keyLayout = GetKeyboardLayout(thread); + m_keyLayout = AppUtilWindows::instance().getCurrentKeyboardLayout(); // tell the key mapper about the keyboard layout m_keyState->setKeyLayout(m_keyLayout); @@ -844,17 +844,17 @@ MSWindowsScreen::updateKeys() void MSWindowsScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button) + KeyButton button, const String& lang) { - PlatformScreen::fakeKeyDown(id, mask, button); + PlatformScreen::fakeKeyDown(id, mask, button, lang); updateForceShowCursor(); } bool MSWindowsScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) + SInt32 count, KeyButton button, const String& lang) { - bool result = PlatformScreen::fakeKeyRepeat(id, mask, count, button); + bool result = PlatformScreen::fakeKeyRepeat(id, mask, count, button, lang); updateForceShowCursor(); return result; } @@ -1963,7 +1963,7 @@ MSWindowsScreen::getDraggingFilename() SWP_SHOWWINDOW); // TODO: fake these keys properly - fakeKeyDown(kKeyEscape, 8192, 1); + fakeKeyDown(kKeyEscape, 8192, 1, AppUtil::instance().getCurrentLanguageCode()); fakeKeyUp(1); fakeMouseButton(kButtonLeft, false); diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h index dbab41285..22c161c18 100644 --- a/src/lib/platform/MSWindowsScreen.h +++ b/src/lib/platform/MSWindowsScreen.h @@ -116,9 +116,9 @@ public: // IKeyState overrides virtual void updateKeys(); virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button); + KeyButton button, const String& lang); virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button); + SInt32 count, KeyButton button, const String& lang); virtual bool fakeKeyUp(KeyButton button); virtual void fakeAllKeysUp(); diff --git a/src/lib/platform/OSXKeyState.cpp b/src/lib/platform/OSXKeyState.cpp index 1a93f09ed..aa7ee6ede 100644 --- a/src/lib/platform/OSXKeyState.cpp +++ b/src/lib/platform/OSXKeyState.cpp @@ -134,14 +134,15 @@ static const KeyEntry s_controlKeys[] = { // OSXKeyState // -OSXKeyState::OSXKeyState(IEventQueue* events) : - KeyState(events) +OSXKeyState::OSXKeyState(IEventQueue* events, std::vector layouts, bool isLangSyncEnabled) : + KeyState(events, std::move(layouts), isLangSyncEnabled) { init(); } -OSXKeyState::OSXKeyState(IEventQueue* events, synergy::KeyMap& keyMap) : - KeyState(events, keyMap) +OSXKeyState::OSXKeyState(IEventQueue* events, synergy::KeyMap& keyMap, + std::vector layouts, bool isLangSyncEnabled) : + KeyState(events, keyMap, std::move(layouts), isLangSyncEnabled) { init(); } @@ -410,7 +411,7 @@ OSXKeyState::pollActiveGroup() const return i->second; } - LOG((CLOG_DEBUG "can't get the active group, use the first group instead")); + LOG((CLOG_WARN "can't get the active group, use the first group instead")); return 0; } @@ -601,13 +602,19 @@ OSXKeyState::fakeKey(const Keystroke& keystroke) case Keystroke::kGroup: { SInt32 group = keystroke.m_data.m_group.m_group; - if (keystroke.m_data.m_group.m_absolute) { - LOG((CLOG_DEBUG1 " group %d", group)); - setGroup(group); - } - else { - LOG((CLOG_DEBUG1 " group %+d", group)); - setGroup(getEffectiveGroup(pollActiveGroup(), group)); + if (!keystroke.m_data.m_group.m_restore) { + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", group)); + setGroup(group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", group)); + setGroup(getEffectiveGroup(pollActiveGroup(), group)); + } + + if(pollActiveGroup() != group) { + LOG((CLOG_WARN "Failed to set new keyboard layout!")); + } } break; } @@ -866,7 +873,29 @@ void OSXKeyState::setGroup(SInt32 group) { TISInputSourceRef keyboardLayout = (TISInputSourceRef)CFArrayGetValueAtIndex(m_groups.get(), group); - TISSetInputMethodKeyboardLayoutOverride(keyboardLayout); + if(!keyboardLayout) { + LOG((CLOG_WARN "Nedeed keyboard layout is null")); + return; + } + auto canBeSetted = (CFBooleanRef)TISGetInputSourceProperty(TISCopyCurrentKeyboardInputSource(), kTISPropertyInputSourceIsEnableCapable); + if(!canBeSetted) { + LOG((CLOG_WARN "Nedeed keyboard layout is disabled for programmatically selection")); + return; + } + + if(TISSelectInputSource(keyboardLayout) != noErr) { + LOG((CLOG_WARN "Failed to set nedeed keyboard layout")); + } + + LOG((CLOG_DEBUG1 "Keyboard layout change to %d", group)); + + //A minimal delay is needed after a group change because the + //keyboard key event often happens immediately after. + //Language (TIS) and event (CG) systems are not in the mutual + //event queue and without a delay the subsequent key press + //event could be applied before the keyboard layout would + //actually be changed. + ARCH->sleep(.01); } void diff --git a/src/lib/platform/OSXKeyState.h b/src/lib/platform/OSXKeyState.h index 67b81d568..00f8e53ac 100644 --- a/src/lib/platform/OSXKeyState.h +++ b/src/lib/platform/OSXKeyState.h @@ -36,8 +36,9 @@ class OSXKeyState : public KeyState { public: typedef std::vector KeyIDs; - OSXKeyState(IEventQueue* events); - OSXKeyState(IEventQueue* events, synergy::KeyMap& keyMap); + OSXKeyState(IEventQueue* events, std::vector layouts, bool isLangSyncEnabled); + OSXKeyState(IEventQueue* events, synergy::KeyMap& keyMap, + std::vector layouts, bool isLangSyncEnabled); virtual ~OSXKeyState(); //! @name modifiers diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index afb28d263..d5d6e83ae 100644 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -125,7 +125,9 @@ OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCurso try { m_screensaver = new OSXScreenSaver(m_events, getEventTarget()); - m_keyState = new OSXKeyState(m_events); + m_keyState = new OSXKeyState(m_events, + AppUtil::instance().getKeyboardLayoutList(), + ClientApp::instance().args().m_enableLangSync); if (App::instance().argsBase().m_preventSleep) { m_powerManager.disableSleep(); @@ -2113,7 +2115,7 @@ OSXScreen::getDraggingFilename() } // fake a escape key down and up then left mouse button up - fakeKeyDown(kKeyEscape, 8192, 1); + fakeKeyDown(kKeyEscape, 8192, 1, AppUtil::instance().getCurrentLanguageCode()); fakeKeyUp(1); fakeMouseButton(kButtonLeft, false); } diff --git a/src/lib/platform/XWindowsKeyState.cpp b/src/lib/platform/XWindowsKeyState.cpp index f9dc722d7..70a5e6902 100644 --- a/src/lib/platform/XWindowsKeyState.cpp +++ b/src/lib/platform/XWindowsKeyState.cpp @@ -16,12 +16,19 @@ * along with this program. If not, see . */ +#ifndef __APPLE__ +#include +#endif + #include "platform/XWindowsKeyState.h" #include "platform/XWindowsUtil.h" #include "base/Log.h" #include "base/String.h" #include "common/stdmap.h" +#include "synergy/AppUtil.h" +#include "synergy/ClientApp.h" +#include "synergy/ClientArgs.h" #include #include @@ -43,7 +50,7 @@ static const size_t ModifiersFromXDefaultSize = 32; XWindowsKeyState::XWindowsKeyState( Display* display, bool useXKB, IEventQueue* events) : - KeyState(events), + KeyState(events, AppUtil::instance().getKeyboardLayoutList(), ClientApp::instance().args().m_enableLangSync), m_display(display), m_modifierFromX(ModifiersFromXDefaultSize) { @@ -53,7 +60,7 @@ XWindowsKeyState::XWindowsKeyState( XWindowsKeyState::XWindowsKeyState( Display* display, bool useXKB, IEventQueue* events, synergy::KeyMap& keyMap) : - KeyState(events, keyMap), + KeyState(events, keyMap, AppUtil::instance().getKeyboardLayoutList(), ClientApp::instance().args().m_enableLangSync), m_display(display), m_modifierFromX(ModifiersFromXDefaultSize) { @@ -82,7 +89,7 @@ XWindowsKeyState::init(Display* display, bool useXKB) m_xkb = NULL; } #endif - setActiveGroup(kGroupPollAndSet); + setActiveGroup(kGroupPoll); } void @@ -195,9 +202,13 @@ XWindowsKeyState::pollActiveGroup() const #if HAVE_XKB_EXTENSION if (m_xkb != NULL) { XkbStateRec state; + XSync(m_display, False); if (XkbGetState(m_display, XkbUseCoreKbd, &state) == Success) { + LOG((CLOG_WARN "LANGUAGE_DEBUG Poll result %d", state.group)); return state.group; } + + LOG((CLOG_WARN "Failed to poll active group")); } #endif return 0; @@ -238,12 +249,55 @@ XWindowsKeyState::getKeyMap(synergy::KeyMap& keyMap) updateKeysymMap(keyMap); } +bool XWindowsKeyState::setCurrentLanguageWithDBus(SInt32 group) const +{ + QString service = "org.gnome.Shell"; + QString path = "/org/gnome/Shell"; + QString method = "Eval"; + QString param = "imports.ui.status.keyboard.getInputSourceManager().inputSources[" + + QString::number(group) + "].activate()"; + + auto bus = QDBusConnection::sessionBus(); + if (!bus.isConnected()) { + return false; + } + + QDBusInterface screenSaverInterface(service, path, service, bus); + if (!screenSaverInterface.isValid()) { + LOG((CLOG_WARN "Keyboard layout fail. DBus interface is invalid")); + return false; + } + + QDBusPendingReply reply = screenSaverInterface.call(method, param); + reply.waitForFinished(); + + if(!reply.isValid()) { + auto qerror = reply.error(); + LOG((CLOG_WARN "Keyboard layout fail %s : %s", qerror.name().toStdString(), qerror.message().toStdString())); + return true; + } + + if(reply.isError()) { + LOG((CLOG_WARN "Keyboard layout fail. reply contains error")); + return true; + } + + if(!reply.argumentAt<0>() || reply.argumentAt<1>() != QString("")) { + LOG((CLOG_WARN "Keyboard layout fail. Reply is unexpected!")); + return true; + } + + return true; +} + void XWindowsKeyState::fakeKey(const Keystroke& keystroke) { switch (keystroke.m_type) { case Keystroke::kButton: - LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + LOG((CLOG_WARN "LANGUAGE_DEBUG %03x (%08x) %s %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, + keystroke.m_data.m_button.m_press ? "down" : "up", + keystroke.m_data.m_button.m_repeat ? "Repeat" : "New")); if (keystroke.m_data.m_button.m_repeat) { int c = keystroke.m_data.m_button.m_button; int i = (c >> 3); @@ -260,8 +314,18 @@ XWindowsKeyState::fakeKey(const Keystroke& keystroke) break; case Keystroke::kGroup: + if (keystroke.m_data.m_group.m_restore) { + break; + } + if (keystroke.m_data.m_group.m_absolute) { - LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); + LOG((CLOG_WARN "LANGUAGE_DEBUG group %d", keystroke.m_data.m_group.m_group)); + +#ifndef __APPLE__ + if(setCurrentLanguageWithDBus(keystroke.m_data.m_group.m_group)) { + break; + } +#endif #if HAVE_XKB_EXTENSION if (m_xkb != NULL) { if (XkbLockGroup(m_display, XkbUseCoreKbd, @@ -276,7 +340,13 @@ XWindowsKeyState::fakeKey(const Keystroke& keystroke) } } else { - LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); + LOG((CLOG_WARN "LANGUAGE_DEBUG group %+d", keystroke.m_data.m_group.m_group)); + +#ifndef __APPLE__ + if(setCurrentLanguageWithDBus(keystroke.m_data.m_group.m_group)) { + break; + } +#endif #if HAVE_XKB_EXTENSION if (m_xkb != NULL) { if (XkbLockGroup(m_display, XkbUseCoreKbd, @@ -291,6 +361,7 @@ XWindowsKeyState::fakeKey(const Keystroke& keystroke) LOG((CLOG_DEBUG1 " ignored")); } } + break; } XFlush(m_display); diff --git a/src/lib/platform/XWindowsKeyState.h b/src/lib/platform/XWindowsKeyState.h index 9b389794c..da1183ce8 100644 --- a/src/lib/platform/XWindowsKeyState.h +++ b/src/lib/platform/XWindowsKeyState.h @@ -123,6 +123,12 @@ private: int getEffectiveGroup(KeyCode, int group) const; UInt32 getGroupFromState(unsigned int state) const; + //! Create and send language change request to \p group by DBus interface + /*! + Returns the existance of nedeed DBus interface. + */ + bool setCurrentLanguageWithDBus(SInt32 group) const; + static void remapKeyModifiers(KeyID, SInt32, synergy::KeyMap::KeyItem&, void*); diff --git a/src/lib/server/BaseClientProxy.h b/src/lib/server/BaseClientProxy.h index 0e6556b71..80b42eec9 100644 --- a/src/lib/server/BaseClientProxy.h +++ b/src/lib/server/BaseClientProxy.h @@ -74,9 +74,9 @@ public: virtual void setClipboard(ClipboardID, const IClipboard*) = 0; virtual void grabClipboard(ClipboardID) = 0; virtual void setClipboardDirty(ClipboardID, bool) = 0; - virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton, const String&) = 0; virtual void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton) = 0; + SInt32 count, KeyButton, const String& lang) = 0; virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; virtual void mouseDown(ButtonID) = 0; virtual void mouseUp(ButtonID) = 0; diff --git a/src/lib/server/ClientProxy.h b/src/lib/server/ClientProxy.h index 283f70be7..7e4e20f8d 100644 --- a/src/lib/server/ClientProxy.h +++ b/src/lib/server/ClientProxy.h @@ -75,9 +75,9 @@ public: void setClipboard(ClipboardID, const IClipboard*) override = 0; void grabClipboard(ClipboardID) override = 0; void setClipboardDirty(ClipboardID, bool) override = 0; - void keyDown(KeyID, KeyModifierMask, KeyButton) override = 0; + void keyDown(KeyID, KeyModifierMask, KeyButton, const String&) override = 0; void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton) override = 0; + SInt32 count, KeyButton, const String& lang) override = 0; void keyUp(KeyID, KeyModifierMask, KeyButton) override = 0; void mouseDown(ButtonID) override = 0; void mouseUp(ButtonID) override = 0; diff --git a/src/lib/server/ClientProxy1_0.cpp b/src/lib/server/ClientProxy1_0.cpp index 5aee1278f..20da3515f 100644 --- a/src/lib/server/ClientProxy1_0.cpp +++ b/src/lib/server/ClientProxy1_0.cpp @@ -296,7 +296,7 @@ ClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) } void -ClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) +ClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton, const String&) { LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); ProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask); @@ -304,7 +304,7 @@ ClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) void ClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, - SInt32 count, KeyButton) + SInt32 count, KeyButton, const String&) { LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count)); ProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count); diff --git a/src/lib/server/ClientProxy1_0.h b/src/lib/server/ClientProxy1_0.h index ce122332b..408a24ad8 100644 --- a/src/lib/server/ClientProxy1_0.h +++ b/src/lib/server/ClientProxy1_0.h @@ -51,9 +51,9 @@ public: void setClipboard(ClipboardID, const IClipboard*) override; void grabClipboard(ClipboardID) override; void setClipboardDirty(ClipboardID, bool) override; - void keyDown(KeyID, KeyModifierMask, KeyButton) override; + void keyDown(KeyID, KeyModifierMask, KeyButton, const String&) override; void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton) override; + SInt32 count, KeyButton, const String&) override; void keyUp(KeyID, KeyModifierMask, KeyButton) override; void mouseDown(ButtonID) override; void mouseUp(ButtonID) override; diff --git a/src/lib/server/ClientProxy1_1.cpp b/src/lib/server/ClientProxy1_1.cpp index e445d322d..249f2f206 100644 --- a/src/lib/server/ClientProxy1_1.cpp +++ b/src/lib/server/ClientProxy1_1.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include "synergy/AppUtil.h" #include "server/ClientProxy1_1.h" #include "synergy/ProtocolUtil.h" @@ -39,18 +40,18 @@ ClientProxy1_1::~ClientProxy1_1() } void -ClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +ClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String& lang) { - LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); - ProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button); + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x, lang=\"%s\"", getName().c_str(), key, mask, button, lang.c_str())); + ProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button, &lang); } void ClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask, - SInt32 count, KeyButton button) + SInt32 count, KeyButton button, const String& lang) { - LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button)); - ProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button); + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x, lang=\"%s\"", getName().c_str(), key, mask, count, button, lang.c_str())); + ProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button, &lang); } void diff --git a/src/lib/server/ClientProxy1_1.h b/src/lib/server/ClientProxy1_1.h index 22cda44f5..8e026ed64 100644 --- a/src/lib/server/ClientProxy1_1.h +++ b/src/lib/server/ClientProxy1_1.h @@ -27,8 +27,8 @@ public: ~ClientProxy1_1(); // IClient overrides - virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton, const String&); virtual void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton); + SInt32 count, KeyButton, const String&); virtual void keyUp(KeyID, KeyModifierMask, KeyButton); }; diff --git a/src/lib/server/ClientProxy1_4.cpp b/src/lib/server/ClientProxy1_4.cpp index 52f105df8..7cfa2042e 100644 --- a/src/lib/server/ClientProxy1_4.cpp +++ b/src/lib/server/ClientProxy1_4.cpp @@ -42,15 +42,15 @@ ClientProxy1_4::~ClientProxy1_4() } void -ClientProxy1_4::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +ClientProxy1_4::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String& lang) { - ClientProxy1_3::keyDown(key, mask, button); + ClientProxy1_3::keyDown(key, mask, button, lang); } void -ClientProxy1_4::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button) +ClientProxy1_4::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button, const String& lang) { - ClientProxy1_3::keyRepeat(key, mask, count, button); + ClientProxy1_3::keyRepeat(key, mask, count, button, lang); } void diff --git a/src/lib/server/ClientProxy1_4.h b/src/lib/server/ClientProxy1_4.h index 8b08bc137..6265a7dd5 100644 --- a/src/lib/server/ClientProxy1_4.h +++ b/src/lib/server/ClientProxy1_4.h @@ -37,8 +37,9 @@ public: //@} // IClient overrides - virtual void keyDown(KeyID key, KeyModifierMask mask, KeyButton button); - virtual void keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button); + virtual void keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String&); + virtual void keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button, + const String& lang); virtual void keyUp(KeyID key, KeyModifierMask mask, KeyButton button); virtual void keepAlive(); diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp index 3b7bfc802..8809a8386 100644 --- a/src/lib/server/ClientProxyUnknown.cpp +++ b/src/lib/server/ClientProxyUnknown.cpp @@ -29,14 +29,17 @@ #include "server/ClientProxy1_7.h" #include "synergy/protocol_types.h" #include "synergy/ProtocolUtil.h" +#include "synergy/AppUtil.h" #include "synergy/XSynergy.h" #include "io/IStream.h" #include "io/XIO.h" #include "base/Log.h" -#include "base/String.h" #include "base/IEventQueue.h" #include "base/TMethodEventJob.h" +#include +#include + // // ClientProxyUnknown // @@ -57,9 +60,14 @@ ClientProxyUnknown::ClientProxyUnknown(synergy::IStream* stream, double timeout, addStreamHandlers(); LOG((CLOG_DEBUG1 "saying hello")); + String allKeyboardLayoutsStr; + for (const auto& layout : AppUtil::instance().getKeyboardLayoutList()) { + allKeyboardLayoutsStr += layout; + } ProtocolUtil::writef(m_stream, kMsgHello, kProtocolMajorVersion, - kProtocolMinorVersion); + kProtocolMinorVersion, + &allKeyboardLayoutsStr); } ClientProxyUnknown::~ClientProxyUnknown() @@ -178,6 +186,7 @@ ClientProxyUnknown::handleData(const Event&, void*) LOG((CLOG_DEBUG1 "parsing hello reply")); String name(""); + String keyboardLayoutList(""); try { // limit the maximum length of the hello UInt32 n = m_stream->getSize(); @@ -189,7 +198,7 @@ ClientProxyUnknown::handleData(const Event&, void*) // parse the reply to hello SInt16 major, minor; if (!ProtocolUtil::readf(m_stream, kMsgHelloBack, - &major, &minor, &name)) { + &major, &minor, &name, &keyboardLayoutList)) { throw XBadClient(); } @@ -245,6 +254,22 @@ ClientProxyUnknown::handleData(const Event&, void*) throw XIncompatibleClient(major, minor); } + std::vector missed; + std::vector supported; + AppUtil::getKeyboardLayoutsDiff(keyboardLayoutList, + AppUtil::instance().getKeyboardLayoutList(), + missed, supported); + + if(!supported.empty()) { + LOG((CLOG_DEBUG "Supported client languages: %s", AppUtil::joinStrVector(supported, ", ").c_str())); + } + + if(!missed.empty()) { + auto result = AppUtil::joinStrVector(missed, ", "); + AppUtil::instance().showNotification("Language synchronization error", + "These languages are required for server proper work: " + result); + } + // the proxy is created and now proxy now owns the stream LOG((CLOG_DEBUG1 "created proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); m_stream = NULL; diff --git a/src/lib/server/PrimaryClient.cpp b/src/lib/server/PrimaryClient.cpp index f4e20ba9a..9c56b0812 100644 --- a/src/lib/server/PrimaryClient.cpp +++ b/src/lib/server/PrimaryClient.cpp @@ -184,7 +184,7 @@ PrimaryClient::setClipboardDirty(ClipboardID id, bool dirty) } void -PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String&) { if (m_fakeInputCount > 0) { // XXX -- don't forward keystrokes to primary screen for now @@ -196,7 +196,7 @@ PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) } void -PrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton) +PrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton, const String&) { // ignore } diff --git a/src/lib/server/PrimaryClient.h b/src/lib/server/PrimaryClient.h index a4d6fcc97..aad050ce8 100644 --- a/src/lib/server/PrimaryClient.h +++ b/src/lib/server/PrimaryClient.h @@ -131,9 +131,9 @@ public: void setClipboard(ClipboardID, const IClipboard*) override; void grabClipboard(ClipboardID) override; void setClipboardDirty(ClipboardID, bool) override; - void keyDown(KeyID, KeyModifierMask, KeyButton) override; + void keyDown(KeyID, KeyModifierMask, KeyButton, const String&) override; void keyRepeat(KeyID, KeyModifierMask, - SInt32 count, KeyButton) override; + SInt32 count, KeyButton, const String&) override; void keyUp(KeyID, KeyModifierMask, KeyButton) override; void mouseDown(ButtonID) override; void mouseUp(ButtonID) override; diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index 4e0715a87..f8ddd12a8 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -33,6 +33,7 @@ #include "synergy/KeyState.h" #include "synergy/Screen.h" #include "synergy/PacketStreamFilter.h" +#include "synergy/AppUtil.h" #include "net/TCPSocket.h" #include "net/IDataSocket.h" #include "net/IListenSocket.h" @@ -100,7 +101,7 @@ Server::Server( m_waitDragInfoThread(true), m_args(args) { - // must have a primary client and it must have a canonical name + // must have a primary client and it must have a canonical name assert(m_primaryClient != NULL); assert(config.isScreen(primaryClient->getName())); assert(m_screen != NULL); @@ -1334,7 +1335,8 @@ Server::handleKeyDownEvent(const Event& event, void*) { IPlatformScreen::KeyInfo* info = static_cast(event.getData()); - onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens); + auto lang = AppUtil::instance().getCurrentLanguageCode(); + onKeyDown(info->m_key, info->m_mask, info->m_button, lang, info->m_screens); } void @@ -1350,7 +1352,8 @@ Server::handleKeyRepeatEvent(const Event& event, void*) { IPlatformScreen::KeyInfo* info = static_cast(event.getData()); - onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button); + auto lang = AppUtil::instance().getCurrentLanguageCode(); + onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button, lang); } void @@ -1666,15 +1669,15 @@ Server::onScreensaver(bool activated) } void -Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, +Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, const String& lang, const char* screens) { - LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); + LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x lang=%s", id, mask, button, lang.c_str())); assert(m_active != NULL); // relay if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) { - m_active->keyDown(id, mask, button); + m_active->keyDown(id, mask, button, lang); } else { if (!screens && m_keyboardBroadcasting) { @@ -1686,7 +1689,7 @@ Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, for (ClientList::const_iterator index = m_clients.begin(); index != m_clients.end(); ++index) { if (IKeyState::KeyInfo::contains(screens, index->first)) { - index->second->keyDown(id, mask, button); + index->second->keyDown(id, mask, button, lang); } } } @@ -1721,13 +1724,13 @@ Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, void Server::onKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) + SInt32 count, KeyButton button, const String& lang) { - LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); + LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x lang=\"%s\"", id, mask, count, button, lang.c_str())); assert(m_active != NULL); // relay - m_active->keyRepeat(id, mask, count, button); + m_active->keyRepeat(id, mask, count, button, lang); } void diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index d7072aba2..1628e4b50 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -325,11 +325,11 @@ private: void onClipboardChanged(BaseClientProxy* sender, ClipboardID id, UInt32 seqNum); void onScreensaver(bool activated); - void onKeyDown(KeyID, KeyModifierMask, KeyButton, + void onKeyDown(KeyID, KeyModifierMask, KeyButton, const String&, const char* screens); void onKeyUp(KeyID, KeyModifierMask, KeyButton, const char* screens); - void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton); + void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton, const String&); void onMouseDown(ButtonID); void onMouseUp(ButtonID); bool onMouseMovePrimary(SInt32 x, SInt32 y); diff --git a/src/lib/synergy/AppUtil.cpp b/src/lib/synergy/AppUtil.cpp index c1f9bb56f..df352e84d 100644 --- a/src/lib/synergy/AppUtil.cpp +++ b/src/lib/synergy/AppUtil.cpp @@ -18,6 +18,8 @@ #include "synergy/AppUtil.h" +#include "algorithm" + AppUtil* AppUtil::s_instance = nullptr; AppUtil::AppUtil() : @@ -50,3 +52,22 @@ AppUtil::instance() assert(s_instance != nullptr); return *s_instance; } + +void +AppUtil::getKeyboardLayoutsDiff(const String& remoteCompressedLanguages, + const std::vector& localLanguages, + std::vector& missedLang, + std::vector& supportedLang) +{ + missedLang.clear(); + supportedLang.clear(); + for(int i = 0; i <= (int)remoteCompressedLanguages.size() - 2; i +=2) { + auto layout = remoteCompressedLanguages.substr(i, 2); + if (std::find(localLanguages.begin(), localLanguages.end(), layout) == localLanguages.end()) { + missedLang.push_back(layout); + } + else { + supportedLang.push_back(layout); + } + } +} diff --git a/src/lib/synergy/AppUtil.h b/src/lib/synergy/AppUtil.h index dedece902..78c0aeb3b 100644 --- a/src/lib/synergy/AppUtil.h +++ b/src/lib/synergy/AppUtil.h @@ -33,6 +33,10 @@ public: static AppUtil& instance(); static void exitAppStatic(int code) { instance().exitApp(code); } virtual void beforeAppExit() {} + static void getKeyboardLayoutsDiff(const String& remoteCompressedLanguages, + const std::vector& localLanguages, + std::vector& missedLang, + std::vector& supportedLang); private: IApp* m_app; diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index df97d2ad0..4d8dd5e34 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -111,6 +111,9 @@ ArgParser::parseClientArgs(lib::synergy::ClientArgs& args, int argc, const char* // define scroll args.m_yscroll = atoi(argv[++i]); } + else if (isArg(i, argc, argv, nullptr, "--sync-language")) { + args.m_enableLangSync = true; + } else { if (i + 1 == argc) { args.m_synergyAddress = argv[i]; diff --git a/src/lib/synergy/ArgsBase.h b/src/lib/synergy/ArgsBase.h index 207c31345..dba7a7651 100644 --- a/src/lib/synergy/ArgsBase.h +++ b/src/lib/synergy/ArgsBase.h @@ -57,7 +57,6 @@ namespace lib { String m_profileDirectory; /// @brief The profile DIR to use for the application String m_pluginDirectory; /// @brief //TODO Plugins? Get set in ARCH but doesn't seem to get used String m_tlsCertFile; /// @brief Contains the location of the TLS certificate file - bool m_enableLangSync = true; /// @brief Should keyboard input be in same language as on server bool m_preventSleep = false; /// @brief Stop this computer from sleeping #if SYSAPI_WIN32 diff --git a/src/lib/synergy/CMakeLists.txt b/src/lib/synergy/CMakeLists.txt index 7bb4aed17..af6ecfbef 100644 --- a/src/lib/synergy/CMakeLists.txt +++ b/src/lib/synergy/CMakeLists.txt @@ -28,6 +28,9 @@ elseif (UNIX) file(GLOB arch_sources "unix/*.cpp") endif() +include_directories("../../../ext/pugixml/src") +list(APPEND arch_sources "../../../ext/pugixml/src/pugixml.cpp") + list(APPEND sources ${arch_sources}) list(APPEND headers ${arch_headers}) diff --git a/src/lib/synergy/ClientApp.cpp b/src/lib/synergy/ClientApp.cpp index 57857c3c5..f11bac721 100644 --- a/src/lib/synergy/ClientApp.cpp +++ b/src/lib/synergy/ClientApp.cpp @@ -126,6 +126,7 @@ ClientApp::help() buffer_size, "Usage: %s" " [--yscroll ]" + " [--sync-language]" WINAPI_ARG HELP_SYS_ARGS HELP_COMMON_ARGS @@ -138,6 +139,7 @@ ClientApp::help() HELP_SYS_INFO " --yscroll defines the vertical scrolling delta, which is\n" " 120 by default.\n" + " --sync-language set this parameter to enable language synchronization.\n" HELP_COMMON_INFO_2 "\n" "* marks defaults.\n" diff --git a/src/lib/synergy/ClientArgs.h b/src/lib/synergy/ClientArgs.h index c43a726f0..444ae4767 100644 --- a/src/lib/synergy/ClientArgs.h +++ b/src/lib/synergy/ClientArgs.h @@ -32,11 +32,12 @@ namespace lib { ~ClientArgs() override; public: - int m_yscroll = 0; + int m_yscroll = 0; + bool m_enableLangSync = false; /// @brief Should keyboard input be in same language as on server private: }; } } -#endif //SYNERGY_CORE_CLIENTARGS_H \ No newline at end of file +#endif //SYNERGY_CORE_CLIENTARGS_H diff --git a/src/lib/synergy/IAppUtil.h b/src/lib/synergy/IAppUtil.h index 657bbfc82..a35fc1ca6 100644 --- a/src/lib/synergy/IAppUtil.h +++ b/src/lib/synergy/IAppUtil.h @@ -29,5 +29,21 @@ public: virtual int run(int argc, char** argv) = 0; virtual void beforeAppExit() = 0; virtual void startNode() = 0; + virtual std::vector getKeyboardLayoutList() = 0; + virtual String getCurrentLanguageCode() = 0; virtual void showNotification(const String& title, const String& text) const = 0; + + static String joinStrVector(const std::vector& source, const String& delim) + { + String result; + for(size_t i = 0; i < source.size(); i++) + { + result += source[i]; + + if(i < source.size() - 1) { + result += delim; + } + } + return std::move(result); + } }; diff --git a/src/lib/synergy/IClient.h b/src/lib/synergy/IClient.h index 0a296bf33..ad889f37d 100644 --- a/src/lib/synergy/IClient.h +++ b/src/lib/synergy/IClient.h @@ -87,7 +87,7 @@ public: synthesize an up or repeat for the same client key synthesized by keyDown(). */ - virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; + virtual void keyDown(KeyID id, KeyModifierMask, KeyButton, const String&) = 0; //! Notify of key repeat /*! @@ -95,7 +95,7 @@ public: \c count times. If possible match the given modifier mask. */ virtual void keyRepeat(KeyID id, KeyModifierMask, - SInt32 count, KeyButton) = 0; + SInt32 count, KeyButton, const String& lang) = 0; //! Notify of key release /*! diff --git a/src/lib/synergy/IKeyState.h b/src/lib/synergy/IKeyState.h index 249f4e8f2..df995fa88 100644 --- a/src/lib/synergy/IKeyState.h +++ b/src/lib/synergy/IKeyState.h @@ -94,14 +94,14 @@ public: Synthesizes a key press event and updates the key state. */ virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button) = 0; + KeyButton button, const String& lang) = 0; //! Fake a key repeat /*! Synthesizes a key repeat event and updates the key state. */ virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) = 0; + SInt32 count, KeyButton button, const String& lang) = 0; //! Fake a key release /*! diff --git a/src/lib/synergy/IPlatformScreen.h b/src/lib/synergy/IPlatformScreen.h index bde73f735..2d95903a7 100644 --- a/src/lib/synergy/IPlatformScreen.h +++ b/src/lib/synergy/IPlatformScreen.h @@ -194,9 +194,9 @@ public: virtual void updateKeyState() = 0; virtual void setHalfDuplexMask(KeyModifierMask) = 0; virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button) = 0; + KeyButton button, const String& lang) = 0; virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) = 0; + SInt32 count, KeyButton button, const String& lang) = 0; virtual bool fakeKeyUp(KeyButton button) = 0; virtual void fakeAllKeysUp() = 0; virtual bool fakeCtrlAltDel() = 0; diff --git a/src/lib/synergy/ISO639Table.h b/src/lib/synergy/ISO639Table.h new file mode 100644 index 000000000..f45b812c7 --- /dev/null +++ b/src/lib/synergy/ISO639Table.h @@ -0,0 +1,232 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "base/String.h" + +//copy from +//https://www.loc.gov/standards/iso639-2/php/code_list.php +//10.06.2021 +//first param - ISO 639-2, second param - 639-1 +const std::vector> ISO_Table = { + std::make_pair("aar", "aa"), + std::make_pair("abk", "ab"), + std::make_pair("afr", "af"), + std::make_pair("aka", "ak"), + std::make_pair("sqi", "sq"), + std::make_pair("amh", "am"), + std::make_pair("ara", "ar"), + std::make_pair("arg", "an"), + std::make_pair("hye", "hy"), + std::make_pair("asm", "as"), + std::make_pair("ava", "av"), + std::make_pair("ave", "ae"), + std::make_pair("aym", "ay"), + std::make_pair("aze", "az"), + std::make_pair("bak", "ba"), + std::make_pair("bam", "bm"), + std::make_pair("eus", "eu"), + std::make_pair("bel", "be"), + std::make_pair("ben", "bn"), + std::make_pair("bih", "bh"), + std::make_pair("bis", "bi"), + std::make_pair("bod", "bo"), + std::make_pair("bos", "bs"), + std::make_pair("bre", "br"), + std::make_pair("bul", "bg"), + std::make_pair("mya", "my"), + std::make_pair("cat", "ca"), + std::make_pair("ces", "cs"), + std::make_pair("cha", "ch"), + std::make_pair("che", "ce"), + std::make_pair("zho", "zh"), + std::make_pair("chu", "cu"), + std::make_pair("chv", "cv"), + std::make_pair("cor", "kw"), + std::make_pair("cos", "co"), + std::make_pair("cre", "cr"), + std::make_pair("cym", "cy"), + std::make_pair("ces", "cs"), + std::make_pair("dan", "da"), + std::make_pair("deu", "de"), + std::make_pair("div", "dv"), + std::make_pair("nld", "nl"), + std::make_pair("dzo", "dz"), + std::make_pair("ell", "el"), + std::make_pair("eng", "en"), + std::make_pair("epo", "eo"), + std::make_pair("est", "et"), + std::make_pair("eus", "eu"), + std::make_pair("ewe", "ee"), + std::make_pair("fao", "fo"), + std::make_pair("fas", "fa"), + std::make_pair("fij", "fj"), + std::make_pair("fin", "fi"), + std::make_pair("fra", "fr"), + std::make_pair("fra", "fr"), + std::make_pair("fry", "fy"), + std::make_pair("ful", "ff"), + std::make_pair("kat", "ka"), + std::make_pair("deu", "de"), + std::make_pair("gla", "gd"), + std::make_pair("gle", "ga"), + std::make_pair("glg", "gl"), + std::make_pair("glv", "gv"), + std::make_pair("ell", "el"), + std::make_pair("grn", "gn"), + std::make_pair("guj", "gu"), + std::make_pair("hat", "ht"), + std::make_pair("hau", "ha"), + std::make_pair("heb", "he"), + std::make_pair("her", "hz"), + std::make_pair("hin", "hi"), + std::make_pair("hmo", "ho"), + std::make_pair("hrv", "hr"), + std::make_pair("hun", "hu"), + std::make_pair("hye", "hy"), + std::make_pair("ibo", "ig"), + std::make_pair("isl", "is"), + std::make_pair("ido", "io"), + std::make_pair("iii", "ii"), + std::make_pair("iku", "iu"), + std::make_pair("ile", "ie"), + std::make_pair("ina", "ia"), + std::make_pair("ind", "id"), + std::make_pair("ipk", "ik"), + std::make_pair("isl", "is"), + std::make_pair("ita", "it"), + std::make_pair("jav", "jv"), + std::make_pair("jpn", "ja"), + std::make_pair("kal", "kl"), + std::make_pair("kan", "kn"), + std::make_pair("kas", "ks"), + std::make_pair("kat", "ka"), + std::make_pair("kau", "kr"), + std::make_pair("kaz", "kk"), + std::make_pair("khm", "km"), + std::make_pair("kik", "ki"), + std::make_pair("kin", "rw"), + std::make_pair("kir", "ky"), + std::make_pair("kom", "kv"), + std::make_pair("kon", "kg"), + std::make_pair("kor", "ko"), + std::make_pair("kua", "kj"), + std::make_pair("kur", "ku"), + std::make_pair("lao", "lo"), + std::make_pair("lat", "la"), + std::make_pair("lav", "lv"), + std::make_pair("lim", "li"), + std::make_pair("lin", "ln"), + std::make_pair("lit", "lt"), + std::make_pair("ltz", "lb"), + std::make_pair("lub", "lu"), + std::make_pair("lug", "lg"), + std::make_pair("mkd", "mk"), + std::make_pair("mah", "mh"), + std::make_pair("mal", "ml"), + std::make_pair("mri", "mi"), + std::make_pair("mar", "mr"), + std::make_pair("msa", "ms"), + std::make_pair("mkd", "mk"), + std::make_pair("mlg", "mg"), + std::make_pair("mlt", "mt"), + std::make_pair("mon", "mn"), + std::make_pair("mri", "mi"), + std::make_pair("msa", "ms"), + std::make_pair("mya", "my"), + std::make_pair("nau", "na"), + std::make_pair("nav", "nv"), + std::make_pair("nbl", "nr"), + std::make_pair("nde", "nd"), + std::make_pair("ndo", "ng"), + std::make_pair("nep", "ne"), + std::make_pair("nld", "nl"), + std::make_pair("nno", "nn"), + std::make_pair("nob", "nb"), + std::make_pair("nor", "no"), + std::make_pair("nya", "ny"), + std::make_pair("oci", "oc"), + std::make_pair("oji", "oj"), + std::make_pair("ori", "or"), + std::make_pair("orm", "om"), + std::make_pair("oss", "os"), + std::make_pair("pan", "pa"), + std::make_pair("fas", "fa"), + std::make_pair("pli", "pi"), + std::make_pair("pol", "pl"), + std::make_pair("por", "pt"), + std::make_pair("pus", "ps"), + std::make_pair("que", "qu"), + std::make_pair("roh", "rm"), + std::make_pair("ron", "ro"), + std::make_pair("ron", "ro"), + std::make_pair("run", "rn"), + std::make_pair("rus", "ru"), + std::make_pair("sag", "sg"), + std::make_pair("san", "sa"), + std::make_pair("sin", "si"), + std::make_pair("slk", "sk"), + std::make_pair("slk", "sk"), + std::make_pair("slv", "sl"), + std::make_pair("sme", "se"), + std::make_pair("smo", "sm"), + std::make_pair("sna", "sn"), + std::make_pair("snd", "sd"), + std::make_pair("som", "so"), + std::make_pair("sot", "st"), + std::make_pair("spa", "es"), + std::make_pair("sqi", "sq"), + std::make_pair("srd", "sc"), + std::make_pair("srp", "sr"), + std::make_pair("ssw", "ss"), + std::make_pair("sun", "su"), + std::make_pair("swa", "sw"), + std::make_pair("swe", "sv"), + std::make_pair("tah", "ty"), + std::make_pair("tam", "ta"), + std::make_pair("tat", "tt"), + std::make_pair("tel", "te"), + std::make_pair("tgk", "tg"), + std::make_pair("tgl", "tl"), + std::make_pair("tha", "th"), + std::make_pair("bod", "bo"), + std::make_pair("tir", "ti"), + std::make_pair("ton", "to"), + std::make_pair("tsn", "tn"), + std::make_pair("tso", "ts"), + std::make_pair("tuk", "tk"), + std::make_pair("tur", "tr"), + std::make_pair("twi", "tw"), + std::make_pair("uig", "ug"), + std::make_pair("ukr", "uk"), + std::make_pair("urd", "ur"), + std::make_pair("uzb", "uz"), + std::make_pair("ven", "ve"), + std::make_pair("vie", "vi"), + std::make_pair("vol", "vo"), + std::make_pair("cym", "cy"), + std::make_pair("wln", "wa"), + std::make_pair("wol", "wo"), + std::make_pair("xho", "xh"), + std::make_pair("yid", "yi"), + std::make_pair("yor", "yo"), + std::make_pair("zha", "za"), + std::make_pair("zho", "zh"), + std::make_pair("zul", "zu"), +}; diff --git a/src/lib/synergy/KeyMap.cpp b/src/lib/synergy/KeyMap.cpp index 824636133..1677804ce 100644 --- a/src/lib/synergy/KeyMap.cpp +++ b/src/lib/synergy/KeyMap.cpp @@ -264,9 +264,10 @@ KeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const + bool isAutoRepeat, const String& lang) const { - LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x", id, id, desiredMask, currentState)); + LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x, group: %d", + id, id, desiredMask, currentState, group)); // handle group change if (id == kKeyNextGroup) { @@ -295,7 +296,7 @@ KeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, case kKeyNumLock: case kKeyScrollLock: item = mapModifierKey(keys, id, group, activeModifiers, - currentState, desiredMask, isAutoRepeat); + currentState, desiredMask, isAutoRepeat, lang); break; case kKeySetModifiers: @@ -318,11 +319,11 @@ KeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, default: if (isCommand(desiredMask)) { item = mapCommandKey(keys, id, group, activeModifiers, - currentState, desiredMask, isAutoRepeat); + currentState, desiredMask, isAutoRepeat, lang); } else { item = mapCharacterKey(keys, id, group, activeModifiers, - currentState, desiredMask, isAutoRepeat); + currentState, desiredMask, isAutoRepeat, lang); } break; } @@ -333,6 +334,11 @@ KeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, return item; } +void KeyMap::setLanguageData(std::vector layouts) +{ + m_keyboardLayouts = std::move(layouts); +} + SInt32 KeyMap::getNumGroups() const { @@ -512,7 +518,7 @@ KeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group, ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const + bool isAutoRepeat, const String& lang) const { static const KeyModifierMask s_overrideModifiers = 0xffffu; @@ -574,7 +580,7 @@ KeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group, // add the key if (!keysForKeyItem(*keyItem, newGroup, newModifiers, newState, desiredMask, - s_overrideModifiers, isAutoRepeat, keys)) { + s_overrideModifiers, isAutoRepeat, keys, lang)) { LOG((CLOG_DEBUG1 "can't map key")); keys.clear(); return NULL; @@ -588,11 +594,6 @@ KeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group, return NULL; } - // add keystrokes to restore group - if (newGroup != group) { - keys.push_back(Keystroke(group, true, true)); - } - // save new modifiers activeModifiers = newModifiers; currentState = newState; @@ -605,7 +606,7 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const + bool isAutoRepeat, const String& lang) const { // find KeySym in table KeyIDMap::const_iterator i = m_keyIDMap.find(id); @@ -613,7 +614,6 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, // unknown key LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); - return NULL; } const KeyGroupTable& keyGroupTable = i->second; @@ -655,7 +655,7 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, for (size_t j = 0; j < itemList.size(); ++j) { if (!keysForKeyItem(itemList[j], newGroup, newModifiers, newState, desiredMask, - 0, isAutoRepeat, keys)) { + 0, isAutoRepeat, keys, lang)) { LOG((CLOG_DEBUG1 "can't map key")); keys.clear(); return NULL; @@ -670,11 +670,6 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, return NULL; } - // add keystrokes to restore group - if (newGroup != group) { - keys.push_back(Keystroke(group, true, true)); - } - // save new modifiers activeModifiers = newModifiers; currentState = newState; @@ -682,15 +677,29 @@ KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, return &keyItem; } +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(std::distance(m_keyboardLayouts.begin(), it)); + } + else { + LOG((CLOG_DEBUG2 "could not found requested language, guessing that correct is %d", group)); + } + keys.push_back(Keystroke(group, true, false)); +} + const KeyMap::KeyItem* KeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group, ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const + bool isAutoRepeat, const String& lang) const { return mapCharacterKey(keys, id, group, activeModifiers, - currentState, desiredMask, isAutoRepeat); + currentState, desiredMask, isAutoRepeat, lang); } SInt32 @@ -759,17 +768,16 @@ KeyMap::keysForKeyItem(const KeyItem& keyItem, SInt32& group, KeyModifierMask& currentState, KeyModifierMask desiredState, KeyModifierMask overrideModifiers, bool isAutoRepeat, - Keystrokes& keystrokes) const + Keystrokes& keystrokes, + const String& lang) const { static const KeyModifierMask s_notRequiredMask = KeyModifierAltGr | KeyModifierNumLock | KeyModifierScrollLock; // add keystrokes to adjust the group - - if (App::instance().argsBase().m_enableLangSync && - group != keyItem.m_group) { + if (group != keyItem.m_group) { group = keyItem.m_group; - keystrokes.push_back(Keystroke(group, true, false)); + addGroupToKeystroke(keystrokes, group, lang); } EKeystroke type; diff --git a/src/lib/synergy/KeyMap.h b/src/lib/synergy/KeyMap.h index 4264c5ad5..d12fd755f 100644 --- a/src/lib/synergy/KeyMap.h +++ b/src/lib/synergy/KeyMap.h @@ -224,7 +224,9 @@ public: ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const; + bool isAutoRepeat, const String& lang) const; + + void setLanguageData(std::vector layouts); //! Get number of groups /*! @@ -375,7 +377,8 @@ private: ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const; + bool isAutoRepeat, + const String& lang) const; // maps a character key. a character key is trying to synthesize a // particular KeyID and isn't entirely concerned with the modifiers @@ -385,15 +388,17 @@ private: ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const; + bool isAutoRepeat, + const String& lang) const; // maps a modifier key - const KeyItem* mapModifierKey(Keystrokes& keys, + const KeyItem* mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group, ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat) const; + bool isAutoRepeat, + const String& lang) const; // returns the index into \p entryList of the KeyItemList requiring // the fewest modifier changes between \p currentState and @@ -419,7 +424,8 @@ private: KeyModifierMask desiredState, KeyModifierMask overrideModifiers, bool isAutoRepeat, - Keystrokes& keystrokes) const; + Keystrokes& keystrokes, + const String& lang) const; // fills \p keystrokes with the keys to synthesize the modifiers // in \p desiredModifiers from the active modifiers listed in @@ -459,6 +465,8 @@ private: // Initialize key name/id maps static void initKeyNameMaps(); + void addGroupToKeystroke(Keystrokes& keys, SInt32& group, const String& lang) const; + // not implemented KeyMap(const KeyMap&); KeyMap& operator=(const KeyMap&); @@ -505,6 +513,9 @@ private: // dummy KeyItem for changing modifiers KeyItem m_modifierKeyItem; + //Language sync data + std::vector m_keyboardLayouts; + // parsing/formatting tables static NameToKeyMap* s_nameToKeyMap; static NameToModifierMap* s_nameToModifierMap; diff --git a/src/lib/synergy/KeyState.cpp b/src/lib/synergy/KeyState.cpp index 8b5452699..63a293051 100644 --- a/src/lib/synergy/KeyState.cpp +++ b/src/lib/synergy/KeyState.cpp @@ -18,6 +18,8 @@ #include "synergy/KeyState.h" #include "base/Log.h" +#include "synergy/ClientApp.h" +#include "synergy/ClientArgs.h" #include #include @@ -383,23 +385,27 @@ static const KeyID s_numpadTable[] = { // KeyState // -KeyState::KeyState(IEventQueue* events) : +KeyState::KeyState(IEventQueue* events, std::vector layouts, bool isLangSyncEnabled) : IKeyState(events), m_keyMapPtr(new synergy::KeyMap()), m_keyMap(*m_keyMapPtr), m_mask(0), - m_events(events) + m_events(events), + m_isLangSyncEnabled(isLangSyncEnabled) { + m_keyMap.setLanguageData(std::move(layouts)); init(); } -KeyState::KeyState(IEventQueue* events, synergy::KeyMap& keyMap) : +KeyState::KeyState(IEventQueue* events, synergy::KeyMap& keyMap, std::vector layouts, bool isLangSyncEnabled) : IKeyState(events), m_keyMapPtr(0), m_keyMap(keyMap), m_mask(0), - m_events(events) + m_events(events), + m_isLangSyncEnabled(isLangSyncEnabled) { + m_keyMap.setLanguageData(std::move(layouts)); init(); } @@ -553,13 +559,13 @@ KeyState::setHalfDuplexMask(KeyModifierMask mask) } void -KeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) +KeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID, const String& lang) { // if this server key is already down then this is probably a // mis-reported autorepeat. serverID &= kButtonMask; if (m_serverKeys[serverID] != 0) { - fakeKeyRepeat(id, mask, 1, serverID); + fakeKeyRepeat(id, mask, 1, serverID, lang); return; } @@ -569,13 +575,13 @@ KeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) return; } - // get keys for key press Keystrokes keys; ModifierToKeys oldActiveModifiers = m_activeModifiers; const synergy::KeyMap::KeyItem* keyItem = - m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, - getActiveModifiersRValue(), mask, false); - if (keyItem == NULL) { + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, false, lang); + + if (keyItem == nullptr) { // a media key won't be mapped on mac, so we need to fake it in a // special way if (id == kKeyAudioDown || id == kKeyAudioUp || @@ -586,10 +592,10 @@ KeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) LOG((CLOG_DEBUG1 "emulating media key")); fakeMediaKey(id); } - + return; } - + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); if (localID != 0) { @@ -607,8 +613,9 @@ KeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) bool KeyState::fakeKeyRepeat( KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton serverID) + SInt32 count, KeyButton serverID, const String& lang) { + LOG((CLOG_DEBUG2 "fakeKeyRepeat")); serverID &= kButtonMask; // if we haven't seen this button go down then ignore it @@ -622,7 +629,7 @@ KeyState::fakeKeyRepeat( ModifierToKeys oldActiveModifiers = m_activeModifiers; const synergy::KeyMap::KeyItem* keyItem = m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, - getActiveModifiersRValue(), mask, true); + getActiveModifiersRValue(), mask, true, lang); if (keyItem == NULL) { return false; } @@ -874,13 +881,18 @@ KeyState::fakeKeys(const Keystrokes& keys, UInt32 count) // note -- k is now on the first non-repeat key after the // repeat keys, exactly where we'd like to continue from. } - else { + else if (k->m_type != Keystroke::kGroup || + (!k->m_data.m_group.m_restore && m_isLangSyncEnabled)) { // send event fakeKey(*k); // next key ++k; } + else { + LOG((CLOG_DEBUG1 "Skipping keystroke (because language sync is disabled)")); + ++k; + } } } diff --git a/src/lib/synergy/KeyState.h b/src/lib/synergy/KeyState.h index 469ecb576..a817bacbf 100644 --- a/src/lib/synergy/KeyState.h +++ b/src/lib/synergy/KeyState.h @@ -28,8 +28,8 @@ platform specific methods. */ class KeyState : public IKeyState { public: - KeyState(IEventQueue* events); - KeyState(IEventQueue* events, synergy::KeyMap& keyMap); + KeyState(IEventQueue* events, std::vector layouts, bool isLangSyncEnabled); + KeyState(IEventQueue* events, synergy::KeyMap& keyMap, std::vector layouts, bool isLangSyncEnabled); virtual ~KeyState(); //! @name manipulators @@ -70,9 +70,9 @@ public: void updateKeyState() override; void setHalfDuplexMask(KeyModifierMask) override; void fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button) override; + KeyButton button, const String& lang) override; bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) override; + SInt32 count, KeyButton button, const String& lang) override; bool fakeKeyUp(KeyButton button) override; void fakeAllKeysUp() override; bool fakeMediaKey(KeyID id) override; @@ -233,4 +233,6 @@ private: KeyButton m_serverKeys[kNumButtons]; IEventQueue* m_events; + + bool m_isLangSyncEnabled; }; diff --git a/src/lib/synergy/PlatformScreen.cpp b/src/lib/synergy/PlatformScreen.cpp index e94b77551..51b374f91 100644 --- a/src/lib/synergy/PlatformScreen.cpp +++ b/src/lib/synergy/PlatformScreen.cpp @@ -53,16 +53,16 @@ PlatformScreen::setHalfDuplexMask(KeyModifierMask mask) void PlatformScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button) + KeyButton button, const String& lang) { - getKeyState()->fakeKeyDown(id, mask, button); + getKeyState()->fakeKeyDown(id, mask, button, lang); } bool PlatformScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button) + SInt32 count, KeyButton button, const String& lang) { - return getKeyState()->fakeKeyRepeat(id, mask, count, button); + return getKeyState()->fakeKeyRepeat(id, mask, count, button, lang); } bool diff --git a/src/lib/synergy/PlatformScreen.h b/src/lib/synergy/PlatformScreen.h index 609131aa5..2a24a56bd 100644 --- a/src/lib/synergy/PlatformScreen.h +++ b/src/lib/synergy/PlatformScreen.h @@ -63,9 +63,9 @@ public: virtual void updateKeyState(); virtual void setHalfDuplexMask(KeyModifierMask); virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, - KeyButton button); + KeyButton button, const String&); virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, - SInt32 count, KeyButton button); + SInt32 count, KeyButton button, const String& lang); virtual bool fakeKeyUp(KeyButton button); virtual void fakeAllKeysUp(); virtual bool fakeCtrlAltDel(); diff --git a/src/lib/synergy/ProtocolUtil.cpp b/src/lib/synergy/ProtocolUtil.cpp index 0ce3df840..e990eb20f 100644 --- a/src/lib/synergy/ProtocolUtil.cpp +++ b/src/lib/synergy/ProtocolUtil.cpp @@ -279,13 +279,11 @@ ProtocolUtil::getLength(const char* fmt, va_list args) case 's': assert(len == 0); len = (UInt32)(va_arg(args, String*))->size() + 4; - (void)va_arg(args, UInt8*); break; case 'S': assert(len == 0); len = va_arg(args, UInt32) + 4; - (void)va_arg(args, UInt8*); break; case '%': diff --git a/src/lib/synergy/Screen.cpp b/src/lib/synergy/Screen.cpp index 89b943851..b9fe223ec 100644 --- a/src/lib/synergy/Screen.cpp +++ b/src/lib/synergy/Screen.cpp @@ -191,7 +191,7 @@ Screen::screensaver(bool) const } void -Screen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +Screen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button, const String &lang) { // check for ctrl+alt+del emulation if (id == kKeyDelete && @@ -202,15 +202,15 @@ Screen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) return; } } - m_screen->fakeKeyDown(id, mask, button); + m_screen->fakeKeyDown(id, mask, button, lang); } void Screen::keyRepeat(KeyID id, - KeyModifierMask mask, SInt32 count, KeyButton button) + KeyModifierMask mask, SInt32 count, KeyButton button, const String& lang) { assert(!m_isPrimary); - m_screen->fakeKeyRepeat(id, mask, count, button); + m_screen->fakeKeyRepeat(id, mask, count, button, lang); } void diff --git a/src/lib/synergy/Screen.h b/src/lib/synergy/Screen.h index 34d5efa89..f79a50db8 100644 --- a/src/lib/synergy/Screen.h +++ b/src/lib/synergy/Screen.h @@ -127,7 +127,7 @@ public: synthesize an up or repeat for the same client key synthesized by keyDown(). */ - void keyDown(KeyID id, KeyModifierMask, KeyButton); + void keyDown(KeyID id, KeyModifierMask, KeyButton, const String&); //! Notify of key repeat /*! @@ -135,7 +135,7 @@ public: \c count times. If possible match the given modifier mask. */ void keyRepeat(KeyID id, KeyModifierMask, - SInt32 count, KeyButton); + SInt32 count, KeyButton, const String& lang); //! Notify of key release /*! diff --git a/src/lib/synergy/X11LayoutsParser.cpp b/src/lib/synergy/X11LayoutsParser.cpp new file mode 100644 index 000000000..e9a6e1159 --- /dev/null +++ b/src/lib/synergy/X11LayoutsParser.cpp @@ -0,0 +1,233 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "base/Log.h" +#include "synergy/X11LayoutsParser.h" +#include "ISO639Table.h" +#include "pugixml.hpp" + +bool +X11LayoutsParser::readXMLConfigItemElem(const pugi::xml_node* root, std::vector& langList) +{ + auto configItemElem = root->child("configItem"); + if(!configItemElem) { + LOG((CLOG_WARN "Failed to read \"configItem\" in xml file")); + return false; + } + + langList.emplace_back(); + auto nameElem = configItemElem.child("name"); + if(nameElem) { + langList.back().name = nameElem.text().as_string(); + } + + auto languageListElem = configItemElem.child("languageList"); + if(languageListElem) { + for (pugi::xml_node isoElem : languageListElem.children("iso639Id")) { + langList.back().layoutBaseISO639_2.emplace_back(isoElem.text().as_string()); + } + } + + return true; +} + +std::vector +X11LayoutsParser::getAllLanguageData(const String& pathToEvdevFile) +{ + std::vector allCodes; + pugi::xml_document doc; + if(!doc.load_file(pathToEvdevFile.c_str())) { + LOG((CLOG_WARN "Failed to open %s", pathToEvdevFile.c_str())); + return allCodes; + } + + auto xkbConfigElem = doc.child("xkbConfigRegistry"); + if(!xkbConfigElem) { + LOG((CLOG_WARN "Failed to read xkbConfigRegistry in %s", pathToEvdevFile.c_str())); + return allCodes; + } + + auto layoutListElem = xkbConfigElem.child("layoutList"); + if(!layoutListElem) { + LOG((CLOG_WARN "Failed to read layoutList in %s", pathToEvdevFile.c_str())); + return allCodes; + } + + for (pugi::xml_node layoutElem : layoutListElem.children("layout")) { + if (!readXMLConfigItemElem(&layoutElem, allCodes)) { + continue; + } + + auto variantListElem = layoutElem.child("variantList"); + if (variantListElem) { + for (pugi::xml_node variantElem : variantListElem.children("variant")) { + readXMLConfigItemElem(&variantElem, allCodes.back().variants); + } + } + } + + return allCodes; +} + +void +X11LayoutsParser::appendVectorUniq(const std::vector& source, std::vector& dst) { + for(const auto& elem : source) { + if(std::find_if(dst.begin(), dst.end(), [elem](const String& s) {return s == elem;}) == dst.end()) { + dst.push_back(elem); + } + } +}; + +void +X11LayoutsParser::convertLayoutToISO639_2(const String& pathToEvdevFile, + bool needToReloadEvdev, + std::vector layoutNames, + std::vector layoutVariantNames, + std::vector& iso639_2Codes) +{ + if(layoutNames.size() != layoutVariantNames.size()) { + LOG((CLOG_WARN "Error in language layout or language layout variants list")); + return; + } + + static std::vector allLang; + if(allLang.empty() || needToReloadEvdev) { + allLang = getAllLanguageData(pathToEvdevFile); + } + for (size_t i = 0; i < layoutNames.size(); i++) { + auto langIter = std::find_if(allLang.begin(), allLang.end(), [n=layoutNames[i]](const Lang& l) {return l.name == n;}); + if(langIter == allLang.end()) { + LOG((CLOG_WARN "Language \"%s\" is unknown", layoutNames[i].c_str())); + continue; + } + + const std::vector* toCopy = nullptr; + if(layoutVariantNames[i].empty()) { + toCopy = &langIter->layoutBaseISO639_2; + } + else { + auto langVariantIter = std::find_if(langIter->variants.begin(), langIter->variants.end(), + [n=layoutVariantNames[i]](const Lang& l) {return l.name == n;}); + if(langVariantIter == langIter->variants.end()) { + LOG((CLOG_WARN "Variant \"%s\" of language \"%s\" is unknown", layoutVariantNames[i].c_str(), layoutNames[i].c_str())); + continue; + } + + if(langVariantIter->layoutBaseISO639_2.empty()) { + toCopy = &langIter->layoutBaseISO639_2; + } + else { + toCopy = &langVariantIter->layoutBaseISO639_2; + } + } + + if(toCopy) { + appendVectorUniq(*toCopy, iso639_2Codes); + } + } +} + +std::vector +X11LayoutsParser::getX11LanguageList(const String& pathToKeyboardFile, const String& pathToEvdevFile) +{ + std::vector layoutNames; + std::vector layoutVariantNames; + std::vector iso639_2Codes; + + parseKeyboardFile(pathToKeyboardFile, layoutNames, layoutVariantNames); + convertLayoutToISO639_2(pathToEvdevFile, true, layoutNames, layoutVariantNames, iso639_2Codes); + return convertISO639_2ToISO639_1(iso639_2Codes); +} + +String +X11LayoutsParser::convertLayotToISO(const String& pathToEvdevFile, const String& layoutLangCode, bool needToReloadFiles) +{ + std::vector iso639_2Codes; + convertLayoutToISO639_2(pathToEvdevFile, needToReloadFiles, {layoutLangCode}, {""}, iso639_2Codes); + if(iso639_2Codes.empty()) { + LOG((CLOG_WARN "Failed to convert layout lang code! Code: \"%s\"", layoutLangCode.c_str())); + return ""; + } + + auto iso639_1Codes = convertISO639_2ToISO639_1(iso639_2Codes); + if(iso639_1Codes.empty()) { + LOG((CLOG_WARN "Failed to convert ISO639/2 lang code to ISO639/1!")); + return ""; + } + + return *iso639_1Codes.begin(); +} + +void +X11LayoutsParser::parseKeyboardFile(const String& pathToKeyboardFile, + std::vector& layoutNames, + std::vector& layoutVariantNames) +{ + layoutNames.clear(); + layoutVariantNames.clear(); + + std::ifstream file(pathToKeyboardFile); + if (!file.is_open()) { + LOG((CLOG_WARN "x11 keyboard layouts file \"%s\" is missed.", pathToKeyboardFile.c_str())); + return; + } + + const String layoutLinePrefix = "XKBLAYOUT="; + const String variantLinePrefix = "XKBVARIANT="; + + auto splitLine = [](std::vector& splitted, const String& line, char delim) { + std::stringstream ss(line); + String code; + while(ss.good()) { + getline(ss, code, delim); + splitted.push_back(code); + } + }; + + String line; + while (std::getline(file, line)) { + if (line.rfind(layoutLinePrefix, 0) == 0) { + splitLine(layoutNames, line.substr(layoutLinePrefix.size()), ','); + } + else if (line.rfind(variantLinePrefix, 0) == 0) { + splitLine(layoutVariantNames, line.substr(variantLinePrefix.size()), ','); + } + } +} + +std::vector +X11LayoutsParser::convertISO639_2ToISO639_1(const std::vector& iso639_2Codes) +{ + std::vector result; + for (const auto& isoCode : iso639_2Codes) { + const auto& tableIter = std::find_if(ISO_Table.begin(), ISO_Table.end(), + [&isoCode](const std::pair& c) {return c.first == isoCode;}); + if(tableIter == ISO_Table.end()) { + LOG((CLOG_WARN "ISO 639-2 code \"%s\" is missed in table", isoCode.c_str())); + continue; + } + + appendVectorUniq({tableIter->second}, result); + } + + return result; +} diff --git a/src/lib/synergy/X11LayoutsParser.h b/src/lib/synergy/X11LayoutsParser.h new file mode 100644 index 000000000..d7c4e24f6 --- /dev/null +++ b/src/lib/synergy/X11LayoutsParser.h @@ -0,0 +1,58 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2002 Chris Schoeneman + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include "base/String.h" + +namespace pugi +{ + class xml_node; +} + +class X11LayoutsParser { +public: + static std::vector getX11LanguageList(const String& pathToKeyboardFile, const String& pathToEvdevFile); + static String convertLayotToISO(const String& pathToEvdevFile, const String& layoutLangCode, bool needToReloadFiles = false); + +private: + struct Lang { + String name = ""; + std::vector layoutBaseISO639_2; + std::vector variants; + }; + + static bool readXMLConfigItemElem(const pugi::xml_node* root, + std::vector& langList); + + static std::vector getAllLanguageData(const String& pathToEvdevFile); + + static void appendVectorUniq(const std::vector& source, + std::vector& dst); + + static void convertLayoutToISO639_2(const String& pathToEvdevFile, + bool needToReloadEvdev, + std::vector layoutNames, + std::vector layoutVariantNames, + std::vector& iso639_2Codes); + + static void parseKeyboardFile(const String& pathToKeyboardFile, + std::vector& layoutNames, + std::vector& layoutVariantNames); + + static std::vector convertISO639_2ToISO639_1(const std::vector& iso639_2Codes); +}; diff --git a/src/lib/synergy/protocol_types.cpp b/src/lib/synergy/protocol_types.cpp index 80f2fe4ec..2685456cb 100644 --- a/src/lib/synergy/protocol_types.cpp +++ b/src/lib/synergy/protocol_types.cpp @@ -18,8 +18,8 @@ #include "synergy/protocol_types.h" -const char* const kMsgHello = "Synergy%2i%2i"; -const char* const kMsgHelloBack = "Synergy%2i%2i%s"; +const char* const kMsgHello = "Synergy%2i%2i%s"; +const char* const kMsgHelloBack = "Synergy%2i%2i%s%s"; const char* const kMsgCNoop = "CNOP"; const char* const kMsgCClose = "CBYE"; const char* const kMsgCEnter = "CINN%2i%2i%4i%2i"; @@ -29,9 +29,9 @@ const char* const kMsgCScreenSaver = "CSEC%1i"; const char* const kMsgCResetOptions = "CROP"; const char* const kMsgCInfoAck = "CIAK"; const char* const kMsgCKeepAlive = "CALV"; -const char* const kMsgDKeyDown = "DKDN%2i%2i%2i"; +const char* const kMsgDKeyDown = "DKDN%2i%2i%2i%s"; const char* const kMsgDKeyDown1_0 = "DKDN%2i%2i"; -const char* const kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"; +const char* const kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i%s"; const char* const kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"; const char* const kMsgDKeyUp = "DKUP%2i%2i%2i"; const char* const kMsgDKeyUp1_0 = "DKUP%2i%2i"; diff --git a/src/lib/synergy/protocol_types.h b/src/lib/synergy/protocol_types.h index 4fec0ac37..67339543d 100644 --- a/src/lib/synergy/protocol_types.h +++ b/src/lib/synergy/protocol_types.h @@ -102,13 +102,14 @@ enum EDataReceived { // say hello to client; primary -> secondary // $1 = protocol major version number supported by server. $2 = -// protocol minor version number supported by server. +// protocol minor version number supported by server. $3 = server +// keyboard layout list. extern const char* const kMsgHello; // respond to hello from server; secondary -> primary // $1 = protocol major version number supported by client. $2 = // protocol minor version number supported by client. $3 = client -// name. +// name. $4 = client language list extern const char* const kMsgHelloBack; @@ -177,7 +178,7 @@ extern const char* const kMsgCKeepAlive; // // key pressed: primary -> secondary -// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton, $4 = languageCode // the KeyButton identifies the physical key on the primary used to // generate this key. the secondary should note the KeyButton along // with the physical key it uses to generate the key press. on @@ -187,6 +188,8 @@ extern const char* const kMsgCKeepAlive; // the press. this can happen with combining (dead) keys or if // the keyboard layouts are not identical and the user releases // a modifier key before releasing the modified key. +// languageCode is parameter which helps client to react on unknwon +// language letters extern const char* const kMsgDKeyDown; // key pressed 1.0: same as above but without KeyButton @@ -194,6 +197,7 @@ extern const char* const kMsgDKeyDown1_0; // key auto-repeat: primary -> secondary // $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats, $4 = KeyButton +// $5 =language code extern const char* const kMsgDKeyRepeat; // key auto-repeat 1.0: same as above but without KeyButton diff --git a/src/lib/synergy/unix/AppUtilUnix.cpp b/src/lib/synergy/unix/AppUtilUnix.cpp index 067583c5c..9b46f444c 100644 --- a/src/lib/synergy/unix/AppUtilUnix.cpp +++ b/src/lib/synergy/unix/AppUtilUnix.cpp @@ -18,6 +18,16 @@ #include "synergy/unix/AppUtilUnix.h" #include "synergy/ArgsBase.h" +#include + +#if WINAPI_XWINDOWS +#include "synergy/X11LayoutsParser.h" +#include +#elif WINAPI_CARBON +#include +#else +#error Platform not supported. +#endif #include "base/Log.h" #include "base/log_outputters.h" @@ -51,6 +61,114 @@ AppUtilUnix::startNode() app().startNode(); } +std::vector +AppUtilUnix::getKeyboardLayoutList() +{ + std::vector layoutLangCodes; + +#if WINAPI_XWINDOWS + layoutLangCodes = X11LayoutsParser::getX11LanguageList("/etc/default/keyboard", "/usr/share/X11/xkb/rules/evdev.xml"); +#elif WINAPI_CARBON + CFStringRef keys[] = { kTISPropertyInputSourceCategory }; + CFStringRef values[] = { kTISCategoryKeyboardInputSource }; + CFDictionaryRef dict = CFDictionaryCreate(NULL, (const void **)keys, (const void **)values, 1, NULL, NULL); + CFArrayRef kbds = TISCreateInputSourceList(dict, false); + + for (CFIndex i = 0; i < CFArrayGetCount(kbds); ++i) { + TISInputSourceRef keyboardLayout = (TISInputSourceRef)CFArrayGetValueAtIndex(kbds, i); + auto layoutLanguages = (CFArrayRef)TISGetInputSourceProperty(keyboardLayout, kTISPropertyInputSourceLanguages); + char temporaryCString[128] = {0}; + for(CFIndex index = 0; index < CFArrayGetCount(layoutLanguages) && layoutLanguages; index++) { + auto languageCode = (CFStringRef)CFArrayGetValueAtIndex(layoutLanguages, index); + if(!languageCode || + !CFStringGetCString(languageCode, temporaryCString, 128, kCFStringEncodingUTF8)) { + continue; + } + + std::string langCode(temporaryCString); + if(langCode.size() == 2 && + std::find(layoutLangCodes.begin(), layoutLangCodes.end(), langCode) == layoutLangCodes.end()){ + layoutLangCodes.push_back(langCode); + } + + //Save only first language code + break; + } + } +#endif + + return layoutLangCodes; +} + +String +AppUtilUnix::getCurrentLanguageCode() +{ + String result = ""; +#if WINAPI_XWINDOWS + + auto display = XOpenDisplay(nullptr); + if (!display) { + LOG((CLOG_WARN "Failed to open x11 default display")); + return result; + } + + auto kbdDescr= XkbAllocKeyboard(); + if (!kbdDescr) { + LOG((CLOG_WARN "Failed to get x11 keyboard description")); + return result; + } + XkbGetNames(display, XkbSymbolsNameMask, kbdDescr); + + Atom symNameAtom = kbdDescr->names->symbols; + auto rawLayouts = std::string(XGetAtomName(display, symNameAtom)); + + XkbStateRec state; + XkbGetState(display, XkbUseCoreKbd, &state); + auto nedeedGroupIndex = static_cast(state.group); + + size_t groupIdx = 0; + size_t groupStartI = 0; + for(size_t strI = 0; strI < rawLayouts.size(); strI++) { + if(rawLayouts[strI] != '+') { + continue; + } + + auto group = rawLayouts.substr(groupStartI, strI - groupStartI); + if(group.find("group", 0, 5) == std::string::npos && + group.find("inet", 0, 4) == std::string::npos && + group.find("pc", 0, 2) == std::string::npos) { + if(nedeedGroupIndex == groupIdx) { + result = group.substr(0, std::min(group.find('(', 0), group.find(':', 0))); + break; + } + groupIdx++; + } + + groupStartI = strI + 1; + } + + XFree(kbdDescr); + XkbFreeNames(kbdDescr, XkbSymbolsNameMask, true); + XCloseDisplay(display); + + result = X11LayoutsParser::convertLayotToISO("/usr/share/X11/xkb/rules/evdev.xml", result); + +#elif WINAPI_CARBON + auto layoutLanguages = (CFArrayRef)TISGetInputSourceProperty(TISCopyCurrentKeyboardInputSource(), kTISPropertyInputSourceLanguages); + char temporaryCString[128] = {0}; + for(CFIndex index = 0; index < CFArrayGetCount(layoutLanguages) && layoutLanguages; index++) { + auto languageCode = (CFStringRef)CFArrayGetValueAtIndex(layoutLanguages, index); + if(!languageCode || !CFStringGetCString(languageCode, temporaryCString, 128, kCFStringEncodingUTF8)) { + continue; + } + + result = std::string(temporaryCString); + break; + } +#endif + return result; +} + void AppUtilUnix::showNotification(const String & title, const String & text) const { diff --git a/src/lib/synergy/unix/AppUtilUnix.h b/src/lib/synergy/unix/AppUtilUnix.h index 63f5007c0..99c668781 100644 --- a/src/lib/synergy/unix/AppUtilUnix.h +++ b/src/lib/synergy/unix/AppUtilUnix.h @@ -31,5 +31,7 @@ public: int run(int argc, char** argv); void startNode(); + std::vector getKeyboardLayoutList() override; + String getCurrentLanguageCode() override; void showNotification(const String& title, const String& text) const override; }; diff --git a/src/lib/synergy/win32/AppUtilWindows.cpp b/src/lib/synergy/win32/AppUtilWindows.cpp index 6480526f7..471098788 100644 --- a/src/lib/synergy/win32/AppUtilWindows.cpp +++ b/src/lib/synergy/win32/AppUtilWindows.cpp @@ -33,11 +33,14 @@ #include "common/Version.h" #include "wintoastlib.h" +#include #include #include #include #include +#include + AppUtilWindows::AppUtilWindows(IEventQueue* events) : m_events(events), m_exitMode(kExitModeNormal) @@ -182,6 +185,57 @@ AppUtilWindows::startNode() app().startNode(); } +std::vector +AppUtilWindows::getKeyboardLayoutList() +{ + std::vector layoutLangCodes; + { + auto uLayouts = GetKeyboardLayoutList(0, NULL); + auto lpList = (HKL*)LocalAlloc(LPTR, (uLayouts * sizeof(HKL))); + uLayouts = GetKeyboardLayoutList(uLayouts, lpList); + + for (int i = 0; i < uLayouts; ++i){ + String code("", 2); + GetLocaleInfoA(MAKELCID(((UINT)lpList[i] & 0xffffffff), SORT_DEFAULT), LOCALE_SISO639LANGNAME, &code[0], code.size()); + layoutLangCodes.push_back(code); + } + + if (lpList) { + LocalFree(lpList); + } + } + return layoutLangCodes; +} + +String +AppUtilWindows::getCurrentLanguageCode() +{ + String code("", 2); + + auto hklLayout = getCurrentKeyboardLayout(); + if (hklLayout) { + auto localLayoutID = MAKELCID(LOWORD(hklLayout), SORT_DEFAULT); + GetLocaleInfoA(localLayoutID, LOCALE_SISO639LANGNAME, &code[0], code.size()); + } + + return code; +} + +HKL AppUtilWindows::getCurrentKeyboardLayout() const +{ + HKL layout = nullptr; + + GUITHREADINFO gti = {sizeof(GUITHREADINFO)}; + if (GetGUIThreadInfo(0, >i) && gti.hwndActive) { + layout = GetKeyboardLayout(GetWindowThreadProcessId(gti.hwndActive, NULL)); + } + else { + LOG((CLOG_WARN "Failed to determine current keyboard layout")); + } + + return layout; +} + class WinToastHandler : public WinToastLib::IWinToastHandler { public: WinToastHandler() {} @@ -225,3 +279,4 @@ AppUtilWindows::showNotification(const String & title, const String & text) cons return; } } + diff --git a/src/lib/synergy/win32/AppUtilWindows.h b/src/lib/synergy/win32/AppUtilWindows.h index 007249bcd..5e0c0f884 100644 --- a/src/lib/synergy/win32/AppUtilWindows.h +++ b/src/lib/synergy/win32/AppUtilWindows.h @@ -19,7 +19,6 @@ #pragma once #include "synergy/AppUtil.h" -#include "base/String.h" #define WIN32_LEAN_AND_MEAN #include "Windows.h" @@ -54,8 +53,15 @@ public: void startNode(); + std::vector getKeyboardLayoutList() override; + + String getCurrentLanguageCode() override; + + HKL getCurrentKeyboardLayout() const; + void showNotification(const String& title, const String& text) const override; + private: AppExitMode m_exitMode; IEventQueue* m_events; diff --git a/src/test/integtests/platform/MSWindowsKeyStateTests.cpp b/src/test/integtests/platform/MSWindowsKeyStateTests.cpp index 21cf7e6f6..8556480f6 100644 --- a/src/test/integtests/platform/MSWindowsKeyStateTests.cpp +++ b/src/test/integtests/platform/MSWindowsKeyStateTests.cpp @@ -73,7 +73,7 @@ TEST_F(MSWindowsKeyStateTests, disable_eventQueueNotUsed) NiceMock eventQueue; MSWindowsDesks* desks = newDesks(&eventQueue); MockKeyMap keyMap; - MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap); + MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap, {"en"}, true); EXPECT_CALL(eventQueue, removeHandler(_, _)).Times(0); @@ -86,7 +86,7 @@ TEST_F(MSWindowsKeyStateTests, testAutoRepeat_noRepeatAndButtonIsZero_resultIsTr NiceMock eventQueue; MSWindowsDesks* desks = newDesks(&eventQueue); MockKeyMap keyMap; - MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap); + MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap, {"en"}, true); keyState.setLastDown(1); bool actual = keyState.testAutoRepeat(true, false, 1); @@ -100,7 +100,7 @@ TEST_F(MSWindowsKeyStateTests, testAutoRepeat_pressFalse_lastDownIsZero) NiceMock eventQueue; MSWindowsDesks* desks = newDesks(&eventQueue); MockKeyMap keyMap; - MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap); + MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap, {"en"}, true); keyState.setLastDown(1); keyState.testAutoRepeat(false, false, 1); @@ -114,7 +114,7 @@ TEST_F(MSWindowsKeyStateTests, saveModifiers_noModifiers_savedModifiers0) NiceMock eventQueue; MSWindowsDesks* desks = newDesks(&eventQueue); MockKeyMap keyMap; - MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap); + MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap, {"en"}, true); keyState.saveModifiers(); @@ -127,7 +127,7 @@ TEST_F(MSWindowsKeyStateTests, testKoreanLocale_inputModeKey_resultCorrectKeyID) NiceMock eventQueue; MSWindowsDesks* desks = newDesks(&eventQueue); MockKeyMap keyMap; - MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap); + MSWindowsKeyState keyState(desks, getEventTarget(), &eventQueue, keyMap, {"en"}, true); keyState.setKeyLayout((HKL)0x00000412u); // for ko-KR local ID ASSERT_EQ(0xEF31, keyState.getKeyID(0x15u, 0x1f2u)); // VK_HANGUL from Hangul key diff --git a/src/test/integtests/platform/OSXKeyStateTests.cpp b/src/test/integtests/platform/OSXKeyStateTests.cpp index 963794d7c..7e531bcdc 100644 --- a/src/test/integtests/platform/OSXKeyStateTests.cpp +++ b/src/test/integtests/platform/OSXKeyStateTests.cpp @@ -42,16 +42,16 @@ TEST_F(OSXKeyStateTests, fakeAndPoll_shift) { synergy::KeyMap keyMap; MockEventQueue eventQueue; - OSXKeyState keyState(&eventQueue, keyMap); + OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true); keyState.updateKeyMap(); - keyState.fakeKeyDown(SHIFT_ID_L, 0, 1); + keyState.fakeKeyDown(SHIFT_ID_L, 0, 1, "en"); EXPECT_TRUE(isKeyPressed(keyState, SHIFT_BUTTON)); keyState.fakeKeyUp(1); EXPECT_TRUE(!isKeyPressed(keyState, SHIFT_BUTTON)); - keyState.fakeKeyDown(SHIFT_ID_R, 0, 2); + keyState.fakeKeyDown(SHIFT_ID_R, 0, 2, "en"); EXPECT_TRUE(isKeyPressed(keyState, SHIFT_BUTTON)); keyState.fakeKeyUp(2); @@ -62,10 +62,10 @@ TEST_F(OSXKeyStateTests, fakeAndPoll_charKey) { synergy::KeyMap keyMap; MockEventQueue eventQueue; - OSXKeyState keyState(&eventQueue, keyMap); + OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true); keyState.updateKeyMap(); - keyState.fakeKeyDown(A_CHAR_ID, 0, 1); + keyState.fakeKeyDown(A_CHAR_ID, 0, 1, "en"); EXPECT_TRUE(isKeyPressed(keyState, A_CHAR_BUTTON)); keyState.fakeKeyUp(1); @@ -73,7 +73,7 @@ TEST_F(OSXKeyStateTests, fakeAndPoll_charKey) // HACK: delete the key in case it was typed into a text editor. // we should really set focus to an invisible window. - keyState.fakeKeyDown(kKeyBackSpace, 0, 2); + keyState.fakeKeyDown(kKeyBackSpace, 0, 2, "en"); keyState.fakeKeyUp(2); } @@ -81,10 +81,10 @@ TEST_F(OSXKeyStateTests, fakeAndPoll_charKeyAndModifier) { synergy::KeyMap keyMap; MockEventQueue eventQueue; - OSXKeyState keyState(&eventQueue, keyMap); + OSXKeyState keyState(&eventQueue, keyMap, {"en"}, true); keyState.updateKeyMap(); - keyState.fakeKeyDown(A_CHAR_ID, KeyModifierShift, 1); + keyState.fakeKeyDown(A_CHAR_ID, KeyModifierShift, 1, "en"); EXPECT_TRUE(isKeyPressed(keyState, A_CHAR_BUTTON)); keyState.fakeKeyUp(1); @@ -92,7 +92,7 @@ TEST_F(OSXKeyStateTests, fakeAndPoll_charKeyAndModifier) // HACK: delete the key in case it was typed into a text editor. // we should really set focus to an invisible window. - keyState.fakeKeyDown(kKeyBackSpace, 0, 2); + keyState.fakeKeyDown(kKeyBackSpace, 0, 2, "en"); keyState.fakeKeyUp(2); } diff --git a/src/test/mock/synergy/MockKeyMap.h b/src/test/mock/synergy/MockKeyMap.h index 919072c26..9ed60d6f2 100644 --- a/src/test/mock/synergy/MockKeyMap.h +++ b/src/test/mock/synergy/MockKeyMap.h @@ -17,7 +17,7 @@ */ #pragma once - +#include "base/String.h" #include "synergy/KeyMap.h" #include "test/global/gmock.h" @@ -31,5 +31,5 @@ public: MOCK_METHOD(void, addHalfDuplexModifier, (KeyID), (override)); MOCK_METHOD(bool, isHalfDuplex, (KeyID, KeyButton), (const, override)); MOCK_METHOD(const KeyMap::KeyItem*, mapKey, (Keystrokes&, KeyID, SInt32, ModifierToKeys&, KeyModifierMask&, - KeyModifierMask, bool), (const, override)); + KeyModifierMask, bool, const String&), (const, override)); }; diff --git a/src/test/mock/synergy/MockKeyState.h b/src/test/mock/synergy/MockKeyState.h index cbc52b312..28cfff464 100644 --- a/src/test/mock/synergy/MockKeyState.h +++ b/src/test/mock/synergy/MockKeyState.h @@ -31,12 +31,12 @@ class MockKeyState : public KeyState { public: MockKeyState(const MockEventQueue& eventQueue) : - KeyState((IEventQueue*)&eventQueue) + KeyState((IEventQueue*)&eventQueue, {"en"}, true) { } MockKeyState(const MockEventQueue& eventQueue, const synergy::KeyMap& keyMap) : - KeyState((IEventQueue*)&eventQueue, (synergy::KeyMap&)keyMap) + KeyState((IEventQueue*)&eventQueue, (synergy::KeyMap&)keyMap, {"en"}, true) { } diff --git a/src/test/unittests/platform/OSXKeyStateTests.cpp b/src/test/unittests/platform/OSXKeyStateTests.cpp index 3222c19fc..ed21f9212 100644 --- a/src/test/unittests/platform/OSXKeyStateTests.cpp +++ b/src/test/unittests/platform/OSXKeyStateTests.cpp @@ -27,7 +27,7 @@ TEST(OSXKeyStateTests, mapModifiersFromOSX_OSXMask_returnSynergyMask) { synergy::KeyMap keyMap; MockEventQueue eventQueue; - OSXKeyState keyState(&eventQueue, keyMap); + OSXKeyState keyState(&eventQueue, keyMap, { "en" }, true); KeyModifierMask outMask = 0; diff --git a/src/test/unittests/synergy/AppUtilTests.cpp b/src/test/unittests/synergy/AppUtilTests.cpp new file mode 100644 index 000000000..a32f79f9b --- /dev/null +++ b/src/test/unittests/synergy/AppUtilTests.cpp @@ -0,0 +1,39 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "test/global/gtest.h" +#include "synergy/AppUtil.h" + +TEST(AppUtilTests, keyboardLayoutTest) +{ + String layoutList1 = "ruenby"; + std::vector layoutList2 = { "ru", "en" }; + std::vector missed; + std::vector supported; + AppUtil::getKeyboardLayoutsDiff(layoutList1, layoutList2, missed, supported); + + EXPECT_EQ(missed, std::vector{ "by" }); + EXPECT_EQ(supported, layoutList2); +} + +TEST(AppUtilTests, joinVectorTest) +{ + std::vector layouts = { "ru", "en" }; + auto result = AppUtil::joinStrVector(layouts, ", "); + + EXPECT_EQ(result, "ru, en"); +} diff --git a/src/test/unittests/synergy/ClientArgsParsingTests.cpp b/src/test/unittests/synergy/ClientArgsParsingTests.cpp index 6d1ee3d72..21df6c602 100644 --- a/src/test/unittests/synergy/ClientArgsParsingTests.cpp +++ b/src/test/unittests/synergy/ClientArgsParsingTests.cpp @@ -21,6 +21,8 @@ #include "test/global/gtest.h" +#include + using ::testing::_; using ::testing::Invoke; using ::testing::NiceMock; @@ -51,6 +53,42 @@ TEST(ClientArgsParsingTests, parseClientArgs_yScrollArg_setYScroll) EXPECT_EQ(1, clientArgs.m_yscroll); } +TEST(ClientArgsParsingTests, parseClientArgs_setLangSync) +{ + NiceMock argParser; + ON_CALL(argParser, parseGenericArgs(_, _, _)).WillByDefault(Invoke(client_stubParseGenericArgs)); + ON_CALL(argParser, checkUnexpectedArgs()).WillByDefault(Invoke(client_stubCheckUnexpectedArgs)); + lib::synergy::ClientArgs clientArgs; + clientArgs.m_enableLangSync = false; + const int argc = 2; + std::array kLangCmd = { "stub", "--sync-language" }; + + argParser.parseClientArgs(clientArgs, argc, kLangCmd.data()); + + EXPECT_TRUE(clientArgs.m_enableLangSync); +} + +TEST(ClientArgsParsingTests, parseClientArgs_setCommonArgs) +{ + NiceMock argParser; + ON_CALL(argParser, parseGenericArgs(_, _, _)).WillByDefault(Invoke(client_stubParseGenericArgs)); + ON_CALL(argParser, checkUnexpectedArgs()).WillByDefault(Invoke(client_stubCheckUnexpectedArgs)); + lib::synergy::ClientArgs clientArgs; + clientArgs.m_enableLangSync = false; + const int argc = 9; + std::array kLangCmd = { "stub", "--enable-crypto", "--profile-dir", "profileDir", + "--plugin-dir", "pluginDir", "--tls-cert", "tlsCertPath", + "--prevent-sleep" }; + + argParser.parseClientArgs(clientArgs, argc, kLangCmd.data()); + + EXPECT_TRUE(clientArgs.m_enableCrypto); + EXPECT_EQ(clientArgs.m_profileDirectory, "profileDir"); + EXPECT_EQ(clientArgs.m_pluginDirectory, "pluginDir"); + EXPECT_EQ(clientArgs.m_tlsCertFile, "tlsCertPath"); + EXPECT_TRUE(clientArgs.m_preventSleep); +} + TEST(ClientArgsParsingTests, parseClientArgs_addressArg_setSynergyAddress) { NiceMock argParser; diff --git a/src/test/unittests/synergy/KeyMapTests.cpp b/src/test/unittests/synergy/KeyMapTests.cpp index cc94f8c1a..df2a85c1b 100644 --- a/src/test/unittests/synergy/KeyMapTests.cpp +++ b/src/test/unittests/synergy/KeyMapTests.cpp @@ -229,10 +229,10 @@ TEST(KeyMapTests, mapkey_handles_setmodifier_with_no_mapped) KeyMap::ModifierToKeys activeModifiers {}; KeyModifierMask currentState {}; KeyModifierMask desiredMask {}; - auto result = keyMap.mapKey(strokes, kKeySetModifiers, 1, activeModifiers, currentState, desiredMask, false); + auto result = keyMap.mapKey(strokes, kKeySetModifiers, 1, activeModifiers, currentState, desiredMask, false, "en"); EXPECT_FALSE(result == nullptr); desiredMask = KeyModifierControl; - result = keyMap.mapKey(strokes, kKeySetModifiers, 1, activeModifiers, currentState, desiredMask, false); + result = keyMap.mapKey(strokes, kKeySetModifiers, 1, activeModifiers, currentState, desiredMask, false, "en"); EXPECT_TRUE(result == nullptr); } diff --git a/src/test/unittests/synergy/KeyStateTests.cpp b/src/test/unittests/synergy/KeyStateTests.cpp index 6b783cfb5..e5cf159bf 100644 --- a/src/test/unittests/synergy/KeyStateTests.cpp +++ b/src/test/unittests/synergy/KeyStateTests.cpp @@ -42,7 +42,8 @@ stubMapKey( synergy::KeyMap::ModifierToKeys& activeModifiers, KeyModifierMask& currentState, KeyModifierMask desiredMask, - bool isAutoRepeat); + bool isAutoRepeat, + const String& lang); synergy::KeyMap::Keystroke s_stubKeystroke(1, false, false); synergy::KeyMap::KeyItem s_stubKeyItem; @@ -100,7 +101,7 @@ TEST(KeyStateTests, sendKeyEvent_halfDuplex_addEventCalledTwice) KeyStateImpl keyState(eventQueue, keyMap); IKeyStateEvents keyStateEvents; keyStateEvents.setEvents(&eventQueue); - + ON_CALL(keyMap, isHalfDuplex(_, _)).WillByDefault(Return(true)); ON_CALL(eventQueue, forIKeyState()).WillByDefault(ReturnRef(keyStateEvents)); @@ -116,7 +117,7 @@ TEST(KeyStateTests, sendKeyEvent_keyRepeat_addEventCalledOnce) KeyStateImpl keyState(eventQueue, keyMap); IKeyStateEvents keyStateEvents; keyStateEvents.setEvents(&eventQueue); - + ON_CALL(eventQueue, forIKeyState()).WillByDefault(ReturnRef(keyStateEvents)); EXPECT_CALL(eventQueue, addEvent(_)).Times(1); @@ -131,7 +132,7 @@ TEST(KeyStateTests, sendKeyEvent_keyDown_addEventCalledOnce) KeyStateImpl keyState(eventQueue, keyMap); IKeyStateEvents keyStateEvents; keyStateEvents.setEvents(&eventQueue); - + ON_CALL(eventQueue, forIKeyState()).WillByDefault(ReturnRef(keyStateEvents)); EXPECT_CALL(eventQueue, addEvent(_)).Times(1); @@ -146,7 +147,7 @@ TEST(KeyStateTests, sendKeyEvent_keyUp_addEventCalledOnce) KeyStateImpl keyState(eventQueue, keyMap); IKeyStateEvents keyStateEvents; keyStateEvents.setEvents(&eventQueue); - + ON_CALL(eventQueue, forIKeyState()).WillByDefault(ReturnRef(keyStateEvents)); EXPECT_CALL(eventQueue, addEvent(_)).Times(1); @@ -270,15 +271,15 @@ TEST(KeyStateTests, fakeKeyDown_serverKeyAlreadyDown_fakeKeyCalledTwice) KeyStateImpl keyState(eventQueue, keyMap); s_stubKeyItem.m_client = 0; s_stubKeyItem.m_button = 1; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); // 2 calls to fakeKeyDown should still call fakeKey, even though // repeated keys are handled differently. EXPECT_CALL(keyState, fakeKey(_)).Times(2); // call twice to simulate server key already down (a misreported autorepeat). - keyState.fakeKeyDown(1, 0, 0); - keyState.fakeKeyDown(1, 0, 0); + keyState.fakeKeyDown(1, 0, 0, "en"); + keyState.fakeKeyDown(1, 0, 0, "en"); } TEST(KeyStateTests, fakeKeyDown_isIgnoredKey_fakeKeyNotCalled) @@ -289,7 +290,7 @@ TEST(KeyStateTests, fakeKeyDown_isIgnoredKey_fakeKeyNotCalled) EXPECT_CALL(keyState, fakeKey(_)).Times(0); - keyState.fakeKeyDown(kKeyCapsLock, 0, 0); + keyState.fakeKeyDown(kKeyCapsLock, 0, 0, "en"); } TEST(KeyStateTests, fakeKeyDown_mapReturnsKeystrokes_fakeKeyCalled) @@ -299,11 +300,11 @@ TEST(KeyStateTests, fakeKeyDown_mapReturnsKeystrokes_fakeKeyCalled) KeyStateImpl keyState(eventQueue, keyMap); s_stubKeyItem.m_button = 0; s_stubKeyItem.m_client = 0; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); EXPECT_CALL(keyState, fakeKey(_)).Times(1); - keyState.fakeKeyDown(1, 0, 0); + keyState.fakeKeyDown(1, 0, 0, "en"); } TEST(KeyStateTests, fakeKeyRepeat_invalidKey_returnsFalse) @@ -312,7 +313,7 @@ TEST(KeyStateTests, fakeKeyRepeat_invalidKey_returnsFalse) MockEventQueue eventQueue; KeyStateImpl keyState(eventQueue, keyMap); - bool actual = keyState.fakeKeyRepeat(0, 0, 0, 0); + bool actual = keyState.fakeKeyRepeat(0, 0, 0, 0, "en"); ASSERT_FALSE(actual); } @@ -327,14 +328,14 @@ TEST(KeyStateTests, fakeKeyRepeat_nullKey_returnsFalse) synergy::KeyMap::KeyItem keyItem; keyItem.m_client = 0; keyItem.m_button = 1; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Return(&keyItem)); - keyState.fakeKeyDown(1, 0, 0); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Return(&keyItem)); + keyState.fakeKeyDown(1, 0, 0, "en"); // change mapKey to return NULL so that fakeKeyRepeat exits early. synergy::KeyMap::KeyItem* nullKeyItem = NULL; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Return(nullKeyItem)); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Return(nullKeyItem)); - bool actual = keyState.fakeKeyRepeat(1, 0, 0, 0); + bool actual = keyState.fakeKeyRepeat(1, 0, 0, 0, "en"); ASSERT_FALSE(actual); } @@ -349,14 +350,14 @@ TEST(KeyStateTests, fakeKeyRepeat_invalidButton_returnsFalse) synergy::KeyMap::KeyItem keyItem; keyItem.m_client = 0; keyItem.m_button = 1; // set to 1 to make fakeKeyDown work. - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Return(&keyItem)); - keyState.fakeKeyDown(1, 0, 0); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Return(&keyItem)); + keyState.fakeKeyDown(1, 0, 0, "en"); // change button to 0 so that fakeKeyRepeat will return early. keyItem.m_button = 0; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Return(&keyItem)); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Return(&keyItem)); - bool actual = keyState.fakeKeyRepeat(1, 0, 0, 0); + bool actual = keyState.fakeKeyRepeat(1, 0, 0, 0, "en"); ASSERT_FALSE(actual); } @@ -372,14 +373,14 @@ TEST(KeyStateTests, fakeKeyRepeat_validKey_returnsTrue) // set the button to 1 for fakeKeyDown call s_stubKeyItem.m_button = 1; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); - keyState.fakeKeyDown(1, 0, 0); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + keyState.fakeKeyDown(1, 0, 0, "en"); // change the button to 2 s_stubKeyItem.m_button = 2; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); - bool actual = keyState.fakeKeyRepeat(1, 0, 0, 0); + bool actual = keyState.fakeKeyRepeat(1, 0, 0, 0, "en"); ASSERT_TRUE(actual); } @@ -407,8 +408,8 @@ TEST(KeyStateTests, fakeKeyUp_buttonAlreadyDown_returnsTrue) // press button 1 down. s_stubKeyItem.m_button = 1; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); - keyState.fakeKeyDown(1, 0, 1); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + keyState.fakeKeyDown(1, 0, 1, "en"); // this takes the button id, which is the 3rd arg of fakeKeyDown bool actual = keyState.fakeKeyUp(1); @@ -424,8 +425,8 @@ TEST(KeyStateTests, fakeAllKeysUp_keysWereDown_keysAreUp) // press button 1 down. s_stubKeyItem.m_button = 1; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); - keyState.fakeKeyDown(1, 0, 1); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + keyState.fakeKeyDown(1, 0, 1, "en"); // method under test keyState.fakeAllKeysUp(); @@ -442,8 +443,8 @@ TEST(KeyStateTests, isKeyDown_keyDown_returnsTrue) // press button 1 down. s_stubKeyItem.m_button = 1; - ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); - keyState.fakeKeyDown(1, 0, 1); + ON_CALL(keyMap, mapKey(_, _, _, _, _, _, _, _)).WillByDefault(Invoke(stubMapKey)); + keyState.fakeKeyDown(1, 0, 1, "en"); // method under test bool actual = keyState.isKeyDown(1); @@ -492,12 +493,9 @@ assertMaskIsOne(ForeachKeyCallback cb, void* userData) } const synergy::KeyMap::KeyItem* -stubMapKey( - synergy::KeyMap::Keystrokes& keys, KeyID id, SInt32 group, - synergy::KeyMap::ModifierToKeys& activeModifiers, - KeyModifierMask& currentState, - KeyModifierMask desiredMask, - bool isAutoRepeat) +stubMapKey(synergy::KeyMap::Keystrokes& keys, KeyID, SInt32, + synergy::KeyMap::ModifierToKeys&, KeyModifierMask&, + KeyModifierMask, bool, const String&) { keys.push_back(s_stubKeystroke); return &s_stubKeyItem; diff --git a/src/test/unittests/synergy/ServerArgsParsingTests.cpp b/src/test/unittests/synergy/ServerArgsParsingTests.cpp index 896bf32fe..31ee63abd 100644 --- a/src/test/unittests/synergy/ServerArgsParsingTests.cpp +++ b/src/test/unittests/synergy/ServerArgsParsingTests.cpp @@ -21,6 +21,8 @@ #include "test/global/gtest.h" +#include + using ::testing::_; using ::testing::Invoke; using ::testing::NiceMock; @@ -72,3 +74,29 @@ TEST(ServerArgsParsingTests, parseServerArgs_configArg_setConfigFile) EXPECT_EQ("mock_configFile", serverArgs.m_configFile); } + +TEST(ServerArgsParsingTests, parseServerArgs_checkSerialKeyParams) +{ + NiceMock argParser; + ON_CALL(argParser, parseGenericArgs(_, _, _)).WillByDefault(Invoke(server_stubParseGenericArgs)); + ON_CALL(argParser, checkUnexpectedArgs()).WillByDefault(Invoke(server_stubCheckUnexpectedArgs)); + lib::synergy::ServerArgs serverArgs; + const int argc = 3; + const char* serial = "7B76323B737562736372697074696F6E3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"; + std::array kSerialCmd = { "stub", "--serial-key", serial }; + + argParser.parseServerArgs(serverArgs, argc, kSerialCmd.data()); + EXPECT_EQ(serial, serverArgs.m_serial.toString()); +} + +TEST(ServerArgsParsingTests, parseServerArgs_checkUnexpectedParams) +{ + NiceMock argParser; + ON_CALL(argParser, parseGenericArgs(_, _, _)).WillByDefault(Invoke(server_stubParseGenericArgs)); + ON_CALL(argParser, checkUnexpectedArgs()).WillByDefault(Invoke(server_stubCheckUnexpectedArgs)); + lib::synergy::ServerArgs serverArgs; + const int argc = 2; + std::array kUnknownCmd = { "stub", "--unknown" }; + + EXPECT_FALSE(argParser.parseServerArgs(serverArgs, argc, kUnknownCmd.data())); +} diff --git a/src/test/unittests/synergy/X11LayoutParserTests.cpp b/src/test/unittests/synergy/X11LayoutParserTests.cpp new file mode 100644 index 000000000..46c32ea20 --- /dev/null +++ b/src/test/unittests/synergy/X11LayoutParserTests.cpp @@ -0,0 +1,221 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "synergy/X11LayoutsParser.h" +#include "test/global/gtest.h" +#include + +void createTestFiles() +{ + std::ofstream correctKeyboardFile ("correctKeyboard"); + if(!correctKeyboardFile.is_open()) { + FAIL(); + } + + correctKeyboardFile << "XKBLAYOUT=us,ru" << std::endl; + correctKeyboardFile << "XKBVARIANT=eng," << std::endl; + correctKeyboardFile << "BACKSPACE=guess" << std::endl; + correctKeyboardFile.close(); + + std::ofstream keyboardFromFutureFile ("keyboardFromFuture"); + if(!keyboardFromFutureFile.is_open()) { + FAIL(); + } + + keyboardFromFutureFile << "XKBLAYOUT=futureLangName" << std::endl; + keyboardFromFutureFile << "XKBVARIANT=" << std::endl; + keyboardFromFutureFile.close(); + + std::ofstream incorrectKeyboardFile1 ("incorrectKeyboard1"); + if(!incorrectKeyboardFile1.is_open()) { + FAIL(); + } + + incorrectKeyboardFile1 << "XKBLAYOUT=unknownLangCode" << std::endl; + incorrectKeyboardFile1 << "XKBVARIANT=eng" << std::endl; + incorrectKeyboardFile1 << "BACKSPACE=guess" << std::endl; + incorrectKeyboardFile1.close(); + + std::ofstream incorrectKeyboardFile2 ("incorrectKeyboard2"); + if(!incorrectKeyboardFile2.is_open()) { + FAIL(); + } + + incorrectKeyboardFile2 << "XKBLAYOUT=us" << std::endl; + incorrectKeyboardFile2 << "XKBVARIANT=unknownLangVariant" << std::endl; + incorrectKeyboardFile2 << "BACKSPACE=guess" << std::endl; + incorrectKeyboardFile2.close(); + + std::ofstream incorrectKeyboardFile3 ("incorrectKeyboard3"); + if(!incorrectKeyboardFile3.is_open()) { + FAIL(); + } + + incorrectKeyboardFile3 << "XKBLAYOUT=us,ru,by" << std::endl; + incorrectKeyboardFile3 << "XKBVARIANT=," << std::endl; + incorrectKeyboardFile3.close(); + + std::ofstream correctEvdevFile ("correctEvdev.xml"); + if(!correctEvdevFile.is_open()) { + FAIL(); + } + + correctEvdevFile << "" << std::endl; + correctEvdevFile << "" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " us" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " en" << std::endl; + correctEvdevFile << " English (US)" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " eng" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " eng" << std::endl; + correctEvdevFile << " eng" << std::endl; + correctEvdevFile << " Cherokee" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " eng" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " ru" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " ru" << std::endl; + correctEvdevFile << " Russian" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " rus" << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << " " << std::endl; + correctEvdevFile << "" << std::endl; + correctEvdevFile.close(); + + std::ofstream evdevFromFutureFile ("evdevFromFuture.xml"); + if(!evdevFromFutureFile.is_open()) { + FAIL(); + } + + evdevFromFutureFile << "" << std::endl; + evdevFromFutureFile << "" << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " futureLangName" << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " fln" << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << " " << std::endl; + evdevFromFutureFile << "" << std::endl; + evdevFromFutureFile.close(); + + std::ofstream incorrectEvdevFile1 ("incorrectEvdev1.xml"); + if(!incorrectEvdevFile1.is_open()) { + FAIL(); + } + + incorrectEvdevFile1 << "" << std::endl; + incorrectEvdevFile1.close(); + + std::ofstream incorrectEvdevFile2 ("incorrectEvdev2.xml"); + if(!incorrectEvdevFile2.is_open()) { + FAIL(); + } + + incorrectEvdevFile2 << "" << std::endl; + incorrectEvdevFile2 << "" << std::endl; + incorrectEvdevFile2 << "" << std::endl; + incorrectEvdevFile2.close(); + + std::ofstream incorrectEvdevFile3 ("incorrectEvdev3.xml"); + if(!incorrectEvdevFile3.is_open()) { + FAIL(); + } + + incorrectEvdevFile3 << "" << std::endl; + incorrectEvdevFile3 << "" << std::endl; + incorrectEvdevFile3 << " " << std::endl; + incorrectEvdevFile3 << " " << std::endl; + incorrectEvdevFile3 << " " << std::endl; + incorrectEvdevFile3 << " " << std::endl; + incorrectEvdevFile3 << "" << std::endl; + incorrectEvdevFile3.close(); +} + +TEST(X11LayoutsParsingTests, xmlCorrectParsingTest) +{ + createTestFiles(); + std::vector expectedResult = { "en", "ru" }; + auto parsedResult = X11LayoutsParser::getX11LanguageList("correctKeyboard", "correctEvdev.xml"); + + EXPECT_EQ(parsedResult, parsedResult); +} + +TEST(X11LayoutsParsingTests, xmlParsingMissedKeyboardFileTest) +{ + auto parsedResult = X11LayoutsParser::getX11LanguageList("missedFile", "correctEvdev.xml"); + EXPECT_TRUE(parsedResult.empty()); +} + +TEST(X11LayoutsParsingTests, xmlParsingMissedEvdevFileTest) +{ + auto parsedResult = X11LayoutsParser::getX11LanguageList("correctKeyboard", "missedFile"); + EXPECT_TRUE(parsedResult.empty()); +} + +TEST(X11LayoutsParsingTests, xmlParsingIncorrectEvdevFileTest) +{ + std::vector parsedResult; + parsedResult = X11LayoutsParser::getX11LanguageList("correctKeyboard", "incorrectEvdev1.xml"); + EXPECT_TRUE(parsedResult.empty()); + parsedResult = X11LayoutsParser::getX11LanguageList("correctKeyboard", "incorrectEvdev2.xml"); + EXPECT_TRUE(parsedResult.empty()); + parsedResult = X11LayoutsParser::getX11LanguageList("correctKeyboard", "incorrectEvdev3.xml"); + EXPECT_TRUE(parsedResult.empty()); +} + +TEST(X11LayoutsParsingTests, xmlParsingIncorrectKeyboardFileTest) +{ + std::vector parsedResult; + parsedResult = X11LayoutsParser::getX11LanguageList("incorrectKeyboard1", "correctEvdev.xml"); + EXPECT_TRUE(parsedResult.empty()); + parsedResult = X11LayoutsParser::getX11LanguageList("incorrectKeyboard2", "correctEvdev.xml"); + EXPECT_TRUE(parsedResult.empty()); + parsedResult = X11LayoutsParser::getX11LanguageList("incorrectKeyboard3", "correctEvdev.xml"); + EXPECT_TRUE(parsedResult.empty()); + parsedResult = X11LayoutsParser::getX11LanguageList("keyboardFromFuture", "evdevFromFuture.xml"); + EXPECT_TRUE(parsedResult.empty()); +} + +TEST(X11LayoutsParsingTests, layoutConvertTest) +{ + EXPECT_EQ(X11LayoutsParser::convertLayotToISO("correctEvdev.xml", "us", true), "en"); + EXPECT_EQ(X11LayoutsParser::convertLayotToISO("incorrectEvdev1.xml", "us", true), ""); + EXPECT_EQ(X11LayoutsParser::convertLayotToISO("evdevFromFuture.xml", "us", true), ""); +}