diff -Nru a/plugins/generic/clientswitcherplugin/accountsettings.cpp b/plugins/generic/clientswitcherplugin/accountsettings.cpp --- a/plugins/generic/clientswitcherplugin/accountsettings.cpp 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/accountsettings.cpp 2020-07-10 11:50:10.000000000 +0000 @@ -38,8 +38,9 @@ bool AccountSettings::isEmpty() { - if (response_mode == RespAllow && !lock_time_requ) { - if (os_name.isNull() && client_name.isEmpty() && caps_node.isEmpty()) { + if (!enable_contacts && !enable_conferences && response_mode == RespAllow && !lock_time_requ + && show_requ_mode == LogNever && log_mode == LogNever) { + if (os_name.isEmpty() && client_name.isEmpty() && caps_node.isEmpty() && caps_version.isEmpty()) { return true; } } @@ -77,6 +78,10 @@ QString param_value = param_list.join("="); if (param_name == "acc_id") { account_id = stripSlashes(param_value); + } else if (param_name == "en_cnt") { + enable_contacts = (param_value == "true") ? true : false; + } else if (param_name == "en_cnf") { + enable_conferences = (param_value == "true") ? true : false; } else if (param_name == "l_req") { if (param_value == "true") response_mode = RespNotImpl; @@ -86,16 +91,30 @@ response_mode = RespAllow; } else if (param_name == "l_treq") { lock_time_requ = (param_value == "true") ? true : false; + } else if (param_name == "s_req") { + show_requ_mode = LogNever; + if (param_value == "true" || param_value == "if-repl") { + show_requ_mode = LogIfReplace; + } else if (param_value == "always") { + show_requ_mode = LogAlways; + } } else if (param_name == "os_nm") { os_name = stripSlashes(param_value); - } else if (param_name == "os_ver") { - os_version = stripSlashes(param_value); } else if (param_name == "cl_nm") { client_name = stripSlashes(param_value); } else if (param_name == "cl_ver") { client_version = stripSlashes(param_value); } else if (param_name == "cp_nd") { caps_node = stripSlashes(param_value); + } else if (param_name == "cp_ver") { + caps_version = stripSlashes(param_value); + } else if (param_name == "log") { + log_mode = LogNever; + if (param_value == "true" || param_value == "if-repl") { + log_mode = LogIfReplace; + } else if (param_value == "always") { + log_mode = LogAlways; + } } } } @@ -104,6 +123,8 @@ QString AccountSettings::toString() { QString s_res = "acc_id=" + addSlashes(account_id); + s_res.append(";en_cnt=").append((enable_contacts) ? "true" : "false"); + s_res.append(";en_cnf=").append((enable_conferences) ? "true" : "false"); QString str1; if (response_mode == RespNotImpl) str1 = "true"; @@ -113,29 +134,44 @@ str1 = "false"; s_res.append(";l_req=").append(str1); s_res.append(";l_treq=").append((lock_time_requ) ? "true" : "false"); - if (!os_name.isNull()) - s_res.append(";os_nm=").append(addSlashes(os_name)); - if (!os_version.isNull()) - s_res.append(";os_ver=").append(addSlashes(os_version)); - if (!client_name.isNull()) - s_res.append(";cl_nm=").append(addSlashes(client_name)); - if (!client_version.isNull()) - s_res.append(";cl_ver=").append(addSlashes(client_version)); - if (!caps_node.isNull()) - s_res.append(";cp_nd=").append(addSlashes(caps_node)); + if (show_requ_mode == LogIfReplace) { + str1 = "if-repl"; + } else if (show_requ_mode == LogAlways) { + str1 = "always"; + } else { + str1 = "never"; + } + s_res.append(";s_req=").append(str1); + s_res.append(";os_nm=").append(addSlashes(os_name)); + s_res.append(";cl_nm=").append(addSlashes(client_name)); + s_res.append(";cl_ver=").append(addSlashes(client_version)); + s_res.append(";cp_nd=").append(addSlashes(caps_node)); + s_res.append(";cp_ver=").append(addSlashes(caps_version)); + if (log_mode == LogIfReplace) { + str1 = "if-repl"; + } else if (log_mode == LogAlways) { + str1 = "always"; + } else { + str1 = "never"; + } + s_res.append(";log=").append(str1); return s_res; } void AccountSettings::init() { - account_id = ""; - response_mode = RespAllow; - lock_time_requ = false; - os_name = ""; - os_version = ""; - client_name = ""; - client_version = ""; - caps_node = ""; + account_id = ""; + enable_contacts = false; + enable_conferences = false; + response_mode = RespAllow; + lock_time_requ = false; + show_requ_mode = LogNever; + os_name = ""; + client_name = ""; + client_version = ""; + caps_node = ""; + caps_version = ""; + log_mode = LogNever; } QString AccountSettings::addSlashes(QString &str) diff -Nru a/plugins/generic/clientswitcherplugin/accountsettings.h b/plugins/generic/clientswitcherplugin/accountsettings.h --- a/plugins/generic/clientswitcherplugin/accountsettings.h 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/accountsettings.h 2020-07-10 11:50:10.000000000 +0000 @@ -31,6 +31,7 @@ public: enum { RespAllow = 0, RespNotImpl = 1, RespIgnore = 2 }; // как номер индекса в combobox + enum { LogNever = 0, LogIfReplace = 1, LogAlways = 2 }; AccountSettings(); AccountSettings(const QString &); @@ -41,13 +42,17 @@ QString toString(); //-- QString account_id; + bool enable_contacts; + bool enable_conferences; int response_mode; bool lock_time_requ; + int show_requ_mode; QString os_name; - QString os_version; QString client_name; QString client_version; QString caps_node; + QString caps_version; + int log_mode; private: //-- diff -Nru a/plugins/generic/clientswitcherplugin/changelog.txt b/plugins/generic/clientswitcherplugin/changelog.txt --- a/plugins/generic/clientswitcherplugin/changelog.txt 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/changelog.txt 2020-07-10 11:50:10.000000000 +0000 @@ -1,11 +1,3 @@ -2024-03-19 -v0.2 - + added initial Qt6 support - -2020-07-22 -v0.1 - * The plugin was rewritten to reuse caps generation of iris - 2013-09-02 v0.0.18 - taurus * Использовать слово Groupchat вместо Conference diff -Nru a/plugins/generic/clientswitcherplugin/clientswitcherplugin.cpp b/plugins/generic/clientswitcherplugin/clientswitcherplugin.cpp --- a/plugins/generic/clientswitcherplugin/clientswitcherplugin.cpp 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/clientswitcherplugin.cpp 2020-07-10 11:50:10.000000000 +0000 @@ -22,21 +22,27 @@ * */ -// #include -// #include +//#include +//#include #include #include "clientswitcherplugin.h" +#include "viewer.h" #define constPluginShortName "clientswitcher" #define constPluginName "Client Switcher Plugin" #define constForAllAcc "for_all_acc" #define constAccSettingList "accsettlist" +#define constShowLogHeight "showlogheight" +#define constShowLogWidth "showlogwidth" +#define constLastLogItem "lastlogview" +#define constPopupDuration "popupduration" ClientSwitcherPlugin::ClientSwitcherPlugin() : - psiOptions(nullptr), psiInfo(nullptr), psiAccount(nullptr), psiAccountCtl(nullptr), enabled(false), - for_all_acc(false), def_os_name(""), def_client_name(""), def_client_version(""), def_caps_node(""), - def_caps_version("") + sender_(nullptr), psiOptions(nullptr), psiPopup(nullptr), psiInfo(nullptr), psiAccount(nullptr), + psiAccountCtl(nullptr), psiContactInfo(nullptr), psiIcon(nullptr), enabled(false), for_all_acc(false), + def_os_name(""), def_client_name(""), def_client_version(""), def_caps_node(""), def_caps_version(""), + heightLogsView(500), widthLogsView(600), lastLogItem(""), popupId(0) { settingsList.clear(); @@ -56,86 +62,73 @@ if (!psiOptions) return false; enabled = true; - - os_presets = { { "Windows", "95" }, - { "Windows", "98" }, - { "Windows", "ME" }, - { "Windows", "2000" }, - { "Windows", "XP" }, - { "Windows", "2003" }, - { "Windows", "2008" }, - { "Windows", "Vista" }, - { "Windows", "7" }, - { "Windows", "10" }, - { "Arch" }, - { "Debian GNU/Linux", "6.0.1 (squeeze)" }, - { "Ubuntu", "10.04.2 LTS" }, - { "RFRemix", "14.1 (Laughlin)" }, - { "openSUSE", "11.4" }, - { "Gentoo Base System", "2.0.3" }, - { "Mac OS X" }, - { "Mac OS X", "10.6" }, - { "Android OS", "2.3.6 (build XXLB1)" }, - { "Plan9" }, - { "Solaris" }, - { "FreeBSD" }, - { "NetBSD" }, - { "OpenBSD" }, - { "Nokia5130c-2", "07.91" }, - { "SonyEricssonW580i", "R8BE001" } }; - + os_presets.clear(); + os_presets << OsStruct("Windows 98") << OsStruct("Windows ME") << OsStruct("Windows 2000"); + os_presets << OsStruct("Windows XP") << OsStruct("Windows Server 2003") << OsStruct("Windows Server 2008"); + os_presets << OsStruct("Windows Vista") << OsStruct("Windows 7, 32-bit") << OsStruct("Windows 7, 64-bit"); + os_presets << OsStruct("Arch Linux") << OsStruct("Debian GNU/Linux 6.0.1 (squeeze)"); + os_presets << OsStruct("Ubuntu 10.04.2 LTS") << OsStruct("RFRemix release 14.1 (Laughlin)"); + os_presets << OsStruct("openSUSE 11.4") << OsStruct("Gentoo Base System release 2.0.3"); + os_presets << OsStruct("Mac OS X") << OsStruct("Mac OS X 10.6"); + os_presets << OsStruct("Android OS 2.3.6 (build XXLB1)"); + os_presets << OsStruct("Plan9") << OsStruct("Solaris"); + os_presets << OsStruct("FreeBSD") << OsStruct("NetBSD") << OsStruct("OpenBSD"); + os_presets << OsStruct("Nokia5130c-2/07.91") << OsStruct("SonyEricssonW580i/R8BE001"); client_presets.clear(); - client_presets << ClientStruct("Bombus", "0.7.1429M-Zlib", "http://bombus-im.org/java"); - client_presets << ClientStruct("Gajim", "0.12.5", "https://gajim.org"); - client_presets << ClientStruct("Mcabber", "0.9.10", "http://mcabber.com/caps"); - client_presets << ClientStruct("Miranda", "0.9.16.0", "http://miranda-im.org/caps"); - client_presets << ClientStruct("Pidgin", "2.8.0", "http://pidgin.im"); - client_presets << ClientStruct("Psi", "0.14", "https://psi-im.org/caps"); - client_presets << ClientStruct("QIP Infium", "9034", "http://qip.ru/caps"); - client_presets << ClientStruct("qutIM", "0.2", "http://qutim.org"); - client_presets << ClientStruct("Swift", "1.0", "http://swift.im"); - client_presets << ClientStruct("Talisman", "0.1.1.15", "http://jabrvista.net.ru"); - client_presets << ClientStruct("Tkabber", "0.11.1", "http://tkabber.jabber.ru"); - client_presets << ClientStruct("Talkonaut", "1.0.0.84", "http://www.google.com/xmpp/client/caps"); - client_presets << ClientStruct(QString::fromUtf8("Я.Онлайн"), "3.2.0.8873", "yandex-webchat"); + client_presets << ClientStruct("Bombus", "0.7.1429M-Zlib", "http://bombus-im.org/java", "0.7.1429M-Zlib"); + client_presets << ClientStruct("Gajim", "0.12.5", "https://gajim.org", "0.12.5"); + client_presets << ClientStruct("Mcabber", "0.9.10", "http://mcabber.com/caps", "0.9.10"); + client_presets << ClientStruct("Miranda", "0.9.16.0", "http://miranda-im.org/caps", "0.9.16.0"); + client_presets << ClientStruct("Pidgin", "2.8.0", "http://pidgin.im", "2.8.0"); + client_presets << ClientStruct("Psi", "0.14", "https://psi-im.org/caps", "0.14"); + client_presets << ClientStruct("QIP Infium", "9034", "http://qip.ru/caps", "9034"); + client_presets << ClientStruct("qutIM", "0.2", "http://qutim.org", "0.2"); + client_presets << ClientStruct("Swift", "1.0", "http://swift.im", "1.0"); + client_presets << ClientStruct("Talisman", "0.1.1.15", "http://jabrvista.net.ru", "0.1.1.15"); + client_presets << ClientStruct("Tkabber", "0.11.1", "http://tkabber.jabber.ru", "0.11.1"); + client_presets << ClientStruct("Talkonaut", "1.0.0.84", "http://www.google.com/xmpp/client/caps", "1.0.0.84"); + client_presets << ClientStruct(QString::fromUtf8("Я.Онлайн"), "3.2.0.8873", "yandex-webchat", "3.2.0.8873"); // Флаг "для всех аккаунтов" for_all_acc = psiOptions->getPluginOption(constForAllAcc, QVariant(false)).toBool(); // Получаем настройки аккаунтов QStringList sett_list = psiOptions->getPluginOption(constAccSettingList, QVariant()).toStringList(); + // Get and register popup option + int popup_duration = psiOptions->getPluginOption(constPopupDuration, QVariant(5000)).toInt() / 1000; + popupId = psiPopup->registerOption(constPluginName, popup_duration, + QLatin1String("plugins.options.clientswitcher.") + constPopupDuration); // Формируем структуры int cnt = sett_list.size(); for (int i = 0; i < cnt; i++) { - AccountSettings *as = new AccountSettings(sett_list.at(i)); - if (as) { - if (as->isValid()) - settingsList.push_back(as); + AccountSettings *ac = new AccountSettings(sett_list.at(i)); + if (ac) { + if (ac->isValid()) + settingsList.push_back(ac); else - delete as; + delete ac; } } + // Настройки для просмотра логов + logsDir = psiInfo->appCurrentProfileDir(ApplicationInfoAccessingHost::DataLocation) + "/logs/clientswitcher"; + QDir dir(logsDir); + if (!dir.exists(logsDir)) + dir.mkpath(logsDir); + logsDir.append("/"); + heightLogsView = psiOptions->getPluginOption(constShowLogHeight, QVariant(heightLogsView)).toInt(); + widthLogsView = psiOptions->getPluginOption(constShowLogWidth, QVariant(widthLogsView)).toInt(); + lastLogItem = psiOptions->getPluginOption(constLastLogItem, QVariant(widthLogsView)).toString(); return true; } bool ClientSwitcherPlugin::disable() { - if (!enabled) - return true; - while (settingsList.size() != 0) { AccountSettings *as = settingsList.takeLast(); if (as) delete as; } - - for (int i = 0;; i++) { - QString id = psiAccount->getId(i); - if (id == "-1") - break; - psiAccountCtl->setClientVersionInfo(i, {}); - } - enabled = false; + psiPopup->unregisterOption(constPluginName); return true; } @@ -151,7 +144,7 @@ ui_options.cb_ospreset->addItem("user defined", "user"); int cnt = os_presets.size(); for (int i = 0; i < cnt; i++) { - ui_options.cb_ospreset->addItem(QString("%1 %2").arg(os_presets.at(i).name, os_presets.at(i).version)); + ui_options.cb_ospreset->addItem(os_presets.at(i).name); } // Заполняем виджет пресетов клиента ui_options.cb_clientpreset->addItem("default", "default"); @@ -160,13 +153,20 @@ for (int i = 0; i < cnt; i++) { ui_options.cb_clientpreset->addItem(client_presets.at(i).name); } - + // Элементы для просмотра логов + QDir dir(logsDir); + int pos = -1; + for (const QString &file : dir.entryList(QDir::Files)) { + ui_options.cb_logslist->addItem(file); + ++pos; + if (file == lastLogItem) + ui_options.cb_logslist->setCurrentIndex(pos); + } + if (pos < 0) + ui_options.bt_viewlog->setEnabled(false); //-- -#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) connect(ui_options.cb_allaccounts, &QCheckBox::stateChanged, this, &ClientSwitcherPlugin::enableAccountsList); -#else - connect(ui_options.cb_allaccounts, &QCheckBox::checkStateChanged, this, &ClientSwitcherPlugin::enableAccountsList); -#endif + connect(ui_options.bt_viewlog, &QPushButton::released, this, &ClientSwitcherPlugin::viewFromOpt); // TODO: update after stopping support of Ubuntu Xenial: connect(ui_options.cb_accounts, SIGNAL(currentIndexChanged(int)), this, SLOT(restoreOptionsAcc(int))); connect(ui_options.cmb_lockrequ, SIGNAL(currentIndexChanged(int)), this, SLOT(enableMainParams(int))); @@ -179,11 +179,13 @@ void ClientSwitcherPlugin::applyOptions() { + bool caps_updated = false; // Аккаунт bool for_all_acc_old = for_all_acc; for_all_acc = ui_options.cb_allaccounts->isChecked(); - bool caps_updated = (for_all_acc != for_all_acc_old); - + if (for_all_acc != for_all_acc_old) { + caps_updated = true; + } int acc_index = ui_options.cb_accounts->currentIndex(); if (acc_index == -1 && !for_all_acc) return; @@ -196,39 +198,53 @@ as->account_id = acc_id; settingsList.push_back(as); } - + // Подмена/блокировка для контактов + bool tmp_flag = ui_options.cb_contactsenable->isChecked(); + if (as->enable_contacts != tmp_flag) { + as->enable_contacts = tmp_flag; + caps_updated = true; + } + // Подмена/блокировка для конференций + tmp_flag = ui_options.cb_conferencesenable->isChecked(); + if (as->enable_conferences != tmp_flag) { + as->enable_conferences = tmp_flag; + caps_updated = true; + } // Блокировка запроса версии int respMode = ui_options.cmb_lockrequ->currentIndex(); if (as->response_mode != respMode) { - caps_updated = (as->response_mode == AccountSettings::RespAllow || respMode == AccountSettings::RespAllow); + if (as->response_mode == AccountSettings::RespAllow || respMode == AccountSettings::RespAllow) + caps_updated = true; as->response_mode = respMode; } // Блокировка запроса времени - bool tmp_flag = ui_options.cb_locktimerequ->isChecked(); + tmp_flag = ui_options.cb_locktimerequ->isChecked(); if (as->lock_time_requ != tmp_flag) { as->lock_time_requ = tmp_flag; caps_updated = true; } + // Уведомления при запросах версии + as->show_requ_mode = ui_options.cmb_showrequ->currentIndex(); + // Ведение лога + as->log_mode = ui_options.cmb_savetolog->currentIndex(); // Наименование ОС if (ui_options.cb_ospreset->currentIndex() == 0) { - caps_updated = !(as->os_name.isEmpty() && as->os_version.isEmpty()); - as->os_name = QString(); - as->os_version = QString(); + as->os_name = ""; } else { - auto name = ui_options.le_osname->text().trimmed(); - auto version = ui_options.le_osversion->text().trimmed(); - caps_updated = !(name == as->os_name && version == as->os_version); - as->os_name = name; - as->os_version = version; + as->os_name = ui_options.le_osname->text().trimmed(); } // Клиент if (ui_options.cb_clientpreset->currentIndex() == 0) { - as->client_name = QString(); - as->client_version = QString(); + as->client_name = ""; + as->client_version = ""; if (!as->caps_node.isEmpty()) { - as->caps_node = QString(); + as->caps_node = ""; caps_updated = true; } + if (!as->caps_version.isEmpty()) { + as->caps_version = ""; + caps_updated = true; + } } else { // Название клиента as->client_name = ui_options.le_clientname->text().trimmed(); @@ -240,6 +256,12 @@ as->caps_node = str1; caps_updated = true; } + // Caps version клиента + str1 = ui_options.le_capsversion->text().trimmed(); + if (as->caps_version != str1) { + as->caps_version = str1; + caps_updated = true; + } } // Сохраняем опции psiOptions->setPluginOption(constForAllAcc, QVariant(for_all_acc)); @@ -284,7 +306,7 @@ QString name = psiAccount->getName(i); if (name.isEmpty()) name = "?"; - ui_options.cb_accounts->addItem(QString("%1 (%2)").arg(name, psiAccount->getJid(i)), QVariant(id)); + ui_options.cb_accounts->addItem(QString("%1 (%2)").arg(name).arg(psiAccount->getJid(i)), QVariant(id)); cnt++; } } @@ -304,8 +326,66 @@ void ClientSwitcherPlugin::optionChanged(const QString & /*option*/) { } +//-- StanzaSender --------------------------------------------------- + +void ClientSwitcherPlugin::setStanzaSendingHost(StanzaSendingHost *host) { sender_ = host; } + // ----------------------- StanzaFilter ------------------------------ -bool ClientSwitcherPlugin::incomingStanza(int, const QDomElement &) { return false; } +bool ClientSwitcherPlugin::incomingStanza(int account, const QDomElement &stanza) +{ + if (!enabled) + return false; + QString acc_id = (for_all_acc) ? "all" : psiAccount->getId(account); + AccountSettings *as = getAccountSetting(acc_id); + if (!as) + return false; + if (!as->enable_contacts && !as->enable_conferences) + return false; + int respMode = as->response_mode; + if (respMode != AccountSettings::RespAllow || as->lock_time_requ || !as->caps_node.isEmpty() + || !as->caps_version.isEmpty()) { + if (stanza.tagName() == "iq" && stanza.attribute("type") == "get") { + const QString s_to = stanza.attribute("from"); + if (isSkipStanza(as, account, s_to)) + return false; + QDomNode s_child = stanza.firstChild(); + while (!s_child.isNull()) { + const QString xmlns = s_child.toElement().namespaceURI(); + if (s_child.toElement().tagName() == "query") { + if (xmlns == "http://jabber.org/protocol/disco#info") { + QString node = s_child.toElement().attribute("node"); + if (!node.isEmpty()) { + QString new_node = def_caps_node; + QStringList split_node = node.split("#"); + if (split_node.size() > 1) { + split_node.removeFirst(); + QString node_ver = split_node.join("#"); + if (node_ver == ((respMode == AccountSettings::RespAllow) ? as->caps_version : "n/a")) { + node_ver = def_caps_version; + } + new_node.append("#" + node_ver); + } + s_child.toElement().setAttribute("node", new_node); + } + } else if (xmlns == "jabber:iq:version") { + if (respMode == AccountSettings::RespIgnore) { + // Showing popup if it is necessary + if (as->show_requ_mode == AccountSettings::LogAlways) + showPopup(jidToNick(account, s_to)); + // Write log if it is necessary + if (as->log_mode == AccountSettings::LogAlways) + saveToLog(account, s_to, "ignored"); + return true; + } + } + } + s_child = s_child.nextSibling(); + } + } + } + + return false; +} bool ClientSwitcherPlugin::outgoingStanza(int account, QDomElement &stanza) { @@ -315,15 +395,216 @@ AccountSettings *as = getAccountSetting(acc_id); if (!as) return false; - + if (as->response_mode != AccountSettings::RespAllow || !as->caps_node.isEmpty() || !as->caps_version.isEmpty()) { + // --------------- presence --------------- + if (stanza.tagName() == "presence") { + if (!as->enable_contacts && !as->enable_conferences) + return false; + // Анализ сообщения о присутствии + bool skipFlag = isSkipStanza(as, account, stanza.attribute("to")); + if (skipFlag && !as->enable_conferences) // Конференция не определяется при входе в нее, проверим позже + return false; + short unsigned int found_flag = 0; // Чтобы исключить перебор лишних элементов + QDomNode s_child = stanza.firstChild(); + QDomNode caps_node; + while (!s_child.isNull()) { + if (((found_flag & 1) == 0) && s_child.toElement().tagName() == "c") { + // Подмена будет отложенной + caps_node = s_child; + found_flag |= 1; + if (found_flag == 3) + break; + s_child = s_child.nextSibling(); + continue; + } + if (((found_flag & 2) == 0) && s_child.toElement().tagName() == "x") { + if (s_child.namespaceURI() == "http://jabber.org/protocol/muc") { + found_flag |= 2; + if (found_flag == 3) + break; + } + } + s_child = s_child.nextSibling(); + } + if ((found_flag == 1 && as->enable_contacts) || (found_flag == 3 && as->enable_conferences)) { + // Подменяем капс + if (as->response_mode != AccountSettings::RespNotImpl) { + caps_node.toElement().setAttribute("node", as->caps_node); + caps_node.toElement().setAttribute("ver", as->caps_version); + } else { + caps_node.toElement().setAttribute("node", "unknown"); + caps_node.toElement().setAttribute("ver", "n/a"); + } + } + return false; + } + } if (stanza.tagName() == "iq" && stanza.attribute("type") == "result") { - // QString s_to = stanza.attribute("to"); + bool is_version_query = false; + bool is_version_replaced = false; + QString s_to = stanza.attribute("to"); QStringList send_ver_list; - auto s_child = stanza.firstChildElement(); + QDomNode s_child = stanza.firstChild(); + int respMode = as->response_mode; while (!s_child.isNull()) { - if (s_child.tagName() == "time") { + if (s_child.toElement().tagName() == "query") { + QString xmlns_str = s_child.namespaceURI(); + if (xmlns_str == "http://jabber.org/protocol/disco#info") { + // --- Ответ disco + if (isSkipStanza(as, account, s_to)) + return false; + if (respMode == AccountSettings::RespAllow && !as->lock_time_requ && as->caps_node.isEmpty() + && as->caps_version.isEmpty()) + return false; + // Подменяем ноду, если она есть + QString node = s_child.toElement().attribute("node"); + if (!node.isEmpty()) { + QString new_node = (respMode == AccountSettings::RespAllow) ? as->caps_node : "unknown"; + QStringList split_node = node.split("#"); + if (split_node.size() > 1) { + split_node.removeFirst(); + QString new_ver = split_node.join("#"); + if (new_ver == def_caps_version) + new_ver = (respMode == AccountSettings::RespAllow) ? as->caps_version : "n/a"; + new_node.append("#" + new_ver); + } + s_child.toElement().setAttribute("node", new_node); + } + // Подменяем identity и удаляем feature для версии, если есть блокировка + int update = 0; + if (respMode == AccountSettings::RespAllow) + ++update; + if (!as->lock_time_requ) + ++update; + QDomNode ver_domnode; + QDomNode time_domnode; + QDomNode q_child = s_child.firstChild(); + while (!q_child.isNull()) { + QString tag_name = q_child.toElement().tagName(); + if (tag_name == "feature") { + if (respMode != AccountSettings::RespAllow + && q_child.toElement().attribute("var") == "jabber:iq:version") { + ver_domnode = q_child; + if (++update >= 3) + break; + } else if (as->lock_time_requ && q_child.toElement().attribute("var") == "urn:xmpp:time") { + time_domnode = q_child; + if (++update >= 3) + break; + } + } else if (tag_name == "identity") { + if (!q_child.toElement().attribute("name").isEmpty()) + q_child.toElement().setAttribute( + "name", (respMode == AccountSettings::RespAllow) ? as->client_name : "unknown"); + if (++update >= 3) + break; + } + q_child = q_child.nextSibling(); + } + if (!s_child.isNull()) { + if (!ver_domnode.isNull()) + s_child.removeChild(ver_domnode); + if (!time_domnode.isNull()) + s_child.removeChild(time_domnode); + } + } else if (xmlns_str == "jabber:iq:version") { + // Ответ version + is_version_query = true; + bool skip_stanza = isSkipStanza(as, account, s_to); + if (skip_stanza && as->log_mode != AccountSettings::LogAlways) + break; + QDomDocument xmldoc = stanza.ownerDocument(); + if (respMode == AccountSettings::RespAllow) { + // Подменяем ответ + bool f_os_name = false; + bool f_client_name = false; + bool f_client_ver = false; + QDomNode q_child = s_child.firstChild(); + while (!q_child.isNull()) { + QString tag_name = q_child.toElement().tagName().toLower(); + if (tag_name == "os") { + QString str1; + if (!skip_stanza && !as->os_name.isEmpty()) { + str1 = as->os_name; + q_child.toElement().replaceChild(xmldoc.createTextNode(str1), q_child.firstChild()); + is_version_replaced = true; + } else { + str1 = q_child.toElement().text(); + } + send_ver_list.push_back("OS: " + str1); + f_os_name = true; + } else if (tag_name == "name") { + QString str1; + if (!skip_stanza && !as->client_name.isEmpty()) { + str1 = as->client_name; + q_child.toElement().replaceChild(xmldoc.createTextNode(str1), q_child.firstChild()); + is_version_replaced = true; + } else { + str1 = q_child.toElement().text(); + } + send_ver_list.push_back("Client name: " + str1); + f_client_name = true; + } else if (tag_name == "version") { + QString str1; + if (!skip_stanza && !as->caps_version.isEmpty()) { + str1 = as->client_version; + q_child.toElement().replaceChild(xmldoc.createTextNode(str1), q_child.firstChild()); + is_version_replaced = true; + } else { + str1 = q_child.toElement().text(); + } + send_ver_list.push_back("Client version: " + str1); + f_client_ver = true; + } + q_child = q_child.nextSibling(); + } + // Создаем теги, если их нет в ответе + QDomDocument doc; + if (!f_client_name && !as->client_name.isEmpty()) { + doc = s_child.ownerDocument(); + QDomElement cl_name = doc.createElement("name"); + cl_name.appendChild(doc.createTextNode(as->client_name)); + s_child.appendChild(cl_name); + } + if (!f_client_ver && !as->client_version.isEmpty()) { + if (doc.isNull()) + doc = s_child.ownerDocument(); + QDomElement cl_ver = doc.createElement("version"); + cl_ver.appendChild(doc.createTextNode(as->client_version)); + s_child.appendChild(cl_ver); + } + if (!f_os_name && !as->os_name.isEmpty()) { + if (doc.isNull()) + doc = s_child.ownerDocument(); + QDomElement os_name = doc.createElement("os"); + os_name.appendChild(doc.createTextNode(as->os_name)); + s_child.appendChild(os_name); + } + } else if (respMode == AccountSettings::RespNotImpl) { + // Отклонение запроса, как будто не реализовано + stanza.setAttribute("type", "error"); + QDomNode q_child = s_child.firstChild(); + while (!q_child.isNull()) { + s_child.removeChild(q_child); + q_child = s_child.firstChild(); + } + QDomElement err = xmldoc.createElement("error"); + err.setAttribute("type", "cancel"); + err.setAttribute("code", "501"); + stanza.appendChild(err); + QDomElement not_imp + = xmldoc.createElementNS("urn:ietf:params:xml:ns:xmpp-stanzas", "feature-not-implemented"); + err.appendChild(not_imp); + send_ver_list.push_back("feature-not-implemented"); + } + } + break; + } else if (s_child.toElement().tagName() == "time") { QString xmlns_str = s_child.namespaceURI(); if (xmlns_str == "urn:xmpp:time") { + // Ответ time + if (isSkipStanza(as, account, s_to)) + break; if (as->lock_time_requ) { QDomDocument xmldoc = stanza.ownerDocument(); // Отклонение запроса, как будто не реализовано @@ -343,8 +624,18 @@ } } } - s_child = s_child.nextSiblingElement(); + s_child = s_child.nextSibling(); } + + // Showing popup if it is necessary + if (is_version_query && as->show_requ_mode != AccountSettings::LogNever + && (as->show_requ_mode == AccountSettings::LogAlways || is_version_replaced)) + showPopup(jidToNick(account, s_to)); + + // Write log if it is necessary + if (is_version_query && as->log_mode != AccountSettings::LogNever + && (as->log_mode == AccountSettings::LogAlways || is_version_replaced)) + saveToLog(account, s_to, send_ver_list.join(", ")); } return false; } @@ -355,6 +646,8 @@ "You can specify the version of the client and OS or to select them from the preset list.\n"); } +void ClientSwitcherPlugin::setPopupAccessingHost(PopupAccessingHost *host) { psiPopup = host; } + // ----------------------- ApplicationInfoAccessor ------------------------------ void ClientSwitcherPlugin::setApplicationInfoAccessingHost(ApplicationInfoAccessingHost *host) { @@ -366,7 +659,6 @@ def_caps_node = psiInfo->appCapsNode(); def_caps_version = psiInfo->appCapsVersion(); def_os_name = psiInfo->appOsName(); - def_os_version = psiInfo->appOsVersion(); } } @@ -374,22 +666,23 @@ void ClientSwitcherPlugin::setAccountInfoAccessingHost(AccountInfoAccessingHost *host) { psiAccount = host; } // ----------------------- PsiAccountController ---------------------------------- -void ClientSwitcherPlugin::setPsiAccountControllingHost(PsiAccountControllingHost *host) -{ - psiAccountCtl = host; +void ClientSwitcherPlugin::setPsiAccountControllingHost(PsiAccountControllingHost *host) { psiAccountCtl = host; } - psiAccountCtl->subscribeBeforeLogin(this, [this](int accountId) { updateInfo(accountId); }); -} +// ----------------------- ContactInfoAccessor ----------------------- +void ClientSwitcherPlugin::setContactInfoAccessingHost(ContactInfoAccessingHost *host) { psiContactInfo = host; } + +// ----------------------- IconFactoryAccessor ----------------------- +void ClientSwitcherPlugin::setIconFactoryAccessingHost(IconFactoryAccessingHost *host) { psiIcon = host; } // ----------------------- Private ------------------------------ -int ClientSwitcherPlugin::getOsTemplateIndex(const QString &os_name, const QString &os_version) +int ClientSwitcherPlugin::getOsTemplateIndex(const QString &os_name) { if (os_name.isEmpty()) return 0; // default int cnt = os_presets.size(); for (int i = 0; i < cnt; i++) { - if (os_name == os_presets.at(i).name && os_version == os_presets.at(i).version) { + if (os_name == os_presets.at(i).name) { i += 2; // Т.к. впереди default и user defined return i; } @@ -397,14 +690,15 @@ return 1; // user defined } -int ClientSwitcherPlugin::getClientTemplateIndex(const QString &cl_name, const QString &cl_ver, const QString &cp_node) +int ClientSwitcherPlugin::getClientTemplateIndex(const QString &cl_name, const QString &cl_ver, const QString &cp_node, + const QString &cp_ver) { - if (cl_name.isEmpty() && cl_ver.isEmpty() && cp_node.isEmpty()) + if (cl_name.isEmpty() && cl_ver.isEmpty() && cp_node.isEmpty() && cp_ver.isEmpty()) return 0; // default int cnt = client_presets.size(); for (int i = 0; i < cnt; i++) { if (cl_name == client_presets.at(i).name && cl_ver == client_presets.at(i).version) { - if (cp_node == client_presets.at(i).caps_node) { + if (cp_node == client_presets.at(i).caps_node && cp_ver == client_presets.at(i).caps_version) { i += 2; // есть еще default и user defined return i; } @@ -438,28 +732,6 @@ return nullptr; } -ClientSwitcherPlugin::UpdateStatus ClientSwitcherPlugin::updateInfo(int account) -{ - if (!enabled || !psiAccount || !psiAccountCtl) - return UpdateStatus::Disabled; - - QString acc_id = psiAccount->getId(account); - if (acc_id == "-1" || acc_id.isEmpty()) - return UpdateStatus::NoAccount; - - AccountSettings *as = getAccountSetting(acc_id); - if (!as || !as->isValid()) - return UpdateStatus::NoSettings; - - QVariantMap info = { { "os-name", as->os_name }, - { "os-version", as->os_version }, - { "client-name", as->client_name }, - { "client-version", as->client_version }, - { "caps-node", as->caps_node } }; - psiAccountCtl->setClientVersionInfo(account, info); - return UpdateStatus::Ok; -} - /** * Отсылка пресентса с новыми капсами от имени указанного аккаунта * Если account == -1, то капсы посылаются всем активным аккаунтам @@ -472,10 +744,10 @@ if (acc == -1) acc = 0; for (;; acc++) { - auto ret = updateInfo(acc); - if (ret == UpdateStatus::NoAccount) + QString acc_id = psiAccount->getId(acc); + if (acc_id == "-1") break; - if (ret == UpdateStatus::Ok) { + if (!acc_id.isEmpty()) { QString acc_status = psiAccount->getStatus(acc); if (!acc_status.isEmpty() && acc_status != "offline" && acc_status != "invisible") { // Отсылаем тот же статус, а капсы заменим в outgoingStanza() @@ -487,6 +759,32 @@ } } +bool ClientSwitcherPlugin::isSkipStanza(AccountSettings *as, const int account, const QString &to) +{ + if (to.isEmpty()) { // Широковещательный + if (!as->enable_contacts) + return true; + } else { // Адресный + QString to_jid = to.split("/").takeFirst(); + if (!to_jid.contains("@")) { + if (as->enable_contacts) { + if (!to.contains("/")) + return false; // Предполагаем что это сервер + return true; // Ошибочный запрос + } + } + if (psiContactInfo->isConference(account, to_jid) || psiContactInfo->isPrivate(account, to)) { + // if (to.contains("conference.")) { + if (!as->enable_conferences) + return true; + } else { + if (!as->enable_contacts) + return true; + } + } + return false; +} + // ----------------------- Slots ------------------------------ void ClientSwitcherPlugin::enableAccountsList(int all_acc_mode) { @@ -516,23 +814,30 @@ as->account_id = acc_id; settingsList.push_back(as); } + // Подмена/блокировка для контактов + ui_options.cb_contactsenable->setChecked(as->enable_contacts); + // Подмена/блокировка для конференций + ui_options.cb_conferencesenable->setChecked(as->enable_conferences); // Блокировка запроса версии ui_options.cmb_lockrequ->setCurrentIndex(as->response_mode); // Блокировка запроса времени ui_options.cb_locktimerequ->setChecked(as->lock_time_requ); + // Уведомления при запросах версии + ui_options.cmb_showrequ->setCurrentIndex(as->show_requ_mode); + // Ведение лога + ui_options.cmb_savetolog->setCurrentIndex(as->log_mode); // Виджет шаблона ОС - QString os_name = as->os_name; - QString os_version = as->os_version; - int os_templ = getOsTemplateIndex(os_name, os_version); + QString os_name = as->os_name; + int os_templ = getOsTemplateIndex(os_name); ui_options.cb_ospreset->setCurrentIndex(os_templ); // Название ОС ui_options.le_osname->setText(os_name); - ui_options.le_osversion->setText(os_version); // Виджет шаблона клиента QString cl_name = as->client_name; QString cl_ver = as->client_version; QString cp_node = as->caps_node; - int cl_templ = getClientTemplateIndex(cl_name, cl_ver, cp_node); + QString cp_ver = as->caps_version; + int cl_templ = getClientTemplateIndex(cl_name, cl_ver, cp_node, cp_ver); ui_options.cb_clientpreset->setCurrentIndex(cl_templ); // Название клиента ui_options.le_clientname->setText(cl_name); @@ -540,7 +845,10 @@ ui_options.le_clientversion->setText(cl_ver); // Caps node клиента ui_options.le_capsnode->setText(cp_node); + // Caps version клиента + ui_options.le_capsversion->setText(cp_ver); // Блокировка/снятие блокировки виджетов + ui_options.gb_enablefor->setEnabled(true); ui_options.cmb_lockrequ->setEnabled(true); enableMainParams(as->response_mode); enableOsParams(os_templ); @@ -549,6 +857,9 @@ } } // Блокировка виджетов при отсутствии валидного акка + ui_options.cb_contactsenable->setChecked(false); + ui_options.cb_conferencesenable->setChecked(false); + ui_options.gb_enablefor->setEnabled(false); ui_options.cmb_lockrequ->setCurrentIndex(AccountSettings::RespAllow); ui_options.cmb_lockrequ->setEnabled(false); ui_options.cb_ospreset->setCurrentIndex(0); @@ -570,20 +881,16 @@ { if (mode == 1) { // user defined ui_options.le_osname->setEnabled(true); - ui_options.le_osversion->setEnabled(true); } else { if (mode == 0) { // default os ui_options.le_osname->setText(def_os_name); - ui_options.le_osversion->setText(def_os_version); } else { int pres_index = mode - 2; if (pres_index >= 0 && pres_index < os_presets.size()) { ui_options.le_osname->setText(os_presets.at(pres_index).name); - ui_options.le_osversion->setText(os_presets.at(pres_index).version); } } ui_options.le_osname->setEnabled(false); - ui_options.le_osversion->setEnabled(false); } } @@ -606,6 +913,7 @@ ui_options.le_clientname->setText(client_presets.at(pres_index).name); ui_options.le_clientversion->setText(client_presets.at(pres_index).version); ui_options.le_capsnode->setText(client_presets.at(pres_index).caps_node); + ui_options.le_capsversion->setText(client_presets.at(pres_index).caps_version); } } ui_options.le_clientname->setEnabled(false); @@ -614,3 +922,74 @@ ui_options.le_capsversion->setEnabled(false); } } + +void ClientSwitcherPlugin::viewFromOpt() +{ + lastLogItem = ui_options.cb_logslist->currentText(); + if (lastLogItem.isEmpty()) + return; + psiOptions->setPluginOption(constLastLogItem, QVariant(lastLogItem)); + showLog(lastLogItem); +} + +QString ClientSwitcherPlugin::jidToNick(int account, const QString &jid) +{ + QString nick; + if (psiContactInfo) + nick = psiContactInfo->name(account, jid); + if (nick.isEmpty()) + nick = jid; + return nick; +} + +void ClientSwitcherPlugin::showPopup(const QString &nick) +{ + int msecs = psiPopup->popupDuration(constPluginName); + if (msecs > 0) + psiPopup->initPopup(tr("%1 has requested your version").arg(sender_->escape(nick)), constPluginName, + "psi/headline", popupId); +} + +void ClientSwitcherPlugin::showLog(const QString &filename) +{ + QString fullname = logsDir + filename; + Viewer *v = new Viewer(fullname, psiIcon); + v->resize(widthLogsView, heightLogsView); + if (!v->init()) { + delete (v); + return; + } + connect(v, &Viewer::onClose, this, &ClientSwitcherPlugin::onCloseView); + v->show(); +} + +void ClientSwitcherPlugin::saveToLog(const int account, const QString &to_jid, const QString &ver_str) +{ + QString acc_jid = psiAccount->getJid(account); + if (acc_jid.isEmpty() || acc_jid == "-1") + return; + QFile file(logsDir + acc_jid.replace("@", "_at_") + ".log"); + if (file.open(QIODevice::WriteOnly | QIODevice::Append)) { + QString time_str = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); + QTextStream out(&file); + out.setCodec("UTF-8"); + out.setGenerateByteOrderMark(false); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + out << time_str << " " << to_jid << " <-- " << ver_str << Qt::endl; +#else + out << time_str << " " << to_jid << " <-- " << ver_str << endl; +#endif + } +} + +void ClientSwitcherPlugin::onCloseView(int w, int h) +{ + if (widthLogsView != w) { + widthLogsView = w; + psiOptions->setPluginOption(constShowLogWidth, QVariant(w)); + } + if (heightLogsView != h) { + heightLogsView = h; + psiOptions->setPluginOption(constShowLogHeight, QVariant(h)); + } +} diff -Nru a/plugins/generic/clientswitcherplugin/clientswitcherplugin.h b/plugins/generic/clientswitcherplugin/clientswitcherplugin.h --- a/plugins/generic/clientswitcherplugin/clientswitcherplugin.h 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/clientswitcherplugin.h 2020-07-10 11:50:10.000000000 +0000 @@ -42,6 +42,8 @@ #include "optionaccessinghost.h" #include "optionaccessor.h" #include "plugininfoprovider.h" +#include "popupaccessinghost.h" +#include "popupaccessor.h" #include "psiaccountcontroller.h" #include "psiaccountcontrollinghost.h" #include "psiplugin.h" @@ -54,17 +56,22 @@ class ClientSwitcherPlugin : public QObject, public PsiPlugin, public OptionAccessor, + public StanzaSender, public StanzaFilter, public PluginInfoProvider, + public PopupAccessor, public ApplicationInfoAccessor, public AccountInfoAccessor, - public PsiAccountController + public PsiAccountController, + public ContactInfoAccessor, + public IconFactoryAccessor { Q_OBJECT Q_PLUGIN_METADATA(IID "com.psi-plus.ClientSwitcherPlugin" FILE "psiplugin.json") - Q_INTERFACES(PsiPlugin OptionAccessor StanzaFilter PluginInfoProvider ApplicationInfoAccessor AccountInfoAccessor - PsiAccountController) + Q_INTERFACES( + PsiPlugin OptionAccessor StanzaSender StanzaFilter PluginInfoProvider PopupAccessor ApplicationInfoAccessor + AccountInfoAccessor PsiAccountController ContactInfoAccessor IconFactoryAccessor) public: ClientSwitcherPlugin(); @@ -78,59 +85,79 @@ // OptionAccessor virtual void setOptionAccessingHost(OptionAccessingHost *); virtual void optionChanged(const QString &); + // StanzaSender + virtual void setStanzaSendingHost(StanzaSendingHost *); // StanzaFilter virtual bool incomingStanza(int, const QDomElement &); virtual bool outgoingStanza(int, QDomElement &); // EventFilter virtual QString pluginInfo(); + // PopupAccessor + virtual void setPopupAccessingHost(PopupAccessingHost *); // ApplicationInfoAccessor virtual void setApplicationInfoAccessingHost(ApplicationInfoAccessingHost *); // AccountInfoAccessing virtual void setAccountInfoAccessingHost(AccountInfoAccessingHost *); // PsiAccountController virtual void setPsiAccountControllingHost(PsiAccountControllingHost *); + // ContactInfoAccessor + virtual void setContactInfoAccessingHost(ContactInfoAccessingHost *); + // IconFactoryAccessor + virtual void setIconFactoryAccessingHost(IconFactoryAccessingHost *host); private: struct OsStruct { QString name; - QString version; - OsStruct(const QString &n, const QString &v = QString()) : name(n), version(v) { } + OsStruct(const QString &n) : name(n) { } }; struct ClientStruct { QString name; QString version; QString caps_node; - ClientStruct(const QString &n, const QString &v, const QString &cn) : name(n), version(v), caps_node(cn) { } + QString caps_version; + ClientStruct(const QString &n, const QString &v, const QString &cn, const QString &cv) : + name(n), version(v), caps_node(cn), caps_version(cv) + { + } }; - Ui::OptionsWidget ui_options; - OptionAccessingHost *psiOptions; + StanzaSendingHost * sender_; + OptionAccessingHost * psiOptions; + PopupAccessingHost * psiPopup; ApplicationInfoAccessingHost *psiInfo; - AccountInfoAccessingHost *psiAccount; - PsiAccountControllingHost *psiAccountCtl; + AccountInfoAccessingHost * psiAccount; + PsiAccountControllingHost * psiAccountCtl; + ContactInfoAccessingHost * psiContactInfo; + IconFactoryAccessingHost * psiIcon; //-- bool enabled; bool for_all_acc; QList settingsList; //-- QString def_os_name; - QString def_os_version; QString def_client_name; QString def_client_version; QString def_caps_node; QString def_caps_version; QList os_presets; QList client_presets; + QString logsDir; + int heightLogsView; + int widthLogsView; + QString lastLogItem; + int popupId; + //-- - int getOsTemplateIndex(const QString &, const QString &os_version); - int getClientTemplateIndex(const QString &, const QString &, const QString &); + int getOsTemplateIndex(const QString &); + int getClientTemplateIndex(const QString &, const QString &, const QString &, const QString &); int getAccountById(const QString &); AccountSettings *getAccountSetting(const QString &); - -private: - enum class UpdateStatus { Ok, Disabled, NoAccount, NoSettings }; - UpdateStatus updateInfo(int account); + bool isSkipStanza(AccountSettings *, const int, const QString &); + QString jidToNick(int account, const QString &jid); + void showPopup(const QString &nick); + void showLog(const QString &filename); + void saveToLog(const int, const QString &, const QString &); private slots: void enableAccountsList(int); @@ -139,6 +166,8 @@ void enableOsParams(int); void enableClientParams(int); void setNewCaps(int); + void viewFromOpt(); + void onCloseView(int, int); }; #endif diff -Nru a/plugins/generic/clientswitcherplugin/CMakeLists.txt b/plugins/generic/clientswitcherplugin/CMakeLists.txt --- a/plugins/generic/clientswitcherplugin/CMakeLists.txt 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/CMakeLists.txt 2020-07-10 11:50:10.000000000 +0000 @@ -6,12 +6,9 @@ set( PLUGIN clientswitcherplugin ) project(${PLUGIN} LANGUAGES CXX) -cmake_minimum_required(VERSION 3.10.0) +cmake_minimum_required(VERSION 3.1.0) if(POLICY CMP0071) - cmake_policy(SET CMP0071 NEW) -endif() -if(POLICY CMP0074) - cmake_policy(SET CMP0074 NEW) + cmake_policy(SET CMP0071 OLD) endif() set( CMAKE_AUTOMOC TRUE ) @@ -24,6 +21,7 @@ find_package(PsiPluginsApi REQUIRED) include(${PsiPluginsApi_DIR}/variables.cmake) include_directories( + ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR} ${PsiPluginsApi_INCLUDE_DIR} @@ -35,20 +33,24 @@ set( _HDRS ${PLUGIN}.h accountsettings.h + viewer.h + typeaheadfind.h ) set( _SRCS ${PLUGIN}.cpp accountsettings.cpp + viewer.cpp + typeaheadfind.cpp ) set( _UIS options.ui ) -find_package( Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Widgets Xml REQUIRED ) +find_package( Qt5 COMPONENTS Widgets Xml REQUIRED ) set(QT_DEPLIBS - Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets - Qt${QT_DEFAULT_MAJOR_VERSION}::Xml + Qt5::Widgets + Qt5::Xml ) -qt_wrap_ui(UIS ${_UIS}) +qt5_wrap_ui(UIS ${_UIS}) if(WIN32) set(LIB_TYPE "MODULE") diff -Nru a/plugins/generic/clientswitcherplugin/options.ui b/plugins/generic/clientswitcherplugin/options.ui --- a/plugins/generic/clientswitcherplugin/options.ui 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/options.ui 2020-07-10 11:50:10.000000000 +0000 @@ -13,109 +13,57 @@ Form - + - - - - - Account: - - - - - - - - 141 - 0 - - - - - - - - For all accounts - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - true + + + + 0 + 1 + + + + 0 - - - - 0 - 0 - 942 - 664 - - - + + + General + + - + - + - Response mode + Account: - - - - allow - - - - - not implemented - - - - - ignore - - + + + + 141 + 0 + + - + - Deny iq time request + For all accounts - + Qt::Horizontal - - QSizePolicy::Expanding - - 18 + 40 20 @@ -124,133 +72,347 @@ - - + + true - - OS - - - - - - - - OS name - - - - - - - Template - - - - - - - - - - - - - OS version - - - - - - - - - - - - - - - Client - - - - - - - - Template - - - - - - - - - - Client name - - - - - - - - - - Client version - - - - - - - - - - Caps node - - - - - - - - - - Caps version - - - - - - - - - + + + + 0 + 0 + 930 + 573 + + + + + + + + + Response mode + + + + + + + + allow + + + + + not implemented + + + + + ignore + + + + + + + + Deny iq time request + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 18 + 20 + + + + + + + + + + true + + + OS + + + + + + + + Template + + + + + + + + + + OS name + + + + + + + + + + + + + + + Client + + + + + + + + Template + + + + + + + + + + Client name + + + + + + + + + + Client version + + + + + + + + + + Caps node + + + + + + + + + + Caps version + + + + + + + + + + + + + + + + 0 + 0 + + + + Enable for: + + + + + + + + Contacts + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 30 + 20 + + + + + + + + Groupchats + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 18 + 20 + + + + + + + + + + + + + + + Show popup at version iq + + + + + + + + never + + + + + if iq replaced + + + + + always + + + + + + + + Save queries to log + + + + + + + + never + + + + + if iq replaced + + + + + always + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Logs + + - - - <b>Attention!</b> Thoughtless usage of Client Switcher Plugin may cause to inability of using OMEMO and OpenPGP encryption. Use functions of this plugin very carefully... - - - true - - + + + + + + 1 + 0 + + + + + + + + View log + + + + - + Qt::Vertical 20 - 40 + 471 @@ -260,6 +422,16 @@ + + + <b>Attention!</b> Thoughtless usage of Client Switcher Plugin may cause to inability of using OMEMO and OpenPGP encryption. Use functions of this plugin very carefully... + + + true + + + + <a href="https://psi-plus.com/wiki/en:plugins#client_switcher_plugin">Wiki (Online)</a> @@ -271,6 +443,27 @@ + + tabWidget + cb_accounts + cb_allaccounts + scrollArea + cmb_lockrequ + cb_locktimerequ + cb_ospreset + le_osname + cb_clientpreset + le_clientname + le_clientversion + le_capsnode + le_capsversion + cb_contactsenable + cb_conferencesenable + cmb_showrequ + cmb_savetolog + cb_logslist + bt_viewlog + diff -Nru a/plugins/generic/clientswitcherplugin/psiplugin.json b/plugins/generic/clientswitcherplugin/psiplugin.json --- a/plugins/generic/clientswitcherplugin/psiplugin.json 2025-08-26 14:50:07.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/psiplugin.json 2020-07-10 11:50:10.000000000 +0000 @@ -2,7 +2,7 @@ "name": "Client Switcher", "name:ru": "Подмена клиента", "shortname": "clientswitcher", - "version": "0.2", + "version": "0.0.18", "priority": 2, "icon": "base64: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", "description": "Hide or replace information about your XMPP client and/or operating system.", diff -Nru a/plugins/generic/clientswitcherplugin/typeaheadfind.cpp b/plugins/generic/clientswitcherplugin/typeaheadfind.cpp --- a/plugins/generic/clientswitcherplugin/typeaheadfind.cpp 1970-01-01 00:00:00.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/typeaheadfind.cpp 2020-07-10 11:50:10.000000000 +0000 @@ -0,0 +1,178 @@ +/* + * typeaheadfind.cpp - plugin + * Copyright (C) 2009-2010 Evgeny Khryukin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 "typeaheadfind.h" + +#include +#include +#include +#include + +using namespace ClientSwitcher; + +class TypeAheadFindBar::Private { +public: + void doFind(bool backward = false) + { + QTextDocument::FindFlags options; + if (caseSensitive) + options |= QTextDocument::FindCaseSensitively; + if (backward) { + options |= QTextDocument::FindBackward; + QTextCursor cursor = te->textCursor(); + cursor.setPosition(cursor.selectionStart()); + cursor.movePosition(QTextCursor::Left); + te->setTextCursor(cursor); + } + if (find(text, options)) { + le_find->setStyleSheet(""); + } else { + le_find->setStyleSheet("QLineEdit { background: #ff6666; color: #ffffff }"); + } + } + + bool find(const QString &str, QTextDocument::FindFlags options, + QTextCursor::MoveOperation start = QTextCursor::NoMove) + { + Q_UNUSED(str); + if (start != QTextCursor::NoMove) { + QTextCursor cursor = te->textCursor(); + cursor.movePosition(start); + te->setTextCursor(cursor); + } + bool found = te->find(text, options); + if (!found) { + if (start == QTextCursor::NoMove) + return find(text, options, + options & QTextDocument::FindBackward ? QTextCursor::End : QTextCursor::Start); + return false; + } + return true; + } + + QString text; + bool caseSensitive; + QTextEdit * te; + QLineEdit * le_find; + QPushButton *but_next; + QPushButton *but_prev; + QPushButton *first_page, *next_page, *last_page, *prev_page; + QCheckBox * cb_case; +}; + +TypeAheadFindBar::TypeAheadFindBar(IconFactoryAccessingHost *IcoHost, QTextEdit *textedit, const QString &title, + QWidget *parent) : + QToolBar(title, parent), + icoHost_(IcoHost) +{ + d = new Private(); + d->te = textedit; + init(); +} + +void TypeAheadFindBar::init() +{ + d->caseSensitive = false; + d->text = ""; + addWidget(new QLabel(tr("Search: "), this)); + + d->le_find = new QLineEdit(this); + d->le_find->setMaximumWidth(128); + connect(d->le_find, &QLineEdit::textEdited, this, &TypeAheadFindBar::textChanged); + addWidget(d->le_find); + + d->but_prev = new QPushButton(this); + d->but_prev->setFixedSize(25, 25); + d->but_prev->setIcon(icoHost_->getIcon("psi/arrowUp")); + d->but_prev->setEnabled(false); + connect(d->but_prev, &QPushButton::released, this, &TypeAheadFindBar::findPrevious); + addWidget(d->but_prev); + + d->but_next = new QPushButton(this); + d->but_next->setFixedSize(25, 25); + d->but_next->setIcon(icoHost_->getIcon("psi/arrowDown")); + d->but_next->setEnabled(false); + connect(d->but_next, &QPushButton::released, this, &TypeAheadFindBar::findNext); + addWidget(d->but_next); + + d->cb_case = new QCheckBox(tr("&Case sensitive"), this); + connect(d->cb_case, &QCheckBox::clicked, this, &TypeAheadFindBar::caseToggled); + addWidget(d->cb_case); + + addSeparator(); + + d->first_page = new QPushButton(this); + d->first_page->setToolTip(tr("First page")); + connect(d->first_page, &QPushButton::released, this, &TypeAheadFindBar::firstPage); + d->first_page->setFixedSize(25, 25); + d->first_page->setIcon(icoHost_->getIcon("psi/doubleBackArrow")); + addWidget(d->first_page); + + d->prev_page = new QPushButton(this); + d->prev_page->setToolTip(tr("Previous page")); + connect(d->prev_page, &QPushButton::released, this, &TypeAheadFindBar::prevPage); + d->prev_page->setFixedSize(25, 25); + d->prev_page->setIcon(icoHost_->getIcon("psi/arrowLeft")); + addWidget(d->prev_page); + + d->next_page = new QPushButton(this); + d->next_page->setToolTip(tr("Next page")); + connect(d->next_page, &QPushButton::released, this, &TypeAheadFindBar::nextPage); + d->next_page->setFixedSize(25, 25); + d->next_page->setIcon(icoHost_->getIcon("psi/arrowRight")); + addWidget(d->next_page); + + d->last_page = new QPushButton(this); + d->last_page->setToolTip(tr("Last page")); + connect(d->last_page, &QPushButton::released, this, &TypeAheadFindBar::lastPage); + d->last_page->setFixedSize(25, 25); + d->last_page->setIcon(icoHost_->getIcon("psi/doubleNextArrow")); + addWidget(d->last_page); +} + +TypeAheadFindBar::~TypeAheadFindBar() +{ + delete d; + d = nullptr; +} + +void TypeAheadFindBar::textChanged(const QString &str) +{ + QTextCursor cursor = d->te->textCursor(); + if (str.isEmpty()) { + d->but_next->setEnabled(false); + d->but_prev->setEnabled(false); + d->le_find->setStyleSheet(""); + cursor.clearSelection(); + d->te->setTextCursor(cursor); + } else { + d->but_next->setEnabled(true); + d->but_prev->setEnabled(true); + cursor.setPosition(cursor.selectionStart()); + d->te->setTextCursor(cursor); + d->text = str; + d->doFind(); + } +} + +void TypeAheadFindBar::findNext() { d->doFind(); } + +void TypeAheadFindBar::findPrevious() { d->doFind(true); } + +void TypeAheadFindBar::caseToggled() { d->caseSensitive = d->cb_case->checkState(); } diff -Nru a/plugins/generic/clientswitcherplugin/typeaheadfind.h b/plugins/generic/clientswitcherplugin/typeaheadfind.h --- a/plugins/generic/clientswitcherplugin/typeaheadfind.h 1970-01-01 00:00:00.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/typeaheadfind.h 2020-07-10 11:50:10.000000000 +0000 @@ -0,0 +1,58 @@ +/* + * typeaheadfind.h - plugin + * Copyright (C) 2009-2010 Evgeny Khryukin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 TYPEAHEADFIND_H +#define TYPEAHEADFIND_H + +#include +#include + +#include "iconfactoryaccessinghost.h" + +namespace ClientSwitcher { + +class TypeAheadFindBar : public QToolBar { + Q_OBJECT +public: + TypeAheadFindBar(IconFactoryAccessingHost *IcoHost, QTextEdit *textedit, const QString &title, + QWidget *parent = nullptr); + + ~TypeAheadFindBar(); + void init(); + +signals: + void firstPage(); + void lastPage(); + void nextPage(); + void prevPage(); + +private slots: + void textChanged(const QString &); + void findNext(); + void findPrevious(); + void caseToggled(); + +private: + class Private; + Private * d; + IconFactoryAccessingHost *icoHost_; +}; + +} +#endif diff -Nru a/plugins/generic/clientswitcherplugin/viewer.cpp b/plugins/generic/clientswitcherplugin/viewer.cpp --- a/plugins/generic/clientswitcherplugin/viewer.cpp 1970-01-01 00:00:00.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/viewer.cpp 2020-07-10 11:50:10.000000000 +0000 @@ -0,0 +1,190 @@ +/* + * viewer.cpp - plugin + * Copyright (C) 2009-2010 Evgeny Khryukin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 "viewer.h" + +#include +#include +#include +#include +#include +#include + +Viewer::Viewer(QString filename, IconFactoryAccessingHost *IcoHost, QWidget *parent) : + QDialog(parent), icoHost_(IcoHost), fileName_(filename) +{ + setAttribute(Qt::WA_DeleteOnClose); + setWindowTitle(filename); + setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint + | Qt::CustomizeWindowHint); + QVBoxLayout *layout = new QVBoxLayout(this); + textWid = new QTextEdit(); + QPalette pal = textWid->palette(); + pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight)); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText)); + textWid->setPalette(pal); + layout->addWidget(textWid); + findBar = new ClientSwitcher::TypeAheadFindBar(icoHost_, textWid, tr("Find"), this); + QPushButton *Close = new QPushButton(icoHost_->getIcon("psi/quit"), tr("Close")); + QPushButton *Save = new QPushButton(icoHost_->getIcon("psi/save"), tr("Save Changes")); + QPushButton *Delete = new QPushButton(icoHost_->getIcon("psi/remove"), tr("Delete Log")); + QPushButton *Update = new QPushButton(icoHost_->getIcon("psi/reload"), tr("Update Log")); + QHBoxLayout *butLayout = new QHBoxLayout(); + butLayout->addWidget(Delete); + butLayout->addStretch(); + butLayout->addWidget(Update); + butLayout->addWidget(Save); + butLayout->addWidget(Close); + layout->addWidget(findBar); + layout->addLayout(butLayout); + connect(Close, &QPushButton::released, this, &Viewer::close); + connect(Delete, &QPushButton::released, this, &Viewer::deleteLog); + connect(Save, &QPushButton::released, this, &Viewer::saveLog); + connect(Update, &QPushButton::released, this, &Viewer::updateLog); + + connect(findBar, &ClientSwitcher::TypeAheadFindBar::firstPage, this, &Viewer::firstPage); + connect(findBar, &ClientSwitcher::TypeAheadFindBar::lastPage, this, &Viewer::lastPage); + connect(findBar, &ClientSwitcher::TypeAheadFindBar::prevPage, this, &Viewer::prevPage); + connect(findBar, &ClientSwitcher::TypeAheadFindBar::nextPage, this, &Viewer::nextPage); +} + +void Viewer::closeEvent(QCloseEvent *e) +{ + emit onClose(width(), height()); + QDialog::closeEvent(e); + e->accept(); +} + +void Viewer::deleteLog() +{ + int ret = QMessageBox::question(this, tr("Delete log file"), tr("Are you sure?"), QMessageBox::Yes, + QMessageBox::Cancel); + if (ret == QMessageBox::Cancel) { + return; + } + close(); + QFile file(fileName_); + if (file.open(QIODevice::ReadWrite)) { + file.remove(); + } +} + +void Viewer::saveLog() +{ + QDateTime Modified = QFileInfo(fileName_).lastModified(); + if (lastModified_ < Modified) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Save log")); + msgBox.setText(tr("New messages has been added to log. If you save your changes, you will lose them")); + msgBox.setInformativeText(tr("Do you want to save your changes?")); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + int ret = msgBox.exec(); + if (ret == QMessageBox::Cancel) { + return; + } + } else { + int ret + = QMessageBox::question(this, tr("Save log"), tr("Are you sure?"), QMessageBox::Yes, QMessageBox::Cancel); + if (ret == QMessageBox::Cancel) { + return; + } + } + QFile file(fileName_); + if (file.open(QIODevice::ReadWrite)) { + file.remove(); + } + if (file.open(QIODevice::ReadWrite)) { + QTextStream out(&file); + out.setCodec("UTF-8"); + QString Text = textWid->toPlainText(); + pages_.insert(currentPage_, Text); + for (int i = 0; i < pages_.size(); i++) { + out.setGenerateByteOrderMark(false); + out << pages_.value(i); + } + } +} + +void Viewer::updateLog() +{ + pages_.clear(); + init(); +} + +bool Viewer::init() +{ + bool b = false; + QFile file(fileName_); + if (file.open(QIODevice::ReadOnly)) { + QString page; + int numPage = 0; + QTextStream in(&file); + in.setCodec("UTF-8"); + while (!in.atEnd()) { + page = ""; + for (int i = 0; i < 500; i++) { + if (in.atEnd()) + break; + page += in.readLine() + "\n"; + } + pages_.insert(numPage++, page); + } + currentPage_ = pages_.size() - 1; + lastModified_ = QDateTime::currentDateTime(); + setPage(); + b = true; + } + return b; +} + +void Viewer::setPage() +{ + QString text = pages_.value(currentPage_); + textWid->setText(text); + QTextCursor cur = textWid->textCursor(); + cur.setPosition(text.length()); + textWid->setTextCursor(cur); +} + +void Viewer::nextPage() +{ + if (currentPage_ < pages_.size() - 1) + currentPage_++; + setPage(); +} + +void Viewer::prevPage() +{ + if (currentPage_ > 0) + currentPage_--; + setPage(); +} + +void Viewer::lastPage() +{ + currentPage_ = pages_.size() - 1; + setPage(); +} + +void Viewer::firstPage() +{ + currentPage_ = 0; + setPage(); +} diff -Nru a/plugins/generic/clientswitcherplugin/viewer.h b/plugins/generic/clientswitcherplugin/viewer.h --- a/plugins/generic/clientswitcherplugin/viewer.h 1970-01-01 00:00:00.000000000 +0000 +++ b/plugins/generic/clientswitcherplugin/viewer.h 2020-07-10 11:50:10.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * viewer.h - plugin + * Copyright (C) 2009-2010 Evgeny Khryukin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 VIEWER_H +#define VIEWER_H + +#include +#include +#include +#include + +#include "iconfactoryaccessinghost.h" +#include "typeaheadfind.h" + +class Viewer : public QDialog { + Q_OBJECT +public: + Viewer(QString filename, IconFactoryAccessingHost *IcoHost, QWidget *parent = nullptr); + bool init(); + +private: + IconFactoryAccessingHost * icoHost_; + QString fileName_; + QDateTime lastModified_; + QTextEdit * textWid; + ClientSwitcher::TypeAheadFindBar *findBar; + QMap pages_; + int currentPage_; + void setPage(); + +private slots: + void saveLog(); + void updateLog(); + void deleteLog(); + void nextPage(); + void prevPage(); + void firstPage(); + void lastPage(); + +protected: + void closeEvent(QCloseEvent *e); + +signals: + void onClose(int, int); +}; + +#endif // VIEWER_H