diff --git a/ChangeLog b/ChangeLog index 1a0f5a8ed..b310f2683 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,7 @@ v1.14.2-snapshot =========== Bug fixes: - #7070 Fix radio button shifted focus on macOS -- #7038 | #7104 | #7111 Fix client and server different keyboard layout errors +- #7038 | #7104 | #7111 | #7112 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/src/lib/client/Client.cpp b/src/lib/client/Client.cpp index 29ef39059..1cda15a60 100644 --- a/src/lib/client/Client.cpp +++ b/src/lib/client/Client.cpp @@ -206,7 +206,6 @@ Client::handshakeComplete() { m_ready = true; m_screen->enable(); - checkMissedLanguages(); sendEvent(m_events->forClient().connected(), NULL); } @@ -720,25 +719,45 @@ Client::handleClipboardGrabbed(const Event& event, void*) } } +bool +Client::isCompatible(int major, int minor) const +{ + const std::map< int, std::set > compatibleTable { + {6, {7, 8}}, //1.6 is compatible with 1.7 and 1.8 + {7, {8}} //1.7 is compatible with 1.8 + }; + + bool isCompatible = false; + + if (major == kProtocolMajorVersion) { + auto versions = compatibleTable.find(minor); + if (versions != compatibleTable.end()) { + auto compatibleVersions = versions->second; + isCompatible = compatibleVersions.find(kProtocolMinorVersion) != compatibleVersions.end(); + } + } + + return isCompatible; +} + void Client::handleHello(const Event&, void*) { SInt16 major, minor; - String remoteLanguages; - if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor, &remoteLanguages)) { + if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) { sendConnectionFailedEvent("Protocol error from server, check encryption settings"); cleanupTimer(); cleanupConnection(); return; } - m_languageManager.setRemoteLanguages(remoteLanguages); // check versions LOG((CLOG_DEBUG1 "got hello version %d.%d", major, minor)); SInt16 helloBackMajor = kProtocolMajorVersion; SInt16 helloBackMinor = kProtocolMinorVersion; - if (major == kProtocolMajorVersion && minor == 6 && kProtocolMinorVersion == 7) { - //because 1.6 and 1.7 is comptable - downgrading protocol for server + + if (isCompatible(major, minor)) { + //because 1.6 is comptable with 1.7 and 1.8 - downgrading protocol for server LOG((CLOG_NOTE "Downgrading protocol version for server")); helloBackMinor = minor; } @@ -814,17 +833,6 @@ Client::onFileRecieveCompleted() } } -void -Client::checkMissedLanguages() const -{ - auto missedLanguages = m_languageManager.getMissedLanguages(); - if (!missedLanguages.empty()) { - AppUtil::instance().showNotification("Language synchronization error", - "You need to install these languages on this computer to enable support for multiple languages: " - + missedLanguages); - } -} - void Client::handleStopRetry(const Event&, void*) { diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h index 0e6565170..8bb80fd40 100644 --- a/src/lib/client/Client.h +++ b/src/lib/client/Client.h @@ -24,7 +24,6 @@ #include "synergy/DragInformation.h" #include "synergy/INode.h" #include "synergy/ClientArgs.h" -#include "synergy/languages/LanguageManager.h" #include "net/NetworkAddress.h" #include "base/EventTypes.h" #include "mt/CondVar.h" @@ -201,6 +200,7 @@ private: void handleDisconnected(const Event&, void*); void handleShapeChanged(const Event&, void*); void handleClipboardGrabbed(const Event&, void*); + bool isCompatible(int major, int minor) const; void handleHello(const Event&, void*); void handleSuspend(const Event& event, void*); void handleResume(const Event& event, void*); @@ -209,7 +209,6 @@ private: void handleStopRetry(const Event&, void*); void onFileRecieveCompleted(); void sendClipboardThread(void*); - void checkMissedLanguages() const; public: bool m_mock; @@ -243,5 +242,4 @@ private: size_t m_maximumClipboardSize; lib::synergy::ClientArgs m_args; size_t m_resolvedAddressesCount = 0; - synergy::languages::LanguageManager m_languageManager; }; diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp index 37fb9bd0d..c8eb13534 100644 --- a/src/lib/client/ServerProxy.cpp +++ b/src/lib/client/ServerProxy.cpp @@ -213,6 +213,9 @@ ServerProxy::parseHandshakeMessage(const UInt8* code) m_client->refuseConnection("server reported a protocol error"); return kDisconnect; } + else if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) { + setServerLanguages(); + } else { return kUnknown; } @@ -236,7 +239,25 @@ ServerProxy::parseMessage(const UInt8* code) } else if (memcmp(code, kMsgDKeyDown, 4) == 0) { - keyDown(); + UInt16 id = 0; + UInt16 mask = 0; + UInt16 button = 0; + 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)); + + keyDown(id, mask, button, ""); + } + + else if (memcmp(code, kMsgDKeyDownLang, 4) == 0) { + String lang; + UInt16 id = 0; + UInt16 mask = 0; + UInt16 button = 0; + + ProtocolUtil::readf(m_stream, kMsgDKeyDownLang + 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())); + + keyDown(id, mask, button, lang); } else if (memcmp(code, kMsgDKeyUp, 4) == 0) { @@ -599,32 +620,11 @@ ServerProxy::grabClipboard() } void -ServerProxy::keyDown() +ServerProxy::keyDown(UInt16 id, UInt16 mask, UInt16 button, const String& lang) { // get mouse up to date flushCompressedMouse(); - - // parse - UInt16 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; - } + setActiveServerLanguage(lang); // translate KeyID id2 = translateKey(static_cast(id)); @@ -954,3 +954,42 @@ ServerProxy::secureInputNotification() "To fix the keyboard, the application must be closed."); } } + +void +ServerProxy::setServerLanguages() +{ + String serverLanguages; + ProtocolUtil::readf(m_stream, kMsgDLanguageSynchronisation + 4, &serverLanguages); + m_languageManager.setRemoteLanguages(serverLanguages); + + auto missedLanguages = m_languageManager.getMissedLanguages(); + if (!missedLanguages.empty()) { + AppUtil::instance().showNotification("Language synchronization error", + "You need to install these languages on this computer to enable support for multiple languages: " + + missedLanguages); + } +} + +void +ServerProxy::setActiveServerLanguage(const String& language) +{ + if (!language.empty()) { + if(m_serverLanguage != language) { + m_isUserNotifiedAboutLanguageSyncError = false; + m_serverLanguage = language; + } + + if (!m_languageManager.isLanguageInstalled(m_serverLanguage)) { + if(!m_isUserNotifiedAboutLanguageSyncError) { + AppUtil::instance().showNotification("Language error", "Current server language is not installed on client!"); + m_isUserNotifiedAboutLanguageSyncError = true; + } + } + else { + m_isUserNotifiedAboutLanguageSyncError = false; + } + } + else { + LOG((CLOG_DEBUG1 "Active server langauge is empty!")); + } +} diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h index deb83d5a1..bd33492f2 100644 --- a/src/lib/client/ServerProxy.h +++ b/src/lib/client/ServerProxy.h @@ -18,6 +18,7 @@ #pragma once +#include "synergy/languages/LanguageManager.h" #include "synergy/clipboard_types.h" #include "synergy/key_types.h" #include "base/Event.h" @@ -96,7 +97,7 @@ private: void leave(); void setClipboard(); void grabClipboard(); - void keyDown(); + void keyDown(UInt16 id, UInt16 mask, UInt16 button, const String& lang); void keyRepeat(); void keyUp(); void mouseDown(); @@ -113,6 +114,8 @@ private: void dragInfoReceived(); void handleClipboardSendingEvent(const Event&, void*); void secureInputNotification(); + void setServerLanguages(); + void setActiveServerLanguage(const String& language); private: typedef EResult (ServerProxy::*MessageParser)(const UInt8*); @@ -138,4 +141,5 @@ private: IEventQueue* m_events; String m_serverLanguage = ""; bool m_isUserNotifiedAboutLanguageSyncError = false; + synergy::languages::LanguageManager m_languageManager; }; diff --git a/src/lib/server/ClientProxy1_1.cpp b/src/lib/server/ClientProxy1_1.cpp index 249f2f206..5aaa41bae 100644 --- a/src/lib/server/ClientProxy1_1.cpp +++ b/src/lib/server/ClientProxy1_1.cpp @@ -40,10 +40,10 @@ ClientProxy1_1::~ClientProxy1_1() } void -ClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String& lang) +ClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String&) { - 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); + 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); } void diff --git a/src/lib/server/ClientProxy1_8.cpp b/src/lib/server/ClientProxy1_8.cpp new file mode 100644 index 000000000..ae354f248 --- /dev/null +++ b/src/lib/server/ClientProxy1_8.cpp @@ -0,0 +1,49 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015-2021 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 "base/Log.h" +#include "synergy/ProtocolUtil.h" +#include "synergy/languages/LanguageManager.h" + +#include "ClientProxy1_8.h" + + +ClientProxy1_8::ClientProxy1_8(const String& name, synergy::IStream* adoptedStream, Server* server, IEventQueue* events) : + ClientProxy1_7(name, adoptedStream, server, events) +{ + synchronizeLanguages(); +} + +void ClientProxy1_8::synchronizeLanguages() const +{ + synergy::languages::LanguageManager languageManager; + auto localLanguages = languageManager.getSerializedLocalLanguages(); + if (!localLanguages.empty()) { + LOG((CLOG_DEBUG1 "send server languages to the client: %s", localLanguages.c_str())); + ProtocolUtil::writef(getStream(), kMsgDLanguageSynchronisation, &localLanguages); + } + else { + LOG((CLOG_ERR "Fail to read server languages")); + } +} + +void +ClientProxy1_8::keyDown(KeyID key, KeyModifierMask mask, KeyButton button, const String& language) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x, language=%s", getName().c_str(), key, mask, button, language.c_str())); + ProtocolUtil::writef(getStream(), kMsgDKeyDownLang, key, mask, button, &language); +} + diff --git a/src/lib/server/ClientProxy1_8.h b/src/lib/server/ClientProxy1_8.h new file mode 100644 index 000000000..2598bfa25 --- /dev/null +++ b/src/lib/server/ClientProxy1_8.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015-2021 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 . + */ +#ifndef SERVER_CLIENTPROXY1_8_H +#define SERVER_CLIENTPROXY1_8_H + +#include "server/ClientProxy1_7.h" + +class ClientProxy1_8 : public ClientProxy1_7 +{ +public: + ClientProxy1_8(const String& name, synergy::IStream* adoptedStream, Server* server, IEventQueue* events); + ~ClientProxy1_8() override = default; + + void keyDown(KeyID, KeyModifierMask, KeyButton, const String&) override; + +private: + void synchronizeLanguages() const; + +}; + +#endif // SERVER_CLIENTPROXY1_8_H diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp index 28b6cebdb..12d8eb23b 100644 --- a/src/lib/server/ClientProxyUnknown.cpp +++ b/src/lib/server/ClientProxyUnknown.cpp @@ -27,6 +27,7 @@ #include "server/ClientProxy1_5.h" #include "server/ClientProxy1_6.h" #include "server/ClientProxy1_7.h" +#include "server/ClientProxy1_8.h" #include "synergy/protocol_types.h" #include "synergy/ProtocolUtil.h" #include "synergy/AppUtil.h" @@ -60,11 +61,7 @@ ClientProxyUnknown::ClientProxyUnknown(synergy::IStream* stream, double timeout, addStreamHandlers(); LOG((CLOG_DEBUG1 "saying hello")); - auto localLanguages = m_languageManager.getSerializedLocalLanguages(); - ProtocolUtil::writef(m_stream, kMsgHello, - kProtocolMajorVersion, - kProtocolMinorVersion, - &localLanguages); + ProtocolUtil::writef(m_stream, kMsgHello, kProtocolMajorVersion, kProtocolMinorVersion); } ClientProxyUnknown::~ClientProxyUnknown() @@ -177,6 +174,55 @@ ClientProxyUnknown::removeTimer() } } +void +ClientProxyUnknown::initProxy(const String& name, int major, int minor) +{ + if (major == 1) { + switch (minor) { + case 0: + m_proxy = new ClientProxy1_0(name, m_stream, m_events); + break; + + case 1: + m_proxy = new ClientProxy1_1(name, m_stream, m_events); + break; + + case 2: + m_proxy = new ClientProxy1_2(name, m_stream, m_events); + break; + + case 3: + m_proxy = new ClientProxy1_3(name, m_stream, m_events); + break; + + case 4: + m_proxy = new ClientProxy1_4(name, m_stream, m_server, m_events); + break; + + case 5: + m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); + break; + + case 6: + m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events); + break; + + case 7: + m_proxy = new ClientProxy1_7(name, m_stream, m_server, m_events); + break; + + case 8: + m_proxy = new ClientProxy1_8(name, m_stream, m_server, m_events); + break; + } + } + + // hangup (with error) if version isn't supported + if (m_proxy == NULL) { + throw XIncompatibleClient(major, minor); + } +} + void ClientProxyUnknown::handleData(const Event&, void*) { @@ -209,46 +255,7 @@ ClientProxyUnknown::handleData(const Event&, void*) removeHandlers(); // create client proxy for highest version supported by the client - if (major == 1) { - switch (minor) { - case 0: - m_proxy = new ClientProxy1_0(name, m_stream, m_events); - break; - - case 1: - m_proxy = new ClientProxy1_1(name, m_stream, m_events); - break; - - case 2: - m_proxy = new ClientProxy1_2(name, m_stream, m_events); - break; - - case 3: - m_proxy = new ClientProxy1_3(name, m_stream, m_events); - break; - - case 4: - m_proxy = new ClientProxy1_4(name, m_stream, m_server, m_events); - break; - - case 5: - m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); - break; - - case 6: - m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events); - break; - - case 7: - m_proxy = new ClientProxy1_7(name, m_stream, m_server, m_events); - break; - } - } - - // hangup (with error) if version isn't supported - if (m_proxy == NULL) { - throw XIncompatibleClient(major, minor); - } + initProxy(name, major, minor); // 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)); diff --git a/src/lib/server/ClientProxyUnknown.h b/src/lib/server/ClientProxyUnknown.h index 6bc471ca7..bd243de49 100644 --- a/src/lib/server/ClientProxyUnknown.h +++ b/src/lib/server/ClientProxyUnknown.h @@ -19,8 +19,8 @@ #pragma once #include "base/Event.h" +#include "base/String.h" #include "base/EventTypes.h" -#include "synergy/languages/LanguageManager.h" class ClientProxy; class EventQueueTimer; @@ -60,6 +60,7 @@ private: void addStreamHandlers(); void addProxyHandlers(); void removeHandlers(); + void initProxy(const String& name, int major, int minor); void removeTimer(); void handleData(const Event&, void*); void handleWriteError(const Event&, void*); @@ -74,5 +75,4 @@ private: bool m_ready; Server* m_server; IEventQueue* m_events; - synergy::languages::LanguageManager m_languageManager; }; diff --git a/src/lib/synergy/languages/LanguageManager.cpp b/src/lib/synergy/languages/LanguageManager.cpp index ca0ac6137..6e51cbf12 100644 --- a/src/lib/synergy/languages/LanguageManager.cpp +++ b/src/lib/synergy/languages/LanguageManager.cpp @@ -71,7 +71,7 @@ String LanguageManager::getMissedLanguages() const String missedLanguages; for (const auto& language : m_remoteLanguages) { - if (std::find(m_localLanguages.begin(), m_localLanguages.end(), language) == m_localLanguages.end()) { + if (!isLanguageInstalled(language)) { if (!missedLanguages.empty()) { missedLanguages += ", "; } @@ -87,6 +87,11 @@ String LanguageManager::getSerializedLocalLanguages() const return vectorToString(m_localLanguages); } +bool LanguageManager::isLanguageInstalled(const String& language) const +{ + return (std::find(m_localLanguages.begin(), m_localLanguages.end(), language) != m_localLanguages.end()); +} + } //namespace languages } //namespace synergy diff --git a/src/lib/synergy/languages/LanguageManager.h b/src/lib/synergy/languages/LanguageManager.h index 5f61e57a8..cb225dddb 100644 --- a/src/lib/synergy/languages/LanguageManager.h +++ b/src/lib/synergy/languages/LanguageManager.h @@ -62,6 +62,13 @@ public: * @return serialized local languages as a string */ String getSerializedLocalLanguages() const; + + /** + * @brief isLanguageInstalled checks if language is installed + * @param language which should be checked + * @return true if the specified language is installed + */ + bool isLanguageInstalled(const String& language) const; }; } //namespace languages diff --git a/src/lib/synergy/protocol_types.cpp b/src/lib/synergy/protocol_types.cpp index fe7e9a545..c98e8efed 100644 --- a/src/lib/synergy/protocol_types.cpp +++ b/src/lib/synergy/protocol_types.cpp @@ -18,7 +18,7 @@ #include "synergy/protocol_types.h" -const char* const kMsgHello = "Synergy%2i%2i%s"; +const char* const kMsgHello = "Synergy%2i%2i"; const char* const kMsgHelloBack = "Synergy%2i%2i%s"; const char* const kMsgCNoop = "CNOP"; const char* const kMsgCClose = "CBYE"; @@ -29,7 +29,8 @@ 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%s"; +const char* const kMsgDKeyDownLang = "DKDL%2i%2i%2i%s"; +const char* const kMsgDKeyDown = "DKDN%2i%2i%2i"; const char* const kMsgDKeyDown1_0 = "DKDN%2i%2i"; const char* const kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i%s"; const char* const kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"; @@ -47,6 +48,7 @@ const char* const kMsgDSetOptions = "DSOP%4I"; const char* const kMsgDFileTransfer = "DFTR%1i%s"; const char* const kMsgDDragInfo = "DDRG%2i%s"; const char* const kMsgDSecureInputNotification = "SECN%s"; +const char* const kMsgDLanguageSynchronisation = "LSYN%s"; const char* const kMsgQInfo = "QINF"; const char* const kMsgEIncompatible = "EICV%2i%2i"; const char* const kMsgEBusy = "EBSY"; diff --git a/src/lib/synergy/protocol_types.h b/src/lib/synergy/protocol_types.h index ea6eeba71..5bd346078 100644 --- a/src/lib/synergy/protocol_types.h +++ b/src/lib/synergy/protocol_types.h @@ -29,9 +29,11 @@ // 1.4: adds crypto support // 1.5: adds file transfer and removes home brew crypto // 1.6: adds clipboard streaming +// 1.7 adds security input notifications +// 1.8 adds language synchronization functionality // NOTE: with new version, synergy minor version should increment static const SInt16 kProtocolMajorVersion = 1; -static const SInt16 kProtocolMinorVersion = 7; +static const SInt16 kProtocolMinorVersion = 8; // default contact port number static const UInt16 kDefaultPort = 24800; @@ -176,9 +178,12 @@ extern const char* const kMsgCKeepAlive; // // data codes // +// The same as kMsgDKeyDown but with languageCode +// $4 = languageCode +extern const char* const kMsgDKeyDownLang; // key pressed: primary -> secondary -// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton, $4 = languageCode +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton // 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 @@ -282,6 +287,10 @@ extern const char* const kMsgDDragInfo; // platform facing secure input problems extern const char* const kMsgDSecureInputNotification; +// language synchronization: primary -> secondary +// $1 = List of server languages +extern const char* const kMsgDLanguageSynchronisation; + // // query codes // diff --git a/src/test/unittests/synergy/languages/LanguageManagerTests.cpp b/src/test/unittests/synergy/languages/LanguageManagerTests.cpp index 9e6af0f0e..58bb3a86e 100644 --- a/src/test/unittests/synergy/languages/LanguageManagerTests.cpp +++ b/src/test/unittests/synergy/languages/LanguageManagerTests.cpp @@ -56,3 +56,12 @@ TEST(LanguageManager, SerializeLocalLanguagesTest) EXPECT_EQ("ruenuk", manager.getSerializedLocalLanguages()); } + +TEST(LanguageManager, LanguageInstalledTest) +{ + std::vector localLanguages = {"ru", "en", "uk"}; + synergy::languages::LanguageManager manager(localLanguages); + + EXPECT_FALSE(manager.isLanguageInstalled("us")); + EXPECT_TRUE(manager.isLanguageInstalled("en")); +}